servcraft 0.1.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.
Files changed (106) hide show
  1. package/.dockerignore +45 -0
  2. package/.env.example +46 -0
  3. package/.husky/commit-msg +1 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.prettierignore +4 -0
  6. package/.prettierrc +11 -0
  7. package/Dockerfile +76 -0
  8. package/Dockerfile.dev +31 -0
  9. package/README.md +232 -0
  10. package/commitlint.config.js +24 -0
  11. package/dist/cli/index.cjs +3968 -0
  12. package/dist/cli/index.cjs.map +1 -0
  13. package/dist/cli/index.d.cts +1 -0
  14. package/dist/cli/index.d.ts +1 -0
  15. package/dist/cli/index.js +3945 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/index.cjs +2458 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.cts +828 -0
  20. package/dist/index.d.ts +828 -0
  21. package/dist/index.js +2332 -0
  22. package/dist/index.js.map +1 -0
  23. package/docker-compose.prod.yml +118 -0
  24. package/docker-compose.yml +147 -0
  25. package/eslint.config.js +27 -0
  26. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  27. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  28. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  29. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  30. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +5 -0
  31. package/npm-cache/_update-notifier-last-checked +0 -0
  32. package/package.json +112 -0
  33. package/prisma/schema.prisma +157 -0
  34. package/src/cli/commands/add-module.ts +422 -0
  35. package/src/cli/commands/db.ts +137 -0
  36. package/src/cli/commands/docs.ts +16 -0
  37. package/src/cli/commands/generate.ts +459 -0
  38. package/src/cli/commands/init.ts +640 -0
  39. package/src/cli/index.ts +32 -0
  40. package/src/cli/templates/controller.ts +67 -0
  41. package/src/cli/templates/dynamic-prisma.ts +89 -0
  42. package/src/cli/templates/dynamic-schemas.ts +232 -0
  43. package/src/cli/templates/dynamic-types.ts +60 -0
  44. package/src/cli/templates/module-index.ts +33 -0
  45. package/src/cli/templates/prisma-model.ts +17 -0
  46. package/src/cli/templates/repository.ts +104 -0
  47. package/src/cli/templates/routes.ts +70 -0
  48. package/src/cli/templates/schemas.ts +26 -0
  49. package/src/cli/templates/service.ts +58 -0
  50. package/src/cli/templates/types.ts +27 -0
  51. package/src/cli/utils/docs-generator.ts +47 -0
  52. package/src/cli/utils/field-parser.ts +315 -0
  53. package/src/cli/utils/helpers.ts +89 -0
  54. package/src/config/env.ts +80 -0
  55. package/src/config/index.ts +97 -0
  56. package/src/core/index.ts +5 -0
  57. package/src/core/logger.ts +43 -0
  58. package/src/core/server.ts +132 -0
  59. package/src/database/index.ts +7 -0
  60. package/src/database/prisma.ts +54 -0
  61. package/src/database/seed.ts +59 -0
  62. package/src/index.ts +63 -0
  63. package/src/middleware/error-handler.ts +73 -0
  64. package/src/middleware/index.ts +3 -0
  65. package/src/middleware/security.ts +116 -0
  66. package/src/modules/audit/audit.service.ts +192 -0
  67. package/src/modules/audit/index.ts +2 -0
  68. package/src/modules/audit/types.ts +37 -0
  69. package/src/modules/auth/auth.controller.ts +182 -0
  70. package/src/modules/auth/auth.middleware.ts +87 -0
  71. package/src/modules/auth/auth.routes.ts +123 -0
  72. package/src/modules/auth/auth.service.ts +142 -0
  73. package/src/modules/auth/index.ts +49 -0
  74. package/src/modules/auth/schemas.ts +52 -0
  75. package/src/modules/auth/types.ts +69 -0
  76. package/src/modules/email/email.service.ts +212 -0
  77. package/src/modules/email/index.ts +10 -0
  78. package/src/modules/email/templates.ts +213 -0
  79. package/src/modules/email/types.ts +57 -0
  80. package/src/modules/swagger/index.ts +3 -0
  81. package/src/modules/swagger/schema-builder.ts +263 -0
  82. package/src/modules/swagger/swagger.service.ts +169 -0
  83. package/src/modules/swagger/types.ts +68 -0
  84. package/src/modules/user/index.ts +30 -0
  85. package/src/modules/user/schemas.ts +49 -0
  86. package/src/modules/user/types.ts +78 -0
  87. package/src/modules/user/user.controller.ts +139 -0
  88. package/src/modules/user/user.repository.ts +156 -0
  89. package/src/modules/user/user.routes.ts +199 -0
  90. package/src/modules/user/user.service.ts +145 -0
  91. package/src/modules/validation/index.ts +18 -0
  92. package/src/modules/validation/validator.ts +104 -0
  93. package/src/types/common.ts +61 -0
  94. package/src/types/index.ts +10 -0
  95. package/src/utils/errors.ts +66 -0
  96. package/src/utils/index.ts +33 -0
  97. package/src/utils/pagination.ts +38 -0
  98. package/src/utils/response.ts +63 -0
  99. package/tests/integration/auth.test.ts +59 -0
  100. package/tests/setup.ts +17 -0
  101. package/tests/unit/modules/validation.test.ts +88 -0
  102. package/tests/unit/utils/errors.test.ts +113 -0
  103. package/tests/unit/utils/pagination.test.ts +82 -0
  104. package/tsconfig.json +33 -0
  105. package/tsup.config.ts +14 -0
  106. package/vitest.config.ts +34 -0
