@nest-arch/tui 0.2.8 → 0.4.3
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/README.md +20 -42
- package/dist/cli.js +1 -1
- package/dist/constants-B5MuV-iH.js +1 -0
- package/dist/index.js +365 -98
- package/dist/templates/addons/husky/pre-commit.hbs +11 -26
- package/dist/templates/base/.env.example.hbs +13 -2
- package/dist/templates/base/.env.hbs +11 -2
- package/dist/templates/base/.gitignore.hbs +2 -1
- package/dist/templates/base/.yarnrc.yml.hbs +0 -5
- package/dist/templates/base/README.md.hbs +49 -5
- package/dist/templates/base/nest-cli.json.hbs +2 -1
- package/dist/templates/base/pnpm-workspace.yaml.hbs +15 -0
- package/dist/templates/base/prisma.config.ts.hbs +32 -6
- package/dist/templates/base/src/app.controller.spec.ts.hbs +3 -11
- package/dist/templates/base/src/app.controller.ts.hbs +2 -2
- package/dist/templates/base/src/app.module.ts.hbs +76 -5
- package/dist/templates/base/src/app.router.ts.hbs +3 -4
- package/dist/templates/base/src/database/adapters/interfaces/db-adapter.interface.ts.hbs +7 -0
- package/dist/templates/base/src/database/adapters/mongo/prisma-mongo.adapter.ts.hbs +36 -4
- package/dist/templates/base/src/database/adapters/mongo/raw-mongo.adapter.ts.hbs +17 -12
- package/dist/templates/base/src/database/adapters/sql/drizzle-sql.adapter.ts.hbs +9 -0
- package/dist/templates/base/src/database/adapters/sql/prisma-sql.adapter.ts.hbs +8 -5
- package/dist/templates/base/src/database/adapters/sql/raw-sql.adapter.ts.hbs +37 -0
- package/dist/templates/base/src/database/adapters/sql/typeorm-sql.adapter.ts.hbs +8 -3
- package/dist/templates/base/src/database/db.module.ts.hbs +55 -42
- package/dist/templates/base/src/database/db.service.ts.hbs +60 -6
- package/dist/templates/base/src/database/entities/app.entity.ts.hbs +25 -0
- package/dist/templates/base/src/database/entities/todo.entity.ts.hbs +12 -7
- package/dist/templates/base/src/database/index.ts.hbs +20 -0
- package/dist/templates/base/src/database/orm/drizzle/schema.ts.hbs +80 -7
- package/dist/templates/base/src/database/orm/typeorm/data-source.ts.hbs +23 -4
- package/dist/templates/base/src/libs/middlewares/auth.trpc.middleware.ts.hbs +47 -0
- package/dist/templates/base/src/libs/middlewares/rate-limit.trpc.middleware.ts.hbs +43 -0
- package/dist/templates/base/src/libs/trpc/error.handler.ts.hbs +1 -1
- package/dist/templates/base/src/libs/trpc/inner.context.ts.hbs +6 -3
- package/dist/templates/base/src/main.ts.hbs +34 -10
- package/dist/templates/base/src/modules/users/entities/user.entity.ts.hbs +22 -0
- package/dist/templates/base/test/app.e2e-spec.ts.hbs +841 -6
- package/dist/templates/base/test/bootstrap.e2e-spec.ts.hbs +84 -0
- package/dist/templates/base/tsconfig.build.json.hbs +2 -0
- package/dist/templates/base/vitest.config.e2e.ts.hbs +4 -3
- package/dist/templates/base/vitest.config.ts.hbs +4 -3
- package/dist/templates/base/yarn.lock.hbs +1 -0
- package/dist/templates/extras/docker/.dockerignore.hbs +8 -1
- package/dist/templates/extras/docker/Dockerfile.hbs +11 -10
- package/dist/templates/extras/health/src/health/health.controller.ts.hbs +54 -0
- package/dist/templates/extras/health/src/health/health.module.ts.hbs +9 -0
- package/dist/templates/extras/swagger/create-docs.ts.hbs +13 -22
- package/dist/templates/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
- package/dist/templates/feature/graphql/src/graphql/models/todo.model.ts.hbs +4 -0
- package/dist/templates/feature/graphql/src/graphql/orm/drizzle/drizzle-graphql.module.ts.hbs +6 -4
- package/dist/templates/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
- package/dist/templates/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +210 -47
- package/dist/templates/feature/rest/todo-example/src/api/todo/dto/create-todo.dto.ts.hbs +12 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/dto/update-todo.dto.ts.hbs +12 -0
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.controller.ts.hbs +21 -9
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.module.ts.hbs +1 -3
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.router.ts.hbs +41 -13
- package/dist/templates/feature/rest/todo-example/src/api/todo/todo.service.ts.hbs +157 -33
- package/dist/templates/monorepo/apps/api-gateway/package.json.hbs +2 -3
- package/dist/templates/monorepo/apps/api-gateway/src/app.controller.spec.ts.hbs +6 -1
- package/dist/templates/monorepo/apps/api-gateway/src/app.controller.ts.hbs +6 -0
- package/dist/templates/monorepo/apps/api-gateway/src/app.module.ts.hbs +32 -1
- package/dist/templates/monorepo/apps/api-gateway/src/app.service.ts.hbs +1 -1
- package/dist/templates/monorepo/apps/api-gateway/src/main.ts.hbs +13 -4
- package/dist/templates/monorepo/apps/api-gateway/test/app.e2e-spec.ts.hbs +45 -1
- package/dist/templates/monorepo/apps/api-gateway/tsconfig.build.json.hbs +2 -1
- package/dist/templates/monorepo/apps/api-gateway/vitest.config.e2e.ts.hbs +3 -2
- package/dist/templates/monorepo/apps/api-gateway/vitest.config.ts.hbs +3 -2
- package/dist/templates/monorepo/feature/graphql/src/graphql/graphql.module.ts.hbs +6 -10
- package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/health.resolver.ts.hbs +14 -0
- package/dist/templates/monorepo/feature/graphql/src/graphql/resolvers/todo.resolver.ts.hbs +151 -42
- package/dist/templates/monorepo/packages/env/src/api-gateway.ts.hbs +16 -3
- package/dist/templates/monorepo/packages/shared/package.json.hbs +1 -0
- package/dist/templates/monorepo/packages/shared/src/auth.ts.hbs +6 -0
- package/dist/templates/monorepo/root/.gitignore.hbs +1 -0
- package/dist/templates/monorepo/root/pnpm-workspace.yaml.hbs +19 -2
- package/dist/templates/monorepo/root/tsconfig.json.hbs +1 -1
- package/dist/templates/monorepo/root/turbo.json.hbs +2 -0
- package/dist/templates/single-project/architecture/nest-api/.env.hbs +9 -1
- package/dist/templates/single-project/architecture/nest-api/package.json.hbs +1 -2
- package/dist/templates/single-project/architecture/nest-api/src/main.ts.hbs +33 -9
- package/dist/templates/single-project/architecture/nest-gateway/src/main.ts.hbs +3 -3
- package/dist/templates/single-project/architecture/nest-microservice/.env.hbs +18 -0
- package/dist/templates/single-project/architecture/nest-microservice/proto/app.proto.hbs +26 -0
- package/dist/templates/single-project/architecture/nest-microservice/src/app.controller.ts.hbs +23 -4
- package/dist/templates/single-project/architecture/nest-microservice/src/app.service.ts.hbs +6 -2
- package/dist/templates/single-project/architecture/nest-microservice/src/main.ts.hbs +16 -5
- package/dist/templates/single-project/architecture/nest-microservice/test/microservice.e2e-spec.ts.hbs +70 -0
- package/dist/templates/single-project/shared/config/env.ts.hbs +38 -3
- package/package.json +5 -2
- package/dist/constants-Cgtoxhmd.js +0 -1
|
@@ -1,4 +1,42 @@
|
|
|
1
|
+
{{#if (and hasMongodb useRawSql)}}
|
|
2
|
+
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
|
3
|
+
import { ObjectId, type WithId } from "mongodb";
|
|
4
|
+
import { DbService } from "../../database/db.service.js";
|
|
5
|
+
import { CreateTodoDto } from "./dto/create-todo.dto.js";
|
|
6
|
+
import { UpdateTodoDto } from "./dto/update-todo.dto.js";
|
|
7
|
+
interface TodoDocument { title: string; completed: boolean; description?: string; }
|
|
8
|
+
const serialize = ({ _id, ...todo }: WithId<TodoDocument>) => ({ ...todo, id: _id.toHexString() });
|
|
9
|
+
function objectId(id: string): ObjectId {
|
|
10
|
+
if (!/^[a-f0-9]{24}$/iu.test(id)) throw new BadRequestException("Invalid todo identifier");
|
|
11
|
+
return new ObjectId(id);
|
|
12
|
+
}
|
|
13
|
+
@Injectable()
|
|
14
|
+
export class TodoService {
|
|
15
|
+
constructor(private readonly db: DbService) {}
|
|
16
|
+
private get todos() { return this.db.raw.mongo.collection<TodoDocument>("todos"); }
|
|
17
|
+
async findAll() { return (await this.todos.find().sort({ _id: 1 }).limit(100).toArray()).map(serialize); }
|
|
18
|
+
async create(input: CreateTodoDto) {
|
|
19
|
+
const todo = { title: input.title, completed: input.completed ?? false, ...(input.description === undefined ? {} : { description: input.description }) };
|
|
20
|
+
const result = await this.todos.insertOne(todo);
|
|
21
|
+
return { ...todo, id: result.insertedId.toHexString() };
|
|
22
|
+
}
|
|
23
|
+
async update(id: string, input: UpdateTodoDto) {
|
|
24
|
+
const todo = await this.todos.findOneAndUpdate({ _id: objectId(id) }, { $set: input }, { returnDocument: "after" });
|
|
25
|
+
if (!todo) throw new NotFoundException("Todo not found");
|
|
26
|
+
return serialize(todo);
|
|
27
|
+
}
|
|
28
|
+
async delete(id: string) {
|
|
29
|
+
const result = await this.todos.deleteOne({ _id: objectId(id) });
|
|
30
|
+
if (result.deletedCount === 0) throw new NotFoundException("Todo not found");
|
|
31
|
+
return { message: "Todo deleted successfully" };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
{{else}}
|
|
35
|
+
{{#if (and hasDatabase hasAuth)}}
|
|
36
|
+
import { Injectable, NotFoundException } from "@nestjs/common";
|
|
37
|
+
{{else}}
|
|
1
38
|
import { Injectable } from "@nestjs/common";
|
|
39
|
+
{{/if}}
|
|
2
40
|
{{#if hasDatabase}}
|
|
3
41
|
{{#if isMonorepo}}
|
|
4
42
|
import { DbService } from "@repo/db";
|
|
@@ -7,8 +45,12 @@ import { DbService } from "../../database/db.service.js";
|
|
|
7
45
|
{{/if}}
|
|
8
46
|
{{#if useDrizzle}}
|
|
9
47
|
import * as schema from "../../database/orm/drizzle/schema.js";
|
|
48
|
+
{{#if hasAuth}}
|
|
49
|
+
import { and, eq } from "drizzle-orm";
|
|
50
|
+
{{else}}
|
|
10
51
|
import { eq } from "drizzle-orm";
|
|
11
52
|
{{/if}}
|
|
53
|
+
{{/if}}
|
|
12
54
|
{{#if useTypeorm}}
|
|
13
55
|
import { Todo } from "../../database/entities/todo.entity.js";
|
|
14
56
|
{{/if}}
|
|
@@ -33,83 +75,163 @@ export class TodoService {
|
|
|
33
75
|
{{#if hasDatabase}}
|
|
34
76
|
{{#if hasAuth}}
|
|
35
77
|
findAll(authorId: string) {
|
|
36
|
-
{{#if
|
|
78
|
+
{{#if isPrisma8}}
|
|
79
|
+
return ((this.db.prisma.orm.public as any).Todo).where({ authorId }).all();
|
|
80
|
+
{{else if usePrisma}}
|
|
37
81
|
return this.db.prisma.todo.findMany({ where: { authorId } });
|
|
38
82
|
{{else if useTypeorm}}
|
|
39
|
-
return this.db.getRepository(Todo).find({ where: { authorId } });
|
|
83
|
+
return this.db.typeorm.getRepository(Todo).find({ where: { authorId } });
|
|
40
84
|
{{else if useDrizzle}}
|
|
41
|
-
return this.db.select().from(schema.todos).where(eq(schema.todos.authorId, authorId));
|
|
85
|
+
return this.db.drizzle.select().from(schema.todos).where(eq(schema.todos.authorId, authorId));
|
|
42
86
|
{{/if}}
|
|
43
87
|
}
|
|
44
88
|
|
|
45
|
-
create(input: CreateTodoDto, authorId: string) {
|
|
46
|
-
{{#if
|
|
47
|
-
return this.db.prisma.
|
|
89
|
+
async create(input: CreateTodoDto, authorId: string) {
|
|
90
|
+
{{#if isPrisma8}}
|
|
91
|
+
return ((this.db.prisma.orm.public as any).Todo).create({ ...input, authorId });
|
|
92
|
+
{{else if usePrisma}}
|
|
93
|
+
const todo = await this.db.prisma.todo.create({ data: { ...input, authorId } });
|
|
94
|
+
return todo;
|
|
48
95
|
{{else if useTypeorm}}
|
|
49
|
-
|
|
96
|
+
const todo = await this.db.typeorm.getRepository(Todo).save(this.db.typeorm.getRepository(Todo).create({ ...input, authorId }));
|
|
97
|
+
return todo;
|
|
50
98
|
{{else if useDrizzle}}
|
|
51
|
-
|
|
99
|
+
{{#if hasMysql}}
|
|
100
|
+
const [inserted] = await this.db.drizzle.insert(schema.todos).values({ ...input, authorId }).$returningId();
|
|
101
|
+
if (!inserted) {
|
|
102
|
+
throw new NotFoundException("Todo not found");
|
|
103
|
+
}
|
|
104
|
+
const [todo] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, inserted.id)));
|
|
105
|
+
return todo;
|
|
106
|
+
{{else}}
|
|
107
|
+
const [todo] = await this.db.drizzle.insert(schema.todos).values({ ...input, authorId }).returning();
|
|
108
|
+
return todo;
|
|
109
|
+
{{/if}}
|
|
52
110
|
{{/if}}
|
|
53
111
|
}
|
|
54
112
|
|
|
55
|
-
update(id: string, input: UpdateTodoDto, authorId: string) {
|
|
56
|
-
{{#if
|
|
57
|
-
|
|
113
|
+
async update(id: string, input: UpdateTodoDto, authorId: string) {
|
|
114
|
+
{{#if isPrisma8}}
|
|
115
|
+
const result = await ((this.db.prisma.orm.public as any).Todo).where({ authorId, id }).update(input);
|
|
116
|
+
if (!result) {
|
|
117
|
+
throw new NotFoundException("Todo not found");
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
{{else if usePrisma}}
|
|
121
|
+
const todo = await this.db.prisma.todo.findFirst({ where: { authorId, id } });
|
|
122
|
+
if (!todo) {
|
|
123
|
+
throw new NotFoundException("Todo not found");
|
|
124
|
+
}
|
|
125
|
+
return this.db.prisma.todo.update({ data: input, where: { id } });
|
|
58
126
|
{{else if useTypeorm}}
|
|
59
|
-
|
|
127
|
+
const repository = this.db.typeorm.getRepository(Todo);
|
|
128
|
+
const result = await repository.update({ authorId, id }, input);
|
|
129
|
+
if (result.affected === 0) {
|
|
130
|
+
throw new NotFoundException("Todo not found");
|
|
131
|
+
}
|
|
132
|
+
const todo = await repository.findOneBy({ authorId, id });
|
|
133
|
+
if (!todo) {
|
|
134
|
+
throw new NotFoundException("Todo not found");
|
|
135
|
+
}
|
|
136
|
+
return todo;
|
|
60
137
|
{{else if useDrizzle}}
|
|
61
|
-
|
|
138
|
+
{{#if hasMysql}}
|
|
139
|
+
await this.db.drizzle.update(schema.todos).set(input).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
|
|
140
|
+
const [todo] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
|
|
141
|
+
{{else}}
|
|
142
|
+
const [todo] = await this.db.drizzle.update(schema.todos).set(input).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id)))).returning();
|
|
143
|
+
{{/if}}
|
|
144
|
+
if (!todo) {
|
|
145
|
+
throw new NotFoundException("Todo not found");
|
|
146
|
+
}
|
|
147
|
+
return todo;
|
|
62
148
|
{{/if}}
|
|
63
149
|
}
|
|
64
150
|
|
|
65
151
|
async delete(id: string, authorId: string) {
|
|
66
|
-
{{#if
|
|
67
|
-
await this.db.prisma.
|
|
152
|
+
{{#if isPrisma8}}
|
|
153
|
+
const count = await ((this.db.prisma.orm.public as any).Todo).where({ authorId, id }).delete();
|
|
154
|
+
if (count === 0) {
|
|
155
|
+
throw new NotFoundException("Todo not found");
|
|
156
|
+
}
|
|
157
|
+
{{else if usePrisma}}
|
|
158
|
+
const result = await this.db.prisma.todo.deleteMany({ where: { authorId, id } });
|
|
159
|
+
if (result.count === 0) {
|
|
160
|
+
throw new NotFoundException("Todo not found");
|
|
161
|
+
}
|
|
68
162
|
{{else if useTypeorm}}
|
|
69
|
-
await this.db.getRepository(Todo).delete({ authorId, id });
|
|
163
|
+
const result = await this.db.typeorm.getRepository(Todo).delete({ authorId, id });
|
|
164
|
+
if (result.affected === 0) {
|
|
165
|
+
throw new NotFoundException("Todo not found");
|
|
166
|
+
}
|
|
70
167
|
{{else if useDrizzle}}
|
|
71
|
-
|
|
168
|
+
{{#if hasMysql}}
|
|
169
|
+
const [existing] = await this.db.drizzle.select().from(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
|
|
170
|
+
if (!existing) {
|
|
171
|
+
throw new NotFoundException("Todo not found");
|
|
172
|
+
}
|
|
173
|
+
await this.db.drizzle.delete(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id))));
|
|
174
|
+
{{else}}
|
|
175
|
+
const [todo] = await this.db.drizzle.delete(schema.todos).where(and(eq(schema.todos.authorId, authorId), eq(schema.todos.id, Number(id)))).returning();
|
|
176
|
+
if (!todo) {
|
|
177
|
+
throw new NotFoundException("Todo not found");
|
|
178
|
+
}
|
|
179
|
+
{{/if}}
|
|
72
180
|
{{/if}}
|
|
73
181
|
return { message: "Todo deleted successfully" };
|
|
74
182
|
}
|
|
75
183
|
{{else}}
|
|
76
184
|
findAll() {
|
|
77
|
-
{{#if
|
|
185
|
+
{{#if isPrisma8}}
|
|
186
|
+
return ((this.db.prisma.orm.public as any).Todo).all();
|
|
187
|
+
{{else if usePrisma}}
|
|
78
188
|
return this.db.prisma.todo.findMany();
|
|
79
189
|
{{else if useTypeorm}}
|
|
80
|
-
return this.db.getRepository(Todo).find();
|
|
190
|
+
return this.db.typeorm.getRepository(Todo).find();
|
|
81
191
|
{{else if useDrizzle}}
|
|
82
|
-
return this.db.select().from(schema.todos);
|
|
192
|
+
return this.db.drizzle.select().from(schema.todos);
|
|
83
193
|
{{/if}}
|
|
84
194
|
}
|
|
85
195
|
|
|
86
|
-
create(input: CreateTodoDto) {
|
|
87
|
-
{{#if
|
|
88
|
-
return this.db.prisma.
|
|
196
|
+
async create(input: CreateTodoDto) {
|
|
197
|
+
{{#if isPrisma8}}
|
|
198
|
+
return ((this.db.prisma.orm.public as any).Todo).create(input);
|
|
199
|
+
{{else if usePrisma}}
|
|
200
|
+
const todo = await this.db.prisma.todo.create({ data: input });
|
|
201
|
+
return todo;
|
|
89
202
|
{{else if useTypeorm}}
|
|
90
|
-
|
|
203
|
+
const todo = await this.db.typeorm.getRepository(Todo).save(this.db.typeorm.getRepository(Todo).create(input));
|
|
204
|
+
return todo;
|
|
91
205
|
{{else if useDrizzle}}
|
|
92
|
-
|
|
206
|
+
const [todo] = await this.db.drizzle.insert(schema.todos).values(input).returning();
|
|
207
|
+
return todo;
|
|
93
208
|
{{/if}}
|
|
94
209
|
}
|
|
95
210
|
|
|
96
|
-
update(id: string, input: UpdateTodoDto) {
|
|
97
|
-
{{#if
|
|
98
|
-
return this.db.prisma.
|
|
211
|
+
async update(id: string, input: UpdateTodoDto) {
|
|
212
|
+
{{#if isPrisma8}}
|
|
213
|
+
return ((this.db.prisma.orm.public as any).Todo).where({ id }).update(input);
|
|
214
|
+
{{else if usePrisma}}
|
|
215
|
+
const result = await this.db.prisma.todo.update({ data: input, where: { id } });
|
|
216
|
+
return result;
|
|
99
217
|
{{else if useTypeorm}}
|
|
100
|
-
|
|
218
|
+
const result = await this.db.typeorm.getRepository(Todo).update(id, input);
|
|
219
|
+
return result;
|
|
101
220
|
{{else if useDrizzle}}
|
|
102
|
-
|
|
221
|
+
const [todo] = await this.db.drizzle.update(schema.todos).set(input).where(eq(schema.todos.id, Number(id))).returning();
|
|
222
|
+
return todo;
|
|
103
223
|
{{/if}}
|
|
104
224
|
}
|
|
105
225
|
|
|
106
226
|
async delete(id: string) {
|
|
107
|
-
{{#if
|
|
227
|
+
{{#if isPrisma8}}
|
|
228
|
+
await ((this.db.prisma.orm.public as any).Todo).where({ id }).delete();
|
|
229
|
+
{{else if usePrisma}}
|
|
108
230
|
await this.db.prisma.todo.delete({ where: { id } });
|
|
109
231
|
{{else if useTypeorm}}
|
|
110
|
-
await this.db.getRepository(Todo).delete(id);
|
|
232
|
+
await this.db.typeorm.getRepository(Todo).delete(id);
|
|
111
233
|
{{else if useDrizzle}}
|
|
112
|
-
await this.db.delete(schema.todos).where(eq(schema.todos.id, id));
|
|
234
|
+
await this.db.drizzle.delete(schema.todos).where(eq(schema.todos.id, Number(id)));
|
|
113
235
|
{{/if}}
|
|
114
236
|
return { message: "Todo deleted successfully" };
|
|
115
237
|
}
|
|
@@ -143,3 +265,5 @@ export class TodoService {
|
|
|
143
265
|
}
|
|
144
266
|
{{/if}}
|
|
145
267
|
}
|
|
268
|
+
|
|
269
|
+
{{/if}}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "@repo/api-gateway",
|
|
3
3
|
"version": "0.0.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "{{description}}",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"test:debug": "vitest --inspect-brk --no-file-parallelism",
|
|
21
21
|
"test:e2e": "vitest run --config ./vitest.config.e2e.ts"{{#if hasUltracite}},
|
|
22
22
|
"check": "ultracite check",
|
|
23
|
-
"fix": "ultracite fix"{{/if}}{{#if hasHusky}},
|
|
23
|
+
"fix": "ultracite fix --ignore-pattern migrations/**"{{/if}}{{#if hasHusky}},
|
|
24
24
|
"prepare": "husky"{{/if}}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"source-map-support": "^0.5.21",
|
|
46
46
|
"supertest": "^7.0.0",
|
|
47
47
|
"typescript": "catalog:",
|
|
48
|
-
"vite-tsconfig-paths": "^5.1.4",
|
|
49
48
|
{{#if hasUltracite}}"ultracite": "7.8.0",{{/if}}
|
|
50
49
|
"vitest": "^4.1.2"
|
|
51
50
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
// oxlint-disable vitest/require-mock-type-parameters
|
|
2
2
|
import type { TestingModule } from "@nestjs/testing";
|
|
3
3
|
import { Test } from "@nestjs/testing";
|
|
4
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { beforeEach, describe, expect, it{{#if (or hasDatabase hasOrm)}}, vi{{/if}} } from "vitest";
|
|
5
5
|
|
|
6
6
|
import { AppController } from "./app.controller.js";
|
|
7
7
|
import { AppService } from "./app.service.js";
|
|
8
8
|
{{#if (or hasDatabase hasOrm)}}
|
|
9
|
+
{{#if isMonorepo}}
|
|
10
|
+
import type { ISqlAdapter } from "@repo/db";
|
|
11
|
+
import { DbService } from "@repo/db";
|
|
12
|
+
{{else}}
|
|
9
13
|
import type { ISqlAdapter } from "./database/adapters/interfaces/db-adapter.interface.js";
|
|
10
14
|
import { DbService } from "./database/db.service.js";
|
|
11
15
|
{{/if}}
|
|
16
|
+
{{/if}}
|
|
12
17
|
|
|
13
18
|
{{#if (or hasDatabase hasOrm)}}
|
|
14
19
|
const mockSqlAdapter: ISqlAdapter = {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { Controller, Get } from "@nestjs/common";
|
|
2
|
+
{{#if (eq auth "better-auth")}}
|
|
3
|
+
import { AllowAnonymous } from "@thallesp/nestjs-better-auth";
|
|
4
|
+
{{/if}}
|
|
2
5
|
{{#if swagger}}
|
|
3
6
|
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
|
4
7
|
{{/if}}
|
|
@@ -16,6 +19,9 @@ export class AppController {
|
|
|
16
19
|
@ApiOperation({ summary: "/api" })
|
|
17
20
|
@ApiResponse({ status: 200, description: "Returns a hello world message" })
|
|
18
21
|
{{/if}}
|
|
22
|
+
{{#if (eq auth "better-auth")}}
|
|
23
|
+
@AllowAnonymous()
|
|
24
|
+
{{/if}}
|
|
19
25
|
@Get()
|
|
20
26
|
getHello(): string {
|
|
21
27
|
return this.appService.getHello();
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common";
|
|
2
2
|
{{#if (or hasDatabase hasOrm)}}
|
|
3
|
+
{{#if isMonorepo}}
|
|
4
|
+
import { DbModule } from "@repo/db";
|
|
5
|
+
{{else}}
|
|
3
6
|
import { DbModule } from "./database/db.module.js";
|
|
4
|
-
|
|
7
|
+
{{/if}}
|
|
8
|
+
import { env } from "@repo/env/api-gateway";
|
|
5
9
|
{{/if}}
|
|
6
10
|
{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
|
|
7
11
|
import { AuthModule } from "./auth/auth.module.js";
|
|
@@ -17,6 +21,13 @@ import { AppErrorHandler } from "./libs/trpc/error.handler.js";
|
|
|
17
21
|
{{#if hasTrpcAndAuth}}
|
|
18
22
|
import { AuthTRPCMiddleware } from "./libs/middlewares/auth.trpc.middleware.js";
|
|
19
23
|
{{/if}}
|
|
24
|
+
{{#if (and hasTrpc hasRateLimiting)}}
|
|
25
|
+
import { RateLimitTRPCMiddleware } from "./libs/middlewares/rate-limit.trpc.middleware.js";
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if hasRateLimiting}}
|
|
29
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
30
|
+
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
|
|
20
31
|
{{/if}}
|
|
21
32
|
|
|
22
33
|
import { AppController } from "./app.controller.js";
|
|
@@ -51,8 +62,19 @@ import { AppService } from "./app.service.js";
|
|
|
51
62
|
TRPCModule.forRoot({
|
|
52
63
|
context: AppContext,
|
|
53
64
|
onError: AppErrorHandler,
|
|
65
|
+
{{#if hasRateLimiting}}
|
|
66
|
+
globalMiddlewares: [RateLimitTRPCMiddleware],
|
|
67
|
+
{{/if}}
|
|
54
68
|
}),
|
|
55
69
|
{{/if}}
|
|
70
|
+
{{#if hasRateLimiting}}
|
|
71
|
+
ThrottlerModule.forRoot([
|
|
72
|
+
{
|
|
73
|
+
ttl: 60_000,
|
|
74
|
+
limit: 60,
|
|
75
|
+
},
|
|
76
|
+
]),
|
|
77
|
+
{{/if}}
|
|
56
78
|
{{#if (and hasTodoExample (or hasRest hasTrpc))}}
|
|
57
79
|
TodoModule,
|
|
58
80
|
{{/if}}
|
|
@@ -63,6 +85,9 @@ import { AppService } from "./app.service.js";
|
|
|
63
85
|
{{#if hasTrpcAndAuth}}
|
|
64
86
|
AuthTRPCMiddleware,
|
|
65
87
|
{{/if}}
|
|
88
|
+
{{#if hasRateLimiting}}
|
|
89
|
+
RateLimitTRPCMiddleware,
|
|
90
|
+
{{/if}}
|
|
66
91
|
AppContext,
|
|
67
92
|
AppErrorHandler,
|
|
68
93
|
AppRouter,
|
|
@@ -70,6 +95,12 @@ import { AppService } from "./app.service.js";
|
|
|
70
95
|
TodoRouter,
|
|
71
96
|
{{/if}}
|
|
72
97
|
{{/if}}
|
|
98
|
+
{{#if hasRateLimiting}}
|
|
99
|
+
{
|
|
100
|
+
provide: APP_GUARD,
|
|
101
|
+
useClass: ThrottlerGuard,
|
|
102
|
+
},
|
|
103
|
+
{{/if}}
|
|
73
104
|
],
|
|
74
105
|
})
|
|
75
106
|
export class AppModule {}
|
|
@@ -16,11 +16,20 @@ import { apiReference } from "@scalar/nestjs-api-reference";
|
|
|
16
16
|
import { SwaggerModule } from "@nestjs/swagger";
|
|
17
17
|
{{/if}}
|
|
18
18
|
{{#if (and swagger (or (eq auth "better-auth") (eq auth "better-auth:stateless")))}}
|
|
19
|
-
import {
|
|
19
|
+
import { AuthService } from "@thallesp/nestjs-better-auth";
|
|
20
20
|
{{/if}}
|
|
21
21
|
|
|
22
22
|
async function bootstrap() {
|
|
23
|
-
const
|
|
23
|
+
const trustedProxyIps = env.TRUSTED_PROXY_IPS
|
|
24
|
+
.split(",")
|
|
25
|
+
.map((ip) => ip.trim())
|
|
26
|
+
.filter(Boolean);
|
|
27
|
+
const app = await NestFactory.create{{#if (eq httpProvider "fastify")}}<NestFastifyApplication>(AppModule, new FastifyAdapter({ trustProxy: trustedProxyIps })){{else}}(AppModule){{/if}};
|
|
28
|
+
{{#unless (eq httpProvider "fastify")}}
|
|
29
|
+
if (trustedProxyIps.length > 0) {
|
|
30
|
+
app.getHttpAdapter().getInstance().set("trust proxy", trustedProxyIps);
|
|
31
|
+
}
|
|
32
|
+
{{/unless}}
|
|
24
33
|
app.enableCors({
|
|
25
34
|
origin: env.CORS_ORIGIN,
|
|
26
35
|
});
|
|
@@ -38,7 +47,7 @@ async function bootstrap() {
|
|
|
38
47
|
integrations: {
|
|
39
48
|
{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
|
|
40
49
|
betterAuth: {
|
|
41
|
-
auth,
|
|
50
|
+
auth: app.get(AuthService).instance,
|
|
42
51
|
pathPrefix: "/api/auth",
|
|
43
52
|
},
|
|
44
53
|
{{/if}}
|
|
@@ -65,7 +74,7 @@ async function bootstrap() {
|
|
|
65
74
|
integrations: {
|
|
66
75
|
{{#if (or (eq auth "better-auth") (eq auth "better-auth:stateless"))}}
|
|
67
76
|
betterAuth: {
|
|
68
|
-
auth,
|
|
77
|
+
auth: app.get(AuthService).instance,
|
|
69
78
|
pathPrefix: "/api/auth",
|
|
70
79
|
},
|
|
71
80
|
{{/if}}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { INestApplication } from "@nestjs/common";
|
|
2
2
|
import { Test, TestingModule } from "@nestjs/testing";
|
|
3
|
+
{{#if (eq httpProvider "fastify")}}
|
|
4
|
+
import { FastifyAdapter } from "@nestjs/platform-fastify";
|
|
5
|
+
{{/if}}
|
|
3
6
|
import request from "supertest";
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
4
8
|
import type { App } from "supertest/types";
|
|
5
9
|
|
|
6
10
|
import { AppModule } from "./../src/app.module.js";
|
|
@@ -13,15 +17,55 @@ describe("AppController (e2e)", () => {
|
|
|
13
17
|
imports: [AppModule],
|
|
14
18
|
}).compile();
|
|
15
19
|
|
|
20
|
+
{{#if (eq httpProvider "fastify")}}
|
|
21
|
+
app = moduleFixture.createNestApplication(new FastifyAdapter());
|
|
22
|
+
{{else}}
|
|
16
23
|
app = moduleFixture.createNestApplication();
|
|
24
|
+
{{/if}}
|
|
17
25
|
await app.init();
|
|
26
|
+
{{#if (eq httpProvider "fastify")}}
|
|
27
|
+
await app.getHttpAdapter().getInstance().ready();
|
|
28
|
+
{{/if}}
|
|
18
29
|
});
|
|
19
30
|
|
|
20
31
|
it("/ (GET)", () => {
|
|
21
32
|
return request(app.getHttpServer()).get("/").expect(200).expect("Hello World!");
|
|
22
33
|
});
|
|
23
34
|
|
|
35
|
+
it("returns 404 for an unknown route", async () => {
|
|
36
|
+
await request(app.getHttpServer()).get("/__missing_route__").expect(404);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("rejects unsupported methods", async () => {
|
|
40
|
+
await request(app.getHttpServer()).post("/").send({ unexpected: true }).expect(404);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
{{#if (eq auth "better-auth")}}
|
|
44
|
+
it("creates a user and resolves the authenticated session", async () => {
|
|
45
|
+
const email = `monorepo-${Date.now()}@example.com`;
|
|
46
|
+
const signup = await request(app.getHttpServer())
|
|
47
|
+
.post("/api/auth/sign-up/email")
|
|
48
|
+
.set("origin", "http://localhost:3000")
|
|
49
|
+
.send({ email, name: "Monorepo Scenario", password: "Scenario-password-123!" })
|
|
50
|
+
.expect(200);
|
|
51
|
+
const cookies = (signup.headers["set-cookie"] ?? []).map((cookie) => cookie.split(";")[0]);
|
|
52
|
+
expect(cookies.length).toBeGreaterThan(0);
|
|
53
|
+
|
|
54
|
+
const session = await request(app.getHttpServer())
|
|
55
|
+
.get("/api/auth/get-session")
|
|
56
|
+
.set("cookie", cookies.join("; "))
|
|
57
|
+
.expect(200);
|
|
58
|
+
expect(session.body.user.email).toBe(email);
|
|
59
|
+
|
|
60
|
+
const profile = await request(app.getHttpServer())
|
|
61
|
+
.get("/auth-profile")
|
|
62
|
+
.set("cookie", cookies.join("; "))
|
|
63
|
+
.expect(200);
|
|
64
|
+
expect(profile.body).toEqual({ id: session.body.user.id, email });
|
|
65
|
+
});
|
|
66
|
+
{{/if}}
|
|
67
|
+
|
|
24
68
|
afterEach(async () => {
|
|
25
|
-
await app
|
|
69
|
+
await app?.close();
|
|
26
70
|
});
|
|
27
71
|
});
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common";
|
|
2
2
|
import { GraphQLModule } from "@nestjs/graphql";
|
|
3
3
|
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
4
|
-
import {
|
|
4
|
+
import type { IncomingMessage } from "node:http";
|
|
5
|
+
import path from "node:path";
|
|
5
6
|
import { env } from "@repo/env/api-gateway";
|
|
6
|
-
{
|
|
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}}
|
|
@@ -16,15 +13,14 @@ import { TodoResolver } from "./resolvers/todo.resolver.js";
|
|
|
16
13
|
imports: [
|
|
17
14
|
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
18
15
|
driver: ApolloDriver,
|
|
19
|
-
autoSchemaFile: join(process.cwd(), "apps/api-gateway/src/schema.gql"),
|
|
16
|
+
autoSchemaFile: path.join(process.cwd(), "apps/api-gateway/src/schema.gql"),
|
|
20
17
|
graphiql: env.GRAPHQL_PLAYGROUND,
|
|
18
|
+
context: ({ req, res }: { req: IncomingMessage; res: unknown }) => ({ req, res }),
|
|
21
19
|
sortSchema: true,
|
|
22
|
-
{{#if (eq httpProvider "fastify")}}
|
|
23
|
-
disableHealthCheck: true,
|
|
24
|
-
{{/if}}
|
|
25
20
|
}),
|
|
26
21
|
],
|
|
27
22
|
providers: [
|
|
23
|
+
HealthResolver,
|
|
28
24
|
{{#if hasTodoExample}}
|
|
29
25
|
TodoResolver,
|
|
30
26
|
{{/if}}
|
|
@@ -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
|
+
}
|