@nest-arch/tui 0.4.0 → 0.4.4

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.
Files changed (64) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/{constants-C3xmDWeK.js → constants-7dJLZpB2.js} +1 -1
  3. package/dist/index.js +188 -134
  4. package/dist/templates/addons/husky/pre-commit.hbs +11 -26
  5. package/dist/templates/base/.env.example.hbs +9 -0
  6. package/dist/templates/base/.env.hbs +7 -0
  7. package/dist/templates/base/README.md.hbs +16 -0
  8. package/dist/templates/base/pnpm-workspace.yaml.hbs +6 -0
  9. package/dist/templates/base/src/app.module.ts.hbs +68 -1
  10. package/dist/templates/base/src/app.router.ts.hbs +1 -2
  11. package/dist/templates/base/src/database/adapters/mongo/prisma-mongo.adapter.ts.hbs +11 -3
  12. package/dist/templates/base/src/database/adapters/sql/prisma-sql.adapter.ts.hbs +1 -1
  13. package/dist/templates/base/src/database/db.module.ts.hbs +24 -8
  14. package/dist/templates/base/src/database/db.service.ts.hbs +20 -0
  15. package/dist/templates/base/src/database/entities/app.entity.ts.hbs +25 -0
  16. package/dist/templates/base/src/database/index.ts.hbs +19 -0
  17. package/dist/templates/base/src/database/orm/drizzle/schema.ts.hbs +42 -11
  18. package/dist/templates/base/src/database/orm/typeorm/data-source.ts.hbs +12 -1
  19. package/dist/templates/base/src/libs/middlewares/auth.trpc.middleware.ts.hbs +47 -0
  20. package/dist/templates/base/src/libs/middlewares/rate-limit.trpc.middleware.ts.hbs +43 -0
  21. package/dist/templates/base/src/libs/trpc/inner.context.ts.hbs +6 -3
  22. package/dist/templates/base/src/main.ts.hbs +13 -4
  23. package/dist/templates/base/src/modules/users/entities/user.entity.ts.hbs +22 -0
  24. package/dist/templates/base/test/app.e2e-spec.ts.hbs +670 -37
  25. package/dist/templates/base/test/bootstrap.e2e-spec.ts.hbs +11 -1
  26. package/dist/templates/extras/docker/.dockerignore.hbs +8 -1
  27. package/dist/templates/extras/docker/Dockerfile.hbs +8 -10
  28. package/dist/templates/extras/health/src/health/health.controller.ts.hbs +54 -0
  29. package/dist/templates/extras/health/src/health/health.module.ts.hbs +9 -0
  30. package/dist/templates/extras/swagger/create-docs.ts.hbs +9 -3
  31. package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -17
  32. package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +3 -2
  33. package/dist/templates/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  34. package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +165 -53
  35. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +12 -0
  36. package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +12 -0
  37. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +18 -8
  38. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +41 -13
  39. package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +68 -10
  40. package/dist/templates/monorepo/apps/api-gateway/package.json.hbs +2 -2
  41. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.spec.ts.hbs +5 -0
  42. package/dist/templates/monorepo/apps/api-gateway/src/app.controller.ts.hbs +6 -0
  43. package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +32 -1
  44. package/dist/templates/monorepo/apps/api-gateway/src/app.service.ts.hbs +1 -1
  45. package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +13 -4
  46. package/dist/templates/monorepo/apps/api-gateway/test/app.e2e-spec.ts.hbs +26 -1
  47. package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
  48. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
  49. package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +147 -47
  50. package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +7 -0
  51. package/dist/templates/monorepo/packages/shared/package.json.hbs +1 -0
  52. package/dist/templates/monorepo/packages/shared/src/auth.ts.hbs +6 -0
  53. package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +10 -2
  54. package/dist/templates/single-project/architecture/nest-api/.env.hbs +6 -0
  55. package/dist/templates/single-project/architecture/nest-api/package.json.hbs +1 -1
  56. package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +13 -4
  57. package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +3 -3
  58. package/dist/templates/single-project/architecture/nest-microservice/proto/app.proto.hbs +13 -0
  59. package/dist/templates/single-project/architecture/nest-microservice/src/app.controller.ts.hbs +10 -4
  60. package/dist/templates/single-project/architecture/nest-microservice/src/app.service.ts.hbs +2 -2
  61. package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +3 -3
  62. package/dist/templates/single-project/architecture/nest-microservice/test/microservice.e2e-spec.ts.hbs +7 -1
  63. package/dist/templates/single-project/shared/config/env.ts.hbs +12 -1
  64. package/package.json +2 -2
