teamshare-bridge 0.2.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge/daemon.js +36 -0
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/local-server.d.ts +1 -1
- package/dist/bridge/spawn.d.ts +2 -0
- package/dist/bridge/spawn.js +17 -0
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.js +75 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/build.js +24 -8
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/chat.js +1 -1
- package/dist/cli/commands/chat.js.map +1 -1
- package/dist/cli/commands/doc.js +2 -2
- package/dist/cli/commands/doc.js.map +1 -1
- package/dist/cli/commands/harness.js +1 -1
- package/dist/cli/commands/harness.js.map +1 -1
- package/dist/cli/commands/run.js +19 -4
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +1 -0
- package/dist/cli/commands/scaffold.js +111 -0
- package/dist/cli/commands/scaffold.js.map +1 -0
- package/dist/cli/commands/worker.d.ts +2 -0
- package/dist/cli/commands/worker.js +110 -0
- package/dist/cli/commands/worker.js.map +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/api.d.ts +35 -4
- package/dist/lib/api.js +20 -3
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/audit/docs.d.ts +5 -0
- package/dist/lib/audit/docs.js +52 -0
- package/dist/lib/audit/docs.js.map +1 -0
- package/dist/lib/audit/errors.d.ts +15 -0
- package/dist/lib/audit/errors.js +154 -0
- package/dist/lib/audit/errors.js.map +1 -0
- package/dist/lib/audit/gates.d.ts +2 -0
- package/dist/lib/audit/gates.js +57 -0
- package/dist/lib/audit/gates.js.map +1 -0
- package/dist/lib/audit/modularity.d.ts +13 -0
- package/dist/lib/audit/modularity.js +200 -0
- package/dist/lib/audit/modularity.js.map +1 -0
- package/dist/lib/audit/quality.d.ts +9 -0
- package/dist/lib/audit/quality.js +128 -0
- package/dist/lib/audit/quality.js.map +1 -0
- package/dist/lib/audit/rbac.d.ts +14 -0
- package/dist/lib/audit/rbac.js +139 -0
- package/dist/lib/audit/rbac.js.map +1 -0
- package/dist/lib/audit/reporter.d.ts +5 -0
- package/dist/lib/audit/reporter.js +107 -0
- package/dist/lib/audit/reporter.js.map +1 -0
- package/dist/lib/audit/runner.d.ts +2 -0
- package/dist/lib/audit/runner.js +150 -0
- package/dist/lib/audit/runner.js.map +1 -0
- package/dist/lib/audit/security.d.ts +9 -0
- package/dist/lib/audit/security.js +151 -0
- package/dist/lib/audit/security.js.map +1 -0
- package/dist/lib/audit/types.d.ts +67 -0
- package/dist/lib/audit/types.js +26 -0
- package/dist/lib/audit/types.js.map +1 -0
- package/dist/lib/audit/ux.d.ts +9 -0
- package/dist/lib/audit/ux.js +112 -0
- package/dist/lib/audit/ux.js.map +1 -0
- package/dist/lib/brief.d.ts +4 -0
- package/dist/lib/brief.js +78 -40
- package/dist/lib/brief.js.map +1 -1
- package/dist/lib/chat-reply.js +2 -2
- package/dist/lib/chat-reply.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/doc-reply.js +1 -1
- package/dist/lib/doc-reply.js.map +1 -1
- package/dist/lib/harness-adapter.d.ts +3 -1
- package/dist/lib/harness-adapter.js +19 -16
- package/dist/lib/harness-adapter.js.map +1 -1
- package/dist/lib/harness.d.ts +3 -1
- package/dist/lib/harness.js +8 -5
- package/dist/lib/harness.js.map +1 -1
- package/dist/lib/llm.js +87 -9
- package/dist/lib/llm.js.map +1 -1
- package/dist/lib/lock.d.ts +1 -1
- package/dist/lib/scaffold/renderer.d.ts +7 -0
- package/dist/lib/scaffold/renderer.js +37 -0
- package/dist/lib/scaffold/renderer.js.map +1 -0
- package/dist/lib/scaffold/structure.d.ts +3 -0
- package/dist/lib/scaffold/structure.js +73 -0
- package/dist/lib/scaffold/structure.js.map +1 -0
- package/dist/lib/scaffold/types.d.ts +34 -0
- package/dist/lib/scaffold/types.js +6 -0
- package/dist/lib/scaffold/types.js.map +1 -0
- package/dist/lib/scaffold/verify.d.ts +9 -0
- package/dist/lib/scaffold/verify.js +48 -0
- package/dist/lib/scaffold/verify.js.map +1 -0
- package/dist/lib/skills.d.ts +2 -2
- package/dist/lib/skills.js +2 -1
- package/dist/lib/skills.js.map +1 -1
- package/package.json +3 -2
- package/skills/teamshare-orchestrator-ask/SKILL.md +14 -0
- package/skills/teamshare-scaffold/SKILL.md +133 -0
- package/templates/backend/.env.example.tpl +13 -0
- package/templates/backend/nest-cli.json.tpl +8 -0
- package/templates/backend/package.json.tpl +41 -0
- package/templates/backend/prisma/schema.prisma.tpl +39 -0
- package/templates/backend/src/app.module.ts.tpl +24 -0
- package/templates/backend/src/common/filters/all-exceptions.filter.ts.tpl +59 -0
- package/templates/backend/src/common/guards/jwt-auth.guard.ts.tpl +27 -0
- package/templates/backend/src/common/guards/rbac.guard.ts.tpl +48 -0
- package/templates/backend/src/common/interceptors/transform.interceptor.ts.tpl +44 -0
- package/templates/backend/src/common/pipes/zod-validation.pipe.ts.tpl +29 -0
- package/templates/backend/src/common/swagger/envelope.schemas.ts.tpl +53 -0
- package/templates/backend/src/common/swagger/error.schemas.ts.tpl +62 -0
- package/templates/backend/src/main.ts.tpl +27 -0
- package/templates/backend/src/modules/example/example.controller.ts.tpl +102 -0
- package/templates/backend/src/modules/example/example.dto.ts.tpl +14 -0
- package/templates/backend/src/modules/example/example.module.ts.tpl +10 -0
- package/templates/backend/src/modules/example/example.service.ts.tpl +45 -0
- package/templates/backend/tsconfig.json.tpl +26 -0
- package/templates/common/.gitignore.tpl +42 -0
- package/templates/common/AGENTS.md.tpl +58 -0
- package/templates/common/docs/adr/README.md +25 -0
- package/templates/common/docs/api-contract.md.tpl +72 -0
- package/templates/web/next.config.ts.tpl +7 -0
- package/templates/web/package.json.tpl +37 -0
- package/templates/web/src/app/globals.css.tpl +48 -0
- package/templates/web/src/app/layout.tsx.tpl +25 -0
- package/templates/web/src/components/shared/app-button.tsx.tpl +59 -0
- package/templates/web/src/components/shared/app-empty-state.tsx.tpl +39 -0
- package/templates/web/src/components/shared/app-list-toolbar.tsx.tpl +63 -0
- package/templates/web/src/components/shared/app-skeleton.tsx.tpl +14 -0
- package/templates/web/src/components/shared/app-table.tsx.tpl +128 -0
- package/templates/web/src/components/shared/index.ts.tpl +5 -0
- package/templates/web/src/hooks/use-list-state.ts.tpl +72 -0
- package/templates/web/src/hooks/use-url-tab.ts.tpl +25 -0
- package/templates/web/src/lib/api/client.ts.tpl +123 -0
- package/templates/web/src/lib/providers.tsx.tpl +35 -0
- package/templates/web/src/lib/query-keys.ts.tpl +21 -0
- package/templates/web/src/lib/utils.ts.tpl +7 -0
- package/templates/web/src/lib/validation/schemas.ts.tpl +15 -0
- package/templates/web/src/modules/example/views/example-list-view.tsx.tpl +98 -0
- package/templates/web/tsconfig.json.tpl +23 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
|
+
import { AuthGuard as PassportAuthGuard } from "@nestjs/passport";
|
|
4
|
+
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class AuthGuard extends PassportAuthGuard("jwt") {
|
|
8
|
+
constructor(private reflector: Reflector) {
|
|
9
|
+
super();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
canActivate(context: ExecutionContext) {
|
|
13
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
14
|
+
context.getHandler(),
|
|
15
|
+
context.getClass(),
|
|
16
|
+
]);
|
|
17
|
+
if (isPublic) return true;
|
|
18
|
+
return super.canActivate(context);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
handleRequest(err: unknown, user: unknown) {
|
|
22
|
+
if (err || !user) {
|
|
23
|
+
throw err ?? new UnauthorizedException("Invalid or missing token");
|
|
24
|
+
}
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CanActivate,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
ForbiddenException,
|
|
5
|
+
Injectable,
|
|
6
|
+
} from "@nestjs/common";
|
|
7
|
+
import { Reflector } from "@nestjs/core";
|
|
8
|
+
import { PERMISSIONS_KEY } from "../decorators/permissions.decorator";
|
|
9
|
+
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class RbacGuard implements CanActivate {
|
|
13
|
+
constructor(private reflector: Reflector) {}
|
|
14
|
+
|
|
15
|
+
canActivate(context: ExecutionContext): boolean {
|
|
16
|
+
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
17
|
+
context.getHandler(),
|
|
18
|
+
context.getClass(),
|
|
19
|
+
]);
|
|
20
|
+
if (isPublic) return true;
|
|
21
|
+
|
|
22
|
+
const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
|
|
23
|
+
PERMISSIONS_KEY,
|
|
24
|
+
[context.getHandler(), context.getClass()]
|
|
25
|
+
);
|
|
26
|
+
if (!requiredPermissions || requiredPermissions.length === 0) return true;
|
|
27
|
+
|
|
28
|
+
const request = context.switchToHttp().getRequest();
|
|
29
|
+
const user = request.user;
|
|
30
|
+
|
|
31
|
+
if (!user) {
|
|
32
|
+
throw new ForbiddenException("Authentication required");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const userPermissions: string[] = user.permissions ?? [];
|
|
36
|
+
const hasPermission = requiredPermissions.every((p) =>
|
|
37
|
+
userPermissions.includes(p)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!hasPermission) {
|
|
41
|
+
throw new ForbiddenException(
|
|
42
|
+
`Missing required permissions: ${requiredPermissions.join(", ")}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
NestInterceptor,
|
|
4
|
+
ExecutionContext,
|
|
5
|
+
CallHandler,
|
|
6
|
+
} from "@nestjs/common";
|
|
7
|
+
import { Observable, map } from "rxjs";
|
|
8
|
+
|
|
9
|
+
export interface SuccessResponse<T> {
|
|
10
|
+
success: true;
|
|
11
|
+
data: T;
|
|
12
|
+
pagination?: {
|
|
13
|
+
page: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
total: number;
|
|
16
|
+
totalPages: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class TransformInterceptor<T>
|
|
22
|
+
implements NestInterceptor<T, SuccessResponse<T>>
|
|
23
|
+
{
|
|
24
|
+
intercept(
|
|
25
|
+
context: ExecutionContext,
|
|
26
|
+
next: CallHandler
|
|
27
|
+
): Observable<SuccessResponse<T>> {
|
|
28
|
+
return next.handle().pipe(
|
|
29
|
+
map((data) => {
|
|
30
|
+
if (data && typeof data === "object" && "data" in data && "pagination" in data) {
|
|
31
|
+
return {
|
|
32
|
+
success: true as const,
|
|
33
|
+
data: data.data,
|
|
34
|
+
pagination: data.pagination,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
success: true as const,
|
|
39
|
+
data,
|
|
40
|
+
};
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PipeTransform,
|
|
3
|
+
Injectable,
|
|
4
|
+
BadRequestException,
|
|
5
|
+
} from "@nestjs/common";
|
|
6
|
+
import { ZodSchema } from "zod";
|
|
7
|
+
|
|
8
|
+
@Injectable()
|
|
9
|
+
export class ZodValidationPipe implements PipeTransform {
|
|
10
|
+
constructor(
|
|
11
|
+
private schema: ZodSchema,
|
|
12
|
+
private source: "body" | "query" | "params" = "body"
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
transform(value: unknown) {
|
|
16
|
+
const result = this.schema.safeParse(value);
|
|
17
|
+
if (!result.success) {
|
|
18
|
+
const issues = result.error.issues.map((i) => ({
|
|
19
|
+
path: i.path.join("."),
|
|
20
|
+
message: i.message,
|
|
21
|
+
}));
|
|
22
|
+
throw new BadRequestException({
|
|
23
|
+
message: "Validation failed",
|
|
24
|
+
details: issues,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return result.data;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
export function successSchema(dataSchema: Record<string, unknown>) {
|
|
3
|
+
return {
|
|
4
|
+
type: "object",
|
|
5
|
+
properties: {
|
|
6
|
+
success: { type: "boolean", example: true },
|
|
7
|
+
data: dataSchema,
|
|
8
|
+
},
|
|
9
|
+
required: ["success", "data"],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function paginatedSchema(dataSchema: Record<string, unknown>) {
|
|
14
|
+
return {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
success: { type: "boolean", example: true },
|
|
18
|
+
data: {
|
|
19
|
+
type: "array",
|
|
20
|
+
items: dataSchema,
|
|
21
|
+
},
|
|
22
|
+
pagination: {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: {
|
|
25
|
+
page: { type: "number", example: 1 },
|
|
26
|
+
limit: { type: "number", example: 20 },
|
|
27
|
+
total: { type: "number", example: 100 },
|
|
28
|
+
totalPages: { type: "number", example: 5 },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["success", "data", "pagination"],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function errorSchema() {
|
|
37
|
+
return {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
success: { type: "boolean", example: false },
|
|
41
|
+
error: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
code: { type: "string", example: "NOT_FOUND" },
|
|
45
|
+
message: { type: "string", example: "Resource not found" },
|
|
46
|
+
details: {},
|
|
47
|
+
},
|
|
48
|
+
required: ["code", "message"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["success", "error"],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { applyDecorators } from "@nestjs/common";
|
|
2
|
+
import { ApiResponse } from "@nestjs/swagger";
|
|
3
|
+
|
|
4
|
+
export const INTERNAL_ERROR_CODES = [
|
|
5
|
+
"INTERNAL_ERROR",
|
|
6
|
+
"BAD_REQUEST",
|
|
7
|
+
"UNAUTHORIZED",
|
|
8
|
+
"FORBIDDEN",
|
|
9
|
+
"NOT_FOUND",
|
|
10
|
+
"CONFLICT",
|
|
11
|
+
"UNPROCESSABLE_ENTITY",
|
|
12
|
+
"TOO_MANY_REQUESTS",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export function errs(...codes: (typeof INTERNAL_ERROR_CODES)[number][]) {
|
|
16
|
+
const descriptions: Record<string, string> = {
|
|
17
|
+
INTERNAL_ERROR: "Internal server error",
|
|
18
|
+
BAD_REQUEST: "Invalid request parameters",
|
|
19
|
+
UNAUTHORIZED: "Authentication required",
|
|
20
|
+
FORBIDDEN: "Insufficient permissions",
|
|
21
|
+
NOT_FOUND: "Resource not found",
|
|
22
|
+
CONFLICT: "Resource already exists",
|
|
23
|
+
UNPROCESSABLE_ENTITY: "Validation failed",
|
|
24
|
+
TOO_MANY_REQUESTS: "Rate limit exceeded",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return applyDecorators(
|
|
28
|
+
...codes.map((code) =>
|
|
29
|
+
ApiResponse({
|
|
30
|
+
status: codeToStatus(code),
|
|
31
|
+
description: descriptions[code] ?? code,
|
|
32
|
+
schema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
success: { type: "boolean", example: false },
|
|
36
|
+
error: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
code: { type: "string", example: code },
|
|
40
|
+
message: { type: "string", example: descriptions[code] },
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function codeToStatus(code: string): number {
|
|
51
|
+
const map: Record<string, number> = {
|
|
52
|
+
BAD_REQUEST: 400,
|
|
53
|
+
UNAUTHORIZED: 401,
|
|
54
|
+
FORBIDDEN: 403,
|
|
55
|
+
NOT_FOUND: 404,
|
|
56
|
+
CONFLICT: 409,
|
|
57
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
58
|
+
TOO_MANY_REQUESTS: 429,
|
|
59
|
+
INTERNAL_ERROR: 500,
|
|
60
|
+
};
|
|
61
|
+
return map[code] ?? 500;
|
|
62
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NestFactory } from "@nestjs/core";
|
|
2
|
+
import { ValidationPipe } from "@nestjs/common";
|
|
3
|
+
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
|
|
4
|
+
import { AppModule } from "./app.module";
|
|
5
|
+
|
|
6
|
+
async function bootstrap() {
|
|
7
|
+
const app = await NestFactory.create(AppModule);
|
|
8
|
+
|
|
9
|
+
app.setGlobalPrefix("api");
|
|
10
|
+
app.enableCors();
|
|
11
|
+
app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
|
|
12
|
+
|
|
13
|
+
const config = new DocumentBuilder()
|
|
14
|
+
.setTitle("{{projectName}} API")
|
|
15
|
+
.setDescription("{{description}}")
|
|
16
|
+
.setVersion("0.1.0")
|
|
17
|
+
.addBearerAuth()
|
|
18
|
+
.build();
|
|
19
|
+
|
|
20
|
+
const document = SwaggerModule.createDocument(app, config);
|
|
21
|
+
SwaggerModule.setup("docs", app, document);
|
|
22
|
+
|
|
23
|
+
await app.listen({{apiPort}});
|
|
24
|
+
console.log(`Application running on: http://localhost:{{apiPort}}`);
|
|
25
|
+
console.log(`Swagger docs: http://localhost:{{apiPort}}/docs`);
|
|
26
|
+
}
|
|
27
|
+
bootstrap();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Controller,
|
|
3
|
+
Get,
|
|
4
|
+
Post,
|
|
5
|
+
Param,
|
|
6
|
+
Query,
|
|
7
|
+
Body,
|
|
8
|
+
ParseUUIDPipe,
|
|
9
|
+
} from "@nestjs/common";
|
|
10
|
+
import {
|
|
11
|
+
ApiTags,
|
|
12
|
+
ApiOperation,
|
|
13
|
+
ApiParam,
|
|
14
|
+
ApiQuery,
|
|
15
|
+
ApiBody,
|
|
16
|
+
ApiOkResponse,
|
|
17
|
+
ApiBearerAuth,
|
|
18
|
+
} from "@nestjs/swagger";
|
|
19
|
+
import { Permissions } from "../../common/decorators/permissions.decorator";
|
|
20
|
+
import { Public } from "../../common/decorators/public.decorator";
|
|
21
|
+
import { ZodValidationPipe } from "../../common/pipes/zod-validation.pipe";
|
|
22
|
+
import { successSchema, paginatedSchema } from "../../common/swagger/envelope.schemas";
|
|
23
|
+
import { errs } from "../../common/swagger/error.schemas";
|
|
24
|
+
import { ExampleService } from "./example.service";
|
|
25
|
+
import { CreateExampleSchema, ExampleQuerySchema } from "./example.dto";
|
|
26
|
+
|
|
27
|
+
@ApiTags("examples")
|
|
28
|
+
@ApiBearerAuth()
|
|
29
|
+
@Controller("examples")
|
|
30
|
+
export class ExampleController {
|
|
31
|
+
constructor(private readonly exampleService: ExampleService) {}
|
|
32
|
+
|
|
33
|
+
@Get()
|
|
34
|
+
@Public()
|
|
35
|
+
@ApiOperation({ summary: "List examples", description: "Retrieve a paginated list of examples" })
|
|
36
|
+
@ApiQuery({ name: "page", required: false, example: 1 })
|
|
37
|
+
@ApiQuery({ name: "limit", required: false, example: 20 })
|
|
38
|
+
@ApiQuery({ name: "search", required: false })
|
|
39
|
+
@ApiOkResponse({
|
|
40
|
+
description: "Paginated list of examples",
|
|
41
|
+
schema: paginatedSchema({
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
id: { type: "string", format: "uuid" },
|
|
45
|
+
name: { type: "string" },
|
|
46
|
+
createdAt: { type: "string", format: "date-time" },
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
})
|
|
50
|
+
@errs("INTERNAL_ERROR")
|
|
51
|
+
async list(
|
|
52
|
+
@Query(new ZodValidationPipe(ExampleQuerySchema, "query")) query: {
|
|
53
|
+
page: number;
|
|
54
|
+
limit: number;
|
|
55
|
+
search?: string;
|
|
56
|
+
}
|
|
57
|
+
) {
|
|
58
|
+
return this.exampleService.list(query);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@Post()
|
|
62
|
+
@Permissions("example:create")
|
|
63
|
+
@ApiOperation({ summary: "Create example", description: "Create a new example" })
|
|
64
|
+
@ApiBody({ schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"] } })
|
|
65
|
+
@ApiOkResponse({
|
|
66
|
+
description: "Created example",
|
|
67
|
+
schema: successSchema({
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
id: { type: "string", format: "uuid" },
|
|
71
|
+
name: { type: "string" },
|
|
72
|
+
createdAt: { type: "string", format: "date-time" },
|
|
73
|
+
},
|
|
74
|
+
}),
|
|
75
|
+
})
|
|
76
|
+
@errs("BAD_REQUEST", "UNAUTHORIZED", "FORBIDDEN", "INTERNAL_ERROR")
|
|
77
|
+
async create(
|
|
78
|
+
@Body(new ZodValidationPipe(CreateExampleSchema)) body: { name: string }
|
|
79
|
+
) {
|
|
80
|
+
return this.exampleService.create(body);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Get(":id")
|
|
84
|
+
@Permissions("example:read")
|
|
85
|
+
@ApiOperation({ summary: "Get example", description: "Get an example by ID" })
|
|
86
|
+
@ApiParam({ name: "id", required: true })
|
|
87
|
+
@ApiOkResponse({
|
|
88
|
+
description: "Example details",
|
|
89
|
+
schema: successSchema({
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
id: { type: "string", format: "uuid" },
|
|
93
|
+
name: { type: "string" },
|
|
94
|
+
createdAt: { type: "string", format: "date-time" },
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
})
|
|
98
|
+
@errs("NOT_FOUND", "UNAUTHORIZED", "FORBIDDEN", "INTERNAL_ERROR")
|
|
99
|
+
async getOne(@Param("id", ParseUUIDPipe) id: string) {
|
|
100
|
+
return this.exampleService.getOne(id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const CreateExampleSchema = z.object({
|
|
4
|
+
name: z.string().min(1).max(200),
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
export const ExampleQuerySchema = z.object({
|
|
8
|
+
page: z.coerce.number().int().positive().default(1),
|
|
9
|
+
limit: z.coerce.number().int().positive().max(100).default(20),
|
|
10
|
+
search: z.string().optional(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type CreateExampleInput = z.infer<typeof CreateExampleSchema>;
|
|
14
|
+
export type ExampleQueryInput = z.infer<typeof ExampleQuerySchema>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Module } from "@nestjs/common";
|
|
2
|
+
import { ExampleController } from "./example.controller";
|
|
3
|
+
import { ExampleService } from "./example.service";
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
controllers: [ExampleController],
|
|
7
|
+
providers: [ExampleService],
|
|
8
|
+
exports: [ExampleService],
|
|
9
|
+
})
|
|
10
|
+
export class ExampleModule {}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
2
|
+
import { PrismaService } from "../../prisma/prisma.service";
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class ExampleService {
|
|
6
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
7
|
+
|
|
8
|
+
async list(query: { page: number; limit: number; search?: string }) {
|
|
9
|
+
const where = query.search
|
|
10
|
+
? { name: { contains: query.search, mode: "insensitive" as const } }
|
|
11
|
+
: {};
|
|
12
|
+
|
|
13
|
+
const [data, total] = await Promise.all([
|
|
14
|
+
this.prisma.example.findMany({
|
|
15
|
+
where,
|
|
16
|
+
skip: (query.page - 1) * query.limit,
|
|
17
|
+
take: query.limit,
|
|
18
|
+
orderBy: { createdAt: "desc" },
|
|
19
|
+
}),
|
|
20
|
+
this.prisma.example.count({ where }),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
data,
|
|
25
|
+
pagination: {
|
|
26
|
+
page: query.page,
|
|
27
|
+
limit: query.limit,
|
|
28
|
+
total,
|
|
29
|
+
totalPages: Math.ceil(total / query.limit),
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async create(data: { name: string }) {
|
|
35
|
+
return this.prisma.example.create({ data });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getOne(id: string) {
|
|
39
|
+
const example = await this.prisma.example.findUnique({ where: { id } });
|
|
40
|
+
if (!example) {
|
|
41
|
+
throw new NotFoundException("Example not found");
|
|
42
|
+
}
|
|
43
|
+
return example;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2021",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": true,
|
|
16
|
+
"noImplicitAny": false,
|
|
17
|
+
"strictBindCallApply": false,
|
|
18
|
+
"forceConsistentCasingInFileNames": false,
|
|
19
|
+
"noFallthroughCasesInSwitch": false,
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["src/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*"],
|
|
25
|
+
"exclude": ["node_modules", "dist"]
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
|
|
4
|
+
# Build output
|
|
5
|
+
dist/
|
|
6
|
+
.next/
|
|
7
|
+
out/
|
|
8
|
+
|
|
9
|
+
# Environment
|
|
10
|
+
.env
|
|
11
|
+
.env.local
|
|
12
|
+
.env.*.local
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.vscode/
|
|
16
|
+
.idea/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
*~
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# Debug
|
|
26
|
+
npm-debug.log*
|
|
27
|
+
yarn-debug.log*
|
|
28
|
+
yarn-error.log*
|
|
29
|
+
|
|
30
|
+
# Prisma
|
|
31
|
+
prisma/migrations/
|
|
32
|
+
|
|
33
|
+
# TypeScript
|
|
34
|
+
*.tsbuildinfo
|
|
35
|
+
next-env.d.ts
|
|
36
|
+
|
|
37
|
+
# Logs
|
|
38
|
+
*.log
|
|
39
|
+
|
|
40
|
+
# Temp
|
|
41
|
+
tmp/
|
|
42
|
+
temp/
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# AGENTS.md - {{projectName}}
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
{{description}}
|
|
6
|
+
|
|
7
|
+
## Tech stack
|
|
8
|
+
|
|
9
|
+
- **Frontend:** Next.js 16, React 19, TypeScript, Tailwind v4, shadcn, react-query v5, TanStack Table v8, react-hook-form, zod, next-auth v4, next-themes, socket.io-client
|
|
10
|
+
- **Backend:** NestJS 11, Prisma 6, zod, Swagger/OpenAPI, passport-jwt, argon2, socket.io
|
|
11
|
+
- **Database:** PostgreSQL
|
|
12
|
+
|
|
13
|
+
## Folder structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
{{slug}}/
|
|
17
|
+
├── frontend/ # Next.js web app
|
|
18
|
+
│ ├── src/
|
|
19
|
+
│ │ ├── app/ # App Router routes
|
|
20
|
+
│ │ ├── components/
|
|
21
|
+
│ │ │ ├── ui/ # Vendor primitives (shadcn) — do not edit
|
|
22
|
+
│ │ │ └── shared/# Shared wrappers (AppButton, AppTable, etc.)
|
|
23
|
+
│ │ ├── hooks/ # Custom React hooks
|
|
24
|
+
│ │ ├── lib/ # Utilities, API client, query keys, validation
|
|
25
|
+
│ │ └── modules/ # Feature modules (views, components, columns)
|
|
26
|
+
│ └── package.json
|
|
27
|
+
├── backend/ # NestJS API
|
|
28
|
+
│ ├── prisma/ # Schema + migrations
|
|
29
|
+
│ ├── src/
|
|
30
|
+
│ │ ├── common/ # Guards, filters, interceptors, pipes, swagger
|
|
31
|
+
│ │ ├── modules/ # Feature modules (controller, service, dto, module)
|
|
32
|
+
│ │ ├── generated/ # Prisma + zod generated output
|
|
33
|
+
│ │ └── main.ts
|
|
34
|
+
│ └── package.json
|
|
35
|
+
└── docs/ # Project documentation
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Build commands
|
|
39
|
+
|
|
40
|
+
| Command | Location | Description |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `npm run dev` | frontend/ | Start Next.js dev server |
|
|
43
|
+
| `npm run build` | frontend/ | Build Next.js app |
|
|
44
|
+
| `npm run dev` | backend/ | Start NestJS dev server |
|
|
45
|
+
| `npm run build` | backend/ | Build NestJS app |
|
|
46
|
+
| `npx prisma generate` | backend/ | Regenerate Prisma client |
|
|
47
|
+
| `npx prisma migrate dev` | backend/ | Run migrations |
|
|
48
|
+
|
|
49
|
+
## Conventions
|
|
50
|
+
|
|
51
|
+
- Shared components use a consistent prefix (e.g., App*, or your own choice)
|
|
52
|
+
- Use `data-slot="app-*"` attribute on shared components for styling hooks
|
|
53
|
+
- Backend uses ZodValidationPipe for request validation
|
|
54
|
+
- All endpoints return `{ success: true, data }` envelope
|
|
55
|
+
- RBAC via `@Permissions()` decorator, `@Public()` for open routes
|
|
56
|
+
- Query keys live in `src/lib/query-keys.ts`
|
|
57
|
+
- Validation schemas live in `src/lib/validation/schemas.ts`
|
|
58
|
+
- Loading states: every query view has skeleton + error + empty state
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Architecture Decision Records
|
|
2
|
+
|
|
3
|
+
One file per decision, named `NNN-short-title.md` (e.g. `001-orm-choice.md`).
|
|
4
|
+
|
|
5
|
+
Format for each ADR:
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
# ADR-NNN: <Title>
|
|
9
|
+
|
|
10
|
+
**Status:** proposed | accepted | superseded by ADR-MMM
|
|
11
|
+
**Date:** YYYY-MM-DD
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
What problem or force drove this decision?
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
What was decided, stated plainly.
|
|
18
|
+
|
|
19
|
+
## Consequences
|
|
20
|
+
What becomes easier, what becomes harder, what must now be true.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Write an ADR whenever a choice is expensive to reverse (ORM, validation
|
|
24
|
+
strategy, folder layout, auth model). Agents working in this repo should
|
|
25
|
+
read existing ADRs before proposing conflicting changes.
|