Skip to content

Commit 0d1e830

Browse files
committed
wip
1 parent 3c9e1d4 commit 0d1e830

59 files changed

Lines changed: 2209 additions & 8079 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1-
This is a RESTful API backend in Fastify with TypeScript, Prisma, PostgreSQL, better-auth, adminjs, openapi, Swagger, Scalar, Typebox, and DDD architecture.
1+
RESTful API backend: Fastify + TypeScript, Prisma/PostgreSQL, better-auth, adminjs, OpenAPI/Swagger/Scalar, Typebox. DDD architecture.
2+
3+
## Rules
4+
- Only use `yarn` for package management, never `npm` or `pnpm`.
5+
- Follow the module layout and dependency rules strictly. Check with `yarn run deps:validate`.
6+
- Write unit tests for domain logic and services. Write integration tests for features.
7+
- If something breaks, capture it in a test first, then fix the code. No untested hotfixes.
8+
9+
## Module layout (`src/modules/<module>/`)
10+
11+
Every module follows the same skeleton - mirror it when adding a new one:
12+
13+
- `domain/` - aggregates, value types, domain errors, pure logic (`<module>.domain.ts`, `<module>.errors.ts`, `<module>.types.ts`).
14+
- `database/` - always three files: `<module>.repository.ts` (Prisma impl), `.repository.port.ts` (interface), `.repository.mock.ts` (for tests). Record shapes live here too (`<module>.record.ts`).
15+
- `dtos/` - Typebox request/response schemas + inferred types. Paginated responses have their own `*.paginated.response.dto.ts`.
16+
- `queries/` - read-side CQRS-like handlers for complex queries (`get-*.query.ts`, `list-*.query.ts`).
17+
- `patches/` - write-side CQRS-like handlers for complex mutations (`fork-*.patch.ts`, `publish-*.patch.ts`).
18+
- `<module>.service.ts` - write-side commands; orchestrates domain + repository + events.
19+
- `<module>.route.ts` - Fastify routes; pulls deps from `fastify.diContainer.cradle` and calls service methods.
20+
- `<module>.mapper.ts` - record ↔ domain ↔ response.
21+
- `index.ts` - awilix registrations for the module.
22+
23+
Reference modules: `src/modules/model/` (full shape), `src/modules/event/` (read-only admin), `src/modules/file/` (S3 integration).
24+
25+
## Writes (Database mutations)
26+
27+
- Always go through `transactionManager.run(async (ctx) => { ... })`.
28+
- Use `repository.insertTx(ctx, entity)` / `updateFields(ctx, ...)` / `softDelete(ctx, ...)` inside the txn.
29+
- Emit domain audit via `eventRepository.insert(ctx, { type, actorId, resourceType, resourceId, payload })` in the same txn. Event types are dotted (`model.created`, `model.deleted`).
30+
- Soft delete, don't hard delete: models/users carry `deletedAt`. Call `modelDomain.assertNotDeleted(entity)` before mutating.
31+
32+
## Routes
33+
34+
- Auth: `preHandler: [requireAuth]` from `#src/shared/hooks/require-auth.ts`.
35+
- Authorization on a model: `resolveModel('read' | 'write' | 'admin')` from `#src/shared/hooks/resolve-model.ts`.
36+
- Admin-only: `[requireAuth, requireRole('admin')]`.
37+
- All routes versioned under `/v1/...`.
38+
- Use Typebox schemas for `body` / `params` / `querystring` / `response`. For querystring-heavy routes.
39+
- Use `fastify.withTypeProvider<TypeBoxTypeProvider>().route({ ... })`.
40+
- Return shapes: `201 { id }` on create (see `idDtoSchema`), `204` on update/delete, full DTO on read.
41+
42+
## Imports
43+
44+
Use path aliases, never relative `../../`:
45+
46+
- `#src/...` - app source
47+
- `#prisma/index` - generated Prisma client (output is `generated/prisma`, not `@prisma/client`)
48+
49+
## Client/request context
50+
51+
- Trusted IP comes from `env.server.ipAddressHeaders` (ordered header list), then `req.ip`. See `src/server/plugins/rate-limit.ts` for the precedence pattern - reuse it, don't reimplement.
52+
- Never store raw IPs. Hash with a rotating salt if you need uniqueness (PII / GDPR).
53+
54+
## Comments
55+
56+
- Write self-explanatory code. No decorative comments.
57+
- A comment earns its place only when the *why* isn't in the code (non-obvious constraint, workaround, surprising invariant).
58+
59+
## Dev servers
60+
61+
Don't start them.
62+
63+
## Decisions
64+
65+
Update this file with brief notes if needed.

