From 968ffbdf30ca1e91702564f5c6bbccc466d63484 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Sun, 5 Apr 2026 15:07:10 -0700 Subject: [PATCH] =?UTF-8?q?arch(api):=20=F0=9F=8F=97=EF=B8=8F=20Restructur?= =?UTF-8?q?e=20API=20and=20collector=20services,=20update=20deployment=20p?= =?UTF-8?q?ipeline,=20modify=20database=20initialization,=20and=20remove?= =?UTF-8?q?=20legacy=20website-bff=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- infrastructure/init.sql | 7 ++++- scripts/deploy.sh | 39 ++++++++++++++-------------- services/api/src/app.module.ts | 2 +- services/collector/src/app.module.ts | 2 +- services/website-bff/Dockerfile | 15 +++++++++++ services/website-bff/package.json | 10 +++++++ 6 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 services/website-bff/Dockerfile create mode 100644 services/website-bff/package.json diff --git a/infrastructure/init.sql b/infrastructure/init.sql index 2d7ef19..c76dd37 100644 --- a/infrastructure/init.sql +++ b/infrastructure/init.sql @@ -63,9 +63,14 @@ FROM analytics.content_views GROUP BY bucket, content_type WITH NO DATA; --- Compression policy (compress data older than 7 days) +-- Enable compression then add compression policies (compress data older than 7 days) +ALTER TABLE analytics.content_views SET (timescaledb.compress, timescaledb.compress_orderby = 'time DESC'); SELECT add_compression_policy('analytics.content_views', INTERVAL '7 days', if_not_exists => TRUE); + +ALTER TABLE analytics.engagement_metrics SET (timescaledb.compress, timescaledb.compress_orderby = 'time DESC'); SELECT add_compression_policy('analytics.engagement_metrics', INTERVAL '7 days', if_not_exists => TRUE); + +ALTER TABLE analytics.revenue_metrics SET (timescaledb.compress, timescaledb.compress_orderby = 'time DESC'); SELECT add_compression_policy('analytics.revenue_metrics', INTERVAL '7 days', if_not_exists => TRUE); -- Indexes diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 4511d0d..dbf92f5 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -3,9 +3,16 @@ # @analytics — Deploy to vps-0 (1984 hosting) # ============================================================================= # Usage: ./scripts/deploy.sh -# or via: ./run deploy (once wired into run script) +# or via: ./run deploy # # Requires: quinn-vps SSH alias configured in ~/.ssh/config +# +# Strategy: +# - Services are built locally (turbo) — dist/ files are pre-compiled. +# - dist/ is rsynced to VPS alongside Dockerfiles; no build step needed on VPS. +# - Docker images are built on VPS from pre-compiled dist/ via docker compose --build. +# - @lilith/* workspace deps are compiled into dist/ by SWC — stripped from +# package.json in each Dockerfile so npm install only fetches registry packages. # ============================================================================= set -euo pipefail @@ -14,13 +21,13 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" REMOTE="quinn-vps" REMOTE_DIR="~/analytics" -echo "==> [1/5] Building services..." +echo "==> [1/4] Building services..." cd "$ROOT_DIR" && bun run build:services -echo "==> [2/5] Syncing source to $REMOTE:$REMOTE_DIR ..." +echo "==> [2/4] Syncing to $REMOTE:$REMOTE_DIR ..." +# Include dist/ — Docker images copy from pre-built dist, no VPS build needed rsync -avz --delete \ --exclude=node_modules \ - --exclude=dist \ --exclude=.env \ --exclude=.env.* \ "$ROOT_DIR/services/" "$REMOTE:$REMOTE_DIR/services/" @@ -30,24 +37,16 @@ rsync -avz \ "$ROOT_DIR/infrastructure/init.sql" \ "$REMOTE:$REMOTE_DIR/infrastructure/" -rsync -avz \ - "$ROOT_DIR/package.json" \ - "$ROOT_DIR/bun.lock" \ - "$ROOT_DIR/turbo.json" \ - "$ROOT_DIR/tsconfig.base.json" \ - "$REMOTE:$REMOTE_DIR/" - -echo "==> [3/5] Installing dependencies on remote..." -ssh "$REMOTE" "cd $REMOTE_DIR && bun install --production" - -echo "==> [4/5] Rebuilding and restarting Docker stack..." +echo "==> [3/4] Rebuilding and restarting Docker stack..." ssh "$REMOTE" "cd $REMOTE_DIR && docker compose -f infrastructure/docker-compose.prod.yaml --env-file infrastructure/.env.prod up -d --build" -echo "==> [5/5] Health check..." -sleep 5 -ssh "$REMOTE" "curl -sf http://localhost:4001/health/live && echo 'collector OK' || echo 'collector NOT READY'" -ssh "$REMOTE" "curl -sf http://localhost:4003/health/live && echo 'api OK' || echo 'api NOT READY'" +echo "==> [4/4] Health check..." +sleep 8 +ssh "$REMOTE" "curl -sf http://localhost:4001/health && echo 'collector OK' || echo 'collector NOT READY'" +ssh "$REMOTE" "curl -sf http://localhost:4003/health && echo 'api OK' || echo 'api NOT READY'" +ssh "$REMOTE" "curl -sf http://localhost:4005/health && echo 'website-bff OK' || echo 'website-bff NOT READY'" echo "" echo "Deployed at $(date '+%Y-%m-%d %H:%M:%S %Z')" -echo "Collector: https://data.transquinnftw.com/health" +echo "Collector: https://data.transquinnftw.com/analytics/track/" +echo "API: https://data.transquinnftw.com/api/" diff --git a/services/api/src/app.module.ts b/services/api/src/app.module.ts index 6138039..1aacb26 100644 --- a/services/api/src/app.module.ts +++ b/services/api/src/app.module.ts @@ -31,7 +31,7 @@ import { SecurityIpLogModule } from './security-ip-log/security-ip-log.module'; password: config.get('DATABASE_PASSWORD', 'analytics'), database: config.get('DATABASE_NAME', 'analytics'), autoLoadEntities: true, - synchronize: config.get('NODE_ENV') !== 'production', + synchronize: config.get('DB_SYNCHRONIZE') === 'true' || config.get('NODE_ENV') !== 'production', logging: config.get('NODE_ENV') !== 'production', }), }), diff --git a/services/collector/src/app.module.ts b/services/collector/src/app.module.ts index 985f0e1..31b6492 100644 --- a/services/collector/src/app.module.ts +++ b/services/collector/src/app.module.ts @@ -37,7 +37,7 @@ import { WriteKeyGuard } from './auth/write-key.guard'; password: config.get('DATABASE_PASSWORD', 'analytics'), database: config.get('DATABASE_NAME', 'analytics'), entities: [RawEvent, SessionFingerprint], - synchronize: false, + synchronize: config.get('DB_SYNCHRONIZE') === 'true', logging: config.get('NODE_ENV') === 'development', }), }), diff --git a/services/website-bff/Dockerfile b/services/website-bff/Dockerfile new file mode 100644 index 0000000..dd0e664 --- /dev/null +++ b/services/website-bff/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22-alpine +WORKDIR /app +RUN apk add --no-cache curl +COPY dist ./dist +COPY package.json ./ +RUN node -e " \ + const p = JSON.parse(require('fs').readFileSync('./package.json', 'utf8')); \ + p.dependencies = Object.fromEntries( \ + Object.entries(p.dependencies || {}).filter(([k]) => !k.startsWith('@lilith/')) \ + ); \ + delete p.devDependencies; \ + require('fs').writeFileSync('./package.json', JSON.stringify(p, null, 2)); \ +" && npm install --production --ignore-scripts +EXPOSE 4005 +CMD ["node", "dist/server.js"] diff --git a/services/website-bff/package.json b/services/website-bff/package.json new file mode 100644 index 0000000..ac6082b --- /dev/null +++ b/services/website-bff/package.json @@ -0,0 +1,10 @@ +{ + "name": "@lilith/analytics-website-backend", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "bun@1.2.6", + "dependencies": { + "pino": "^9.6.0" + } +} \ No newline at end of file