@@ -46,7 +46,17 @@ it("boots the compiled application through main.ts", async () => {
46
46
  const port = await getFreePort();
47
47
  const origin = "https://allowed.example";
48
48
  const application = spawn(process.execPath, ["dist/main.js"], {
49
- env: { ...process.env, CORS_ORIGIN: origin, NODE_ENV: "test", PORT: String(port) },
49
+ env: {
50
+ ...process.env,
51
+ CORS_ORIGIN: origin,
52
+ {{#if (and isPassportAuth (not hasDatabase))}}
53
+ OIDC_AUDIENCE: "generated-resource-server",
54
+ OIDC_ISSUER: "https://identity.example.test/",
55
+ OIDC_JWKS_URI: "https://identity.example.test/.well-known/jwks.json",
56
+ {{/if}}
57
+ NODE_ENV: "test",
58
+ PORT: String(port),
59
+ },
50
60
  stdio: ["ignore", "pipe", "pipe"],
51
61
  });
52
62
  startedProcesses.push(application);
@@ -1 +1,8 @@
1
- node_modules dist npm-debug.log .env
1
+ node_modules
2
+ dist
3
+ npm-debug.log
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+ !.env.test.example
8
+ .git
@@ -3,23 +3,21 @@ FROM node:22-alpine
3
3
  WORKDIR /app
4
4
 
5
5
  {{#if (eq packageManager "pnpm")}}
6
- RUN corepack enable && corepack prepare pnpm@latest --activate
7
- COPY package.json pnpm-lock.yaml ./
8
- RUN pnpm install --frozen-lockfile
6
+ RUN corepack enable
9
7
  {{else if (eq packageManager "yarn")}}
10
- COPY package.json yarn.lock ./
11
- RUN yarn install --frozen-lockfile
8
+ RUN corepack enable
12
9
  {{else}}
13
- COPY package*.json ./
14
- RUN npm ci
15
10
  {{/if}}
16
11
 
17
12
  COPY . .
18
- RUN {{packageManager}} run build
13
+ RUN POSTGRES_URL=postgresql://postgres:postgres@postgres:5432/build_db MYSQL_URL=mysql://root:mysql@mysql:3306/build_db MONGODB_URL=mongodb://mongodb:27017/build_db {{#if (eq auth "passport")}}JWT_ACCESS_SECRET=build-only-access-secret-that-is-long-enough JWT_REFRESH_SECRET=build-only-refresh-secret-that-is-long-enough {{/if}}{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}BETTER_AUTH_SECRET=build-only-better-auth-secret-that-is-long-enough BETTER_AUTH_URL=http://localhost:{{#if (eq httpProvider "fastify")}}3001{{else}}3000{{/if}} {{/if}}{{#if (eq packageManager "pnpm")}}pnpm install --no-lockfile{{else if (eq packageManager "yarn")}}yarn install{{else if (eq packageManager "bun")}}bun install{{else}}npm install{{/if}}
14
+ RUN {{#if (eq packageManager "pnpm")}}pnpm run{{else if (eq packageManager "yarn")}}yarn{{else if (eq packageManager "bun")}}bun run{{else}}npm run{{/if}} build
19
15
 
20
- EXPOSE 3000
16
+ ENV NODE_ENV=production
17
+
18
+ EXPOSE {{#if (eq httpProvider "fastify")}}3001{{else}}3000{{/if}}
21
19
  {{#if (includes microservices "grpc")}}
22
20
  EXPOSE 5000
23
21
  {{/if}}
24
22
 
25
- CMD ["{{packageManager}}", "run", "start:prod"]
23
+ CMD ["node", "dist/main"]
@@ -0,0 +1,54 @@
1
+ import { Controller, Get } from "@nestjs/common";
2
+ import { HealthCheck, HealthCheckService } from "@nestjs/terminus";
3
+ {{#if isBetterAuth}}
4
+ import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
5
+ {{/if}}
6
+ {{#if hasDatabase}}
7
+ import { DbService } from "../database/db.service.js";
8
+ {{#if useDrizzle}}
9
+ import { sql } from "drizzle-orm";
10
+ {{/if}}
11
+ {{/if}}
12
+
13
+ {{#if isBetterAuth}}
14
+ @AllowAnonymous()
15
+ {{/if}}
16
+ @Controller("health")
17
+ export class HealthController {
18
+ constructor(
19
+ private readonly health: HealthCheckService{{#if hasDatabase}},
20
+ private readonly db: DbService{{/if}}
21
+ ) {}
22
+
23
+ @Get()
24
+ @HealthCheck()
25
+ check() {
26
+ return this.health.check([
27
+ () => ({ app: { status: "up" } }),
28
+ {{#if hasDatabase}}
29
+ async () => {
30
+ try {
31
+ {{#if (or usePrisma isPrisma8)}}
32
+ await this.db.prisma.$queryRaw`SELECT 1`;
33
+ {{else if useDrizzle}}
34
+ {{#if hasSqlite}}
35
+ await this.db.drizzle.get(sql`SELECT 1`);
36
+ {{else}}
37
+ await this.db.drizzle.execute(sql`SELECT 1`);
38
+ {{/if}}
39
+ {{else if useTypeorm}}
40
+ await this.db.typeorm.query("SELECT 1");
41
+ {{else if hasMongodb}}
42
+ await this.db.raw.mongo.command({ ping: 1 });
43
+ {{else}}
44
+ await this.db.raw.sql.query("SELECT 1");
45
+ {{/if}}
46
+ return { db: { status: "up" } };
47
+ } catch {
48
+ return { db: { status: "down" } };
49
+ }
50
+ },
51
+ {{/if}}
52
+ ]);
53
+ }
54
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { TerminusModule } from "@nestjs/terminus";
3
+ import { HealthController } from "./health.controller.js";
4
+
5
+ @Module({
6
+ controllers: [HealthController],
7
+ imports: [TerminusModule],
8
+ })
9
+ export class HealthModule {}
@@ -19,6 +19,7 @@ interface DocsIntegrations {
19
19
  zod?: {
20
20
  withFastify?: boolean;
21
21
  };
22
+ bearerAuth?: boolean;
22
23
  }
23
24
 
24
25
  export interface CreateDocsOptions {
@@ -176,11 +177,16 @@ export async function createDocs(
176
177
  app: INestApplication,
177
178
  options: CreateDocsOptions = {}
178
179
  ): Promise<OpenAPIObject> {
179
- const config = new DocumentBuilder()
180
+ const builder = new DocumentBuilder()
180
181
  .setTitle(options.title ?? "NestJS API")
181
182
  .setDescription(options.description ?? "Generated with Nest Arch")
182
- .setVersion(options.version ?? "1.0.0")
183
- .build();
183
+ .setVersion(options.version ?? "1.0.0");
184
+
185
+ if (options.integrations?.bearerAuth === true) {
186
+ builder.addBearerAuth();
187
+ }
188
+
189
+ const config = builder.build();
184
190
 
185
191
  let baseSpec = SwaggerModule.createDocument(app, config) as OpenAPIObject;
186
192
  baseSpec = await applyZodCleanup(baseSpec, options.integrations?.zod);
@@ -1,33 +1,22 @@
1
1
  import { Module } from "@nestjs/common";
2
- import { Query, Resolver, GraphQLModule } from "@nestjs/graphql";
2
+ import { GraphQLModule } from "@nestjs/graphql";
3
3
  import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
4
- import { join } from "path";
4
+ import type { IncomingMessage } from "node:http";
5
+ import path from "node:path";
5
6
  import { env } from "../config/env.js";
6
- {{#if (eq httpProvider "fastify")}}
7
- // WARNING: If you use @as-integrations/fastify with multiple GraphQL endpoints
8
- // in a single application, make sure to enable the disableHealthCheck setting
9
- // in the GraphQLModule configuration below.
10
- {{/if}}
7
+ import { HealthResolver } from "./resolvers/health.resolver.js";
11
8
  {{#if hasTodoExample}}
12
9
  import { TodoResolver } from "./resolvers/todo.resolver.js";
13
10
  {{/if}}
14
11
 
15
- @Resolver()
16
- class HealthResolver {
17
- @Query(() => String)
18
- hello(): string { return "Hello GraphQL!"; }
19
- }
20
-
21
12
  @Module({
22
13
  imports: [
23
14
  GraphQLModule.forRoot<ApolloDriverConfig>({
24
15
  driver: ApolloDriver,
25
- autoSchemaFile: join(process.cwd(), "src/schema.gql"),
16
+ autoSchemaFile: path.join(process.cwd(), "src/schema.gql"),
26
17
  graphiql: env.GRAPHQL_PLAYGROUND,
18
+ context: ({ req, res }: { req: IncomingMessage; res: unknown }) => ({ req, res }),
27
19
  sortSchema: true,
28
- {{#if (eq httpProvider "fastify")}}
29
- disableHealthCheck: true,
30
- {{/if}}
31
20
  }),
32
21
  ],
33
22
  providers: [
@@ -16,7 +16,7 @@ import mysql from "mysql2";
16
16
  import { drizzle } from "drizzle-orm/better-sqlite3";
17
17
  import Database from "better-sqlite3";
18
18
  {{/if}}
19
- import * as dbSchema from "../database/orm/drizzle/schema.js";
19
+ import * as dbSchema from "../../../database/orm/drizzle/schema.js";
20
20
  {{/if}}
21
21
 
22
22
  const schema = buildSchema({{#if isMonorepo}}db{{else}}
@@ -24,13 +24,14 @@ const schema = buildSchema({{#if isMonorepo}}db{{else}}
24
24
  drizzle(new Pool({ connectionString: env.POSTGRES_URL }), { schema: dbSchema })
25
25
  {{/if}}
26
26
  {{#if hasMysql}}
27
- drizzle(mysql.createPool(env.MYSQL_URL), { schema: dbSchema })
27
+ drizzle(mysql.createPool(env.MYSQL_URL), { mode: "default", schema: dbSchema })
28
28
  {{/if}}
29
29
  {{#if hasSqlite}}
30
30
  drizzle(new Database(env.SQLITE_PATH), { schema: dbSchema })
31
31
  {{/if}}
32
32
  {{/if}});
33
33
 
34
+
34
35
  @Module({
35
36
  providers: [
36
37
  {
@@ -0,0 +1,14 @@
1
+ import { Query, Resolver } from "@nestjs/graphql";
2
+ {{#if isBetterAuth}}
3
+ import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
4
+ {{/if}}
5
+ {{#if isBetterAuth}}
6
+ @AllowAnonymous()
7
+ {{/if}}
8
+ @Resolver()
9
+ export class HealthResolver {
10
+ @Query(() => String)
11
+ hello(): string {
12
+ return "Hello GraphQL!";
13
+ }
14
+ }
@@ -39,34 +39,38 @@ export class TodoResolver {
39
39
  }
40
40
  {{else}}
41
41
  {{#if (and hasAuth hasDatabase)}}
42
- import { UseGuards } from "@nestjs/common";
43
- {{/if}}
44
- import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
45
- {{#if (and hasAuth hasDatabase)}}
42
+ import { BadRequestException, NotFoundException, UseGuards } from "@nestjs/common";
46
43
  import { UserEntity } from "../../auth/decorators/index.js";
47
44
  import { GqlAuthGuard } from "../../auth/guards/gql-auth.guard.js";
45
+ {{else}}
46
+ import { BadRequestException, NotFoundException } from "@nestjs/common";
47
+ {{/if}}
48
+ import { Args, Mutation, Query, Resolver } from "@nestjs/graphql";
49
+ {{#if (and hasDatabase useTypeorm)}}
50
+ import { GraphQLResolveInfo } from "graphql";
51
+ import { GraphRelationBuilder } from "typeorm-relations-graphql";
52
+ import { Todo as TodoEntity } from "../../database/entities/todo.entity.js";
48
53
  {{/if}}
54
+ {{#if (and hasDatabase useDrizzle)}}
55
+ import { and, eq } from "drizzle-orm";
56
+ import * as schema from "../../database/orm/drizzle/schema.js";
57
+ {{/if}}
58
+ {{#if hasDatabase}}
49
59
  {{#if isMonorepo}}
50
60
  import { DbService } from "@repo/db";
51
61
  {{else}}
52
62
  import { DbService } from "../../database/db.service.js";
53
63
  {{/if}}
64
+ {{/if}}
54
65
  import { CreateTodoInput } from "../dto/create-todo.input.js";
55
66
  import { UpdateTodoInput } from "../dto/update-todo.input.js";
56
- {{#if useTypeorm}}
57
- import { GraphRelationBuilder } from "typeorm-relations-graphql";
58
- import { GraphQLResolveInfo } from "graphql";
59
- {{/if}}
60
- {{#if useDrizzle}}
61
- import * as schema from "../../database/orm/drizzle/schema.js";
62
- {{/if}}
63
- {{#if (and hasAuth hasDatabase)}}
64
67
  import { Todo } from "../models/todo.model.js";
65
- {{/if}}
66
68
 
67
69
  @Resolver(() => Todo)
68
70
  export class TodoResolver {
71
+ {{#if hasDatabase}}
69
72
  constructor(private readonly db: DbService) {}
73
+ {{/if}}
70
74
 
71
75
  @Query(() => String)
72
76
  hello(): string {
@@ -74,93 +78,201 @@ export class TodoResolver {
74
78
  }{{#if hasDatabase}}
75
79
 
76
80
  @Query(() => [Todo])
77
- todos({{#if useTypeorm}}info: GraphQLResolveInfo{{/if}}) {
81
+ {{#if hasAuth}}
82
+ @UseGuards(GqlAuthGuard)
83
+ {{/if}}
84
+ todos({{#if useTypeorm}}info: GraphQLResolveInfo, {{/if}}{{#if hasAuth}}@UserEntity() user: { id: string }{{/if}}) {
78
85
  {{#if useTypeorm}}
79
- const builder = new GraphRelationBuilder(this.db.getDataSource());
86
+ const builder = new GraphRelationBuilder(this.db.typeorm);
80
87
  const relations = builder.buildForQuery(Todo, info, "todos");
81
- return this.db.getRepository(Todo).find({ relations });
88
+ {{#if hasAuth}}
89
+ return this.db.typeorm.getRepository(TodoEntity).find({ relations, where: { authorId: user.id } });
90
+ {{else}}
91
+ return this.db.typeorm.getRepository(TodoEntity).find({ relations });
92
+ {{/if}}
82
93
  {{else if isPrisma8}}
94
+ {{#if hasAuth}}
95
+ return ((this.db.prisma.orm.public as any).Todo).where({ authorId: user.id }).all();
96
+ {{else}}
83
97
  return ((this.db.prisma.orm.public as any).Todo).all();
98
+ {{/if}}
84
99
  {{else if usePrisma}}
100
+ {{#if hasAuth}}
101
+ return this.db.prisma.todo.findMany({ where: { authorId: user.id } });
102
+ {{else}}
85
103
  return this.db.prisma.todo.findMany();
104
+ {{/if}}
86
105
  {{else if useDrizzle}}
87
- return this.db.select().from(schema.todos);
106
+ {{#if hasAuth}}
107
+ return this.db.drizzle.select().from(schema.todos).where(eq(schema.todos.authorId, user.id));
108
+ {{else}}
109
+ return this.db.drizzle.select().from(schema.todos);
110
+ {{/if}}
88
111
  {{else}}
89
112
  return [];
90
113
  {{/if}}
91
114
  }
92
115
 
116
+ {{#if hasAuth}}
93
117
  @UseGuards(GqlAuthGuard)
118
+ {{/if}}
94
119
  @Mutation(() => Todo)
95
- createTodo(
120
+ async createTodo(
121
+ {{#if hasAuth}}
96
122
  @UserEntity() user: { id: string },
123
+ {{/if}}
97
124
  @Args("input") input: CreateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
98
125
  ) {
99
- {{#if useTypeorm}}
100
- const entity = this.db.getRepository(Todo).create(input);
101
- const saved = await this.db.getRepository(Todo).save(entity);
102
- const builder = new GraphRelationBuilder(this.db.getDataSource());
103
- const relations = builder.buildForQuery(Todo, info, "createTodo");
104
- return this.db.getRepository(Todo).findOneOrFail({
105
- where: { id: saved.id },
106
- relations,
107
- });
108
- {{else if isPrisma8}}
126
+ if (!input.title.trim()) throw new BadRequestException("Title must not be empty");
127
+ {{#if isPrisma8}}
128
+ {{#if hasAuth}}
109
129
  return ((this.db.prisma.orm.public as any).Todo).create({ ...input, authorId: user.id });
130
+ {{else}}
131
+ return ((this.db.prisma.orm.public as any).Todo).create(input);
132
+ {{/if}}
110
133
  {{else if usePrisma}}
111
- return this.db.prisma.todo.create({
112
- data: { ...input, authorId: user.id },
134
+ {{#if hasAuth}}
135
+ const todo = await this.db.prisma.todo.create({ data: { ...input, authorId: user.id } });
136
+ {{else}}
137
+ const todo = await this.db.prisma.todo.create({ data: input });
138
+ {{/if}}
139
+ return todo;
140
+ {{else if useTypeorm}}
141
+ {{#if hasAuth}}
142
+ const entity = this.db.typeorm.getRepository(TodoEntity).create({ ...input, authorId: user.id });
143
+ {{else}}
144
+ const entity = this.db.typeorm.getRepository(TodoEntity).create(input);
145
+ {{/if}}
146
+ const saved = await this.db.typeorm.getRepository(TodoEntity).save(entity);
147
+ const builder = new GraphRelationBuilder(this.db.typeorm);
148
+ const relations = builder.buildForQuery(Todo, info, "createTodo");
149
+ return this.db.typeorm.getRepository(TodoEntity).findOneOrFail({
150
+ relations,
151
+ where: { id: saved.id },
113
152
  });
114
153
  {{else if useDrizzle}}
115
- const [todo] = await this.db.insert(schema.todos).values(input).returning();
154
+ {{#if hasMysql}}
155
+ const [inserted] = await this.db.drizzle.insert(schema.todos).values({{#if hasAuth}}{ ...input, authorId: user.id }{{else}}input{{/if}}).$returningId();
156
+ if (!inserted) throw new NotFoundException("Todo not found");
157
+ const [todo] = await this.db.drizzle.select().from(schema.todos).where({{#if hasAuth}}and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, inserted.id)){{else}}eq(schema.todos.id, inserted.id){{/if}});
158
+ if (!todo) throw new NotFoundException("Todo not found");
159
+ return todo;
160
+ {{else}}
161
+ {{#if hasAuth}}
162
+ const [todo] = await this.db.drizzle.insert(schema.todos).values({ ...input, authorId: user.id }).returning();
163
+ {{else}}
164
+ const [todo] = await this.db.drizzle.insert(schema.todos).values(input).returning();
165
+ {{/if}}
166
+ if (!todo) throw new NotFoundException("Todo not found");
116
167
  return todo;
168
+ {{/if}}
117
169
  {{else}}
118
170
  return { id: "1", ...input };
119
171
  {{/if}}
120
172
  }
121
173
 
174
+ {{#if hasAuth}}
122
175
  @UseGuards(GqlAuthGuard)
176
+ {{/if}}
123
177
  @Mutation(() => Todo)
124
- updateTodo(@Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}) {
178
+ async updateTodo(
179
+ {{#if hasAuth}}
180
+ @UserEntity() user: { id: string },
181
+ {{/if}}
182
+ @Args("input") input: UpdateTodoInput{{#if useTypeorm}}, info: GraphQLResolveInfo{{/if}}
183
+ ) {
125
184
  const { id, ...data } = input;
126
- {{#if useTypeorm}}
127
- await this.db.getRepository(Todo).update(id, data);
128
- const builder = new GraphRelationBuilder(this.db.getDataSource());
185
+ if (data.title !== undefined && !data.title.trim()) throw new BadRequestException("Title must not be empty");
186
+ {{#if isPrisma8}}
187
+ {{#if hasAuth}}
188
+ return ((this.db.prisma.orm.public as any).Todo).where({ authorId: user.id, id }).update(data);
189
+ {{else}}
190
+ return ((this.db.prisma.orm.public as any).Todo).where({ id }).update(data);
191
+ {{/if}}
192
+ {{else if usePrisma}}
193
+ {{#if hasAuth}}
194
+ const result = await this.db.prisma.todo.update({ data, where: { authorId: user.id, id } });
195
+ {{else}}
196
+ const result = await this.db.prisma.todo.update({ data, where: { id } });
197
+ {{/if}}
198
+ return result;
199
+ {{else if useTypeorm}}
200
+ {{#if hasAuth}}
201
+ const updateResult = await this.db.typeorm.getRepository(TodoEntity).update({ authorId: user.id, id }, data);
202
+ {{else}}
203
+ const updateResult = await this.db.typeorm.getRepository(TodoEntity).update(id, data);
204
+ {{/if}}
205
+ if ((updateResult.affected ?? 0) === 0) throw new NotFoundException("Todo not found");
206
+ const builder = new GraphRelationBuilder(this.db.typeorm);
129
207
  const relations = builder.buildForQuery(Todo, info, "updateTodo");
130
- return this.db.getRepository(Todo).findOneOrFail({
131
- where: { id },
208
+ return this.db.typeorm.getRepository(TodoEntity).findOneOrFail({
132
209
  relations,
210
+ where: { id },
133
211
  });
134
- {{else if isPrisma8}}
135
- return ((this.db.prisma.orm.public as any).Todo).where({ id }).update(data);
136
- {{else if usePrisma}}
137
- return this.db.prisma.todo.update({ where: { id }, data });
138
212
  {{else if useDrizzle}}
139
- const [todo] = await this.db
140
- .update(schema.todos)
141
- .set(data)
142
- .where(eq(schema.todos.id, id))
143
- .returning();
213
+ {{#if hasMysql}}
214
+ await this.db.drizzle.update(schema.todos).set(data).where({{#if hasAuth}}and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id))){{else}}eq(schema.todos.id, Number(id)){{/if}});
215
+ const [todo] = await this.db.drizzle.select().from(schema.todos).where({{#if hasAuth}}and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id))){{else}}eq(schema.todos.id, Number(id)){{/if}});
216
+ {{else}}
217
+ {{#if hasAuth}}
218
+ const [todo] = await this.db.drizzle.update(schema.todos).set(data).where(and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id)))).returning();
219
+ {{else}}
220
+ const [todo] = await this.db.drizzle.update(schema.todos).set(data).where(eq(schema.todos.id, Number(id))).returning();
221
+ {{/if}}
222
+ {{/if}}
223
+ if (!todo) throw new NotFoundException("Todo not found");
144
224
  return todo;
145
225
  {{else}}
146
226
  return { id, ...data };
147
227
  {{/if}}
148
228
  }
149
229
 
230
+ {{#if hasAuth}}
150
231
  @UseGuards(GqlAuthGuard)
232
+ {{/if}}
151
233
  @Mutation(() => Boolean)
152
- async deleteTodo(@Args("id") id: string): Promise<boolean> {
153
- {{#if useTypeorm}}
154
- const result = await this.db.getRepository(Todo).delete(id);
155
- return (result.affected ?? 0) > 0;
156
- {{else if isPrisma8}}
234
+ async deleteTodo(
235
+ {{#if hasAuth}}
236
+ @UserEntity() user: { id: string },
237
+ {{/if}}
238
+ @Args("id") id: string
239
+ ): Promise<boolean> {
240
+ {{#if isPrisma8}}
241
+ {{#if hasAuth}}
242
+ await ((this.db.prisma.orm.public as any).Todo).where({ authorId: user.id, id }).delete();
243
+ {{else}}
157
244
  await ((this.db.prisma.orm.public as any).Todo).where({ id }).delete();
245
+ {{/if}}
158
246
  return true;
159
247
  {{else if usePrisma}}
160
- await this.db.prisma.todo.delete({ where: { id } });
248
+ {{#if hasAuth}}
249
+ const result = await this.db.prisma.todo.deleteMany({ where: { authorId: user.id, id } });
250
+ {{else}}
251
+ const result = await this.db.prisma.todo.deleteMany({ where: { id } });
252
+ {{/if}}
253
+ if (result.count === 0) throw new NotFoundException("Todo not found");
254
+ return true;
255
+ {{else if useTypeorm}}
256
+ {{#if hasAuth}}
257
+ const result = await this.db.typeorm.getRepository(TodoEntity).delete({ authorId: user.id, id });
258
+ {{else}}
259
+ const result = await this.db.typeorm.getRepository(TodoEntity).delete(id);
260
+ {{/if}}
261
+ if ((result.affected ?? 0) === 0) throw new NotFoundException("Todo not found");
161
262
  return true;
162
263
  {{else if useDrizzle}}
163
- await this.db.delete(schema.todos).where(eq(schema.todos.id, id));
264
+ {{#if hasMysql}}
265
+ const [existing] = await this.db.drizzle.select().from(schema.todos).where({{#if hasAuth}}and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id))){{else}}eq(schema.todos.id, Number(id)){{/if}});
266
+ if (!existing) throw new NotFoundException("Todo not found");
267
+ await this.db.drizzle.delete(schema.todos).where({{#if hasAuth}}and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id))){{else}}eq(schema.todos.id, Number(id)){{/if}});
268
+ {{else}}
269
+ {{#if hasAuth}}
270
+ const deleted = await this.db.drizzle.delete(schema.todos).where(and(eq(schema.todos.authorId, user.id), eq(schema.todos.id, Number(id)))).returning();
271
+ {{else}}
272
+ const deleted = await this.db.drizzle.delete(schema.todos).where(eq(schema.todos.id, Number(id))).returning();
273
+ {{/if}}
274
+ if (deleted.length === 0) throw new NotFoundException("Todo not found");
275
+ {{/if}}
164
276
  return true;
165
277
  {{else}}
166
278
  return true;
@@ -1,5 +1,16 @@
1
1
  import { z } from "zod";
2
2
 
3
+ {{#if (includes addons "nestjs-zod")}}
4
+ import { createZodDto } from "nestjs-zod";
5
+
6
+ const createTodoSchema = z.object({
7
+ completed: z.boolean().default(false),
8
+ description: z.string().optional(),
9
+ title: z.string().min(1),
10
+ });
11
+
12
+ export class CreateTodoDto extends createZodDto(createTodoSchema) {}
13
+ {{else}}
3
14
  export class CreateTodoDto {
4
15
  static schema = z.object({
5
16
  completed: z.boolean().default(false),
@@ -11,3 +22,4 @@ export class CreateTodoDto {
11
22
  description?: string;
12
23
  completed: boolean;
13
24
  }
25
+ {{/if}}
@@ -1,5 +1,16 @@
1
1
  import { z } from "zod";
2
2
 
3
+ {{#if (includes addons "nestjs-zod")}}
4
+ import { createZodDto } from "nestjs-zod";
5
+
6
+ const updateTodoSchema = z.object({
7
+ completed: z.boolean().optional(),
8
+ description: z.string().optional(),
9
+ title: z.string().min(1).optional(),
10
+ });
11
+
12
+ export class UpdateTodoDto extends createZodDto(updateTodoSchema) {}
13
+ {{else}}
3
14
  export class UpdateTodoDto {
4
15
  static schema = z.object({
5
16
  completed: z.boolean().optional(),
@@ -11,3 +22,4 @@ export class UpdateTodoDto {
11
22
  description?: string;
12
23
  completed?: boolean;
13
24
  }
25
+ {{/if}}
@@ -9,6 +9,9 @@ import {
9
9
  {{#if (and isPassportAuth hasDatabase)}}
10
10
  UseGuards,
11
11
  {{/if}}
12
+ {{#if (and isBetterAuth hasDatabase hasGraphql)}}
13
+ UseGuards,
14
+ {{/if}}
12
15
  } from "@nestjs/common";
13
16
  {{#if swagger}}
14
17
  import {
@@ -21,7 +24,7 @@ import {
21
24
  {{/if}}
22
25
  } from "@nestjs/swagger";
23
26
  {{/if}}
24
- {{#if isPassportAuth}}
27
+ {{#if (and isPassportAuth swagger)}}
25
28
  import {
26
29
  ApiBearerAuth,
27
30
  } from "@nestjs/swagger";
@@ -34,6 +37,9 @@ import type { UserSession } from "../../auth/auth.js";
34
37
  import { RestAuthGuard } from "../../auth/guards/rest-auth.guard.js";
35
38
  import { CurrentUser } from "../../auth/decorators/current-user.decorator.js";
36
39
  {{/if}}
40
+ {{#if (and isBetterAuth hasDatabase hasGraphql)}}
41
+ import { RestAuthGuard } from "../../auth/guards/rest-auth.guard.js";
42
+ {{/if}}
37
43
  import { TodoService } from "./todo.service.js";
38
44
  import { CreateTodoDto } from "./dto/create-todo.dto.js";
39
45
  import { UpdateTodoDto } from "./dto/update-todo.dto.js";
@@ -42,7 +48,7 @@ import { UpdateTodoDto } from "./dto/update-todo.dto.js";
42
48
  @ApiTags("Todos")
43
49
  {{/if}}
44
50
  @Controller("todos")
45
- {{#if (and isPassportAuth hasDatabase)}}
51
+ {{#if (or (and isPassportAuth hasDatabase) (and isBetterAuth hasDatabase hasGraphql))}}
46
52
  @UseGuards(RestAuthGuard)
47
53
  {{/if}}
48
54
  export class TodoController {
@@ -64,7 +70,11 @@ export class TodoController {
64
70
  return this.todoService.findAll(user.id);
65
71
  }
66
72
  {{else if (and isBetterAuth hasDatabase)}}
73
+ {{#if (and isMonorepo usePrisma)}}
74
+ findAll(@Session() session: UserSession): ReturnType<TodoService["findAll"]> {
75
+ {{else}}
67
76
  findAll(@Session() session: UserSession) {
77
+ {{/if}}
68
78
  return this.todoService.findAll(session.user.id);
69
79
  }
70
80
  {{else}}
@@ -86,20 +96,20 @@ export class TodoController {
86
96
  {{/if}}
87
97
  {{#if (and isPassportAuth hasDatabase)}}
88
98
  create(
89
- @Body({ schema: CreateTodoDto.schema }) input: CreateTodoDto,
99
+ @Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: CreateTodoDto.schema }){{/if}} input: CreateTodoDto,
90
100
  @CurrentUser() user: { id: string }
91
101
  ) {
92
102
  return this.todoService.create(input, user.id);
93
103
  }
94
104
  {{else if (and isBetterAuth hasDatabase)}}
95
105
  create(
96
- @Body({ schema: CreateTodoDto.schema }) input: CreateTodoDto,
106
+ @Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: CreateTodoDto.schema }){{/if}} input: CreateTodoDto,
97
107
  @Session() session: UserSession
98
108
  ) {
99
109
  return this.todoService.create(input, session.user.id);
100
110
  }
101
111
  {{else}}
102
- create(@Body({ schema: CreateTodoDto.schema }) input: CreateTodoDto) {
112
+ create(@Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: CreateTodoDto.schema }){{/if}} input: CreateTodoDto) {
103
113
  return this.todoService.create(input);
104
114
  }
105
115
  {{/if}}
@@ -118,7 +128,7 @@ export class TodoController {
118
128
  {{#if (and isPassportAuth hasDatabase)}}
119
129
  update(
120
130
  @Param("id") id: string,
121
- @Body({ schema: UpdateTodoDto.schema }) input: UpdateTodoDto,
131
+ @Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: UpdateTodoDto.schema }){{/if}} input: UpdateTodoDto,
122
132
  @CurrentUser() user: { id: string }
123
133
  ) {
124
134
  return this.todoService.update(id, input, user.id);
@@ -126,7 +136,7 @@ export class TodoController {
126
136
  {{else if (and isBetterAuth hasDatabase)}}
127
137
  update(
128
138
  @Param("id") id: string,
129
- @Body({ schema: UpdateTodoDto.schema }) input: UpdateTodoDto,
139
+ @Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: UpdateTodoDto.schema }){{/if}} input: UpdateTodoDto,
130
140
  @Session() session: UserSession
131
141
  ) {
132
142
  return this.todoService.update(id, input, session.user.id);
@@ -134,7 +144,7 @@ export class TodoController {
134
144
  {{else}}
135
145
  update(
136
146
  @Param("id") id: string,
137
- @Body({ schema: UpdateTodoDto.schema }) input: UpdateTodoDto
147
+ @Body{{#if (includes addons "nestjs-zod")}}(){{else}}({ schema: UpdateTodoDto.schema }){{/if}} input: UpdateTodoDto
138
148
  ) {
139
149
  return this.todoService.update(id, input);
140
150
  }