Skip to content

feat(apps/tracking): add tracking app #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions apps/track/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@stack/track",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "bun --watch run ./src/index.ts",
"lint:fix": "prettier --check . && eslint . --fix"
},
"dependencies": {
"@clickhouse/client": "1.4.1",
"@hattip/adapter-bun": "0.0.47",
"@hattip/response": "0.0.47",
"@hattip/router": "0.0.47"
},
"devDependencies": {
"@stack/typescript-config": "workspace:*",
"@types/bun": "1.1.6"
}
}
42 changes: 42 additions & 0 deletions apps/track/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createClient } from '@clickhouse/client';

import { PAGE_VIEWS_TABLE } from './db';

type Statistics = {
bytes_read: number;
elapsed: number;
rows_read: number;
};

type CHResult<Data, Meta = Record<string, string>> = {
data: Data[];
meta: Meta[];
rows: number;
statistics: Statistics;
};

export const initClient = async () => {
const client = createClient({
password: process.env['DB_PASSOWRD'],
url: process.env['DB_URL']
});

// Check if the database contains all the tables
const row = await client.query({
query: 'show tables from default'
});
const json = (await row.json()) as CHResult<{ name: string }>;
if (!json.data || json.data.length === 0) {
// TODO: Create the different tables
await client.query({
query: PAGE_VIEWS_TABLE
});
} else {
console.log(
'[Server] Fond tables',
`[${json.data.map((t) => t.name).join(', ')}]`
);
}

return client;
};
81 changes: 81 additions & 0 deletions apps/track/src/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
export const PAGE_VIEWS_TABLE = `
CREATE TABLE page_views (
event_id UUID DEFAULT generateUUIDv4(),
app_name String,
user_id String,
session_id String,
page_url String,
referrer_url String,
timestamp DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (app_name, timestamp, user_id)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 1 YEAR
SETTINGS index_granularity = 8192;
`;

export const USER_ACTIONS_TABLE = `
CREATE TABLE user_actions (
event_id UUID DEFAULT generateUUIDv4(),
app_name String,
user_id String,
session_id String,
action_type String,
action_details String,
timestamp DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (app_name, timestamp, user_id, action_type)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 1 YEAR
SETTINGS index_granularity = 8192;
`;

export const SESSIONS_TABLE = `
CREATE TABLE conversions (
event_id UUID DEFAULT generateUUIDv4(),
app_name String,
user_id String,
session_id String,
conversion_type String,
conversion_value Float32,
timestamp DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (app_name, timestamp, user_id, conversion_type)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 1 YEAR
SETTINGS index_granularity = 8192;
`;

export const ERROR_TABLE = `
CREATE TABLE errors (
event_id UUID DEFAULT generateUUIDv4(),
app_name String,
user_id String,
session_id String,
error_type String,
error_message String,
stack_trace String,
page_url String,
timestamp DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (app_name, timestamp, user_id, error_type)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 1 YEAR
SETTINGS index_granularity = 8192;
`;

export const USER_FLOW_TALBE = `
CREATE TABLE user_flow (
event_id UUID DEFAULT generateUUIDv4(),
app_name String,
user_id String,
session_id String,
from_page String,
to_page String,
timestamp DateTime DEFAULT now()
) ENGINE = MergeTree()
ORDER BY (app_name, timestamp, user_id, from_page)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 1 YEAR
SETTINGS index_granularity = 8192;
`;
15 changes: 15 additions & 0 deletions apps/track/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// entry-node.js
import adapter from '@hattip/adapter-bun';

import { initClient } from './client';
import { router } from './routes';

const client = await initClient();
client.close();

// export default router;
export default adapter(
router,
// @ts-expect-error
{ port: 3001 }
);
13 changes: 13 additions & 0 deletions apps/track/src/routes/events/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { TrackRouter } from '../router';

import { eventRoute } from './shared';

export const actionRoute = (router: TrackRouter) => {
router.get(
eventRoute('action'),
async () =>
new Response(undefined, {
status: 200
})
);
};
13 changes: 13 additions & 0 deletions apps/track/src/routes/events/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { TrackRouter } from '../router';

import { eventRoute } from './shared';

export const errorRoute = (router: TrackRouter) => {
router.get(
eventRoute('error'),
async () =>
new Response(undefined, {
status: 200
})
);
};
8 changes: 8 additions & 0 deletions apps/track/src/routes/events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { TrackRouter } from '../router';

import { pageViewRote } from './page-view';

export const addEventsRoutes = (router: TrackRouter) => {
// TODO: Add other routers
pageViewRote(router);
};
13 changes: 13 additions & 0 deletions apps/track/src/routes/events/page-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { TrackRouter } from '../router';

import { eventRoute } from './shared';

export const pageViewRote = (router: TrackRouter) => {
router.get(
eventRoute('page-view'),
async () =>
new Response(undefined, {
status: 200
})
);
};
23 changes: 23 additions & 0 deletions apps/track/src/routes/events/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { TrackRouter } from '../router';

import { eventRoute } from './shared';

export const startSessionRoute = (router: TrackRouter) => {
router.get(
eventRoute('session-start'),
async () =>
new Response(undefined, {
status: 200
})
);
};

export const endSessionRoute = (router: TrackRouter) => {
router.get(
eventRoute('session-end'),
async () =>
new Response(undefined, {
status: 200
})
);
};
1 change: 1 addition & 0 deletions apps/track/src/routes/events/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const eventRoute = (route: string) => `/events/${route}`;
13 changes: 13 additions & 0 deletions apps/track/src/routes/events/user-flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { TrackRouter } from '../router';

import { eventRoute } from './shared';

export const userFlowRoute = (router: TrackRouter) => {
router.get(
eventRoute('flow'),
async () =>
new Response(undefined, {
status: 200
})
);
};
1 change: 1 addition & 0 deletions apps/track/src/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as router } from './router';
13 changes: 13 additions & 0 deletions apps/track/src/routes/router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { BunPlatformInfo } from '@hattip/adapter-bun';
import type { Router } from '@hattip/router';

import { createRouter } from '@hattip/router';

import { addEventsRoutes } from './events';

const router = createRouter<BunPlatformInfo>();

addEventsRoutes(router);

export type TrackRouter = Router<BunPlatformInfo>;
export default router.buildHandler();
3 changes: 3 additions & 0 deletions apps/track/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@stack/typescript-config/server.json"
}
Binary file modified bun.lockb
Binary file not shown.
19 changes: 19 additions & 0 deletions packages/config-typescript/server.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"allowJs": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": true
}
}