Files
optima/.github/copilot-instructions.md
T
2026-02-22 19:12:34 -06:00

9.2 KiB

Copilot / AI Agent Instructions for ttscm-api

Purpose: make AI coding agents immediately productive in this repository by describing architecture, conventions, workflows, and helpful code pointers.

-- Big picture: This is a TypeScript API service (runs on Bun) using the Hono framework. The HTTP surface is implemented in src/api where small router files are mounted on a versioned router in src/api/server.ts (see the /v1 mount). Typical request flow is:

  • router (in src/api/routers/*) → controller (in src/controllers/*) → manager (in src/managers/*) → module / generated/prisma for persistence and external integrations.

  • Keep each layer focused: routers handle routing & middleware, controllers handle request validation and high-level orchestration, managers encapsulate domain/persistence logic, modules provide shared utilities (external API clients, helpers).

  • Runtime / tooling: The project runs on Bun. Dev command: npm run dev (runs bun --watch src/index.ts). DB tooling uses Prisma; generated client lives under generated/prisma (do NOT edit generated files). Key scripts in package.json:

    • dev — start the server with Bun in watch mode
    • db:genprisma generate
    • db:pushprisma migrate dev --skip-generate
  • Data layer: Prisma schema is at prisma/schema.prisma. The app imports the generated Prisma client from generated/prisma/client.ts (or generated/prisma/browser.ts in browser contexts). Always run the db:gen script after updating schema.prisma.

  • Routing & controllers: Example flow: a request hits a router in src/api/routers/* → router delegates to a controller in src/controllers/* → controller calls a manager in src/managers/* for domain/persistence logic. Important concrete patterns:

    • src/api/server.ts mounts v1 and uses v1.route("/auth", require("./routers/authRouter").default) style requires; preserve this shape when adding routes.
    • Router files export a default Hono router object (CommonJS module.exports/export default mixture is used across the codebase).
    • Controllers are single-export modules named like CompanyController.ts with named methods per action (e.g., fetch, update). Prefer small methods that call into managers/*.
    • Managers are thin domain layers (e.g., managers/companies.ts) that wrap generated/prisma calls and other I/O. Keep side effects here, keep controllers pure orchestration.
    • Use src/modules/api-utils/apiResponse.ts for every HTTP response shape (successful/created/error/internalError/zodError).
    • Use Zod schemas in controllers for request validation; server-level app.onError maps ZodError to apiResponse.zodError.

API layout & conventions (how to add a new endpoint)

  • Add router: create src/api/routers/<thing>Router.ts exporting a Hono router and mount it in src/api/server.ts under the v1 router.
  • Add controller: create src/controllers/<Thing>Controller.ts exporting functions for each action. Controllers should validate input with Zod, call managers, and return apiResponse.* results.
  • Add manager: create src/managers/<things>.ts for persistence/domain logic. Use the generated Prisma client (generated/prisma/client.ts) here; do not import Prisma directly in controllers.
  • Add modules/types: if needed, add helpers to src/modules/* and runtime types to src/types/*.
  • Middleware & auth: use src/api/middleware/authorization.ts for protecting routes; follow existing token/session patterns from src/controllers/SessionController.ts and src/Errors/*.
  • Error handling: throw repository-specific errors from src/Errors/* (include status, name, message, optional cause) and let src/api/server.ts map them via apiResponse.error.
  • Naming: prefer plural manager filenames (companies.ts) and singular controller names (CompanyController.ts) — follow existing files.

Examples & notable files

  • src/api/server.ts — mounts v1, registers cors, central onError handler and notFound response.

  • src/api/routers/companyRouter.ts and src/controllers/CompanyController.ts — canonical example for adding company endpoints.

  • src/api/user/@me/* — nested route example (use Hono sub-routers for subpaths).

  • src/modules/cw-utils/* — external API integrations; keep interfaces stable and return domain objects consumed by managers.

  • Validation & errors: Zod is used for input validation; Zod errors are handled centrally in src/api/server.ts via apiResponse.zodError. Application errors use custom error classes in src/Errors/* (e.g., AuthenticationError.ts, GenericError.ts). When creating errors, follow the shape used in existing errors (include status, name, message, and optional cause).

  • Response pattern: Use the apiResponse helpers in src/modules/api-utils/apiResponse.ts for formatting responses and status codes (successful, created, error, internalError, zodError).

  • Auth & external integrations: Microsoft OAuth flow is under src/api/auth/* and src/modules/fetchMicrosoftUser.ts. If touching authentication, follow existing redirect/refresh patterns in src/api/auth and preserve token-refresh semantics.

  • ConnectWise integration: Utilities for ConnectWise interactions live in src/modules/cw-utils/* (e.g., fetchCompanyConfigurations.ts). These modules often call external APIs and return domain data; preserve the module contracts (input types and returned shapes) when refactoring.

  • Generated files and CI: generated/ is a build artifact. Do not modify. When updating Prisma models, run npm run db:gen and commit changes to generated/prisma only if that's the established workflow.

  • Files to inspect for context / examples:

    • src/api/server.ts — central error handling, router mounting, CORS and not-found handling.
    • src/modules/api-utils/apiResponse.ts — response shaping used across controllers.
    • src/controllers/CompanyController.ts — example controller calling managers.
    • src/modules/cw-utils/fetchCompanyConfigurations.ts — example external integration utility.
    • generated/prisma/client.ts — generated Prisma client imports; avoid editing.
  • Coding conventions & patterns specific to this repo:

    • Prefer the existing layered architecture: routers → controllers → managers → modules.
    • Use the apiResponse helpers for all HTTP responses.
    • Throw or propagate repository-specific custom errors (from src/Errors/*) rather than returning bare objects.
    • Keep TypeScript types in src/types/* and use Zod for runtime checks.
    • Avoid else statements — prefer ternary operators, early returns, or other control flow patterns. Only use else if there is absolutely no other way.
  • Local dev / quick checks:

    • Start dev server: npm run dev
    • Regenerate Prisma client: npm run db:gen
    • Apply DB migrations locally: npm run db:push
  • When editing generated or infra files: if you need to change generated/prisma/* (rare), explain why in the PR and show commands used to regenerate.

  • Documentation sync (IMPORTANT): Whenever you add, remove, or modify API routes or permission nodes, you must update all three of the following files to keep them in sync:

    1. src/types/PermissionNodes.ts — the single source of truth for all permission node definitions, categories, descriptions, and usedIn references.
    2. PERMISSIONS.md — human-readable documentation of all permission nodes; must strictly reflect the data in PermissionNodes.ts.
    3. API_ROUTES.md — comprehensive documentation of all API routes, including method, path, auth requirements, permissions, request/response examples. Always verify that new routes have their required permissions listed in PermissionNodes.ts, that PERMISSIONS.md tables match the TS file exactly, and that API_ROUTES.md includes full documentation for every mounted route. Run through all three files at the end of any route or permission change to catch discrepancies.
  • Field-level permission gating (processObjectValuePerms): Some routes use processObjectValuePerms from src/modules/permission-utils/processObjectPermissions.ts to filter response objects on a per-field basis. When this pattern is used, every key of the response object becomes a permission node in the form <scope>.<field> (e.g., unifi.site.wifi.read.passphrase). Only fields whose corresponding permission the user holds are included in the response.

    When documenting a route that uses field-level gating, you must:

    1. Note in API_ROUTES.md that the route uses field-level gating, explain the behaviour, and list every <scope>.<field> permission node in a collapsible table.
    2. Add a unifi.site.wifi.read-style parent permission node in PermissionNodes.ts with a fieldLevelPermissions array listing every <scope>.<field> node.
    3. Add matching rows/notes to PERMISSIONS.md including the full list of field-level nodes.

    Current routes using field-level gating:

    • GET /v1/unifi/site/:id/wifi — scope unifi.site.wifi.read, gates every field on the WlanConf object.

If anything here is unclear or you'd like more examples (e.g., a walk-through editing a controller + manager + test run), tell me which area to expand and I'll iterate.