Skip to content

Commit

Permalink
Add view counter
Browse files Browse the repository at this point in the history
  • Loading branch information
timsexperiments committed Jun 17, 2024
1 parent 616f492 commit 4e5b5e6
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 38 deletions.
3 changes: 2 additions & 1 deletion apps/timsexperiments/.env.local
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
PUBLIC_VIEW_COUNTER_API=http://localhost:8787/views
# PUBLIC_VIEW_COUNTER_API=http://localhost:8787/views
PUBLIC_VIEW_COUNTER_API=https://dev.views.timsexperiments.foo/
2 changes: 1 addition & 1 deletion apps/timsexperiments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@timsexperiments/theme": "^0.0.0",
"@timsexperiments/three-rubiks-cube": "^0.0.3",
"@timsexperiments/three-rubiks-cube": "^0.0.5",
"@timsexperiments/views-client": "workspace:*",
"@types/hast": "^3.0.4",
"@types/react": "^18.2.47",
Expand Down
2 changes: 1 addition & 1 deletion apps/timsexperiments/src/components/nav/Nav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ import { Home, Compass, FlaskConical, Mail, UserRoundPlus } from 'lucide-react';
}
</style>

<script src="./nav.ts"></script>
<script src="@/scripts/nav.ts"></script>
8 changes: 8 additions & 0 deletions apps/timsexperiments/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/// <reference path="../.astro/db-types.d.ts" />
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

interface ImportMetaEnv {
readonly PUBLIC_VIEW_COUNTER_API: string;
}

