Files
flyer-crawler.projectium.com/docs/SUBAGENT-CODER-REFERENCE.md

266 lines
10 KiB
Markdown

# Coder Subagent Reference
## Quick Navigation
| Category | Key Files |
| ------------ | ------------------------------------------------------------------ |
| Routes | `src/routes/*.routes.ts` |
| Services | `src/services/*.server.ts` (backend), `src/services/*.ts` (shared) |
| Repositories | `src/services/db/*.db.ts` |
| Types | `src/types.ts`, `src/types/*.ts` |
| Schemas | `src/schemas/*.schemas.ts` |
| Config | `src/config/env.ts` |
| Utils | `src/utils/*.ts` |
---
## Architecture Patterns (ADR Summary)
### Layer Flow
```
Route → validateRequest(schema) → Service → Repository → Database
External APIs
```
### Repository Naming Convention (ADR-034)
| Prefix | Behavior | Return |
| --------- | --------------------------------- | -------------- |
| `get*` | Throws `NotFoundError` if missing | Entity |
| `find*` | Returns `null` if missing | Entity \| null |
| `list*` | Returns empty array if none | Entity[] |
| `create*` | Creates new record | Entity |
| `update*` | Updates existing | Entity |
| `delete*` | Removes record | void |
### Error Handling (ADR-001)
```typescript
import { handleDbError, NotFoundError } from '../services/db/errors.db';
import { logger } from '../services/logger.server';
// Repository pattern
async function getById(id: string): Promise<Entity> {
try {
const result = await pool.query('SELECT * FROM table WHERE id = $1', [id]);
if (result.rows.length === 0) throw new NotFoundError('Entity not found');
return result.rows[0];
} catch (error) {
handleDbError(error, logger, 'Failed to get entity', { id });
}
}
```
### API Response Helpers (ADR-028)
```typescript
import {
sendSuccess,
sendPaginated,
sendError,
sendNoContent,
sendMessage,
ErrorCode,
} from '../utils/apiResponse';
// Success with data
sendSuccess(res, data); // 200
sendSuccess(res, data, 201); // 201 Created
// Paginated
sendPaginated(res, items, { page, limit, total });
// Error
sendError(res, ErrorCode.NOT_FOUND, 'User not found', 404);
sendError(res, ErrorCode.VALIDATION_ERROR, 'Invalid input', 400, validationErrors);
// No content / Message
sendNoContent(res); // 204
sendMessage(res, 'Password updated');
```
### Transaction Pattern (ADR-002)
```typescript
import { withTransaction } from '../services/db/connection.db';
const result = await withTransaction(async (client) => {
await client.query('INSERT INTO a ...');
await client.query('INSERT INTO b ...');
return { success: true };
});
```
---
## Adding New Features
### New API Endpoint Checklist
1. **Schema** (`src/schemas/{domain}.schemas.ts`)
```typescript
import { z } from 'zod';
export const createEntitySchema = z.object({
body: z.object({ name: z.string().min(1) }),
});
```
2. **Route** (`src/routes/{domain}.routes.ts`)
```typescript
import { validateRequest } from '../middleware/validation.middleware';
import { createEntitySchema } from '../schemas/{domain}.schemas';
router.post('/', validateRequest(createEntitySchema), async (req, res, next) => {
try {
const result = await entityService.create(req.body);
sendSuccess(res, result, 201);
} catch (error) {
next(error);
}
});
```
3. **Service** (`src/services/{domain}Service.server.ts`)
```typescript
export async function create(data: CreateInput): Promise<Entity> {
// Business logic here
return repository.create(data);
}
```
4. **Repository** (`src/services/db/{domain}.db.ts`)
```typescript
export async function create(data: CreateInput, client?: PoolClient): Promise<Entity> {
const pool = client || getPool();
try {
const result = await pool.query('INSERT INTO ...', [data.name]);
return result.rows[0];
} catch (error) {
handleDbError(error, logger, 'Failed to create entity', { data });
}
}
```
### New Background Job Checklist
1. **Queue** (`src/services/queues.server.ts`)
```typescript
export const myQueue = new Queue('my-queue', { connection: redisConnection });
```
2. **Worker** (`src/services/workers.server.ts`)
```typescript
new Worker(
'my-queue',
async (job) => {
// Process job
},
{ connection: redisConnection },
);
```
3. **Trigger** (in service)
```typescript
await myQueue.add('job-name', { data });
```
---
## Key Files Reference
### Database Repositories
| Repository | Purpose | Path |
| -------------------- | ----------------------------- | ------------------------------------ |
| `flyer.db.ts` | Flyer CRUD, processing status | `src/services/db/flyer.db.ts` |
| `store.db.ts` | Store management | `src/services/db/store.db.ts` |
| `user.db.ts` | User accounts | `src/services/db/user.db.ts` |
| `shopping.db.ts` | Shopping lists, watchlists | `src/services/db/shopping.db.ts` |
| `gamification.db.ts` | Achievements, points | `src/services/db/gamification.db.ts` |
| `category.db.ts` | Item categories | `src/services/db/category.db.ts` |
| `price.db.ts` | Price history, comparisons | `src/services/db/price.db.ts` |
| `recipe.db.ts` | Recipe management | `src/services/db/recipe.db.ts` |
### Services
| Service | Purpose | Path |
| ---------------------------------- | -------------------------------- | ----------------------------------------------- |
| `flyerProcessingService.server.ts` | Orchestrates flyer AI extraction | `src/services/flyerProcessingService.server.ts` |
| `flyerAiProcessor.server.ts` | Gemini AI integration | `src/services/flyerAiProcessor.server.ts` |
| `cacheService.server.ts` | Redis caching | `src/services/cacheService.server.ts` |
| `queues.server.ts` | BullMQ queue definitions | `src/services/queues.server.ts` |
| `workers.server.ts` | BullMQ workers | `src/services/workers.server.ts` |
| `emailService.server.ts` | Nodemailer integration | `src/services/emailService.server.ts` |
| `geocodingService.server.ts` | Address geocoding | `src/services/geocodingService.server.ts` |
### Routes
| Route | Base Path | Auth Required |
| ------------------ | ------------- | ------------- |
| `flyer.routes.ts` | `/api/flyers` | Mixed |
| `store.routes.ts` | `/api/stores` | Mixed |
| `user.routes.ts` | `/api/users` | Yes |
| `auth.routes.ts` | `/api/auth` | No |
| `admin.routes.ts` | `/api/admin` | Admin only |
| `deals.routes.ts` | `/api/deals` | No |
| `health.routes.ts` | `/api/health` | No |
---
## Error Types
| Error Class | HTTP Status | Use Case |
| --------------------------- | ----------- | ------------------------- |
| `NotFoundError` | 404 | Resource not found |
| `ForbiddenError` | 403 | Access denied |
| `ValidationError` | 400 | Input validation failed |
| `UniqueConstraintError` | 409 | Duplicate record |
| `ForeignKeyConstraintError` | 400 | Referenced record missing |
| `NotNullConstraintError` | 400 | Required field null |
Import: `import { NotFoundError, ... } from '../services/db/errors.db'`
---
## Middleware
| Middleware | Purpose | Usage |
| ------------------------- | -------------------- | ------------------------------------------------------------ |
| `validateRequest(schema)` | Zod validation | `router.post('/', validateRequest(schema), handler)` |
| `requireAuth` | JWT authentication | `router.get('/', requireAuth, handler)` |
| `requireAdmin` | Admin role check | `router.delete('/', requireAuth, requireAdmin, handler)` |
| `fileUpload` | Multer file handling | `router.post('/upload', fileUpload.single('file'), handler)` |
---
## Type Definitions
| File | Contains |
| --------------------------- | ----------------------------------------------- |
| `src/types.ts` | Main types: User, Flyer, FlyerItem, Store, etc. |
| `src/types/api.ts` | API response envelopes, pagination |
| `src/types/auth.ts` | Auth-related types |
| `src/types/gamification.ts` | Achievement types |
---
## Naming Conventions (ADR-027)
| Context | Convention | Example |
| ----------------- | ---------------- | ----------------------------------- |
| AI output types | `Ai*` prefix | `AiFlyerItem`, `AiExtractionResult` |
| Database types | `Db*` prefix | `DbFlyer`, `DbUser` |
| API types | No prefix | `Flyer`, `User` |
| Schema validation | `*Schema` suffix | `createFlyerSchema` |
| Routes | `*.routes.ts` | `flyer.routes.ts` |
| Repositories | `*.db.ts` | `flyer.db.ts` |
| Server services | `*.server.ts` | `aiService.server.ts` |
| Client services | `*.client.ts` | `logger.client.ts` |