diff --git a/apps/sdk-api/Dockerfile b/apps/sdk-api/Dockerfile new file mode 100644 index 00000000..43b83093 --- /dev/null +++ b/apps/sdk-api/Dockerfile @@ -0,0 +1,80 @@ +# Dockerfile that builds the web app only + +FROM --platform=linux/amd64 node:20-slim AS base + +ARG DATABASE_URL +ENV DATABASE_URL=$DATABASE_URL + +ENV PNPM_HOME="/pnpm" + +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable + +ARG NODE_VERSION=20 + +RUN apt update \ + && apt install -y curl \ + && curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n \ + && bash n $NODE_VERSION \ + && rm n \ + && npm install -g n + +WORKDIR /app + +COPY package.json package.json +COPY pnpm-lock.yaml pnpm-lock.yaml +COPY pnpm-workspace.yaml pnpm-workspace.yaml +COPY apps/sdk-api/package.json apps/sdk-api/package.json +COPY packages/db/package.json packages/db/package.json +COPY packages/queue/package.json packages/queue/package.json +COPY packages/types/package.json packages/types/package.json + +# BUILD +FROM base AS build + +WORKDIR /app/apps/sdk-api +RUN pnpm install --frozen-lockfile --ignore-scripts + +WORKDIR /app +COPY apps apps +COPY packages packages +COPY tooling tooling +RUN pnpm db:codegen + +WORKDIR /app/apps/sdk-api +RUN pnpm run build + +# PROD +FROM base AS prod + +WORKDIR /app/apps/sdk-api +RUN pnpm install --frozen-lockfile --prod --ignore-scripts + +# FINAL +FROM base AS runner + +COPY --from=build /app/package.json /app/package.json +COPY --from=prod /app/node_modules /app/node_modules + +# Apps +COPY --from=build /app/apps/sdk-api /app/apps/sdk-api + +# Apps node_modules +COPY --from=prod /app/apps/sdk-api/node_modules /app/apps/sdk-api/node_modules + +# Packages +COPY --from=build /app/packages/db /app/packages/db +COPY --from=build /app/packages/queue /app/packages/queue + +# Packages node_modules +COPY --from=prod /app/packages/db/node_modules /app/packages/db/node_modules +COPY --from=prod /app/packages/queue/node_modules /app/packages/queue/node_modules + +RUN pnpm db:codegen + +WORKDIR /app/apps/sdk-api + +EXPOSE 3000 + +CMD ["pnpm", "start"] \ No newline at end of file diff --git a/apps/sdk-api/src/controllers/event.controller.ts b/apps/sdk-api/src/controllers/event.controller.ts index 48a90c0e..fea68b2e 100644 --- a/apps/sdk-api/src/controllers/event.controller.ts +++ b/apps/sdk-api/src/controllers/event.controller.ts @@ -1,6 +1,7 @@ import { parseIp } from '@/utils/parseIp'; import { parseUserAgent } from '@/utils/parseUserAgent'; import type { FastifyReply, FastifyRequest } from 'fastify'; +import { omit } from 'ramda'; import { getClientIp } from 'request-ip'; import { generateProfileId, getTime, toISOString } from '@mixan/common'; @@ -8,19 +9,44 @@ import type { IServiceCreateEventPayload } from '@mixan/db'; import { getSalts } from '@mixan/db'; import type { JobsOptions } from '@mixan/queue'; import { eventsQueue, findJobByPrefix } from '@mixan/queue'; - -export interface PostEventPayload { - profileId?: string; - name: string; - timestamp: string; - properties: Record; - referrer: string | undefined; - path: string; -} +import type { PostEventPayload } from '@mixan/types'; const SESSION_TIMEOUT = 1000 * 30 * 1; const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000; +function parseSearchParams(params: URLSearchParams): Record { + const result: Record = {}; + for (const [key, value] of params.entries()) { + result[key] = value; + } + return result; +} + +function parsePath(path?: string): { + query?: Record; + path: string; + hash?: string; +} { + if (!path) { + return { + path: '', + }; + } + + try { + const url = new URL(path); + return { + query: parseSearchParams(url.searchParams), + path: url.pathname, + hash: url.hash, + }; + } catch (error) { + return { + path, + }; + } +} + export async function postEvent( request: FastifyRequest<{ Body: PostEventPayload; @@ -30,7 +56,10 @@ export async function postEvent( let profileId: string | null = null; const projectId = request.projectId; const body = request.body; - const path = body.path; + const { path, hash, query } = parsePath( + body.properties?.path as string | undefined + ); + const referrer = body.properties?.referrer as string | undefined; const ip = getClientIp(request)!; const origin = request.headers.origin!; const ua = request.headers['user-agent']!; @@ -101,7 +130,10 @@ export async function postEvent( name: body.name, profileId, projectId, - properties: body.properties, + properties: Object.assign({}, omit(['path', 'referrer'], body.properties), { + hash, + query, + }), createdAt: body.timestamp, country: geo.country, city: geo.city, @@ -115,13 +147,11 @@ export async function postEvent( brand: uaInfo.brand, model: uaInfo.model, duration: 0, - path, - referrer: body.referrer, // TODO - referrerName: body.referrer, // TODO + path: path, + referrer, + referrerName: referrer, // TODO }; - console.log(payload); - const job = findJobByPrefix(eventsJobs, `event:${projectId}:${profileId}:`); if (job?.isDelayed && job.data.type === 'createEvent') { diff --git a/apps/sdk-api/src/index.ts b/apps/sdk-api/src/index.ts index 259b46ba..3269a549 100644 --- a/apps/sdk-api/src/index.ts +++ b/apps/sdk-api/src/index.ts @@ -11,7 +11,7 @@ declare module 'fastify' { } } -const port = parseInt(process.env.API_PORT || '3030', 10); +const port = parseInt(process.env.API_PORT || '3000', 10); const startServer = async () => { try { @@ -43,7 +43,7 @@ const startServer = async () => { fastify.log.error(error); }); fastify.get('/', (request, reply) => { - reply.send({ name: 'fastify-typescript' }); + reply.send({ name: 'openpanel sdk api' }); }); // fastify.get('/health-check', async (request, reply) => { // try { diff --git a/apps/test/src/analytics.ts b/apps/test/src/analytics.ts index 6296f019..8f38e2c8 100644 --- a/apps/test/src/analytics.ts +++ b/apps/test/src/analytics.ts @@ -1,14 +1,14 @@ -import { MixanWeb } from '@mixan-test/sdk-web'; +// import { MixanWeb } from '@mixan-test/sdk-web'; -export const mixan = new MixanWeb({ - verbose: true, - url: 'http://localhost:3000/api/sdk', - clientId: '568b4ed1-5d00-4f27-88a7-b8959e6674bd', - clientSecret: '1e362905-d352-44c4-9263-e037a2ad52fb', - trackIp: true, -}); +// export const mixan = new MixanWeb({ +// verbose: true, +// url: 'http://localhost:3000/api/sdk', +// clientId: '568b4ed1-5d00-4f27-88a7-b8959e6674bd', +// clientSecret: '1e362905-d352-44c4-9263-e037a2ad52fb', +// trackIp: true, +// }); -mixan.init({ - appVersion: '1.0.0', -}); -mixan.trackOutgoingLinks(); +// mixan.init({ +// appVersion: '1.0.0', +// }); +// mixan.trackOutgoingLinks(); diff --git a/apps/test/src/pages/_app.tsx b/apps/test/src/pages/_app.tsx index 89aa1f5c..57bbd9ff 100644 --- a/apps/test/src/pages/_app.tsx +++ b/apps/test/src/pages/_app.tsx @@ -1,15 +1,15 @@ import { useEffect } from 'react'; -import { mixan } from '@/analytics'; +// import { mixan } from '@/analytics'; import type { AppProps } from 'next/app'; import { useRouter } from 'next/router'; export default function MyApp({ Component, pageProps }: AppProps) { const router = useRouter(); - useEffect(() => { - mixan.screenView(); - return router.events.on('routeChangeComplete', () => { - mixan.screenView(); - }); - }, []); + // useEffect(() => { + // mixan.screenView(); + // return router.events.on('routeChangeComplete', () => { + // mixan.screenView(); + // }); + // }, []); return ; } diff --git a/apps/test/src/pages/_document.tsx b/apps/test/src/pages/_document.tsx new file mode 100644 index 00000000..32c9e027 --- /dev/null +++ b/apps/test/src/pages/_document.tsx @@ -0,0 +1,21 @@ +import { Head, Html, Main, NextScript } from 'next/document'; + +export default function Document() { + return ( + + +