Setup initial project
This commit is contained in:
commit
db79af06f7
119 changed files with 20761 additions and 0 deletions
259
docs/instructions/conventions.md
Normal file
259
docs/instructions/conventions.md
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
# Coding Conventions & Style Guide — MeshiTrack
|
||||
|
||||
> Cross-cutting conventions that apply to all packages in the monorepo.
|
||||
|
||||
## General Rules
|
||||
|
||||
### No emojis
|
||||
|
||||
Never use emoji characters anywhere in this codebase: not in source code, UI text, console output, log messages, comments, or documentation. Use plain text instead.
|
||||
|
||||
### Language: TypeScript everywhere
|
||||
|
||||
- All packages use TypeScript with `strict: true`
|
||||
- No `.js` files in source (except config files: `jest.config.js`, `next.config.js`)
|
||||
- All files use `.ts` or `.tsx` extension
|
||||
|
||||
### Formatting: Prettier
|
||||
|
||||
```json
|
||||
// .prettierrc
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
```
|
||||
|
||||
### Linting: ESLint
|
||||
|
||||
Use a flat config (`eslint.config.js`) with TypeScript support:
|
||||
|
||||
- `@typescript-eslint/recommended`
|
||||
- `@typescript-eslint/no-explicit-any` → error
|
||||
- `@typescript-eslint/no-unused-vars` → error (with `_` prefix exception)
|
||||
- `import/order` → enforce consistent import ordering
|
||||
|
||||
### Import ordering
|
||||
|
||||
```typescript
|
||||
// 1. Node built-ins
|
||||
import { readFile } from 'fs/promises';
|
||||
|
||||
// 2. External packages
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
|
||||
// 3. Monorepo packages
|
||||
import type { Product } from '@meshitrack/shared';
|
||||
import { ProductCategory } from '@meshitrack/shared';
|
||||
|
||||
// 4. Internal (relative) imports
|
||||
import { ProductsRepository } from './products.repository';
|
||||
import type { ProductQueryDto } from './dto/product-query.dto';
|
||||
```
|
||||
|
||||
Separate each group with a blank line.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Files
|
||||
|
||||
| Kind | Pattern | Example |
|
||||
| ----------------- | -------------------------------------- | --------------------------- |
|
||||
| Module | `kebab-case.module.ts` | `products.module.ts` |
|
||||
| Controller | `kebab-case.controller.ts` | `products.controller.ts` |
|
||||
| Service | `kebab-case.service.ts` | `products.service.ts` |
|
||||
| Repository | `kebab-case.repository.ts` | `products.repository.ts` |
|
||||
| Schema (Mongoose) | `kebab-case.schema.ts` | `product.schema.ts` |
|
||||
| DTO | `kebab-case.dto.ts` | `create-product.dto.ts` |
|
||||
| Guard | `kebab-case.guard.ts` | `keycloak-auth.guard.ts` |
|
||||
| Decorator | `kebab-case.decorator.ts` | `current-user.decorator.ts` |
|
||||
| Interface | `kebab-case.interface.ts` | `llm-provider.interface.ts` |
|
||||
| React component | `PascalCase.tsx` | `ProductCard.tsx` |
|
||||
| React hook | `use-kebab-case.ts` | `use-products.ts` |
|
||||
| Test file | `*.spec.ts` (API) / `*.test.tsx` (Web) | `products.service.spec.ts` |
|
||||
| Zod schema | `kebab-case.schemas.ts` | `product.schemas.ts` |
|
||||
| Enum file | `kebab-case.enums.ts` | `product.enums.ts` |
|
||||
|
||||
### Code
|
||||
|
||||
| Kind | Style | Example |
|
||||
| ------------------------------------- | -------------------------------- | -------------------------------------- |
|
||||
| Class | PascalCase | `ProductsService`, `KeycloakAuthGuard` |
|
||||
| Interface | PascalCase + I prefix (optional) | `ILlmProvider` or `LlmProvider` |
|
||||
| Type alias | PascalCase | `ProductQuery`, `CreateProductInput` |
|
||||
| Enum | PascalCase | `ProductCategory` |
|
||||
| Enum member | UPPER_SNAKE | `ProductCategory.DAIRY` |
|
||||
| Function | camelCase | `calculateNutrition()` |
|
||||
| Variable | camelCase | `servingSize`, `totalCalories` |
|
||||
| Constant | UPPER_SNAKE | `LLM_PROVIDER`, `MAX_RETRY_COUNT` |
|
||||
| Private field (backing getter/setter) | `_camelCase` | `_accessToken`, `_householdId` |
|
||||
| React component | PascalCase | `ProductCard`, `NutritionBadge` |
|
||||
| React hook | camelCase | `useProducts`, `usePantryItems` |
|
||||
| CSS class (Tailwind) | kebab-case | via Tailwind utilities |
|
||||
|
||||
## Code Organization Rules
|
||||
|
||||
### API (NestJS)
|
||||
|
||||
1. **One module per feature** — self-contained folder with controller, service, repository, schemas, DTOs, tests
|
||||
2. **No cross-module imports of internal files** — only import from exported module API
|
||||
3. **Services contain business logic** — controllers are thin, repositories handle data access
|
||||
4. **Throw NestJS exceptions** — `NotFoundException`, `ConflictException`, etc.
|
||||
5. **Use custom decorators for request context** — `@CurrentUser()`, `@CurrentHousehold()`
|
||||
|
||||
### Web (Next.js)
|
||||
|
||||
1. **App Router conventions** — `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`
|
||||
2. **Server Components by default** — only add `'use client'` when interactive
|
||||
3. **Feature components** in `components/features/` — organized by domain (products, pantry, etc.)
|
||||
4. **UI components** in `components/ui/` — generic, reusable, no domain logic
|
||||
5. **Hooks** in `hooks/` — custom hooks for data fetching, state management
|
||||
6. **Services** in `services/` — API client wrappers
|
||||
|
||||
### Shared
|
||||
|
||||
1. **Only pure TypeScript** — no Node.js, no browser, no framework code
|
||||
2. **Types, enums, Zod schemas, pure utils** — nothing else
|
||||
3. **Barrel exports** — every directory has `index.ts`
|
||||
4. **Use `type` modifier for type-only exports/imports**
|
||||
|
||||
## Getters and Setters
|
||||
|
||||
Use TypeScript `get`/`set` accessors when a property simply exposes or lightly wraps a private backing field. Use a plain method only when the operation is async, takes multiple parameters, or has meaningful side effects beyond assignment.
|
||||
|
||||
```typescript
|
||||
// Good:Simple exposure of a private field — use accessor
|
||||
class ApiClient {
|
||||
private _accessToken: string | null = null;
|
||||
|
||||
set accessToken(token: string) {
|
||||
this._accessToken = token;
|
||||
}
|
||||
|
||||
get accessToken(): string | null {
|
||||
return this._accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
// Good:Side effects / async / multiple params — use a method
|
||||
class TokenManager {
|
||||
async setTokenFromCode(code: string, redirectUri: string) {
|
||||
const token = await exchangeCode(code, redirectUri);
|
||||
this._accessToken = token;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Backing fields use the `_camelCase` prefix to avoid name collisions with the accessor.
|
||||
|
||||
## Error Messages
|
||||
|
||||
### API error response format
|
||||
|
||||
```json
|
||||
{
|
||||
"statusCode": 404,
|
||||
"error": "Not Found",
|
||||
"message": "Product abc123 not found in household xyz",
|
||||
"timestamp": "2026-01-15T10:30:00.000Z",
|
||||
"path": "/api/v1/products/abc123"
|
||||
}
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- Messages are **end-user readable** — no stack traces, no internal IDs in production
|
||||
- Include **resource type and identifier** in not-found messages
|
||||
- Log full error details server-side with a correlation ID
|
||||
- Never leak database field names or query details
|
||||
|
||||
## Git Conventions
|
||||
|
||||
### Branch naming
|
||||
|
||||
```
|
||||
feature/MESH-001-product-crud
|
||||
fix/MESH-042-barcode-duplicate
|
||||
chore/update-dependencies
|
||||
docs/phase-2-recipe-notes
|
||||
```
|
||||
|
||||
### Commit messages (Conventional Commits)
|
||||
|
||||
```
|
||||
feat(products): add barcode lookup via Open Food Facts
|
||||
fix(pantry): correct freshness calculation for opened items
|
||||
docs: update phase-2 recipe instructions
|
||||
chore(deps): bump @nestjs/core to 10.4.0
|
||||
test(api): add integration tests for product search
|
||||
refactor(api): extract repository pattern from service
|
||||
```
|
||||
|
||||
### PR template
|
||||
|
||||
```markdown
|
||||
## What
|
||||
|
||||
Brief description of the change.
|
||||
|
||||
## Why
|
||||
|
||||
Link to issue or explanation of the need.
|
||||
|
||||
## How
|
||||
|
||||
Key implementation decisions.
|
||||
|
||||
## Testing
|
||||
|
||||
- [ ] Unit tests added/updated
|
||||
- [ ] Integration tests (if DB changes)
|
||||
- [ ] Manual testing done
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Types updated in `packages/shared`
|
||||
- [ ] Zod schemas match new fields
|
||||
- [ ] API docs (Swagger decorators) updated
|
||||
- [ ] No `any` types introduced
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
### Use NestJS Logger
|
||||
|
||||
```typescript
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class ProductsService {
|
||||
private readonly logger = new Logger(ProductsService.name);
|
||||
|
||||
async create(householdId: string, userId: string, dto: CreateProductDto) {
|
||||
this.logger.log(`Creating product "${dto.name}" for household ${householdId}`);
|
||||
// ...
|
||||
this.logger.debug(`Product created with ID ${product.id}`);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Log levels
|
||||
|
||||
- `error` — unexpected failures, unhandled exceptions
|
||||
- `warn` — recoverable issues, deprecated usage, slow queries
|
||||
- `log` — significant operations (create, delete, status transitions)
|
||||
- `debug` — detailed flow information (only shown in dev)
|
||||
- `verbose` — very detailed (disabled by default)
|
||||
|
||||
### Never log:
|
||||
|
||||
- Passwords, tokens, API keys
|
||||
- Full request bodies with sensitive user data
|
||||
- PII (unless specifically needed and compliant with privacy policy)
|
||||
Loading…
Add table
Add a link
Reference in a new issue