@@ -0,0 +1,67 @@
1
+ export function controllerTemplate(name: string, pascalName: string, camelName: string): string {
2
+ return `import type { FastifyRequest, FastifyReply } from 'fastify';
3
+ import type { ${pascalName}Service } from './${name}.service.js';
4
+ import { create${pascalName}Schema, update${pascalName}Schema, ${camelName}QuerySchema } from './${name}.schemas.js';
5
+ import { success, created, noContent } from '../../utils/response.js';
6
+ import { parsePaginationParams } from '../../utils/pagination.js';
7
+ import { validateBody, validateQuery } from '../validation/validator.js';
8
+
9
+ export class ${pascalName}Controller {
10
+ constructor(private ${camelName}Service: ${pascalName}Service) {}
11
+
12
+ async list(request: FastifyRequest, reply: FastifyReply): Promise<void> {
13
+ const query = validateQuery(${camelName}QuerySchema, request.query);
14
+ const pagination = parsePaginationParams(query);
15
+ const filters = {
16
+ search: query.search,
17
+ };
18
+
19
+ const result = await this.${camelName}Service.findMany(pagination, filters);
20
+ success(reply, result);
21
+ }
22
+
23
+ async getById(
24
+ request: FastifyRequest<{ Params: { id: string } }>,
25
+ reply: FastifyReply
26
+ ): Promise<void> {
27
+ const item = await this.${camelName}Service.findById(request.params.id);
28
+
29
+ if (!item) {
30
+ return reply.status(404).send({
31
+ success: false,
32
+ message: '${pascalName} not found',
33
+ });
34
+ }
35
+
36
+ success(reply, item);
37
+ }
38
+
39
+ async create(request: FastifyRequest, reply: FastifyReply): Promise<void> {
40
+ const data = validateBody(create${pascalName}Schema, request.body);
41
+ const item = await this.${camelName}Service.create(data);
42
+ created(reply, item);
43
+ }
44
+
45
+ async update(
46
+ request: FastifyRequest<{ Params: { id: string } }>,
47
+ reply: FastifyReply
48
+ ): Promise<void> {
49
+ const data = validateBody(update${pascalName}Schema, request.body);
50
+ const item = await this.${camelName}Service.update(request.params.id, data);
51
+ success(reply, item);
52
+ }
53
+
54
+ async delete(
55
+ request: FastifyRequest<{ Params: { id: string } }>,
56
+ reply: FastifyReply
57
+ ): Promise<void> {
58
+ await this.${camelName}Service.delete(request.params.id);
59
+ noContent(reply);
60
+ }
61
+ }
62
+
63
+ export function create${pascalName}Controller(${camelName}Service: ${pascalName}Service): ${pascalName}Controller {
64
+ return new ${pascalName}Controller(${camelName}Service);
65
+ }
66
+ `;
67
+ }
@@ -0,0 +1,89 @@
1
+ import type { FieldDefinition } from '../utils/field-parser.js';
2
+ import { prismaTypeMap } from '../utils/field-parser.js';
3
+
4
+ export function dynamicPrismaTemplate(
5
+ modelName: string,
6
+ tableName: string,
7
+ fields: FieldDefinition[]
8
+ ): string {
9
+ const fieldLines: string[] = [];
10
+
11
+ for (const field of fields) {
12
+ const prismaType = prismaTypeMap[field.type];
13
+ const optionalMark = field.isOptional ? '?' : '';
14
+ const arrayMark = field.isArray ? '[]' : '';
15
+ const annotations: string[] = [];
16
+
17
+ if (field.isUnique) {
18
+ annotations.push('@unique');
19
+ }
20
+
21
+ if (field.defaultValue !== undefined) {
22
+ // Handle different default value types
23
+ if (field.type === 'boolean') {
24
+ annotations.push(`@default(${field.defaultValue})`);
25
+ } else if (field.type === 'number' || field.type === 'int' || field.type === 'float') {
26
+ annotations.push(`@default(${field.defaultValue})`);
27
+ } else {
28
+ annotations.push(`@default("${field.defaultValue}")`);
29
+ }
30
+ }
31
+
32
+ // Database-specific annotations
33
+ if (field.type === 'text') {
34
+ annotations.push('@db.Text');
35
+ }
36
+
37
+ if (field.type === 'decimal') {
38
+ annotations.push('@db.Decimal(10, 2)');
39
+ }
40
+
41
+ const annotationStr = annotations.length > 0 ? ' ' + annotations.join(' ') : '';
42
+ const typePart = `${prismaType}${optionalMark}${arrayMark}`;
43
+
44
+ fieldLines.push(` ${field.name.padEnd(15)} ${typePart.padEnd(12)}${annotationStr}`);
45
+ }
46
+
47
+ // Generate indexes
48
+ const indexLines: string[] = [];
49
+
50
+ // Index for unique fields
51
+ const uniqueFields = fields.filter((f) => f.isUnique);
52
+ for (const field of uniqueFields) {
53
+ indexLines.push(` @@index([${field.name}])`);
54
+ }
55
+
56
+ // Index for common search fields
57
+ const searchableFields = fields.filter(
58
+ (f) => ['string', 'email'].includes(f.type) && !f.isUnique
59
+ );
60
+ if (searchableFields.length > 0) {
61
+ const firstSearchable = searchableFields[0];
62
+ if (firstSearchable) {
63
+ indexLines.push(` @@index([${firstSearchable.name}])`);
64
+ }
65
+ }
66
+
67
+ return `
68
+ // ==========================================
69
+ // Add this model to your prisma/schema.prisma file
70
+ // ==========================================
71
+
72
+ model ${modelName} {
73
+ id String @id @default(uuid())
74
+
75
+ ${fieldLines.join('\n')}
76
+
77
+ createdAt DateTime @default(now())
78
+ updatedAt DateTime @updatedAt
79
+
80
+ ${indexLines.join('\n')}
81
+ @@map("${tableName}")
82
+ }
83
+
84
+ // ==========================================
85
+ // After adding the model, run:
86
+ // npm run db:migrate -- --name add_${tableName}
87
+ // ==========================================
88
+ `;
89
+ }
@@ -0,0 +1,232 @@
1
+ import type { FieldDefinition } from '../utils/field-parser.js';
2
+ import { zodTypeMap, joiTypeMap, yupTypeMap } from '../utils/field-parser.js';
3
+
4
+ export type ValidatorType = 'zod' | 'joi' | 'yup';
5
+
6
+ export function dynamicSchemasTemplate(
7
+ name: string,
8
+ pascalName: string,
9
+ camelName: string,
10
+ fields: FieldDefinition[],
11
+ validator: ValidatorType = 'zod'
12
+ ): string {
13
+ switch (validator) {
14
+ case 'joi':
15
+ return generateJoiSchemas(pascalName, camelName, fields);
16
+ case 'yup':
17
+ return generateYupSchemas(pascalName, camelName, fields);
18
+ default:
19
+ return generateZodSchemas(pascalName, camelName, fields);
20
+ }
21
+ }
22
+
23
+ function generateZodSchemas(
24
+ pascalName: string,
25
+ camelName: string,
26
+ fields: FieldDefinition[]
27
+ ): string {
28
+ const createFields = fields.map((field) => {
29
+ let validator = zodTypeMap[field.type];
30
+
31
+ if (field.isArray) {
32
+ validator = `z.array(${validator})`;
33
+ }
34
+
35
+ if (field.isOptional) {
36
+ validator += '.optional()';
37
+ }
38
+
39
+ if (field.defaultValue) {
40
+ validator += `.default(${field.defaultValue})`;
41
+ }
42
+
43
+ // Add extra validations based on type
44
+ if (field.type === 'string' && !field.isOptional) {
45
+ validator = validator.replace('z.string()', 'z.string().min(1)');
46
+ }
47
+
48
+ return ` ${field.name}: ${validator},`;
49
+ });
50
+
51
+ const updateFields = fields.map((field) => {
52
+ let validator = zodTypeMap[field.type];
53
+
54
+ if (field.isArray) {
55
+ validator = `z.array(${validator})`;
56
+ }
57
+
58
+ validator += '.optional()';
59
+
60
+ return ` ${field.name}: ${validator},`;
61
+ });
62
+
63
+ return `import { z } from 'zod';
64
+
65
+ export const create${pascalName}Schema = z.object({
66
+ ${createFields.join('\n')}
67
+ });
68
+
69
+ export const update${pascalName}Schema = z.object({
70
+ ${updateFields.join('\n')}
71
+ });
72
+
73
+ export const ${camelName}QuerySchema = z.object({
74
+ page: z.string().transform(Number).optional(),
75
+ limit: z.string().transform(Number).optional(),
76
+ sortBy: z.string().optional(),
77
+ sortOrder: z.enum(['asc', 'desc']).optional(),
78
+ search: z.string().optional(),
79
+ });
80
+
81
+ export type Create${pascalName}Input = z.infer<typeof create${pascalName}Schema>;
82
+ export type Update${pascalName}Input = z.infer<typeof update${pascalName}Schema>;
83
+ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
84
+ `;
85
+ }
86
+
87
+ function generateJoiSchemas(
88
+ pascalName: string,
89
+ camelName: string,
90
+ fields: FieldDefinition[]
91
+ ): string {
92
+ const createFields = fields.map((field) => {
93
+ let validator = joiTypeMap[field.type];
94
+
95
+ if (field.isArray) {
96
+ validator = `Joi.array().items(${validator})`;
97
+ }
98
+
99
+ if (!field.isOptional) {
100
+ validator += '.required()';
101
+ }
102
+
103
+ if (field.defaultValue) {
104
+ validator += `.default(${field.defaultValue})`;
105
+ }
106
+
107
+ return ` ${field.name}: ${validator},`;
108
+ });
109
+
110
+ const updateFields = fields.map((field) => {
111
+ let validator = joiTypeMap[field.type];
112
+
113
+ if (field.isArray) {
114
+ validator = `Joi.array().items(${validator})`;
115
+ }
116
+
117
+ return ` ${field.name}: ${validator},`;
118
+ });
119
+
120
+ return `import Joi from 'joi';
121
+
122
+ export const create${pascalName}Schema = Joi.object({
123
+ ${createFields.join('\n')}
124
+ });
125
+
126
+ export const update${pascalName}Schema = Joi.object({
127
+ ${updateFields.join('\n')}
128
+ });
129
+
130
+ export const ${camelName}QuerySchema = Joi.object({
131
+ page: Joi.number().integer().min(1),
132
+ limit: Joi.number().integer().min(1).max(100),
133
+ sortBy: Joi.string(),
134
+ sortOrder: Joi.string().valid('asc', 'desc'),
135
+ search: Joi.string(),
136
+ });
137
+
138
+ export type Create${pascalName}Input = {
139
+ ${fields.map((f) => ` ${f.name}${f.isOptional ? '?' : ''}: ${getJsType(f)};`).join('\n')}
140
+ };
141
+
142
+ export type Update${pascalName}Input = Partial<Create${pascalName}Input>;
143
+ export type ${pascalName}QueryInput = {
144
+ page?: number;
145
+ limit?: number;
146
+ sortBy?: string;
147
+ sortOrder?: 'asc' | 'desc';
148
+ search?: string;
149
+ };
150
+ `;
151
+ }
152
+
153
+ function generateYupSchemas(
154
+ pascalName: string,
155
+ camelName: string,
156
+ fields: FieldDefinition[]
157
+ ): string {
158
+ const createFields = fields.map((field) => {
159
+ let validator = yupTypeMap[field.type];
160
+
161
+ if (field.isArray) {
162
+ validator = `yup.array().of(${validator})`;
163
+ }
164
+
165
+ if (!field.isOptional) {
166
+ validator += '.required()';
167
+ }
168
+
169
+ if (field.defaultValue) {
170
+ validator += `.default(${field.defaultValue})`;
171
+ }
172
+
173
+ return ` ${field.name}: ${validator},`;
174
+ });
175
+
176
+ const updateFields = fields.map((field) => {
177
+ let validator = yupTypeMap[field.type];
178
+
179
+ if (field.isArray) {
180
+ validator = `yup.array().of(${validator})`;
181
+ }
182
+
183
+ validator += '.optional()';
184
+
185
+ return ` ${field.name}: ${validator},`;
186
+ });
187
+
188
+ return `import * as yup from 'yup';
189
+
190
+ export const create${pascalName}Schema = yup.object({
191
+ ${createFields.join('\n')}
192
+ });
193
+
194
+ export const update${pascalName}Schema = yup.object({
195
+ ${updateFields.join('\n')}
196
+ });
197
+
198
+ export const ${camelName}QuerySchema = yup.object({
199
+ page: yup.number().integer().min(1),
200
+ limit: yup.number().integer().min(1).max(100),
201
+ sortBy: yup.string(),
202
+ sortOrder: yup.string().oneOf(['asc', 'desc']),
203
+ search: yup.string(),
204
+ });
205
+
206
+ export type Create${pascalName}Input = yup.InferType<typeof create${pascalName}Schema>;
207
+ export type Update${pascalName}Input = yup.InferType<typeof update${pascalName}Schema>;
208
+ export type ${pascalName}QueryInput = yup.InferType<typeof ${camelName}QuerySchema>;
209
+ `;
210
+ }
211
+
212
+ function getJsType(field: FieldDefinition): string {
213
+ const typeMap: Record<string, string> = {
214
+ string: 'string',
215
+ number: 'number',
216
+ boolean: 'boolean',
217
+ date: 'Date',
218
+ datetime: 'Date',
219
+ text: 'string',
220
+ json: 'Record<string, unknown>',
221
+ email: 'string',
222
+ url: 'string',
223
+ uuid: 'string',
224
+ int: 'number',
225
+ float: 'number',
226
+ decimal: 'number',
227
+ enum: 'string',
228
+ };
229
+
230
+ const baseType = typeMap[field.type] || 'unknown';
231
+ return field.isArray ? `${baseType}[]` : baseType;
232
+ }
@@ -0,0 +1,60 @@
1
+ import type { FieldDefinition } from '../utils/field-parser.js';
2
+ import { tsTypeMap } from '../utils/field-parser.js';
3
+
4
+ export function dynamicTypesTemplate(
5
+ name: string,
6
+ pascalName: string,
7
+ fields: FieldDefinition[]
8
+ ): string {
9
+ const fieldLines = fields.map((field) => {
10
+ const tsType = tsTypeMap[field.type];
11
+ const arrayMark = field.isArray ? '[]' : '';
12
+ const optionalMark = field.isOptional ? '?' : '';
13
+ return ` ${field.name}${optionalMark}: ${tsType}${arrayMark};`;
14
+ });
15
+
16
+ const createFieldLines = fields
17
+ .filter((f) => !f.isOptional)
18
+ .map((field) => {
19
+ const tsType = tsTypeMap[field.type];
20
+ const arrayMark = field.isArray ? '[]' : '';
21
+ return ` ${field.name}: ${tsType}${arrayMark};`;
22
+ });
23
+
24
+ const createOptionalLines = fields
25
+ .filter((f) => f.isOptional)
26
+ .map((field) => {
27
+ const tsType = tsTypeMap[field.type];
28
+ const arrayMark = field.isArray ? '[]' : '';
29
+ return ` ${field.name}?: ${tsType}${arrayMark};`;
30
+ });
31
+
32
+ const updateFieldLines = fields.map((field) => {
33
+ const tsType = tsTypeMap[field.type];
34
+ const arrayMark = field.isArray ? '[]' : '';
35
+ return ` ${field.name}?: ${tsType}${arrayMark};`;
36
+ });
37
+
38
+ return `import type { BaseEntity } from '../../types/index.js';
39
+
40
+ export interface ${pascalName} extends BaseEntity {
41
+ ${fieldLines.join('\n')}
42
+ }
43
+
44
+ export interface Create${pascalName}Data {
45
+ ${[...createFieldLines, ...createOptionalLines].join('\n')}
46
+ }
47
+
48
+ export interface Update${pascalName}Data {
49
+ ${updateFieldLines.join('\n')}
50
+ }
51
+
52
+ export interface ${pascalName}Filters {
53
+ search?: string;
54
+ ${fields
55
+ .filter((f) => ['string', 'enum', 'boolean'].includes(f.type))
56
+ .map((f) => ` ${f.name}?: ${tsTypeMap[f.type]};`)
57
+ .join('\n')}
58
+ }
59
+ `;
60
+ }
@@ -0,0 +1,33 @@
1
+ export function moduleIndexTemplate(name: string, pascalName: string, camelName: string): string {
2
+ return `import type { FastifyInstance } from 'fastify';
3
+ import { logger } from '../../core/logger.js';
4
+ import { ${pascalName}Service, create${pascalName}Service } from './${name}.service.js';
5
+ import { ${pascalName}Controller, create${pascalName}Controller } from './${name}.controller.js';
6
+ import { ${pascalName}Repository, create${pascalName}Repository } from './${name}.repository.js';
7
+ import { register${pascalName}Routes } from './${name}.routes.js';
8
+ import type { AuthService } from '../auth/auth.service.js';
9
+
10
+ export async function register${pascalName}Module(
11
+ app: FastifyInstance,
12
+ authService: AuthService
13
+ ): Promise<void> {
14
+ // Create repository and service
15
+ const repository = create${pascalName}Repository();
16
+ const ${camelName}Service = create${pascalName}Service(repository);
17
+
18
+ // Create controller
19
+ const ${camelName}Controller = create${pascalName}Controller(${camelName}Service);
20
+
21
+ // Register routes
22
+ register${pascalName}Routes(app, ${camelName}Controller, authService);
23
+
24
+ logger.info('${pascalName} module registered');
25
+ }
26
+
27
+ export { ${pascalName}Service, create${pascalName}Service } from './${name}.service.js';
28
+ export { ${pascalName}Controller, create${pascalName}Controller } from './${name}.controller.js';
29
+ export { ${pascalName}Repository, create${pascalName}Repository } from './${name}.repository.js';
30
+ export * from './${name}.types.js';
31
+ export * from './${name}.schemas.js';
32
+ `;
33
+ }
@@ -0,0 +1,17 @@
1
+ export function prismaModelTemplate(name: string, pascalName: string, tableName: string): string {
2
+ return `
3
+ // Add this model to your prisma/schema.prisma file
4
+
5
+ model ${pascalName} {
6
+ id String @id @default(uuid())
7
+ name String
8
+ description String?
9
+
10
+ createdAt DateTime @default(now())
11
+ updatedAt DateTime @updatedAt
12
+
13
+ @@index([name])
14
+ @@map("${tableName}")
15
+ }
16
+ `;
17
+ }
@@ -0,0 +1,104 @@
1
+ export function repositoryTemplate(name: string, pascalName: string, camelName: string, pluralName: string): string {
2
+ return `import { randomUUID } from 'crypto';
3
+ import type { PaginatedResult, PaginationParams } from '../../types/index.js';
4
+ import { createPaginatedResult, getSkip } from '../../utils/pagination.js';
5
+ import type { ${pascalName}, Create${pascalName}Data, Update${pascalName}Data, ${pascalName}Filters } from './${name}.types.js';
6
+
7
+ // In-memory storage (will be replaced by Prisma in production)
8
+ const ${pluralName} = new Map<string, ${pascalName}>();
9
+
10
+ export class ${pascalName}Repository {
11
+ async findById(id: string): Promise<${pascalName} | null> {
12
+ return ${pluralName}.get(id) || null;
13
+ }
14
+
15
+ async findMany(
16
+ params: PaginationParams,
17
+ filters?: ${pascalName}Filters
18
+ ): Promise<PaginatedResult<${pascalName}>> {
19
+ let items = Array.from(${pluralName}.values());
20
+
21
+ // Apply filters
22
+ if (filters?.search) {
23
+ const search = filters.search.toLowerCase();
24
+ items = items.filter((item) =>
25
+ JSON.stringify(item).toLowerCase().includes(search)
26
+ );
27
+ }
28
+
29
+ // Sort
30
+ if (params.sortBy) {
31
+ const sortKey = params.sortBy as keyof ${pascalName};
32
+ items.sort((a, b) => {
33
+ const aVal = a[sortKey];
34
+ const bVal = b[sortKey];
35
+ if (aVal === undefined || bVal === undefined) return 0;
36
+ if (aVal < bVal) return params.sortOrder === 'desc' ? 1 : -1;
37
+ if (aVal > bVal) return params.sortOrder === 'desc' ? -1 : 1;
38
+ return 0;
39
+ });
40
+ }
41
+
42
+ const total = items.length;
43
+ const skip = getSkip(params);
44
+ const data = items.slice(skip, skip + params.limit);
45
+
46
+ return createPaginatedResult(data, total, params);
47
+ }
48
+
49
+ async create(data: Create${pascalName}Data): Promise<${pascalName}> {
50
+ const now = new Date();
51
+ const item: ${pascalName} = {
52
+ id: randomUUID(),
53
+ ...data,
54
+ createdAt: now,
55
+ updatedAt: now,
56
+ };
57
+
58
+ ${pluralName}.set(item.id, item);
59
+ return item;
60
+ }
61
+
62
+ async update(id: string, data: Update${pascalName}Data): Promise<${pascalName} | null> {
63
+ const item = ${pluralName}.get(id);
64
+ if (!item) return null;
65
+
66
+ const updated: ${pascalName} = {
67
+ ...item,
68
+ ...data,
69
+ updatedAt: new Date(),
70
+ };
71
+
72
+ ${pluralName}.set(id, updated);
73
+ return updated;
74
+ }
75
+
76
+ async delete(id: string): Promise<boolean> {
77
+ return ${pluralName}.delete(id);
78
+ }
79
+
80
+ async count(filters?: ${pascalName}Filters): Promise<number> {
81
+ if (!filters) return ${pluralName}.size;
82
+
83
+ let count = 0;
84
+ for (const item of ${pluralName}.values()) {
85
+ if (filters.search) {
86
+ const search = filters.search.toLowerCase();
87
+ if (!JSON.stringify(item).toLowerCase().includes(search)) continue;
88
+ }
89
+ count++;
90
+ }
91
+ return count;
92
+ }
93
+
94
+ // Clear all (for testing)
95
+ async clear(): Promise<void> {
96
+ ${pluralName}.clear();
97
+ }
98
+ }
99
+
100
+ export function create${pascalName}Repository(): ${pascalName}Repository {
101
+ return new ${pascalName}Repository();
102
+ }
103
+ `;
104
+ }
@@ -0,0 +1,70 @@
1
+ import type { FieldDefinition } from '../utils/field-parser.js';
2
+
3
+ export function routesTemplate(
4
+ name: string,
5
+ pascalName: string,
6
+ camelName: string,
7
+ pluralName: string,
8
+ fields: FieldDefinition[] = []
9
+ ): string {
10
+ const serializedFields = JSON.stringify(fields, null, 2);
11
+ return `import type { FastifyInstance } from 'fastify';
12
+ import type { ${pascalName}Controller } from './${name}.controller.js';
13
+ import type { AuthService } from '../auth/auth.service.js';
14
+ import { createAuthMiddleware, createRoleMiddleware } from '../auth/auth.middleware.js';
15
+ import { generateRouteSchema } from '../swagger/schema-builder.js';
16
+ import type { FieldDefinition } from '../cli/utils/field-parser.js';
17
+
18
+ const ${camelName}Fields: FieldDefinition[] = ${serializedFields};
19
+ const ${camelName}Schemas = {
20
+ list: generateRouteSchema('${pascalName}', ${camelName}Fields, 'list'),
21
+ get: generateRouteSchema('${pascalName}', ${camelName}Fields, 'get'),
22
+ create: generateRouteSchema('${pascalName}', ${camelName}Fields, 'create'),
23
+ update: generateRouteSchema('${pascalName}', ${camelName}Fields, 'update'),
24
+ delete: generateRouteSchema('${pascalName}', ${camelName}Fields, 'delete'),
25
+ };
26
+
27
+ export function register${pascalName}Routes(
28
+ app: FastifyInstance,
29
+ controller: ${pascalName}Controller,
30
+ authService: AuthService
31
+ ): void {
32
+ const authenticate = createAuthMiddleware(authService);
33
+ const isAdmin = createRoleMiddleware(['admin', 'super_admin']);
34
+
35
+ // Public routes (if any)
36
+ // app.get('/${pluralName}/public', controller.publicList.bind(controller));
37
+
38
+ // Protected routes
39
+ app.get(
40
+ '/${pluralName}',
41
+ { preHandler: [authenticate], ...${camelName}Schemas.list },
42
+ controller.list.bind(controller)
43
+ );
44
+
45
+ app.get(
46
+ '/${pluralName}/:id',
47
+ { preHandler: [authenticate], ...${camelName}Schemas.get },
48
+ controller.getById.bind(controller)
49
+ );
50
+
51
+ app.post(
52
+ '/${pluralName}',
53
+ { preHandler: [authenticate], ...${camelName}Schemas.create },
54
+ controller.create.bind(controller)
55
+ );
56
+
57
+ app.patch(
58
+ '/${pluralName}/:id',
59
+ { preHandler: [authenticate], ...${camelName}Schemas.update },
60
+ controller.update.bind(controller)
61
+ );
62
+
63
+ app.delete(
64
+ '/${pluralName}/:id',
65
+ { preHandler: [authenticate, isAdmin], ...${camelName}Schemas.delete },
66
+ controller.delete.bind(controller)
67
+ );
68
+ }
69
+ `;
70
+ }
@@ -0,0 +1,26 @@
1
+ export function schemasTemplate(name: string, pascalName: string, camelName: string): string {
2
+ return `import { z } from 'zod';
3
+
4
+ export const create${pascalName}Schema = z.object({
5
+ name: z.string().min(1, 'Name is required').max(255),
6
+ description: z.string().max(1000).optional(),
7
+ });
8
+
9
+ export const update${pascalName}Schema = z.object({
10
+ name: z.string().min(1).max(255).optional(),
11
+ description: z.string().max(1000).optional(),
12
+ });
13
+
14
+ export const ${camelName}QuerySchema = z.object({
15
+ page: z.string().transform(Number).optional(),
16
+ limit: z.string().transform(Number).optional(),
17
+ sortBy: z.string().optional(),
18
+ sortOrder: z.enum(['asc', 'desc']).optional(),
19
+ search: z.string().optional(),
20
+ });
21
+
22
+ export type Create${pascalName}Input = z.infer<typeof create${pascalName}Schema>;
23
+ export type Update${pascalName}Input = z.infer<typeof update${pascalName}Schema>;
24
+ export type ${pascalName}QueryInput = z.infer<typeof ${camelName}QuerySchema>;
25
+ `;
26
+ }