nestcraftx 0.3.0 → 1.0.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/.github/workflows/ci.yml +35 -0
- package/CHANGELOG.fr.md +74 -0
- package/CHANGELOG.md +74 -0
- package/PROGRESS.md +70 -67
- package/README.fr.md +60 -69
- package/bin/nestcraft.js +8 -0
- package/commands/demo.js +12 -46
- package/commands/generate.js +551 -2
- package/commands/generateConf.js +4 -4
- package/commands/help.js +11 -0
- package/commands/info.js +2 -3
- package/commands/list.js +93 -0
- package/commands/new.js +28 -56
- package/jest.config.js +9 -0
- package/package.json +7 -2
- package/readme.md +59 -68
- package/tests/e2e/generator.spec.js +71 -0
- package/tests/unit/appModuleUpdater.spec.js +111 -0
- package/tests/unit/cliParser.spec.js +74 -0
- package/tests/unit/controllerGenerator.spec.js +145 -0
- package/tests/unit/dtoGenerator.spec.js +87 -0
- package/tests/unit/entityGenerator.spec.js +65 -0
- package/tests/unit/generateCommand.spec.js +100 -0
- package/tests/unit/health.spec.js +69 -0
- package/tests/unit/listCommand.spec.js +91 -0
- package/tests/unit/middlewareGenerator.spec.js +64 -0
- package/tests/unit/newCommand.spec.js +88 -0
- package/tests/unit/relationCommand.spec.js +202 -0
- package/tests/unit/throttler.spec.js +117 -0
- package/utils/app-module.updater.js +6 -0
- package/utils/configs/setupCleanArchitecture.js +1 -1
- package/utils/configs/setupLightArchitecture.js +98 -26
- package/utils/envGenerator.js +9 -1
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +9 -0
- package/utils/fullModeInput.js +12 -229
- package/utils/generators/application/dtoGenerator.js +26 -8
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/cleanModuleGenerator.js +40 -11
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +114 -4
- package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
- package/utils/generators/lightModuleGenerator.js +14 -0
- package/utils/generators/presentation/controllerGenerator.js +70 -19
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/askRelationCommand.js +98 -0
- package/utils/interactive/entityBuilder.js +411 -0
- package/utils/lightModeInput.js +22 -241
- package/utils/setups/orms/typeOrmSetup.js +42 -8
- package/utils/setups/projectSetup.js +88 -10
- package/utils/setups/setupAuth.js +335 -20
- package/utils/setups/setupDatabase.js +4 -1
- package/utils/setups/setupHealth.js +74 -0
- package/utils/setups/setupMongoose.js +84 -32
- package/utils/setups/setupPrisma.js +151 -4
- package/utils/setups/setupSwagger.js +15 -8
- package/utils/setups/setupThrottler.js +92 -0
- package/utils/shell.js +61 -9
|
@@ -17,11 +17,11 @@ async function setupAuth(inputs) {
|
|
|
17
17
|
const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
|
|
18
18
|
|
|
19
19
|
await runCommand(
|
|
20
|
-
`${installCmd} @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
|
|
20
|
+
`${installCmd} @nestjs/jwt@^10.2.0 @nestjs/passport@^10.0.3 passport@^0.7.0 passport-jwt@^4.0.1 bcrypt@^5.1.1 uuid@^9.0.1`,
|
|
21
21
|
"Erreur install deps auth",
|
|
22
22
|
);
|
|
23
23
|
await runCommand(
|
|
24
|
-
`${installCmd} -D @types/passport-jwt @types/bcrypt @types/uuid`,
|
|
24
|
+
`${installCmd} -D @types/passport-jwt@^4.0.1 @types/bcrypt@^5.0.2 @types/uuid@^9.0.8`,
|
|
25
25
|
"Erreur install dev-deps auth",
|
|
26
26
|
);
|
|
27
27
|
|
|
@@ -120,6 +120,109 @@ export class Session {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export const SessionSchema = SchemaFactory.createForClass(Session);
|
|
123
|
+
`.trim(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await createFile({
|
|
127
|
+
path: `${sessionSchemaPath}/password-reset.schema.ts`,
|
|
128
|
+
contente: `
|
|
129
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|
130
|
+
import { Document } from 'mongoose';
|
|
131
|
+
|
|
132
|
+
export type PasswordResetDocument = PasswordReset & Document;
|
|
133
|
+
|
|
134
|
+
@Schema({ timestamps: true })
|
|
135
|
+
export class PasswordReset {
|
|
136
|
+
@Prop({ required: true })
|
|
137
|
+
email: string;
|
|
138
|
+
|
|
139
|
+
@Prop({ required: true, unique: true })
|
|
140
|
+
token: string;
|
|
141
|
+
|
|
142
|
+
@Prop({ required: true })
|
|
143
|
+
expiresAt: Date;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const PasswordResetSchema = SchemaFactory.createForClass(PasswordReset);
|
|
147
|
+
`.trim(),
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
await createFile({
|
|
151
|
+
path: `${sessionSchemaPath}/otp.schema.ts`,
|
|
152
|
+
contente: `
|
|
153
|
+
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|
154
|
+
import { Document } from 'mongoose';
|
|
155
|
+
|
|
156
|
+
export type OtpDocument = Otp & Document;
|
|
157
|
+
|
|
158
|
+
@Schema({ timestamps: true })
|
|
159
|
+
export class Otp {
|
|
160
|
+
@Prop({ required: true })
|
|
161
|
+
email: string;
|
|
162
|
+
|
|
163
|
+
@Prop({ required: true })
|
|
164
|
+
otp: string;
|
|
165
|
+
|
|
166
|
+
@Prop({ required: true })
|
|
167
|
+
expiresAt: Date;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const OtpSchema = SchemaFactory.createForClass(Otp);
|
|
171
|
+
`.trim(),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// --- GÉNÉRATION DES ENTITÉS (Spécifique TypeORM) ---
|
|
176
|
+
if (dbConfig.orm === "typeorm") {
|
|
177
|
+
await createDirectory("src/entities");
|
|
178
|
+
|
|
179
|
+
await createFile({
|
|
180
|
+
path: `src/entities/PasswordReset.entity.ts`,
|
|
181
|
+
contente: `
|
|
182
|
+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
|
183
|
+
|
|
184
|
+
@Entity('password_reset')
|
|
185
|
+
export class PasswordReset {
|
|
186
|
+
@PrimaryGeneratedColumn('uuid')
|
|
187
|
+
id: string;
|
|
188
|
+
|
|
189
|
+
@Column({ type: 'varchar' })
|
|
190
|
+
email: string;
|
|
191
|
+
|
|
192
|
+
@Column({ type: 'varchar', unique: true })
|
|
193
|
+
token: string;
|
|
194
|
+
|
|
195
|
+
@Column({ type: 'timestamp' })
|
|
196
|
+
expiresAt: Date;
|
|
197
|
+
|
|
198
|
+
@CreateDateColumn()
|
|
199
|
+
createdAt: Date;
|
|
200
|
+
}
|
|
201
|
+
`.trim(),
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await createFile({
|
|
205
|
+
path: `src/entities/Otp.entity.ts`,
|
|
206
|
+
contente: `
|
|
207
|
+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
|
|
208
|
+
|
|
209
|
+
@Entity('otp')
|
|
210
|
+
export class Otp {
|
|
211
|
+
@PrimaryGeneratedColumn('uuid')
|
|
212
|
+
id: string;
|
|
213
|
+
|
|
214
|
+
@Column({ type: 'varchar' })
|
|
215
|
+
email: string;
|
|
216
|
+
|
|
217
|
+
@Column({ type: 'varchar' })
|
|
218
|
+
otp: string;
|
|
219
|
+
|
|
220
|
+
@Column({ type: 'timestamp' })
|
|
221
|
+
expiresAt: Date;
|
|
222
|
+
|
|
223
|
+
@CreateDateColumn()
|
|
224
|
+
createdAt: Date;
|
|
225
|
+
}
|
|
123
226
|
`.trim(),
|
|
124
227
|
});
|
|
125
228
|
}
|
|
@@ -252,6 +355,8 @@ import { SendOtpDto } from '${paths.appDtos}/sendOtp.dto';
|
|
|
252
355
|
import { VerifyOtpDto } from '${paths.appDtos}/verifyOtp.dto';
|
|
253
356
|
import { ForgotPasswordDto } from '${paths.appDtos}/forgotPassword.dto';
|
|
254
357
|
import { ResetPasswordDto } from '${paths.appDtos}/resetPassword.dto';
|
|
358
|
+
import { OtpRepository } from '${paths.persistence}/otp.repository';
|
|
359
|
+
import { PasswordResetRepository } from '${paths.persistence}/password-reset.repository';
|
|
255
360
|
${enumImport}
|
|
256
361
|
${
|
|
257
362
|
mode === "light"
|
|
@@ -263,10 +368,11 @@ ${enumImport}
|
|
|
263
368
|
|
|
264
369
|
@Injectable()
|
|
265
370
|
export class AuthService {
|
|
266
|
-
private otps = new Map<string, string>();
|
|
267
371
|
constructor(
|
|
268
372
|
private readonly jwtService: JwtService,
|
|
269
373
|
private readonly sessionService: SessionService,
|
|
374
|
+
private readonly otpRepository: OtpRepository,
|
|
375
|
+
private readonly passwordResetRepository: PasswordResetRepository,
|
|
270
376
|
${
|
|
271
377
|
mode === "light"
|
|
272
378
|
? `private readonly userRepository: UserRepository,`
|
|
@@ -345,21 +451,26 @@ export class AuthService {
|
|
|
345
451
|
}
|
|
346
452
|
|
|
347
453
|
// 📲 Send OTP
|
|
348
|
-
sendOtp(dto: SendOtpDto) {
|
|
454
|
+
async sendOtp(dto: SendOtpDto) {
|
|
349
455
|
const otp = Math.floor(100000 + Math.random() * 900000).toString();
|
|
350
|
-
|
|
456
|
+
const expiresAt = new Date();
|
|
457
|
+
expiresAt.setMinutes(expiresAt.getMinutes() + 5); // Valid for 5 mins
|
|
458
|
+
|
|
459
|
+
await this.otpRepository.deleteByEmail(dto.email);
|
|
460
|
+
await this.otpRepository.save({ email: dto.email, otp, expiresAt });
|
|
461
|
+
|
|
351
462
|
console.log(\`[OTP] for \${dto.email} is \${otp}\`);
|
|
352
463
|
return { message: 'OTP sent' };
|
|
353
464
|
}
|
|
354
465
|
|
|
355
466
|
// Verify OTP
|
|
356
|
-
verifyOtp(dto: VerifyOtpDto) {
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
467
|
+
async verifyOtp(dto: VerifyOtpDto) {
|
|
468
|
+
const record = await this.otpRepository.findLatestByEmail(dto.email);
|
|
469
|
+
if (record && record.otp === dto.otp && record.expiresAt > new Date()) {
|
|
470
|
+
await this.otpRepository.deleteByEmail(dto.email);
|
|
471
|
+
return { message: 'OTP verified' };
|
|
361
472
|
}
|
|
362
|
-
throw new UnauthorizedException('Invalid OTP');
|
|
473
|
+
throw new UnauthorizedException('Invalid or expired OTP');
|
|
363
474
|
}
|
|
364
475
|
|
|
365
476
|
// 📬 Forgot Password
|
|
@@ -368,18 +479,31 @@ export class AuthService {
|
|
|
368
479
|
if (!existingUser) throw new NotFoundException('User not found');
|
|
369
480
|
|
|
370
481
|
const token = uuidv4();
|
|
482
|
+
const expiresAt = new Date();
|
|
483
|
+
expiresAt.setHours(expiresAt.getHours() + 1); // Valid for 1 hour
|
|
484
|
+
|
|
485
|
+
await this.passwordResetRepository.deleteByEmail(dto.email);
|
|
486
|
+
await this.passwordResetRepository.save({ email: dto.email, token, expiresAt });
|
|
487
|
+
|
|
371
488
|
console.log(\`[ResetToken] for \${dto.email} is \${token}\`);
|
|
372
489
|
return { message: 'Reset token sent' };
|
|
373
490
|
}
|
|
374
491
|
|
|
375
492
|
// 🔄 Reset Password
|
|
376
493
|
async resetPassword(dto: ResetPasswordDto) {
|
|
494
|
+
const record = await this.passwordResetRepository.findByToken(dto.token);
|
|
495
|
+
if (!record || record.email !== dto.email || record.expiresAt < new Date()) {
|
|
496
|
+
throw new UnauthorizedException('Invalid or expired reset token');
|
|
497
|
+
}
|
|
498
|
+
|
|
377
499
|
const existingUser = await this.userRepository.findByEmail(dto.email);
|
|
378
|
-
if (!existingUser) throw new UnauthorizedException('
|
|
500
|
+
if (!existingUser) throw new UnauthorizedException('User not found');
|
|
379
501
|
|
|
380
502
|
const password = await this.hashPassword(dto.newPassword);
|
|
381
503
|
await this.userRepository.update(existingUser.getId(), { password });
|
|
382
504
|
|
|
505
|
+
await this.passwordResetRepository.deleteByEmail(dto.email);
|
|
506
|
+
|
|
383
507
|
return { message: 'Password reset successful' };
|
|
384
508
|
}
|
|
385
509
|
|
|
@@ -566,6 +690,188 @@ export class SessionRepository ${implementsClause} {
|
|
|
566
690
|
contente: repoContent.trim(),
|
|
567
691
|
});
|
|
568
692
|
|
|
693
|
+
// --- PASSWORD RESET REPOSITORY ---
|
|
694
|
+
let resetRepoContent = "";
|
|
695
|
+
if (dbConfig.orm === "typeorm") {
|
|
696
|
+
resetRepoContent = `
|
|
697
|
+
import { Injectable } from '@nestjs/common';
|
|
698
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
699
|
+
import { Repository } from 'typeorm';
|
|
700
|
+
import { PasswordReset as PasswordResetEntity } from 'src/entities/PasswordReset.entity';
|
|
701
|
+
|
|
702
|
+
@Injectable()
|
|
703
|
+
export class PasswordResetRepository {
|
|
704
|
+
constructor(
|
|
705
|
+
@InjectRepository(PasswordResetEntity)
|
|
706
|
+
private readonly repo: Repository<PasswordResetEntity>,
|
|
707
|
+
) {}
|
|
708
|
+
|
|
709
|
+
async save(data: { email: string; token: string; expiresAt: Date }): Promise<PasswordResetEntity> {
|
|
710
|
+
return this.repo.save(data);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
async findByToken(token: string): Promise<PasswordResetEntity | null> {
|
|
714
|
+
return this.repo.findOne({ where: { token } });
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async deleteByEmail(email: string): Promise<void> {
|
|
718
|
+
await this.repo.delete({ email });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
`.trim();
|
|
722
|
+
} else if (dbConfig.orm === "prisma") {
|
|
723
|
+
resetRepoContent = `
|
|
724
|
+
import { Injectable } from '@nestjs/common';
|
|
725
|
+
import { PrismaService } from 'src/prisma/prisma.service';
|
|
726
|
+
|
|
727
|
+
@Injectable()
|
|
728
|
+
export class PasswordResetRepository {
|
|
729
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
730
|
+
|
|
731
|
+
async save(data: { email: string; token: string; expiresAt: Date }) {
|
|
732
|
+
return this.prisma.passwordReset.create({ data });
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async findByToken(token: string) {
|
|
736
|
+
return this.prisma.passwordReset.findUnique({ where: { token } });
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async deleteByEmail(email: string) {
|
|
740
|
+
await this.prisma.passwordReset.deleteMany({ where: { email } });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
`.trim();
|
|
744
|
+
} else if (dbConfig.orm === "mongoose") {
|
|
745
|
+
const mongooseSchemaPath = isFull
|
|
746
|
+
? "src/auth/infrastructure/persistence/mongoose"
|
|
747
|
+
: "src/auth/persistence";
|
|
748
|
+
resetRepoContent = `
|
|
749
|
+
import { Injectable } from '@nestjs/common';
|
|
750
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
751
|
+
import { Model } from 'mongoose';
|
|
752
|
+
import { PasswordReset, PasswordResetDocument } from '${mongooseSchemaPath}/password-reset.schema';
|
|
753
|
+
|
|
754
|
+
@Injectable()
|
|
755
|
+
export class PasswordResetRepository {
|
|
756
|
+
constructor(
|
|
757
|
+
@InjectModel(PasswordReset.name)
|
|
758
|
+
private readonly model: Model<PasswordResetDocument>,
|
|
759
|
+
) {}
|
|
760
|
+
|
|
761
|
+
async save(data: { email: string; token: string; expiresAt: Date }) {
|
|
762
|
+
return new this.model(data).save();
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async findByToken(token: string) {
|
|
766
|
+
return this.model.findOne({ token }).exec();
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async deleteByEmail(email: string) {
|
|
770
|
+
await this.model.deleteMany({ email }).exec();
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
`.trim();
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await createFile({
|
|
777
|
+
path: `${paths.persistence}/password-reset.repository.ts`,
|
|
778
|
+
contente: resetRepoContent,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// --- OTP REPOSITORY ---
|
|
782
|
+
let otpRepoContent = "";
|
|
783
|
+
if (dbConfig.orm === "typeorm") {
|
|
784
|
+
otpRepoContent = `
|
|
785
|
+
import { Injectable } from '@nestjs/common';
|
|
786
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
787
|
+
import { Repository } from 'typeorm';
|
|
788
|
+
import { Otp as OtpEntity } from 'src/entities/Otp.entity';
|
|
789
|
+
|
|
790
|
+
@Injectable()
|
|
791
|
+
export class OtpRepository {
|
|
792
|
+
constructor(
|
|
793
|
+
@InjectRepository(OtpEntity)
|
|
794
|
+
private readonly repo: Repository<OtpEntity>,
|
|
795
|
+
) {}
|
|
796
|
+
|
|
797
|
+
async save(data: { email: string; otp: string; expiresAt: Date }): Promise<OtpEntity> {
|
|
798
|
+
return this.repo.save(data);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
async findLatestByEmail(email: string): Promise<OtpEntity | null> {
|
|
802
|
+
return this.repo.findOne({
|
|
803
|
+
where: { email },
|
|
804
|
+
order: { createdAt: 'DESC' }
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async deleteByEmail(email: string): Promise<void> {
|
|
809
|
+
await this.repo.delete({ email });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
`.trim();
|
|
813
|
+
} else if (dbConfig.orm === "prisma") {
|
|
814
|
+
otpRepoContent = `
|
|
815
|
+
import { Injectable } from '@nestjs/common';
|
|
816
|
+
import { PrismaService } from 'src/prisma/prisma.service';
|
|
817
|
+
|
|
818
|
+
@Injectable()
|
|
819
|
+
export class OtpRepository {
|
|
820
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
821
|
+
|
|
822
|
+
async save(data: { email: string; otp: string; expiresAt: Date }) {
|
|
823
|
+
return this.prisma.otp.create({ data });
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
async findLatestByEmail(email: string) {
|
|
827
|
+
return this.prisma.otp.findFirst({
|
|
828
|
+
where: { email },
|
|
829
|
+
orderBy: { createdAt: 'desc' }
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async deleteByEmail(email: string) {
|
|
834
|
+
await this.prisma.otp.deleteMany({ where: { email } });
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
`.trim();
|
|
838
|
+
} else if (dbConfig.orm === "mongoose") {
|
|
839
|
+
const mongooseSchemaPath = isFull
|
|
840
|
+
? "src/auth/infrastructure/persistence/mongoose"
|
|
841
|
+
: "src/auth/persistence";
|
|
842
|
+
otpRepoContent = `
|
|
843
|
+
import { Injectable } from '@nestjs/common';
|
|
844
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
845
|
+
import { Model } from 'mongoose';
|
|
846
|
+
import { Otp, OtpDocument } from '${mongooseSchemaPath}/otp.schema';
|
|
847
|
+
|
|
848
|
+
@Injectable()
|
|
849
|
+
export class OtpRepository {
|
|
850
|
+
constructor(
|
|
851
|
+
@InjectModel(Otp.name)
|
|
852
|
+
private readonly model: Model<OtpDocument>,
|
|
853
|
+
) {}
|
|
854
|
+
|
|
855
|
+
async save(data: { email: string; otp: string; expiresAt: Date }) {
|
|
856
|
+
return new this.model(data).save();
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async findLatestByEmail(email: string) {
|
|
860
|
+
return this.model.findOne({ email }).sort({ createdAt: -1 }).exec();
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
async deleteByEmail(email: string) {
|
|
864
|
+
await this.model.deleteMany({ email }).exec();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
`.trim();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
await createFile({
|
|
871
|
+
path: `${paths.persistence}/otp.repository.ts`,
|
|
872
|
+
contente: otpRepoContent,
|
|
873
|
+
});
|
|
874
|
+
|
|
569
875
|
// --- 6. INFRASTRUCTURE WEB (CONTROLLER, GUARD, STRATEGY) ---
|
|
570
876
|
|
|
571
877
|
await createFile({
|
|
@@ -788,25 +1094,30 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
|
788
1094
|
let dbProviders = "";
|
|
789
1095
|
if (dbConfig.orm === "typeorm") {
|
|
790
1096
|
dbImports =
|
|
791
|
-
"import { TypeOrmModule } from '@nestjs/typeorm';\nimport { Session as SessionEntity } from 'src/entities/Session.entity';";
|
|
792
|
-
dbProviders = "TypeOrmModule.forFeature([SessionEntity]),";
|
|
1097
|
+
"import { TypeOrmModule } from '@nestjs/typeorm';\nimport { Session as SessionEntity } from 'src/entities/Session.entity';\nimport { PasswordReset as PasswordResetEntity } from 'src/entities/PasswordReset.entity';\nimport { Otp as OtpEntity } from 'src/entities/Otp.entity';";
|
|
1098
|
+
dbProviders = "TypeOrmModule.forFeature([SessionEntity, PasswordResetEntity, OtpEntity]),";
|
|
793
1099
|
} else if (dbConfig.orm === "mongoose") {
|
|
794
1100
|
const schemaRelativePath = isFull
|
|
795
1101
|
? "./infrastructure/persistence/mongoose/session.schema"
|
|
796
1102
|
: "./persistence/session.schema";
|
|
797
1103
|
|
|
798
1104
|
dbImports = `import { MongooseModule } from '@nestjs/mongoose';
|
|
799
|
-
import { Session, SessionSchema } from '${schemaRelativePath}'
|
|
800
|
-
|
|
801
|
-
|
|
1105
|
+
import { Session, SessionSchema } from '${schemaRelativePath}';
|
|
1106
|
+
import { PasswordReset, PasswordResetSchema } from '${schemaRelativePath.replace("session.schema", "password-reset.schema")}';
|
|
1107
|
+
import { Otp, OtpSchema } from '${schemaRelativePath.replace("session.schema", "otp.schema")}';`;
|
|
1108
|
+
|
|
1109
|
+
dbProviders = `MongooseModule.forFeature([
|
|
1110
|
+
{ name: Session.name, schema: SessionSchema },
|
|
1111
|
+
{ name: PasswordReset.name, schema: PasswordResetSchema },
|
|
1112
|
+
{ name: Otp.name, schema: OtpSchema }
|
|
1113
|
+
]),`;
|
|
802
1114
|
} else if (dbConfig.orm === "prisma") {
|
|
803
1115
|
dbImports = "import { PrismaModule } from 'src/prisma/prisma.module';";
|
|
804
1116
|
dbProviders = "PrismaModule,";
|
|
805
1117
|
}
|
|
806
1118
|
|
|
807
1119
|
if (inputs.mode == "full") {
|
|
808
|
-
dbImports
|
|
809
|
-
+"import { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';";
|
|
1120
|
+
dbImports += `\nimport { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';`;
|
|
810
1121
|
}
|
|
811
1122
|
|
|
812
1123
|
await createFile({
|
|
@@ -823,6 +1134,8 @@ import { SessionService } from '${paths.services}/session.service';
|
|
|
823
1134
|
import { AuthController } from '${paths.controllers}/auth.controller';
|
|
824
1135
|
import { JwtStrategy } from '${paths.strategies}/jwt.strategy';
|
|
825
1136
|
import { SessionRepository } from '${paths.persistence}/session.repository';
|
|
1137
|
+
import { PasswordResetRepository } from '${paths.persistence}/password-reset.repository';
|
|
1138
|
+
import { OtpRepository } from '${paths.persistence}/otp.repository';
|
|
826
1139
|
|
|
827
1140
|
@Module({
|
|
828
1141
|
imports: [
|
|
@@ -843,12 +1156,13 @@ import { SessionRepository } from '${paths.persistence}/session.repository';
|
|
|
843
1156
|
AuthService,
|
|
844
1157
|
SessionService,
|
|
845
1158
|
JwtStrategy,
|
|
1159
|
+
PasswordResetRepository,
|
|
1160
|
+
OtpRepository,
|
|
846
1161
|
${
|
|
847
1162
|
isFull
|
|
848
1163
|
? "{ provide: ISessionRepositoryName, useClass: SessionRepository }"
|
|
849
1164
|
: "SessionRepository"
|
|
850
1165
|
}
|
|
851
|
-
|
|
852
1166
|
],
|
|
853
1167
|
exports: [AuthService, SessionService],
|
|
854
1168
|
})
|
|
@@ -901,6 +1215,7 @@ export class AuthModule {}`.trim(),
|
|
|
901
1215
|
name: "resetPassword",
|
|
902
1216
|
fields: [
|
|
903
1217
|
{ name: "email", type: "string", swaggerExample: "user@example.com" },
|
|
1218
|
+
{ name: "token", type: "string", swaggerExample: "uuid-reset-token" },
|
|
904
1219
|
{
|
|
905
1220
|
name: "newPassword",
|
|
906
1221
|
type: "string",
|
|
@@ -7,8 +7,11 @@ const { setupPrisma } = require("./setupPrisma");
|
|
|
7
7
|
async function setupDatabase(inputs) {
|
|
8
8
|
logInfo("🚀 Configuring the database...");
|
|
9
9
|
|
|
10
|
+
const pkgManager = inputs.packageManager || "npm";
|
|
11
|
+
const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
|
|
12
|
+
|
|
10
13
|
await runCommand(
|
|
11
|
-
|
|
14
|
+
`${installCmd} dotenv`,
|
|
12
15
|
"Error installing dotenv automatically, please run it manually now",
|
|
13
16
|
{ critical: false },
|
|
14
17
|
);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// utils/setups/setupHealth.js
|
|
2
|
+
//
|
|
3
|
+
// Script d'initialisation du Health Check.
|
|
4
|
+
// Crée HealthController et HealthModule dans src/health
|
|
5
|
+
// et met à jour app.module.ts de manière sécurisée et idempotente.
|
|
6
|
+
|
|
7
|
+
const { createDirectory, createFile } = require("../userInput");
|
|
8
|
+
const { safeUpdateAppModule } = require("../app-module.updater");
|
|
9
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
10
|
+
const { logSuccess } = require("../loggers/logSuccess");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configure un module de Health Check natif sur la route /health.
|
|
14
|
+
*/
|
|
15
|
+
async function setupHealth() {
|
|
16
|
+
logInfo("Génération du module Health Check (/health)...");
|
|
17
|
+
|
|
18
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
19
|
+
if (isDryRun) {
|
|
20
|
+
console.log("[DRY-RUN] Simulated: create src/health directory, files and register in app.module.ts");
|
|
21
|
+
logSuccess(" Module Health Check configuré avec succès !");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 1. Créer le dossier src/health
|
|
26
|
+
await createDirectory("src/health");
|
|
27
|
+
|
|
28
|
+
// 2. Créer health.controller.ts
|
|
29
|
+
const controllerContent = `import { Controller, Get } from '@nestjs/common';
|
|
30
|
+
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
|
31
|
+
|
|
32
|
+
@ApiTags('Health')
|
|
33
|
+
@Controller('health')
|
|
34
|
+
export class HealthController {
|
|
35
|
+
@Get()
|
|
36
|
+
@ApiOperation({ summary: 'Check application health status' })
|
|
37
|
+
@ApiResponse({ status: 200, description: 'Application is healthy and running.' })
|
|
38
|
+
check() {
|
|
39
|
+
return {
|
|
40
|
+
status: 'ok',
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
uptime: process.uptime(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
await createFile({
|
|
49
|
+
path: "src/health/health.controller.ts",
|
|
50
|
+
contente: controllerContent,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// 3. Créer health.module.ts
|
|
54
|
+
const moduleContent = `import { Module } from '@nestjs/common';
|
|
55
|
+
import { HealthController } from './health.controller';
|
|
56
|
+
|
|
57
|
+
@Module({
|
|
58
|
+
controllers: [HealthController],
|
|
59
|
+
})
|
|
60
|
+
export class HealthModule {}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
await createFile({
|
|
64
|
+
path: "src/health/health.module.ts",
|
|
65
|
+
contente: moduleContent,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// 4. Mettre à jour app.module.ts
|
|
69
|
+
await safeUpdateAppModule("health");
|
|
70
|
+
|
|
71
|
+
logSuccess(" Module Health Check configuré avec succès !");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { setupHealth };
|