interface ImportMeta {
readonly env: ImportMetaEnv;
}
1 change: 1 addition & 0 deletions apps/timsexperiments/src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ const { title, description } = Astro?.props ?? {};
<Toaster client:load />
<ScrollTracker />
<script src="@/scripts/animate.ts"></script>
<script src="@/scripts/count_view.ts"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ const observer = new IntersectionObserver(
if (entry.isIntersecting && !anyIntersecting) {
anyIntersecting = true;
navigator.classList.add('active');
console.log(
'scrolling the navigator for',
entry.target.id,
'into view'
);
navigator.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} else {
navigator.classList.remove('active');
Expand Down
4 changes: 2 additions & 2 deletions apps/view-counter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { ViewsHandler } from './views';
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `bun run dev` in your terminal to start a development server
* - Run `pnpm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `bun run deploy` to publish your worker
* - Run `pnpm run deploy` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/
Expand Down
12 changes: 9 additions & 3 deletions apps/view-counter/src/views/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class ViewsHandler implements RouteHandler {
env: Env,
baseHeaders: Record<string, string>,
) {
console.log(env.TURSO_AUTH_TOKEN);
const db = createDb({
url: env.TURSO_URL,
authToken: env.TURSO_AUTH_TOKEN,
Expand Down Expand Up @@ -66,8 +65,15 @@ export class ViewsHandler implements RouteHandler {

private async POST(): Promise<Response> {
const clientIP = this.request.headers.get('CF-Connecting-IP')!;
const view = (await this.request.json()) as Omit<View, 'ipAddress'>;
await this.db.add({ ipAddress: clientIP, ...view });
const view = (await this.request.json()) as Omit<View, 'ipAddress' | 'path'>;
const { page, ...rest } = view;
let pageUrl: URL;
try {
pageUrl = new URL(page);
} catch {
return badRequestResponse({ message: 'Page must be a valid fully qualified URL.', headers: this.baseHeaders });
}
await this.db.add({ ipAddress: clientIP, ...rest, page: pageUrl.href, path: pageUrl.pathname });
return responseNoContent({ headers: this.baseHeaders });
}
}
8 changes: 4 additions & 4 deletions packages/view-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
"module": "src/index.ts",
"types": "src/index.ts",
"type": "module",
"dependencies": {
"@libsql/client": "^0.6.0",
"drizzle-orm": "^0.30.8"
},
"devDependencies": {
"@types/bun": "latest",
"dotenv": "^16.4.5",
"drizzle-kit": "^0.20.17"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@libsql/client": "^0.6.0",
"drizzle-orm": "^0.30.8"
}
}
6 changes: 3 additions & 3 deletions packages/view-storage/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";

export type CreateDbOptions = {
authToken: string;
url: string;
authToken?: string;
};

export function createDb({ url, authToken }: CreateDbOptions) {
export function createDb({ authToken, url }: CreateDbOptions) {
const client = createClient({
url,
authToken,
url,
});
return drizzle(client);
}
Expand Down
1 change: 1 addition & 0 deletions packages/view-storage/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const views = sqliteTable(
{
id: integer("id").primaryKey(),
page: text("page", { length: 256 }).notNull(),
path: text("path", { length: 256 }).notNull(),
ipAddress: text("ip_address", { length: 40 }).notNull(),
viewedAt: integer("viewed_at", { mode: "timestamp_ms" })
.default(sql`CURRENT_TIMESTAMP`)
Expand Down
3 changes: 3 additions & 0 deletions packages/views-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"valibot": "^0.32.0"
}
}
62 changes: 49 additions & 13 deletions packages/views-client/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import { type View } from "@timsexperiments/view-storage";
import * as v from "valibot";

export type GetViewsOptions = {
page: string;
};
const GetViewsSchema = v.object({
page: v.pipe(v.string(), v.url()),
});

export type AddViewOpitons = {
page: string;
};
export type GetViewsOptions = v.InferOutput<typeof GetViewsSchema>;

export type ViewsClientOptions = {
host: string;
};
const AddViewSchema = v.object({
page: v.pipe(v.string(), v.url()),
});

export type AddViewOpitons = v.InferOutput<typeof AddViewSchema>;

const ViewsClientOptionsSchema = v.object({
host: v.pipe(v.string(), v.url()),
});

export type ViewsClientOptions = v.InferOutput<typeof ViewsClientOptionsSchema>;

/**
* Client for interacting with the timsexperiments views api.
*/
export class ViewsClient {
private readonly url;

constructor({ host }: ViewsClientOptions) {
constructor(options: ViewsClientOptions) {
const parsed = v.safeParse(ViewsClientOptionsSchema, options);
if (!parsed.success) {
throw new ViewClientError(
"Invalid options provided: " + JSON.stringify(parsed.issues)
);
}
const host = parsed.output;
this.url = new URL(host);
}

Expand All @@ -29,7 +43,14 @@ export class ViewsClient {
* @param {string} options.page - The page for which to retrieve views.
* @returns {Promise<View>} - A promise that resolves to the retrieved views.
*/
async getViews({ page }: GetViewsOptions) {
async getViews(options: GetViewsOptions) {
const parsed = v.safeParse(GetViewsSchema, options);
if (!parsed.success) {
throw new ViewClientError(
"Invalid options provided: " + JSON.stringify(parsed.issues)
);
}
const { page } = parsed.output;
const url = this.viewsUrl;
url.searchParams.append("page", page);
const response = await fetch(url.href);
Expand All @@ -43,12 +64,20 @@ export class ViewsClient {
* @param {string} options.page - The page for which to add a view.
* @returns {Promise<void>} - A promise that resolves when the view is added successfully.
*/
async addView({ page }: AddViewOpitons) {
async addView(options: AddViewOpitons) {
const parsed = v.safeParse(AddViewSchema, options);
if (!parsed.success) {
throw new ViewClientError(
"Invalid options provided: " + JSON.stringify(parsed.issues)
);
}
const { page } = parsed.output;
const pageUrl = new URL(page);
const url = this.viewsUrl;
await fetch(url.href, {
method: "POST",
body: JSON.stringify({
page,
page: pageUrl.href,
}),
headers: {
"Content-Type": "application/json",
Expand All @@ -64,3 +93,10 @@ export class ViewsClient {
}

export default ViewsClient;

export class ViewClientError extends Error {
constructor(message: string, options?: ErrorOptions) {
super(message, options);
this.name = "ViewClientError";
}
}
18 changes: 13 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4e5b5e6

Please sign in to comment.