apps/modeling-commons-backend/client/rest.d.ts

Lines changed: 215 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -368,19 +368,23 @@ export interface paths {
368368
visibility: "public" | "private" | "unlisted";
369369
isEndorsed: boolean;
370370
};
371-
latestVersion: {
371+
latestVersion: ({
372372
/** Format: uuid */
373373
modelId: string;
374+
/** @description The version number, starting at 1 and incrementing by 1 for each new version of a model. */
374375
versionNumber: number;
375376
title: string;
376377
description: string | null;
377-
netlogoFileKey: string;
378+
netlogoFileKey: string | null;
378379
netlogoVersion: string | null;
379380
infoTab: string | null;
380381
/** Format: date-time */
381382
createdAt: string;
382383
isFinalized: boolean;
383-
} | null;
384+
} & {
385+
netlogoFileDownloadUrl: string | null;
386+
previewImageUrl: string | null;
387+
}) | null;
384388
authors: ({
385389
/** Format: uuid */
386390
modelId: string;
@@ -415,6 +419,7 @@ export interface paths {
415419
*/
416420
createdAt: string;
417421
}[];
422+
previewImageUrl: string | null;
418423
counts: {
419424
versions: number;
420425
children: number;
@@ -432,6 +437,108 @@ export interface paths {
432437
patch?: never;
433438
trace?: never;
434439
};
440+
"/api/v1/models/{id}/family/card": {
441+
parameters: {
442+
query?: never;
443+
header?: never;
444+
path?: never;
445+
cookie?: never;
446+
};
447+
get: {
448+
parameters: {
449+
query?: never;
450+
header?: never;
451+
path: {
452+
id: string;
453+
};
454+
cookie?: never;
455+
};
456+
requestBody?: never;
457+
responses: {
458+
/** @description Default Response */
459+
200: {
460+
headers: {
461+
[name: string]: unknown;
462+
};
463+
content: {
464+
"application/json": {
465+
self: {
466+
/** Format: uuid */
467+
id: string;
468+
title: string;
469+
description: string | null;
470+
visibility: string;
471+
isEndorsed: boolean;
472+
/** Format: date-time */
473+
createdAt: string;
474+
latestVersionNumber: number | null;
475+
parentModelId: string | null;
476+
parentVersionNumber: number | null;
477+
authorName: string | null;
478+
versionCount: number;
479+
linkedVersionNumber: number | null;
480+
};
481+
parent: {
482+
/** Format: uuid */
483+
id: string;
484+
title: string;
485+
description: string | null;
486+
visibility: string;
487+
isEndorsed: boolean;
488+
/** Format: date-time */
489+
createdAt: string;
490+
latestVersionNumber: number | null;
491+
parentModelId: string | null;
492+
parentVersionNumber: number | null;
493+
authorName: string | null;
494+
versionCount: number;
495+
linkedVersionNumber: number | null;
496+
} | null;
497+
siblings: {
498+
/** Format: uuid */
499+
id: string;
500+
title: string;
501+
description: string | null;
502+
visibility: string;
503+
isEndorsed: boolean;
504+
/** Format: date-time */
505+
createdAt: string;
506+
latestVersionNumber: number | null;
507+
parentModelId: string | null;
508+
parentVersionNumber: number | null;
509+
authorName: string | null;
510+
versionCount: number;
511+
linkedVersionNumber: number | null;
512+
}[];
513+
children: {
514+
/** Format: uuid */
515+
id: string;
516+
title: string;
517+
description: string | null;
518+
visibility: string;
519+
isEndorsed: boolean;
520+
/** Format: date-time */
521+
createdAt: string;
522+
latestVersionNumber: number | null;
523+
parentModelId: string | null;
524+
parentVersionNumber: number | null;
525+
authorName: string | null;
526+
versionCount: number;
527+
linkedVersionNumber: number | null;
528+
}[];
529+
};
530+
};
531+
};
532+
};
533+
};
534+
put?: never;
535+
post?: never;
536+
delete?: never;
537+
options?: never;
538+
head?: never;
539+
patch?: never;
540+
trace?: never;
541+
};
435542
"/api/v1/models/{id}/children": {
436543
parameters: {
437544
query?: never;
@@ -1086,10 +1193,11 @@ export interface paths {
10861193
data: {
10871194
/** Format: uuid */
10881195
modelId: string;
1196+
/** @description The version number, starting at 1 and incrementing by 1 for each new version of a model. */
10891197
versionNumber: number;
10901198
title: string;
10911199
description: string | null;
1092-
netlogoFileKey: string;
1200+
netlogoFileKey: string | null;
10931201
netlogoVersion: string | null;
10941202
infoTab: string | null;
10951203
/** Format: date-time */
@@ -1102,6 +1210,7 @@ export interface paths {
11021210
};
11031211
};
11041212
put?: never;
1213+
/** @description Create a new model version. Send as multipart/form-data with a required "file" field (the .nlogox) plus optional "title" and "description" text fields. */
11051214
post: {
11061215
parameters: {
11071216
query?: never;
@@ -1111,14 +1220,7 @@ export interface paths {
11111220
};
11121221
cookie?: never;
11131222
};
1114-
requestBody: {
1115-
content: {
1116-
"application/json": {
1117-
title?: string;
1118-
description?: string;
1119-
};
1120-
};
1121-
};
1223+
requestBody?: never;
11221224
responses: {
11231225
/** @description Default Response */
11241226
201: {
@@ -1209,10 +1311,11 @@ export interface paths {
12091311
"application/json": {
12101312
/** Format: uuid */
12111313
modelId: string;
1314+
/** @description The version number, starting at 1 and incrementing by 1 for each new version of a model. */
12121315
versionNumber: number;
12131316
title: string;
12141317
description: string | null;
1215-
netlogoFileKey: string;
1318+
netlogoFileKey: string | null;
12161319
netlogoVersion: string | null;
12171320
infoTab: string | null;
12181321
/** Format: date-time */
@@ -1231,6 +1334,105 @@ export interface paths {
12311334
patch?: never;
12321335
trace?: never;
12331336
};
1337+
"/api/v1/models/{id}/versions/{version}/card": {
1338+
parameters: {
1339+
query?: never;
1340+
header?: never;
1341+
path?: never;
1342+
cookie?: never;
1343+
};
1344+
get: {
1345+
parameters: {
1346+
query?: never;
1347+
header?: never;
1348+
path: {
1349+
id: string;
1350+
version: number;
1351+
};
1352+
cookie?: never;
1353+
};
1354+
requestBody?: never;
1355+
responses: {
1356+
/** @description Default Response */
1357+
200: {
1358+
headers: {
1359+
[name: string]: unknown;
1360+
};
1361+
content: {
1362+
"application/json": {
1363+
version: {
1364+
/** Format: uuid */
1365+
modelId: string;
1366+
/** @description The version number, starting at 1 and incrementing by 1 for each new version of a model. */
1367+
versionNumber: number;
1368+
title: string;
1369+
description: string | null;
1370+
netlogoFileKey: string | null;
1371+
netlogoVersion: string | null;
1372+
infoTab: string | null;
1373+
/** Format: date-time */
1374+
createdAt: string;
1375+
isFinalized: boolean;
1376+
};
1377+
model: ({
1378+
/**
1379+
* Format: uuid
1380+
* @description Entity's id
1381+
* @example 2cdc8ab1-6d50-49cc-ba14-54e4ac7ec231
1382+
*/
1383+
id: string;
1384+
} & {
1385+
/**
1386+
* @description Entity creation date
1387+
* @example 2020-11-24T17:43:15.970Z
1388+
*/
1389+
createdAt: string;
1390+
/**
1391+
* @description Entity last update date
1392+
* @example 2020-11-24T17:43:15.970Z
1393+
*/
1394+
updatedAt: string;
1395+
}) & {
1396+
latestVersionNumber: number | null;
1397+
parentModelId: string | null;
1398+
parentVersionNumber: number | null;
1399+
/** @enum {unknown} */
1400+
visibility: "public" | "private" | "unlisted";
1401+
isEndorsed: boolean;
1402+
};
1403+
tags: {
1404+
/**
1405+
* Format: uuid
1406+
* @description Tag id
1407+
* @example 2cdc8ab1-6d50-49cc-ba14-54e4ac7ec231
1408+
*/
1409+
id: string;
1410+
/**
1411+
* @description Tag name
1412+
* @example climate
1413+
*/
1414+
name: string;
1415+
/**
1416+
* @description Tag creation date
1417+
* @example 2020-11-24T17:43:15.970Z
1418+
*/
1419+
createdAt: string;
1420+
}[];
1421+
netlogoFileDownloadUrl: string;
1422+
previewImageUrl: string | null;
1423+
};
1424+
};
1425+
};
1426+
};
1427+
};
1428+
put?: never;
1429+
post?: never;
1430+
delete?: never;
1431+
options?: never;
1432+
head?: never;
1433+
patch?: never;
1434+
trace?: never;
1435+
};
12341436
"/api/v1/models/{id}/versions/{version}/preview-image": {
12351437
parameters: {
12361438
query?: never;

0 commit comments

Comments
 (0)