Refactor/monorepo with spec-driven development#321
Refactor/monorepo with spec-driven development#321wildan3105 wants to merge 73 commits intomasterfrom
Conversation
| name: Docker Build | ||
| runs-on: ubuntu-latest | ||
| needs: test | ||
|
|
||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Build Docker image | ||
| uses: docker/build-push-action@v5 | ||
| with: | ||
| context: ./backend | ||
| push: false | ||
| tags: gitlingo-backend:${{ github.sha }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
To fix the problem, add an explicit permissions: block that grants only the minimal rights needed by these jobs. Since the jobs only check out code, set up Node, run tests/lint/build, upload coverage to Codecov, and build a Docker image without pushing, they only need read access to the repository contents.
The most straightforward fix without changing functionality is to add a workflow-level permissions: block right after the name: and before on::
permissions:
contents: readThis will apply to both test and docker jobs, restricting GITHUB_TOKEN to read-only repository contents, which is sufficient for actions/checkout@v4 and the other used actions. No other scopes (like packages, pull-requests, or id-token) are required based on the provided snippet, and we are not pushing Docker images or writing to GitHub APIs.
Concretely:
- Edit
.github/workflows/backend-ci.yml. - Insert the
permissions:block near the top of the file (e.g., between line 1 and line 3, aftername: Backend CIand the blank line). - No imports, methods, or additional definitions are needed, as this is purely a workflow configuration change.
| @@ -1,5 +1,8 @@ | ||
| name: Backend CI | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| on: | ||
| push: | ||
| branches: [master, main, develop] |
- Destructure profile and repositories from fetchRepositories result - Display profile information including providerUserId - Replace all instances of repos with repositories
|
@copilot please review |
|
@wildan3105 I've opened a new pull request, #322, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
This PR is a large refactor toward a spec-driven monorepo, replacing the legacy JS/Handlebars implementation with a new TypeScript backend under backend/ that follows a DDD + ports/adapters architecture and exposes a JSON API (/health, /api/v1/search) aligned with the new backend specification.
Changes:
- Remove the legacy Express + Handlebars web app, client JS assets, and Mocha-based utility tests.
- Introduce a new
backend/TypeScript service (DDD structure, GitHub GraphQL adapter, validation, error handling) plus Jest unit/integration tests. - Add/update specs, task docs, Docker/CI setup, and sample API responses to support spec-driven development.
Reviewed changes
Copilot reviewed 86 out of 97 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/utils/language-colors-generator.test.js | Removed legacy Mocha test for language color generation. |
| tests/utils/emoji-generator.test.js | Removed legacy Mocha test for emoji generator. |
| src/utils/language-colors-generator.js | Removed legacy language color generator utility. |
| src/utils/emoji-generator.js | Removed legacy emoji generator utility. |
| src/pages/partials/head.handlebars | Removed legacy Handlebars head partial (old UI). |
| src/pages/partials/footer.handlebars | Removed legacy Handlebars footer partial (analytics snippet). |
| src/pages/partials/error.handlebars | Removed legacy error partial. |
| src/pages/partials/copyright.handlebars | Removed legacy copyright partial. |
| src/pages/partials/body.handlebars | Removed legacy main UI body (chart + search UI). |
| src/pages/layouts/main.handlebars | Removed legacy Handlebars layout wrapper. |
| src/config.js | Removed legacy config/constants for the old app. |
| src/api/routes.js | Removed legacy Express routes file. |
| src/api/octokit.js | Removed legacy REST-based Octokit wrapper. |
| src/api/controller.js | Removed legacy controller/aggregation logic (REST-based). |
| src/api/app.js | Removed legacy Express app entrypoint. |
| samples/400-response.json | Added sample error response payload for the new API contract. |
| samples/200-response.json | Added sample success response payload for the new API contract. |
| public/js/spinner.js | Removed legacy browser spinner script. |
| public/js/social.js | Removed legacy social share script. |
| public/js/language-colors.js | Removed legacy browser language-colors map. |
| public/js/chart.js | Removed legacy Chart.js integration script. |
| public/css/themes/_light.scss | Removed legacy theme styles. |
| public/css/themes/_dark.scss | Removed legacy theme styles. |
| public/css/base.scss | Removed legacy SCSS entrypoint for UI styling. |
| package.json | Removed legacy root package configuration (pre-monorepo). |
| gulpfile.js | Removed legacy gulp build pipeline (CSS). |
| eslint.config.mjs | Removed legacy root ESLint config. |
| docs/tasks/7-testing-polish.md | Added spec-driven task document for testing/polish steps. |
| docs/tasks/6-http-layer.md | Added spec-driven task document for HTTP layer design. |
| docs/tasks/5-application-layer.md | Added spec-driven task document for application/use-case layer. |
| docs/tasks/4-github-adapter.md | Added spec-driven task document for GitHub GraphQL adapter. |
| docs/tasks/3-project-structure.md | Added spec-driven task document for DDD project structure. |
| docs/tasks/2-init-deps.md | Added spec-driven task document for dependency initialization. |
| docs/tasks/1-setup-project.md | Added spec-driven task document for backend bootstrap. |
| docs/product-spec.md | Added product spec defining goals and refactor plan. |
| docs/backend-spec.md | Added backend architecture + API contract specification. |
| docker-compose.yaml | Removed legacy docker-compose setup. |
| README.md | Replaced root README with a placeholder for the refactor/monorepo docs. |
| Dockerfile | Removed legacy root Dockerfile. |
| CLAUDE.md | Added contributor/agent guidance tying work to specs + iteration practices. |
| .nvmrc | Updated Node version pinned for local development. |
| .github/workflows/backend-ci.yml | Added backend-focused CI workflow (lint/typecheck/test/build + Docker build). |
| backend/tsconfig.json | Added backend TypeScript compiler configuration. |
| backend/src/shared/types/common.ts | Added shared type aliases (provider/env/log level). |
| backend/src/shared/index.ts | Added shared barrel exports. |
| backend/src/shared/constants/providers.ts | Added provider enum + default provider constant. |
| backend/src/shared/constants/languageColors.ts | Added language color map + helpers for consistent coloring. |
| backend/src/shared/config/env.ts | Added dotenv-based config loader/validator. |
| backend/src/interfaces/validation/searchSchema.ts | Added Zod validation + middleware for /api/v1/search. |
| backend/src/interfaces/routes/searchRoutes.ts | Added search routes factory. |
| backend/src/interfaces/routes/index.ts | Added routes root with /health + mounting /api/v1. |
| backend/src/interfaces/middleware/errorHandler.ts | Added global Express error handler middleware. |
| backend/src/interfaces/index.ts | Added interfaces barrel exports. |
| backend/src/interfaces/controllers/SearchController.ts | Added controller mapping HTTP -> SearchService + error code -> status. |
| backend/src/infrastructure/providers/types/GitHubTypes.ts | Added GitHub GraphQL response types (infra-only). |
| backend/src/infrastructure/providers/GitHubGraphQLAdapter.ts | Added GitHub GraphQL provider adapter with pagination + error mapping. |
| backend/src/infrastructure/index.ts | Added infrastructure barrel exports. |
| backend/src/infrastructure/errors/ProviderError.ts | Added ProviderError type for infra failures + helpers. |
| backend/src/index.ts | Added backend entrypoint wiring middleware, DI, routes, and graceful shutdown. |
| backend/src/domain/ports/ProviderPort.ts | Added domain provider port contract returning profile + repositories. |
| backend/src/domain/models/index.ts | Added domain model barrel exports. |
| backend/src/domain/models/Repository.ts | Added provider-agnostic repository domain model. |
| backend/src/domain/models/Profile.ts | Added profile domain model. |
| backend/src/domain/models/LanguageStatistic.ts | Added language statistic domain model for chart-ready output. |
| backend/src/domain/index.ts | Added domain barrel exports. |
| backend/src/application/types/SearchResult.ts | Added success response type matching the spec contract. |
| backend/src/application/types/SearchError.ts | Added error response type matching the spec contract. |
| backend/src/application/services/SearchService.ts | Added use-case orchestration: aggregation, fork handling, color mapping, error transform. |
| backend/src/application/index.ts | Added application barrel exports. |
| backend/src/tests/unit/errorHandler.test.ts | Added unit tests for error handler middleware. |
| backend/src/tests/unit/SearchService.test.ts | Added unit tests for aggregation logic and error transformation. |
| backend/src/tests/unit/SearchController.test.ts | Added unit tests for controller status mapping and provider handling. |
| backend/src/tests/unit/ProviderError.test.ts | Added unit tests for ProviderError behavior. |
| backend/src/tests/unit/GitHubAdapter.test.ts | Added unit tests for GitHub adapter (mocked GraphQL). |
| backend/src/tests/setup.ts | Added Jest setup (env + timeouts). |
| backend/src/tests/integration/api.test.ts | Added integration tests for /health and /api/v1/search. |
| backend/package.json | Added backend package definition, scripts, and dependencies. |
| backend/jest.config.js | Added Jest + ts-jest config and coverage rules. |
| backend/eslint.config.js | Added backend ESLint config (TypeScript + Prettier). |
| backend/mocks/@octokit/graphql.js | Added manual mock for @octokit/graphql intended for network-interceptable tests. |
| backend/README.md | Added backend-specific README with setup, endpoints, and behavior notes. |
| backend/Dockerfile | Added backend production multi-stage Dockerfile + healthcheck. |
| backend/.prettierrc | Added Prettier configuration for backend TS formatting. |
| backend/.gitignore | Added backend-specific gitignore. |
| backend/.env.example | Added backend env example template. |
| backend/.dockerignore | Added backend dockerignore for smaller images. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const limiter = rateLimit({ | ||
| windowMs: 15 * 60 * 1000, // 15 minutes | ||
| max: 100, // Max 100 requests per window | ||
| message: { | ||
| ok: false, | ||
| provider: 'unknown', | ||
| error: { | ||
| code: 'rate_limited', | ||
| message: 'Too many requests, please try again later', | ||
| }, | ||
| meta: { | ||
| generatedAt: new Date().toISOString(), | ||
| }, | ||
| }, |
There was a problem hiding this comment.
express-rate-limit message is constructed once at startup, so meta.generatedAt will be stale and identical for all rate-limited responses. Consider using a handler (or a message function, if supported) to generate generatedAt per request.
| throw error; | ||
| } | ||
|
|
||
| return response.json(); |
There was a problem hiding this comment.
This manual mock returns response.json() (which includes { data, errors }), but @octokit/graphql resolves to the unwrapped data payload. As written, tests that rely on this mock will see a different shape than production code. Consider returning const { data, errors } = await response.json() and throwing on errors, otherwise returning data.
| return response.json(); | |
| const { data, errors } = await response.json(); | |
| if (errors && errors.length > 0) { | |
| const graphQLError = new Error(errors[0]?.message || 'GraphQL error'); | |
| graphQLError.errors = errors; | |
| throw graphQLError; | |
| } | |
| return data; |
|
|
||
| provider: z.enum(Object.values(Providers)).default(DEFAULT_PROVIDER).optional(), | ||
| }); |
There was a problem hiding this comment.
z.enum(Object.values(Providers)) won’t type-check cleanly because Object.values() is string[] at compile time, but z.enum() expects a non-empty tuple of string literals. Consider defining a const PROVIDERS = ['github','gitlab','bitbucket'] as const (or casting to [string, ...string[]]) and using that for both the enum and the exported types.
There was a problem hiding this comment.
What if I still want to use Providers? What should we do in this file?
| provider: z.enum(Object.values(Providers)).default(DEFAULT_PROVIDER).optional(), | ||
| }); |
There was a problem hiding this comment.
default(DEFAULT_PROVIDER).optional() likely defeats the defaulting behavior (optional can short-circuit to undefined). If you want a default provider when the param is missing, prefer .optional().default(DEFAULT_PROVIDER) or drop .optional() entirely.
| // 2. Aggregate and count repositories by language | ||
| const series = this.aggregateLanguageStatistics(repositories); | ||
|
|
||
| // 3. Return successful result | ||
| return { | ||
| ok: true, | ||
| provider: this.provider.getProviderName(), | ||
| profile, | ||
| series, | ||
| metadata: { | ||
| generatedAt: new Date().toISOString(), | ||
| unit: 'repos', | ||
| limit: repositories.length, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
metadata.limit is currently set to repositories.length, but the API contract/spec uses this as the response limit (e.g., top 20 languages) rather than the number of repos fetched. Also, series is not limited to the top N buckets. Consider introducing a constant (e.g., SERIES_LIMIT = 20), trimming/summing “others” if desired, and setting metadata.limit to that limit.
| import { Request, Response, NextFunction } from 'express'; | ||
| import pino from 'pino'; | ||
| import { isProduction } from '../../shared/config/env'; | ||
|
|
||
| const logger = pino(); | ||
|
|
There was a problem hiding this comment.
The error handler creates its own pino() instance with default settings, which can diverge from the app-wide logger config (level, serializers, destinations) and makes logs harder to correlate. Consider passing the configured logger in (DI) or initializing it with config.logLevel so it matches the rest of the service.
| @@ -1 +1 @@ | |||
| 18.18.0 | |||
| 24.3.0 | |||
There was a problem hiding this comment.
Root .nvmrc is set to Node 24.3.0, but the backend CI matrix runs Node 20.x and backend/package.json requires >=20. Consider aligning .nvmrc to the CI/runtime baseline (or documenting that Node 24 is required for the repo) to avoid local/CI drift.
| 24.3.0 | |
| 20 |
| // Validate query parameters (throws if invalid) | ||
| searchQuerySchema.parse(req.query); | ||
|
|
||
| // If validation passes, continue to controller | ||
| next(); |
There was a problem hiding this comment.
The validation middleware parses req.query but discards the parsed/sanitized output, so downstream code still sees the untyped Express query object (e.g., string | string[]). Consider assigning const parsed = searchQuerySchema.parse(req.query) and attaching it to req (e.g., req.query = parsed as any or res.locals.searchQuery = parsed) so the controller can use the validated values.
| res.status(400).json({ | ||
| ok: false, | ||
| provider: 'unknown', | ||
| error: { | ||
| code: 'validation_error', | ||
| message: 'Invalid query parameters', |
There was a problem hiding this comment.
Validation error responses always return provider: 'unknown', which diverges from the API contract/samples where provider is still set (typically github). Consider returning the default provider (or the validated provider, if present) to keep responses consistent for clients.
…ecially forked repos
…esence and isVerified field value for user and org, respectively
Description
Major overhaul
Also:
Type of change
Screenshot of change (if there's UI update)