diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 808e19db..52255b84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -252,6 +252,9 @@ importers: '@azure/storage-blob': specifier: ^12.31.0 version: 12.31.0 + '@bytelyst/auth': + specifier: workspace:* + version: link:../../packages/auth '@bytelyst/blob': specifier: workspace:* version: link:../../packages/blob @@ -270,6 +273,9 @@ importers: '@fastify/cors': specifier: ^10.0.2 version: 10.1.0 + '@fastify/rate-limit': + specifier: ^10.3.0 + version: 10.3.0 '@fastify/swagger': specifier: ^9.4.2 version: 9.7.0 @@ -308,61 +314,6 @@ importers: specifier: ^3.0.5 version: 3.2.4(@types/node@22.19.11)(happy-dom@18.0.1)(jsdom@28.0.0)(tsx@4.21.0)(yaml@2.8.2) - services/tracker-service: - dependencies: - '@azure/cosmos': - specifier: ^4.2.0 - version: 4.9.1(@azure/core-client@1.10.1) - '@bytelyst/auth': - specifier: workspace:* - version: link:../../packages/auth - '@bytelyst/config': - specifier: workspace:* - version: link:../../packages/config - '@bytelyst/cosmos': - specifier: workspace:* - version: link:../../packages/cosmos - '@bytelyst/errors': - specifier: workspace:* - version: link:../../packages/errors - '@bytelyst/fastify-core': - specifier: workspace:* - version: link:../../packages/fastify-core - '@fastify/cors': - specifier: ^10.0.2 - version: 10.1.0 - '@fastify/rate-limit': - specifier: ^10.3.0 - version: 10.3.0 - '@fastify/swagger': - specifier: ^9.4.2 - version: 9.7.0 - fastify: - specifier: ^5.2.1 - version: 5.7.4 - fastify-metrics: - specifier: ^10.3.0 - version: 10.6.0(fastify@5.7.4) - jose: - specifier: ^6.0.8 - version: 6.1.3 - zod: - specifier: ^3.24.2 - version: 3.25.76 - devDependencies: - '@types/node': - specifier: ^22.12.0 - version: 22.19.11 - tsx: - specifier: ^4.19.2 - version: 4.21.0 - typescript: - specifier: ^5.7.3 - version: 5.9.3 - vitest: - specifier: ^3.0.5 - version: 3.2.4(@types/node@22.19.11)(happy-dom@18.0.1)(jsdom@28.0.0)(tsx@4.21.0)(yaml@2.8.2) - packages: '@acemir/cssom@0.9.31': diff --git a/services/platform-service/package.json b/services/platform-service/package.json index 4ab3d2aa..dd7e42cc 100644 --- a/services/platform-service/package.json +++ b/services/platform-service/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@bytelyst/blob": "workspace:*", + "@bytelyst/auth": "workspace:*", "@bytelyst/config": "workspace:*", "@bytelyst/cosmos": "workspace:*", "@bytelyst/errors": "workspace:*", @@ -21,6 +22,7 @@ "@azure/cosmos": "^4.2.0", "@azure/storage-blob": "^12.31.0", "@fastify/cors": "^10.0.2", + "@fastify/rate-limit": "^10.3.0", "@fastify/swagger": "^9.4.2", "bcryptjs": "^2.4.3", "fastify": "^5.2.1", diff --git a/services/tracker-service/src/lib/auth.ts b/services/platform-service/src/lib/auth.ts similarity index 100% rename from services/tracker-service/src/lib/auth.ts rename to services/platform-service/src/lib/auth.ts diff --git a/services/platform-service/src/lib/product-config.ts b/services/platform-service/src/lib/product-config.ts index 32d322f0..7312924a 100644 --- a/services/platform-service/src/lib/product-config.ts +++ b/services/platform-service/src/lib/product-config.ts @@ -7,3 +7,5 @@ const _id = loadProductIdentity(); export const PRODUCT_ID = _id.productId; export const DISPLAY_NAME = _id.displayName; export const LICENSE_PREFIX = _id.licensePrefix; +/** Alias for tracker modules that use DEFAULT_PRODUCT_ID */ +export const DEFAULT_PRODUCT_ID = PRODUCT_ID; diff --git a/services/tracker-service/src/modules/comments/comments.test.ts b/services/platform-service/src/modules/comments/comments.test.ts similarity index 100% rename from services/tracker-service/src/modules/comments/comments.test.ts rename to services/platform-service/src/modules/comments/comments.test.ts diff --git a/services/tracker-service/src/modules/comments/repository.ts b/services/platform-service/src/modules/comments/repository.ts similarity index 100% rename from services/tracker-service/src/modules/comments/repository.ts rename to services/platform-service/src/modules/comments/repository.ts diff --git a/services/tracker-service/src/modules/comments/routes.ts b/services/platform-service/src/modules/comments/routes.ts similarity index 100% rename from services/tracker-service/src/modules/comments/routes.ts rename to services/platform-service/src/modules/comments/routes.ts diff --git a/services/tracker-service/src/modules/comments/types.ts b/services/platform-service/src/modules/comments/types.ts similarity index 100% rename from services/tracker-service/src/modules/comments/types.ts rename to services/platform-service/src/modules/comments/types.ts diff --git a/services/tracker-service/src/modules/items/items.test.ts b/services/platform-service/src/modules/items/items.test.ts similarity index 100% rename from services/tracker-service/src/modules/items/items.test.ts rename to services/platform-service/src/modules/items/items.test.ts diff --git a/services/tracker-service/src/modules/items/repository.ts b/services/platform-service/src/modules/items/repository.ts similarity index 100% rename from services/tracker-service/src/modules/items/repository.ts rename to services/platform-service/src/modules/items/repository.ts diff --git a/services/tracker-service/src/modules/items/routes.ts b/services/platform-service/src/modules/items/routes.ts similarity index 100% rename from services/tracker-service/src/modules/items/routes.ts rename to services/platform-service/src/modules/items/routes.ts diff --git a/services/tracker-service/src/modules/items/types.ts b/services/platform-service/src/modules/items/types.ts similarity index 100% rename from services/tracker-service/src/modules/items/types.ts rename to services/platform-service/src/modules/items/types.ts diff --git a/services/tracker-service/src/modules/public/public.test.ts b/services/platform-service/src/modules/public/public.test.ts similarity index 100% rename from services/tracker-service/src/modules/public/public.test.ts rename to services/platform-service/src/modules/public/public.test.ts diff --git a/services/tracker-service/src/modules/public/routes.ts b/services/platform-service/src/modules/public/routes.ts similarity index 100% rename from services/tracker-service/src/modules/public/routes.ts rename to services/platform-service/src/modules/public/routes.ts diff --git a/services/tracker-service/src/modules/public/types.ts b/services/platform-service/src/modules/public/types.ts similarity index 100% rename from services/tracker-service/src/modules/public/types.ts rename to services/platform-service/src/modules/public/types.ts diff --git a/services/tracker-service/src/modules/votes/repository.ts b/services/platform-service/src/modules/votes/repository.ts similarity index 100% rename from services/tracker-service/src/modules/votes/repository.ts rename to services/platform-service/src/modules/votes/repository.ts diff --git a/services/tracker-service/src/modules/votes/routes.ts b/services/platform-service/src/modules/votes/routes.ts similarity index 100% rename from services/tracker-service/src/modules/votes/routes.ts rename to services/platform-service/src/modules/votes/routes.ts diff --git a/services/tracker-service/src/modules/votes/types.ts b/services/platform-service/src/modules/votes/types.ts similarity index 100% rename from services/tracker-service/src/modules/votes/types.ts rename to services/platform-service/src/modules/votes/types.ts diff --git a/services/tracker-service/src/modules/votes/votes.test.ts b/services/platform-service/src/modules/votes/votes.test.ts similarity index 100% rename from services/tracker-service/src/modules/votes/votes.test.ts rename to services/platform-service/src/modules/votes/votes.test.ts diff --git a/services/platform-service/src/server.ts b/services/platform-service/src/server.ts index f5a9bfee..523493f8 100644 --- a/services/platform-service/src/server.ts +++ b/services/platform-service/src/server.ts @@ -3,7 +3,8 @@ * * Modules: auth, audit, notifications, feature flags, blob, * invitations, referrals, promos (merged from growth-service), - * subscriptions, usage, plans, licenses, stripe (merged from billing-service). + * subscriptions, usage, plans, licenses, stripe (merged from billing-service), + * items, comments, votes, public (merged from tracker-service). * Port: 4003 (configurable via PORT env var). */ @@ -22,6 +23,10 @@ import { usageRoutes } from './modules/usage/routes.js'; import { planRoutes } from './modules/plans/routes.js'; import { licenseRoutes } from './modules/licenses/routes.js'; import { stripeRoutes } from './modules/stripe/routes.js'; +import { itemRoutes } from './modules/items/routes.js'; +import { commentRoutes } from './modules/comments/routes.js'; +import { voteRoutes } from './modules/votes/routes.js'; +import { publicRoutes } from './modules/public/routes.js'; import { initCosmosIfNeeded } from './lib/cosmos-init.js'; import { config } from './lib/config.js'; @@ -75,5 +80,11 @@ if (BILLING_KEY) { } // Stripe routes outside billing scope (webhook has its own signature verification) await app.register(stripeRoutes, { prefix: '/api' }); +// Tracker modules (merged from tracker-service) +await app.register(itemRoutes, { prefix: '/api' }); +await app.register(commentRoutes, { prefix: '/api' }); +await app.register(voteRoutes, { prefix: '/api' }); +// Public routes — no auth, registered at top level +await app.register(publicRoutes, { prefix: '/api' }); await startService(app, { port: config.PORT, host: config.HOST }); diff --git a/services/tracker-service/.gitignore b/services/tracker-service/.gitignore deleted file mode 100644 index b9470778..00000000 --- a/services/tracker-service/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -dist/ diff --git a/services/tracker-service/Dockerfile b/services/tracker-service/Dockerfile deleted file mode 100644 index 76a3ef6c..00000000 --- a/services/tracker-service/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# Build context: repo root (docker compose sets context: .) -FROM node:22-alpine AS builder -RUN npm install -g pnpm@10 -WORKDIR /app - -# Copy workspace config + lockfile for dependency resolution -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.base.json ./ - -# Copy all package.json files (pnpm needs these for workspace resolution) -COPY packages/errors/package.json packages/errors/ -COPY packages/cosmos/package.json packages/cosmos/ -COPY packages/blob/package.json packages/blob/ -COPY packages/config/package.json packages/config/ -COPY packages/auth/package.json packages/auth/ -COPY packages/api-client/package.json packages/api-client/ -COPY packages/fastify-core/package.json packages/fastify-core/ -COPY packages/logger/package.json packages/logger/ -COPY packages/monitoring/package.json packages/monitoring/ -COPY packages/react-auth/package.json packages/react-auth/ -COPY packages/design-tokens/package.json packages/design-tokens/ -COPY packages/testing/package.json packages/testing/ -COPY services/tracker-service/package.json services/tracker-service/ - -# Install all workspace deps -RUN pnpm install --frozen-lockfile - -# Copy source -COPY packages/ packages/ -COPY services/tracker-service/tsconfig.json services/tracker-service/ -COPY services/tracker-service/src/ services/tracker-service/src/ - -# Build packages first, then service -RUN pnpm -r --filter @lysnrai/tracker-service... build - -# Deploy to isolated directory (production deps only) -RUN pnpm --filter @lysnrai/tracker-service deploy --legacy /app/deploy - -# ── Production ───────────────────────────────────────────── -FROM node:22-alpine -WORKDIR /app -COPY --from=builder /app/deploy ./ -ENV NODE_ENV=production -EXPOSE 4004 -CMD ["node", "dist/server.js"] diff --git a/services/tracker-service/README.md b/services/tracker-service/README.md deleted file mode 100644 index 10b9dbc6..00000000 --- a/services/tracker-service/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# Tracker Service - -Product-agnostic issue tracker for feature requests, bugs, and task management. -Built with Fastify + TypeScript + Azure Cosmos DB. - -## Port - -`4004` (configurable via `PORT` env var) - -## Modules - -- **items** — CRUD for tracker items (bugs, features, tasks) with filtering, pagination, and stats -- **comments** — Threaded discussion on items -- **votes** — Upvote toggle (1 per user per item) - -## API Endpoints - -| Method | Path | Description | -| ------ | ----------------------------- | ---------------------------------------- | -| GET | `/items` | List/filter/search items | -| POST | `/items` | Create item | -| GET | `/items/stats` | Aggregate counts by type/status/priority | -| GET | `/items/:id` | Get single item | -| PUT | `/items/:id` | Update item | -| PATCH | `/items/:id/status` | Quick status transition | -| DELETE | `/items/:id` | Delete item | -| GET | `/items/:itemId/comments` | List comments | -| POST | `/items/:itemId/comments` | Add comment | -| PUT | `/items/:itemId/comments/:id` | Edit comment | -| DELETE | `/items/:itemId/comments/:id` | Delete comment | -| POST | `/items/:itemId/vote` | Toggle upvote | -| GET | `/items/:itemId/votes` | List voters | -| GET | `/health` | Health check | - -## Setup - -```bash -cp .env .env # fill in values -npm install -npm run dev # starts with tsx watch on port 4004 -``` - -## Testing - -```bash -npm test # vitest run (29 tests) -``` - -## Environment Variables - -See `.env` for required variables: - -- `COSMOS_ENDPOINT` — Azure Cosmos DB endpoint -- `COSMOS_KEY` — Cosmos DB primary key -- `COSMOS_DATABASE` — Database name -- `JWT_SECRET` — Shared secret for JWT verification (from platform-service) -- `DEFAULT_PRODUCT_ID` — Default product scope (e.g., `lysnrai`) -- `PORT` — Server port (default `4004`) diff --git a/services/tracker-service/package.json b/services/tracker-service/package.json deleted file mode 100644 index e7c67456..00000000 --- a/services/tracker-service/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@lysnrai/tracker-service", - "version": "0.1.0", - "private": true, - "description": "Tracker Service — feature requests, bugs, tasks management (product-agnostic)", - "type": "module", - "scripts": { - "dev": "tsx watch src/server.ts", - "build": "tsc", - "start": "node dist/server.js", - "test": "vitest run", - "test:watch": "vitest", - "lint": "eslint src/" - }, - "dependencies": { - "@bytelyst/auth": "workspace:*", - "@bytelyst/config": "workspace:*", - "@bytelyst/cosmos": "workspace:*", - "@bytelyst/errors": "workspace:*", - "@bytelyst/fastify-core": "workspace:*", - "@azure/cosmos": "^4.2.0", - "@fastify/cors": "^10.0.2", - "@fastify/rate-limit": "^10.3.0", - "@fastify/swagger": "^9.4.2", - "fastify": "^5.2.1", - "fastify-metrics": "^10.3.0", - "jose": "^6.0.8", - "zod": "^3.24.2" - }, - "devDependencies": { - "@types/node": "^22.12.0", - "tsx": "^4.19.2", - "typescript": "^5.7.3", - "vitest": "^3.0.5" - } -} diff --git a/services/tracker-service/src/lib/config.ts b/services/tracker-service/src/lib/config.ts deleted file mode 100644 index 5de9bd9a..00000000 --- a/services/tracker-service/src/lib/config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from 'zod'; - -const envSchema = z.object({ - PORT: z.coerce.number().default(4004), - HOST: z.string().default('0.0.0.0'), - NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), - CORS_ORIGIN: z.string().optional(), - SERVICE_NAME: z.string().default('tracker-service'), - COSMOS_ENDPOINT: z.string().min(1, 'COSMOS_ENDPOINT is required'), - COSMOS_KEY: z.string().min(1, 'COSMOS_KEY is required'), - COSMOS_DATABASE: z.string().default('lysnrai'), - JWT_SECRET: z.string().min(1, 'JWT_SECRET is required'), - DEFAULT_PRODUCT_ID: z.string().default('lysnrai'), -}); - -export const config = envSchema.parse(process.env); diff --git a/services/tracker-service/src/lib/cosmos-init.ts b/services/tracker-service/src/lib/cosmos-init.ts deleted file mode 100644 index ed46287f..00000000 --- a/services/tracker-service/src/lib/cosmos-init.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { initializeAllContainers, registerContainers } from '@bytelyst/cosmos'; -import type { ContainerConfig } from '@bytelyst/cosmos'; -import { config } from './config.js'; - -const CONTAINER_DEFS: Record = { - tracker_items: { partitionKeyPath: '/id' }, - tracker_comments: { partitionKeyPath: '/id' }, - tracker_votes: { partitionKeyPath: '/id' }, -}; - -export async function initCosmosIfNeeded(): Promise { - registerContainers(CONTAINER_DEFS); - - const shouldInit = config.NODE_ENV !== 'production' || process.env.COSMOS_AUTO_INIT === 'true'; - if (!shouldInit) return; - - try { - await initializeAllContainers(); - // eslint-disable-next-line no-console - console.info('[tracker-service] Cosmos containers ensured'); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - // eslint-disable-next-line no-console - console.warn(`[tracker-service] Cosmos init failed: ${msg}`); - } -} diff --git a/services/tracker-service/src/lib/cosmos.ts b/services/tracker-service/src/lib/cosmos.ts deleted file mode 100644 index e82cae59..00000000 --- a/services/tracker-service/src/lib/cosmos.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Re-export from @bytelyst/cosmos — shared across all services. - */ -export { getContainer, getCosmosClient, getDatabase } from '@bytelyst/cosmos'; diff --git a/services/tracker-service/src/lib/errors.test.ts b/services/tracker-service/src/lib/errors.test.ts deleted file mode 100644 index ee55720c..00000000 --- a/services/tracker-service/src/lib/errors.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Error classes unit tests. - */ - -import { describe, it, expect } from 'vitest'; -import { - ServiceError, - NotFoundError, - BadRequestError, - UnauthorizedError, - ForbiddenError, - ConflictError, -} from './errors.js'; - -describe('ServiceError', () => { - it('creates error with status code', () => { - const err = new ServiceError(418, "I'm a teapot"); - expect(err.statusCode).toBe(418); - expect(err.message).toBe("I'm a teapot"); - expect(err.name).toBe('ServiceError'); - }); -}); - -describe('typed errors', () => { - it('NotFoundError is 404', () => { - const err = new NotFoundError(); - expect(err.statusCode).toBe(404); - expect(err.message).toBe('Not found'); - }); - - it('BadRequestError is 400', () => { - const err = new BadRequestError('Invalid input'); - expect(err.statusCode).toBe(400); - expect(err.message).toBe('Invalid input'); - }); - - it('UnauthorizedError is 401', () => { - const err = new UnauthorizedError(); - expect(err.statusCode).toBe(401); - }); - - it('ForbiddenError is 403', () => { - const err = new ForbiddenError(); - expect(err.statusCode).toBe(403); - }); - - it('ConflictError is 409', () => { - const err = new ConflictError(); - expect(err.statusCode).toBe(409); - }); - - it('all extend ServiceError', () => { - expect(new NotFoundError()).toBeInstanceOf(ServiceError); - expect(new BadRequestError()).toBeInstanceOf(ServiceError); - expect(new UnauthorizedError()).toBeInstanceOf(ServiceError); - expect(new ForbiddenError()).toBeInstanceOf(ServiceError); - expect(new ConflictError()).toBeInstanceOf(ServiceError); - }); -}); diff --git a/services/tracker-service/src/lib/errors.ts b/services/tracker-service/src/lib/errors.ts deleted file mode 100644 index dd79e9e9..00000000 --- a/services/tracker-service/src/lib/errors.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Re-export from @bytelyst/errors — shared across all services. - */ -export { - ServiceError, - BadRequestError, - UnauthorizedError, - ForbiddenError, - NotFoundError, - ConflictError, - TooManyRequestsError, -} from '@bytelyst/errors'; diff --git a/services/tracker-service/src/lib/product-config.ts b/services/tracker-service/src/lib/product-config.ts deleted file mode 100644 index 39612a32..00000000 --- a/services/tracker-service/src/lib/product-config.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Re-export from @bytelyst/config — shared product identity. - * The tracker service is product-agnostic; every document carries its own productId. - */ -import { getProductId } from '@bytelyst/config'; - -export const DEFAULT_PRODUCT_ID = process.env.DEFAULT_PRODUCT_ID || getProductId(); diff --git a/services/tracker-service/src/server.ts b/services/tracker-service/src/server.ts deleted file mode 100644 index 6088dc08..00000000 --- a/services/tracker-service/src/server.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Tracker Service — Fastify server entry point. - * - * Modules: items, comments, votes. - * Port: 4004 (configurable via PORT env var). - * Product-agnostic: all data scoped by productId. - */ - -import { createServiceApp, startService } from '@bytelyst/fastify-core'; -import { itemRoutes } from './modules/items/routes.js'; -import { commentRoutes } from './modules/comments/routes.js'; -import { voteRoutes } from './modules/votes/routes.js'; -import { publicRoutes } from './modules/public/routes.js'; -import { initCosmosIfNeeded } from './lib/cosmos-init.js'; -import { config } from './lib/config.js'; - -await initCosmosIfNeeded(); - -const app = await createServiceApp({ - name: 'tracker-service', - version: '0.1.0', - description: 'Feature requests, bugs, tasks — product-agnostic', - corsOrigin: config.CORS_ORIGIN, - swagger: { - title: 'Tracker Service', - description: 'Feature requests, bugs, tasks — product-agnostic', - port: config.PORT, - }, - metrics: true, -}); - -// Register route modules -await app.register(itemRoutes, { prefix: '/api' }); -await app.register(commentRoutes, { prefix: '/api' }); -await app.register(voteRoutes, { prefix: '/api' }); -await app.register(publicRoutes, { prefix: '/api' }); - -await startService(app, { port: config.PORT, host: config.HOST }); diff --git a/services/tracker-service/tsconfig.json b/services/tracker-service/tsconfig.json deleted file mode 100644 index d155b3cc..00000000 --- a/services/tracker-service/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "outDir": "dist", - "rootDir": "src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "src/**/*.test.ts"] -}