@exanderal/stackcraft 0.1.2 → 0.2.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/README.md +34 -10
- package/dist/create/index.d.ts.map +1 -1
- package/dist/create/index.js +12 -1
- package/dist/create/index.js.map +1 -1
- package/dist/create/scaffold.d.ts.map +1 -1
- package/dist/create/scaffold.js +8 -2
- package/dist/create/scaffold.js.map +1 -1
- package/dist/create/scaffolders/api-nestjs-graphql.d.ts +3 -0
- package/dist/create/scaffolders/api-nestjs-graphql.d.ts.map +1 -0
- package/dist/create/scaffolders/api-nestjs-graphql.js +31 -0
- package/dist/create/scaffolders/api-nestjs-graphql.js.map +1 -0
- package/dist/create/types.d.ts +1 -1
- package/dist/create/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/templates/api-nestjs-graphql/.prettierrc +4 -0
- package/templates/api-nestjs-graphql/eslint.config.mjs +34 -0
- package/templates/api-nestjs-graphql/nest-cli.json +8 -0
- package/templates/api-nestjs-graphql/package.json +81 -0
- package/templates/api-nestjs-graphql/project.json +21 -0
- package/templates/api-nestjs-graphql/src/app.module.ts +21 -0
- package/templates/api-nestjs-graphql/src/common/entities/base.entity.ts +21 -0
- package/templates/api-nestjs-graphql/src/common/repositories/entity.repository.ts +21 -0
- package/templates/api-nestjs-graphql/src/common/repositories/readonly-entity.repository.ts +22 -0
- package/templates/api-nestjs-graphql/src/common/services/entity.service.ts +24 -0
- package/templates/api-nestjs-graphql/src/common/services/readonly-entity.service.ts +23 -0
- package/templates/api-nestjs-graphql/src/main.ts +8 -0
- package/templates/api-nestjs-graphql/src/modules/database/database.module.ts +18 -0
- package/templates/api-nestjs-graphql/src/modules/health/__tests__/health.controller.spec.ts +20 -0
- package/templates/api-nestjs-graphql/src/modules/health/__tests__/health.service.spec.ts +18 -0
- package/templates/api-nestjs-graphql/src/modules/health/health.controller.ts +12 -0
- package/templates/api-nestjs-graphql/src/modules/health/health.module.ts +9 -0
- package/templates/api-nestjs-graphql/src/modules/health/health.service.ts +8 -0
- package/templates/api-nestjs-graphql/test/app.e2e-spec.ts +29 -0
- package/templates/api-nestjs-graphql/test/jest-e2e.json +9 -0
- package/templates/api-nestjs-graphql/tsconfig.build.json +4 -0
- package/templates/api-nestjs-graphql/tsconfig.json +21 -0
- package/templates/base/package.json +3 -1
- package/templates/base/tools/generators/controller/index.js +22 -0
- package/templates/base/tools/generators/controller/schema.json +18 -0
- package/templates/base/tools/generators/generators.json +11 -1
- package/templates/base/tools/generators/module/graphql-files/__fileName__.model.ts__tmpl__ +11 -0
- package/templates/base/tools/generators/module/index.js +4 -22
- package/templates/base/tools/generators/module/schema.json +4 -5
- package/templates/base/tools/generators/resolver/files/__fileName__.resolver.ts__tmpl__ +28 -0
- package/templates/base/tools/generators/resolver/index.js +22 -0
- package/templates/base/tools/generators/resolver/schema.json +18 -0
- /package/templates/base/tools/generators/{module/crud-files → controller/files}/__fileName__.controller.ts__tmpl__ +0 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Follow the prompts — you'll have an Nx monorepo with deps installed and ready
|
|
|
17
17
|
```
|
|
18
18
|
your-project/
|
|
19
19
|
├── apps/
|
|
20
|
-
│ ├── backend/ # NestJS REST API
|
|
20
|
+
│ ├── backend/ # NestJS REST or GraphQL API
|
|
21
21
|
│ └── web/ # Vite + React or Next.js
|
|
22
22
|
├── packages/ # shared code
|
|
23
23
|
└── tools/
|
|
@@ -26,11 +26,20 @@ your-project/
|
|
|
26
26
|
|
|
27
27
|
### Backend (`apps/backend`)
|
|
28
28
|
|
|
29
|
+
Choose between REST or GraphQL at setup time — both use the same underlying structure:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
src/
|
|
33
|
+
├── modules/ # domain layer — model, repository, service, module
|
|
34
|
+
├── api/ # REST controllers (REST only)
|
|
35
|
+
├── resolvers/ # GraphQL resolvers (GraphQL only)
|
|
36
|
+
└── common/ # shared base classes
|
|
37
|
+
```
|
|
38
|
+
|
|
29
39
|
- NestJS with TypeORM
|
|
30
40
|
- PostgreSQL or MySQL
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- HTTP controllers live separately in `src/api/`
|
|
41
|
+
- `EntityRepository` and `EntityService` base classes — extend them for each module
|
|
42
|
+
- UUID primary keys
|
|
34
43
|
|
|
35
44
|
### Frontend (`apps/web`)
|
|
36
45
|
|
|
@@ -40,8 +49,6 @@ your-project/
|
|
|
40
49
|
|
|
41
50
|
## Running the project
|
|
42
51
|
|
|
43
|
-
Each app has the same scripts:
|
|
44
|
-
|
|
45
52
|
```sh
|
|
46
53
|
pnpm dev # start all apps in parallel
|
|
47
54
|
pnpm build # build all apps
|
|
@@ -49,7 +56,7 @@ pnpm test # run all tests
|
|
|
49
56
|
pnpm lint # lint all apps
|
|
50
57
|
```
|
|
51
58
|
|
|
52
|
-
|
|
59
|
+
Target a specific app:
|
|
53
60
|
|
|
54
61
|
```sh
|
|
55
62
|
pnpm nx run backend:dev
|
|
@@ -58,13 +65,27 @@ pnpm nx run web:dev
|
|
|
58
65
|
|
|
59
66
|
## Code generation
|
|
60
67
|
|
|
61
|
-
Generate a new
|
|
68
|
+
Generate a new domain module:
|
|
62
69
|
|
|
63
70
|
```sh
|
|
64
71
|
pnpm generate:module --name=trainer
|
|
72
|
+
# add --graphql to include @ObjectType() on the model
|
|
73
|
+
pnpm generate:module --name=trainer --graphql
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Generate a REST controller for an existing module:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
pnpm generate:controller --name=trainer
|
|
80
|
+
# creates apps/backend/src/api/trainer/trainer.controller.ts
|
|
65
81
|
```
|
|
66
82
|
|
|
67
|
-
|
|
83
|
+
Generate a GraphQL resolver for an existing module:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pnpm generate:resolver --name=trainer
|
|
87
|
+
# creates apps/backend/src/resolvers/trainer/trainer.resolver.ts
|
|
88
|
+
```
|
|
68
89
|
|
|
69
90
|
## Stack
|
|
70
91
|
|
|
@@ -75,7 +96,10 @@ This creates `apps/backend/src/modules/trainer/` with a model, repository, servi
|
|
|
75
96
|
|
|
76
97
|
## Roadmap
|
|
77
98
|
|
|
78
|
-
- [
|
|
99
|
+
- [x] NestJS REST API
|
|
100
|
+
- [x] NestJS GraphQL API
|
|
101
|
+
- [x] Module, controller, and resolver generators
|
|
102
|
+
- [ ] GraphQL codegen pipeline
|
|
79
103
|
- [ ] Expo mobile
|
|
80
104
|
- [ ] `stackcraft add` addon system (auth, Supabase, etc.)
|
|
81
105
|
- [ ] Presets and `--config` for non-interactive use
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,MAAM,kBA4E3B"}
|
package/dist/create/index.js
CHANGED
|
@@ -12,6 +12,17 @@ export async function create() {
|
|
|
12
12
|
cancel('Cancelled.');
|
|
13
13
|
process.exit(0);
|
|
14
14
|
}
|
|
15
|
+
const backend = await select({
|
|
16
|
+
message: 'Backend',
|
|
17
|
+
options: [
|
|
18
|
+
{ value: 'nestjs-rest', label: 'NestJS REST', hint: 'REST API with TypeORM' },
|
|
19
|
+
{ value: 'nestjs-graphql', label: 'NestJS GraphQL', hint: 'Code-first GraphQL with TypeORM' },
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
if (isCancel(backend)) {
|
|
23
|
+
cancel('Cancelled.');
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
15
26
|
const frontend = await select({
|
|
16
27
|
message: 'Frontend',
|
|
17
28
|
options: [
|
|
@@ -48,7 +59,7 @@ export async function create() {
|
|
|
48
59
|
const config = {
|
|
49
60
|
projectName: projectName,
|
|
50
61
|
frontend: frontend,
|
|
51
|
-
backend:
|
|
62
|
+
backend: backend,
|
|
52
63
|
database: database,
|
|
53
64
|
packageManager: packageManager,
|
|
54
65
|
targetDir: resolve(process.cwd(), projectName),
|
package/dist/create/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAA;AACtF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAGxC,MAAM,CAAC,KAAK,UAAU,MAAM;IAC1B,KAAK,CAAC,kDAAkD,CAAC,CAAA;IAEzD,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;QAC7B,OAAO,EAAE,cAAc;QACvB,WAAW,EAAE,QAAQ;QACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;KAC/C,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC;QAC5B,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,iCAAiC,EAAE;YACjF,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,+BAA+B,EAAE;SAC7E;KACF,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC;QAC5B,OAAO,EAAE,UAAU;QACnB,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE;YAC/D,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;SACnC;KACF,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC;QAClC,OAAO,EAAE,iBAAiB;QAC1B,OAAO,EAAE;YACP,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE;YACrD,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE;SAC/B;KACF,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7B,MAAM,CAAC,YAAY,CAAC,CAAA;QACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,MAAM,GAAkB;QAC5B,WAAW,EAAE,WAAqB;QAClC,QAAQ,EAAE,QAAoB;QAC9B,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/create/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/create/scaffold.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/create/scaffold.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,wBAAsB,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,iBAqBlF"}
|
package/dist/create/scaffold.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
|
+
import { scaffoldNestjsGraphql } from './scaffolders/api-nestjs-graphql.js';
|
|
2
3
|
import { scaffoldNestjsRest } from './scaffolders/api-nestjs-rest.js';
|
|
3
4
|
import { scaffoldBase } from './scaffolders/base.js';
|
|
4
5
|
import { scaffoldNextjs } from './scaffolders/web-nextjs.js';
|
|
@@ -6,8 +7,13 @@ import { scaffoldVite } from './scaffolders/web-vite.js';
|
|
|
6
7
|
export async function scaffold(config, onStep) {
|
|
7
8
|
onStep('Creating workspace...');
|
|
8
9
|
await scaffoldBase(config);
|
|
9
|
-
onStep('Adding
|
|
10
|
-
|
|
10
|
+
onStep('Adding backend...');
|
|
11
|
+
if (config.backend === 'nestjs-graphql') {
|
|
12
|
+
await scaffoldNestjsGraphql(config);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
await scaffoldNestjsRest(config);
|
|
16
|
+
}
|
|
11
17
|
if (config.frontend === 'vite') {
|
|
12
18
|
onStep('Adding Vite + React app...');
|
|
13
19
|
await scaffoldVite(config);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/create/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAGxD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAqB,EAAE,MAA6B;IACjF,MAAM,CAAC,uBAAuB,CAAC,CAAA;IAC/B,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;IAE1B,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/create/scaffold.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAA;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAGxD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAqB,EAAE,MAA6B;IACjF,MAAM,CAAC,uBAAuB,CAAC,CAAA;IAC/B,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;IAE1B,MAAM,CAAC,mBAAmB,CAAC,CAAA;IAC3B,IAAI,MAAM,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;QACxC,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAA;IACrC,CAAC;SAAM,CAAC;QACN,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAClC,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC/B,MAAM,CAAC,4BAA4B,CAAC,CAAA;QACpC,MAAM,YAAY,CAAC,MAAM,CAAC,CAAA;IAC5B,CAAC;SAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,CAAC,uBAAuB,CAAC,CAAA;QAC/B,MAAM,cAAc,CAAC,MAAM,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,CAAC,4BAA4B,CAAC,CAAA;IACpC,MAAM,KAAK,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAA;AAC5E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-nestjs-graphql.d.ts","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-graphql.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAWhD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,aAAa,iBAahE"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { copyTemplate } from './utils/copy.js';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const TEMPLATES_DIR = join(__dirname, '..', '..', '..', 'templates');
|
|
7
|
+
const DB_CONFIG = {
|
|
8
|
+
postgres: { type: 'postgres', port: '5432', driver: 'pg', driverVersion: '^8.0.0', typesPackage: '@types/pg', typesVersion: '^8.0.0' },
|
|
9
|
+
mysql: { type: 'mysql', port: '3306', driver: 'mysql2', driverVersion: '^3.0.0', typesPackage: null, typesVersion: null },
|
|
10
|
+
};
|
|
11
|
+
export async function scaffoldNestjsGraphql(config) {
|
|
12
|
+
const appDir = join(config.targetDir, 'apps', 'backend');
|
|
13
|
+
await mkdir(appDir, { recursive: true });
|
|
14
|
+
const db = DB_CONFIG[config.database];
|
|
15
|
+
await copyTemplate(join(TEMPLATES_DIR, 'api-nestjs-graphql'), appDir, {
|
|
16
|
+
projectName: config.projectName,
|
|
17
|
+
dbType: db.type,
|
|
18
|
+
dbPort: db.port,
|
|
19
|
+
});
|
|
20
|
+
await injectDbDriver(appDir, db);
|
|
21
|
+
}
|
|
22
|
+
async function injectDbDriver(appDir, db) {
|
|
23
|
+
const pkgPath = join(appDir, 'package.json');
|
|
24
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
|
|
25
|
+
pkg.dependencies[db.driver] = db.driverVersion;
|
|
26
|
+
if (db.typesPackage) {
|
|
27
|
+
pkg.devDependencies[db.typesPackage] = db.typesVersion;
|
|
28
|
+
}
|
|
29
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=api-nestjs-graphql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-nestjs-graphql.js","sourceRoot":"","sources":["../../../src/create/scaffolders/api-nestjs-graphql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;AAEpE,MAAM,SAAS,GAAG;IAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,EAAE;IACtI,KAAK,EAAK,EAAE,IAAI,EAAE,OAAO,EAAK,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;CACvH,CAAA;AAEV,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAAqB;IAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;IACxD,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAExC,MAAM,EAAE,GAAG,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAErC,MAAM,YAAY,CAAC,IAAI,CAAC,aAAa,EAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE;QACpE,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,MAAM,EAAE,EAAE,CAAC,IAAI;QACf,MAAM,EAAE,EAAE,CAAC,IAAI;KAChB,CAAC,CAAA;IAEF,MAAM,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;AAClC,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,MAAc,EAAE,EAA4C;IACxF,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;IAExD,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAA;IAE9C,IAAI,EAAE,CAAC,YAAY,EAAE,CAAC;QACpB,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,YAAY,CAAA;IACxD,CAAC;IAED,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAA;AACxE,CAAC"}
|
package/dist/create/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type PackageManager = 'pnpm' | 'npm';
|
|
2
2
|
export type Frontend = 'vite' | 'nextjs';
|
|
3
|
-
export type Backend = 'nestjs-rest';
|
|
3
|
+
export type Backend = 'nestjs-rest' | 'nestjs-graphql';
|
|
4
4
|
export type Database = 'postgres' | 'mysql';
|
|
5
5
|
export interface ProjectConfig {
|
|
6
6
|
projectName: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/create/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,CAAA;AAC3C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AACxC,MAAM,MAAM,OAAO,GAAG,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/create/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,KAAK,CAAA;AAC3C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAA;AACxC,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,gBAAgB,CAAA;AACtD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAA;AAE3C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,QAAQ,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,QAAQ,CAAA;IAClB,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;CAClB"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import eslint from '@eslint/js';
|
|
3
|
+
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
|
|
4
|
+
import globals from 'globals';
|
|
5
|
+
import tseslint from 'typescript-eslint';
|
|
6
|
+
|
|
7
|
+
export default tseslint.config(
|
|
8
|
+
{
|
|
9
|
+
ignores: ['eslint.config.mjs'],
|
|
10
|
+
},
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
...tseslint.configs.recommendedTypeChecked,
|
|
13
|
+
eslintPluginPrettierRecommended,
|
|
14
|
+
{
|
|
15
|
+
languageOptions: {
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.jest,
|
|
19
|
+
},
|
|
20
|
+
sourceType: 'commonjs',
|
|
21
|
+
parserOptions: {
|
|
22
|
+
projectService: true,
|
|
23
|
+
tsconfigRootDir: import.meta.dirname,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
rules: {
|
|
29
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
30
|
+
'@typescript-eslint/no-floating-promises': 'warn',
|
|
31
|
+
'@typescript-eslint/no-unsafe-argument': 'warn'
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}-backend",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"author": "",
|
|
6
|
+
"private": true,
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "nest start --watch",
|
|
10
|
+
"build": "nest build",
|
|
11
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
12
|
+
"start": "nest start",
|
|
13
|
+
"start:dev": "nest start --watch",
|
|
14
|
+
"start:debug": "nest start --debug --watch",
|
|
15
|
+
"start:prod": "node dist/main",
|
|
16
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"test:watch": "jest --watch",
|
|
19
|
+
"test:cov": "jest --coverage",
|
|
20
|
+
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
21
|
+
"test:e2e": "jest --config ./test/jest-e2e.json"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@apollo/server": "^4.0.0",
|
|
25
|
+
"@nestjs/apollo": "^13.0.0",
|
|
26
|
+
"@nestjs/common": "^11.0.1",
|
|
27
|
+
"@nestjs/config": "^3.0.0",
|
|
28
|
+
"@nestjs/core": "^11.0.1",
|
|
29
|
+
"@nestjs/graphql": "^13.0.0",
|
|
30
|
+
"@nestjs/platform-express": "^11.0.1",
|
|
31
|
+
"@nestjs/typeorm": "^10.0.0",
|
|
32
|
+
"graphql": "^16.0.0",
|
|
33
|
+
"reflect-metadata": "^0.2.2",
|
|
34
|
+
"rxjs": "^7.8.1",
|
|
35
|
+
"typeorm": "^0.3.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
39
|
+
"@eslint/js": "^9.18.0",
|
|
40
|
+
"@nestjs/cli": "^11.0.0",
|
|
41
|
+
"@nestjs/schematics": "^11.0.0",
|
|
42
|
+
"@nestjs/testing": "^11.0.1",
|
|
43
|
+
"@swc/cli": "^0.6.0",
|
|
44
|
+
"@swc/core": "^1.10.7",
|
|
45
|
+
"@types/express": "^5.0.0",
|
|
46
|
+
"@types/jest": "^29.5.14",
|
|
47
|
+
"@types/node": "^22.10.7",
|
|
48
|
+
"@types/supertest": "^6.0.2",
|
|
49
|
+
"eslint": "^9.18.0",
|
|
50
|
+
"eslint-config-prettier": "^10.0.1",
|
|
51
|
+
"eslint-plugin-prettier": "^5.2.2",
|
|
52
|
+
"globals": "^16.0.0",
|
|
53
|
+
"jest": "^29.7.0",
|
|
54
|
+
"prettier": "^3.4.2",
|
|
55
|
+
"source-map-support": "^0.5.21",
|
|
56
|
+
"supertest": "^7.0.0",
|
|
57
|
+
"ts-jest": "^29.2.5",
|
|
58
|
+
"ts-loader": "^9.5.2",
|
|
59
|
+
"ts-node": "^10.9.2",
|
|
60
|
+
"tsconfig-paths": "^4.2.0",
|
|
61
|
+
"typescript": "^5.7.3",
|
|
62
|
+
"typescript-eslint": "^8.20.0"
|
|
63
|
+
},
|
|
64
|
+
"jest": {
|
|
65
|
+
"moduleFileExtensions": [
|
|
66
|
+
"js",
|
|
67
|
+
"json",
|
|
68
|
+
"ts"
|
|
69
|
+
],
|
|
70
|
+
"rootDir": "src",
|
|
71
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
72
|
+
"transform": {
|
|
73
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
74
|
+
},
|
|
75
|
+
"collectCoverageFrom": [
|
|
76
|
+
"**/*.(t|j)s"
|
|
77
|
+
],
|
|
78
|
+
"coverageDirectory": "../coverage",
|
|
79
|
+
"testEnvironment": "node"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"targets": {
|
|
4
|
+
"dev": {
|
|
5
|
+
"executor": "nx:run-commands",
|
|
6
|
+
"options": { "command": "pnpm run dev", "cwd": "{projectRoot}" }
|
|
7
|
+
},
|
|
8
|
+
"build": {
|
|
9
|
+
"executor": "nx:run-commands",
|
|
10
|
+
"options": { "command": "pnpm run build", "cwd": "{projectRoot}" }
|
|
11
|
+
},
|
|
12
|
+
"lint": {
|
|
13
|
+
"executor": "nx:run-commands",
|
|
14
|
+
"options": { "command": "pnpm run lint", "cwd": "{projectRoot}" }
|
|
15
|
+
},
|
|
16
|
+
"test": {
|
|
17
|
+
"executor": "nx:run-commands",
|
|
18
|
+
"options": { "command": "pnpm run test", "cwd": "{projectRoot}" }
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
|
2
|
+
import { Module } from '@nestjs/common';
|
|
3
|
+
import { ConfigModule } from '@nestjs/config';
|
|
4
|
+
import { GraphQLModule } from '@nestjs/graphql';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { DatabaseModule } from './modules/database/database.module';
|
|
7
|
+
import { HealthModule } from './modules/health/health.module';
|
|
8
|
+
|
|
9
|
+
@Module({
|
|
10
|
+
imports: [
|
|
11
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
12
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
13
|
+
driver: ApolloDriver,
|
|
14
|
+
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
|
|
15
|
+
sortSchema: true,
|
|
16
|
+
}),
|
|
17
|
+
DatabaseModule,
|
|
18
|
+
HealthModule,
|
|
19
|
+
],
|
|
20
|
+
})
|
|
21
|
+
export class AppModule {}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import {
|
|
3
|
+
CreateDateColumn,
|
|
4
|
+
PrimaryGeneratedColumn,
|
|
5
|
+
UpdateDateColumn,
|
|
6
|
+
} from 'typeorm';
|
|
7
|
+
|
|
8
|
+
@ObjectType({ isAbstract: true })
|
|
9
|
+
export abstract class BaseEntity {
|
|
10
|
+
@Field(() => ID)
|
|
11
|
+
@PrimaryGeneratedColumn('uuid')
|
|
12
|
+
id: string;
|
|
13
|
+
|
|
14
|
+
@Field()
|
|
15
|
+
@CreateDateColumn()
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
|
|
18
|
+
@Field()
|
|
19
|
+
@UpdateDateColumn()
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DeepPartial } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { ReadonlyEntityRepository } from './readonly-entity.repository';
|
|
4
|
+
|
|
5
|
+
export abstract class EntityRepository<
|
|
6
|
+
T extends BaseEntity,
|
|
7
|
+
> extends ReadonlyEntityRepository<T> {
|
|
8
|
+
async create(data: DeepPartial<T>): Promise<T> {
|
|
9
|
+
const entity = this.repo.create(data);
|
|
10
|
+
return this.repo.save(entity);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async update(id: string, data: DeepPartial<T>): Promise<T> {
|
|
14
|
+
await this.repo.update(id, data as any);
|
|
15
|
+
return this.findById(id) as Promise<T>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async remove(id: string): Promise<void> {
|
|
19
|
+
await this.repo.delete(id);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FindManyOptions, FindOptionsWhere, In, Repository } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
|
|
4
|
+
export abstract class ReadonlyEntityRepository<T extends BaseEntity> {
|
|
5
|
+
constructor(protected readonly repo: Repository<T>) {}
|
|
6
|
+
|
|
7
|
+
findAll(options?: FindManyOptions<T>): Promise<T[]> {
|
|
8
|
+
return this.repo.find(options);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
findByIds(ids: string[]): Promise<T[]> {
|
|
12
|
+
return this.repo.findBy({ id: In(ids) } as FindOptionsWhere<T>);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
findById(id: string): Promise<T | null> {
|
|
16
|
+
return this.repo.findOneBy({ id } as FindOptionsWhere<T>);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
findOne(where: FindOptionsWhere<T>): Promise<T | null> {
|
|
20
|
+
return this.repo.findOneBy(where);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { DeepPartial } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { EntityRepository } from '../repositories/entity.repository';
|
|
4
|
+
import { ReadonlyEntityService } from './readonly-entity.service';
|
|
5
|
+
|
|
6
|
+
export abstract class EntityService<
|
|
7
|
+
T extends BaseEntity,
|
|
8
|
+
> extends ReadonlyEntityService<T> {
|
|
9
|
+
constructor(protected readonly repository: EntityRepository<T>) {
|
|
10
|
+
super(repository);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
create(data: DeepPartial<T>): Promise<T> {
|
|
14
|
+
return this.repository.create(data);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
update(id: string, data: DeepPartial<T>): Promise<T> {
|
|
18
|
+
return this.repository.update(id, data);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
remove(id: string): Promise<void> {
|
|
22
|
+
return this.repository.remove(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FindManyOptions, FindOptionsWhere } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../entities/base.entity';
|
|
3
|
+
import { ReadonlyEntityRepository } from '../repositories/readonly-entity.repository';
|
|
4
|
+
|
|
5
|
+
export abstract class ReadonlyEntityService<T extends BaseEntity> {
|
|
6
|
+
constructor(protected readonly repository: ReadonlyEntityRepository<T>) {}
|
|
7
|
+
|
|
8
|
+
findAll(options?: FindManyOptions<T>): Promise<T[]> {
|
|
9
|
+
return this.repository.findAll(options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
findByIds(ids: string[]): Promise<T[]> {
|
|
13
|
+
return this.repository.findByIds(ids);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
findById(id: string): Promise<T | null> {
|
|
17
|
+
return this.repository.findById(id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
findOne(where: FindOptionsWhere<T>): Promise<T | null> {
|
|
21
|
+
return this.repository.findOne(where);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
|
|
4
|
+
@Module({
|
|
5
|
+
imports: [
|
|
6
|
+
TypeOrmModule.forRoot({
|
|
7
|
+
type: '{{dbType}}' as 'postgres' | 'mysql',
|
|
8
|
+
host: process.env.DB_HOST ?? 'localhost',
|
|
9
|
+
port: parseInt(process.env.DB_PORT ?? '{{dbPort}}', 10),
|
|
10
|
+
username: process.env.DB_USER ?? 'postgres',
|
|
11
|
+
password: process.env.DB_PASSWORD ?? '',
|
|
12
|
+
database: process.env.DB_NAME ?? '{{projectName}}',
|
|
13
|
+
entities: [],
|
|
14
|
+
synchronize: process.env.NODE_ENV !== 'production',
|
|
15
|
+
}),
|
|
16
|
+
],
|
|
17
|
+
})
|
|
18
|
+
export class DatabaseModule {}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { HealthController } from './health.controller';
|
|
3
|
+
import { HealthService } from './health.service';
|
|
4
|
+
|
|
5
|
+
describe('HealthController', () => {
|
|
6
|
+
let controller: HealthController;
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
10
|
+
controllers: [HealthController],
|
|
11
|
+
providers: [HealthService],
|
|
12
|
+
}).compile();
|
|
13
|
+
|
|
14
|
+
controller = module.get<HealthController>(HealthController);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('returns ok status', () => {
|
|
18
|
+
expect(controller.check()).toEqual({ status: 'ok' });
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { HealthService } from './health.service';
|
|
3
|
+
|
|
4
|
+
describe('HealthService', () => {
|
|
5
|
+
let service: HealthService;
|
|
6
|
+
|
|
7
|
+
beforeEach(async () => {
|
|
8
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
9
|
+
providers: [HealthService],
|
|
10
|
+
}).compile();
|
|
11
|
+
|
|
12
|
+
service = module.get<HealthService>(HealthService);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns ok status', () => {
|
|
16
|
+
expect(service.check()).toEqual({ status: 'ok' });
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller, Get } from '@nestjs/common';
|
|
2
|
+
import { HealthService } from './health.service';
|
|
3
|
+
|
|
4
|
+
@Controller('health')
|
|
5
|
+
export class HealthController {
|
|
6
|
+
constructor(private readonly healthService: HealthService) {}
|
|
7
|
+
|
|
8
|
+
@Get()
|
|
9
|
+
check() {
|
|
10
|
+
return this.healthService.check();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { HealthController } from './health.controller';
|
|
3
|
+
import { HealthService } from './health.service';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
controllers: [HealthController],
|
|
7
|
+
providers: [HealthService],
|
|
8
|
+
})
|
|
9
|
+
export class HealthModule {}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { INestApplication } from '@nestjs/common';
|
|
2
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
3
|
+
import * as request from 'supertest';
|
|
4
|
+
import { App } from 'supertest/types';
|
|
5
|
+
import { AppModule } from './../src/app.module';
|
|
6
|
+
|
|
7
|
+
describe('App (e2e)', () => {
|
|
8
|
+
let app: INestApplication<App>;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
12
|
+
imports: [AppModule],
|
|
13
|
+
}).compile();
|
|
14
|
+
|
|
15
|
+
app = moduleFixture.createNestApplication();
|
|
16
|
+
await app.init();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await app.close();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('/health (GET)', () => {
|
|
24
|
+
return request(app.getHttpServer())
|
|
25
|
+
.get('/health')
|
|
26
|
+
.expect(200)
|
|
27
|
+
.expect({ status: 'ok' });
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2023",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strictNullChecks": true,
|
|
16
|
+
"forceConsistentCasingInFileNames": true,
|
|
17
|
+
"noImplicitAny": false,
|
|
18
|
+
"strictBindCallApply": false,
|
|
19
|
+
"noFallthroughCasesInSwitch": false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
"build": "nx run-many -t build",
|
|
8
8
|
"test": "nx run-many -t test",
|
|
9
9
|
"lint": "nx run-many -t lint",
|
|
10
|
-
"generate:module": "nx g @local/generators:module"
|
|
10
|
+
"generate:module": "nx g @local/generators:module",
|
|
11
|
+
"generate:resolver": "nx g @local/generators:resolver",
|
|
12
|
+
"generate:controller": "nx g @local/generators:controller"
|
|
11
13
|
},
|
|
12
14
|
"devDependencies": {
|
|
13
15
|
"@nx/devkit": "^20.0.0",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { formatFiles, generateFiles, names } = require('@nx/devkit');
|
|
2
|
+
const { execSync } = require('node:child_process');
|
|
3
|
+
const { join } = require('node:path');
|
|
4
|
+
|
|
5
|
+
module.exports = async function (tree, options) {
|
|
6
|
+
const n = names(options.name);
|
|
7
|
+
const controllerPath = `apps/backend/src/api/${n.fileName}`;
|
|
8
|
+
|
|
9
|
+
generateFiles(tree, join(__dirname, 'files'), controllerPath, { ...n, tmpl: '' });
|
|
10
|
+
|
|
11
|
+
await formatFiles(tree);
|
|
12
|
+
|
|
13
|
+
console.log(`\n✓ Controller created at ${controllerPath}/`);
|
|
14
|
+
console.log(` → Import ${n.className}Module and register ${n.className}Controller in your module\n`);
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
execSync(`npx prettier --write ${controllerPath}`, {
|
|
18
|
+
cwd: tree.root,
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"cli": "nx",
|
|
4
|
+
"id": "controller",
|
|
5
|
+
"title": "Create a REST controller",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Module name to generate a controller for (e.g. trainer)",
|
|
11
|
+
"$default": {
|
|
12
|
+
"$source": "argv",
|
|
13
|
+
"index": 0
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["name"]
|
|
18
|
+
}
|
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
"module": {
|
|
4
4
|
"factory": "./module/index",
|
|
5
5
|
"schema": "./module/schema.json",
|
|
6
|
-
"description": "Create a NestJS module"
|
|
6
|
+
"description": "Create a NestJS domain module (model, repository, service)"
|
|
7
|
+
},
|
|
8
|
+
"resolver": {
|
|
9
|
+
"factory": "./resolver/index",
|
|
10
|
+
"schema": "./resolver/schema.json",
|
|
11
|
+
"description": "Create a GraphQL resolver in src/resolvers/"
|
|
12
|
+
},
|
|
13
|
+
"controller": {
|
|
14
|
+
"factory": "./controller/index",
|
|
15
|
+
"schema": "./controller/schema.json",
|
|
16
|
+
"description": "Create a REST controller in src/api/"
|
|
7
17
|
}
|
|
8
18
|
}
|
|
9
19
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
+
import { Entity } from 'typeorm';
|
|
3
|
+
import { BaseEntity } from '../../common/entities/base.entity';
|
|
4
|
+
|
|
5
|
+
@ObjectType()
|
|
6
|
+
@Entity('<%= fileName %>s')
|
|
7
|
+
export class <%= className %>Model extends BaseEntity {
|
|
8
|
+
// @Field()
|
|
9
|
+
// @Column()
|
|
10
|
+
// name: string;
|
|
11
|
+
}
|
|
@@ -6,20 +6,10 @@ module.exports = async function (tree, options) {
|
|
|
6
6
|
const n = names(options.name);
|
|
7
7
|
const modulePath = `apps/backend/src/modules/${n.fileName}`;
|
|
8
8
|
|
|
9
|
-
generateFiles(
|
|
10
|
-
tree,
|
|
11
|
-
join(__dirname, 'files'),
|
|
12
|
-
modulePath,
|
|
13
|
-
{ ...n, tmpl: '' },
|
|
14
|
-
);
|
|
9
|
+
generateFiles(tree, join(__dirname, 'files'), modulePath, { ...n, tmpl: '' });
|
|
15
10
|
|
|
16
|
-
if (options.
|
|
17
|
-
generateFiles(
|
|
18
|
-
tree,
|
|
19
|
-
join(__dirname, 'crud-files'),
|
|
20
|
-
`apps/backend/src/api/${n.fileName}`,
|
|
21
|
-
{ ...n, tmpl: '' },
|
|
22
|
-
);
|
|
11
|
+
if (options.graphql) {
|
|
12
|
+
generateFiles(tree, join(__dirname, 'graphql-files'), modulePath, { ...n, tmpl: '' });
|
|
23
13
|
}
|
|
24
14
|
|
|
25
15
|
await formatFiles(tree);
|
|
@@ -27,16 +17,8 @@ module.exports = async function (tree, options) {
|
|
|
27
17
|
console.log(`\n✓ Module created at ${modulePath}/`);
|
|
28
18
|
console.log(` → Import ${n.className}Module in app.module.ts\n`);
|
|
29
19
|
|
|
30
|
-
if (options.crud) {
|
|
31
|
-
console.log(`✓ Controller created at apps/backend/src/api/${n.fileName}/`);
|
|
32
|
-
console.log(` → Add ${n.className}Controller to a module and import ${n.className}Module there\n`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
20
|
return () => {
|
|
36
|
-
|
|
37
|
-
if (options.crud) targets.push(`apps/backend/src/api/${n.fileName}`);
|
|
38
|
-
|
|
39
|
-
execSync(`npx prettier --write ${targets.join(' ')}`, {
|
|
21
|
+
execSync(`npx prettier --write ${modulePath}`, {
|
|
40
22
|
cwd: tree.root,
|
|
41
23
|
stdio: 'inherit',
|
|
42
24
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/schema",
|
|
3
3
|
"cli": "nx",
|
|
4
4
|
"id": "module",
|
|
5
|
-
"title": "Create a NestJS module",
|
|
5
|
+
"title": "Create a NestJS domain module",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"name": {
|
|
@@ -13,11 +13,10 @@
|
|
|
13
13
|
"index": 0
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"
|
|
16
|
+
"graphql": {
|
|
17
17
|
"type": "boolean",
|
|
18
|
-
"description": "
|
|
19
|
-
"default": false
|
|
20
|
-
"x-prompt": "Generate a CRUD controller?"
|
|
18
|
+
"description": "Add @ObjectType() to the model (required for GraphQL resolvers)",
|
|
19
|
+
"default": false
|
|
21
20
|
}
|
|
22
21
|
},
|
|
23
22
|
"required": ["name"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Args,
|
|
3
|
+
Mutation,
|
|
4
|
+
Query,
|
|
5
|
+
Resolver,
|
|
6
|
+
} from '@nestjs/graphql';
|
|
7
|
+
import { <%= className %>Model } from '../../modules/<%= fileName %>/<%= fileName %>.model';
|
|
8
|
+
import { <%= className %>Service } from '../../modules/<%= fileName %>/<%= fileName %>.service';
|
|
9
|
+
|
|
10
|
+
@Resolver(() => <%= className %>Model)
|
|
11
|
+
export class <%= className %>Resolver {
|
|
12
|
+
constructor(private readonly service: <%= className %>Service) {}
|
|
13
|
+
|
|
14
|
+
@Query(() => [<%= className %>Model])
|
|
15
|
+
<%= propertyName %>s() {
|
|
16
|
+
return this.service.findAll();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Query(() => <%= className %>Model, { nullable: true })
|
|
20
|
+
<%= propertyName %>(@Args('id') id: string) {
|
|
21
|
+
return this.service.findById(id);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Mutation(() => Boolean)
|
|
25
|
+
remove<%= className %>(@Args('id') id: string) {
|
|
26
|
+
return this.service.remove(id).then(() => true);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const { formatFiles, generateFiles, names } = require('@nx/devkit');
|
|
2
|
+
const { execSync } = require('node:child_process');
|
|
3
|
+
const { join } = require('node:path');
|
|
4
|
+
|
|
5
|
+
module.exports = async function (tree, options) {
|
|
6
|
+
const n = names(options.name);
|
|
7
|
+
const resolverPath = `apps/backend/src/resolvers/${n.fileName}`;
|
|
8
|
+
|
|
9
|
+
generateFiles(tree, join(__dirname, 'files'), resolverPath, { ...n, tmpl: '' });
|
|
10
|
+
|
|
11
|
+
await formatFiles(tree);
|
|
12
|
+
|
|
13
|
+
console.log(`\n✓ Resolver created at ${resolverPath}/`);
|
|
14
|
+
console.log(` → Import ${n.className}Module and register ${n.className}Resolver in your module\n`);
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
execSync(`npx prettier --write ${resolverPath}`, {
|
|
18
|
+
cwd: tree.root,
|
|
19
|
+
stdio: 'inherit',
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"cli": "nx",
|
|
4
|
+
"id": "resolver",
|
|
5
|
+
"title": "Create a GraphQL resolver",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Module name to generate a resolver for (e.g. trainer)",
|
|
11
|
+
"$default": {
|
|
12
|
+
"$source": "argv",
|
|
13
|
+
"index": 0
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["name"]
|
|
18
|
+
}
|
|
File without changes
|