Skip to content

Commit 5d4c780

Browse files
authored
Merge pull request #34 from No-Country-simulation/develop
Develop
2 parents d7ef2e5 + 6b78095 commit 5d4c780

6 files changed

Lines changed: 120 additions & 0 deletions

File tree

Backend/app-talen-backend/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,20 @@ DB_DATABASE=talent_db
9797
TYPEORM_SYNC=true
9898
JWT_SECRET=change-this-secret
9999
JWT_EXPIRES_IN=7d
100+
SEED_DEFAULT_ADMIN=true
101+
DEFAULT_ADMIN_EMAIL=admin@talen.local
102+
DEFAULT_ADMIN_PASSWORD=AdminTemp2026!
103+
DEFAULT_ADMIN_FORCE_SYNC=false
100104
```
101105

102106
`TYPEORM_SYNC=true` permite que TypeORM cree o sincronice tablas automaticamente durante desarrollo. Para produccion se recomienda usar migraciones y dejarlo en `false`.
103107

104108
`JWT_EXPIRES_IN=7d` define que los tokens de autenticacion expiran a los 7 dias.
105109

110+
Cuando `SEED_DEFAULT_ADMIN=true`, al iniciar la API se crea automaticamente un usuario ADMIN en la base de datos si no existe. Los datos se configuran con `DEFAULT_ADMIN_EMAIL` y `DEFAULT_ADMIN_PASSWORD`.
111+
112+
Si `DEFAULT_ADMIN_FORCE_SYNC=true`, en cada inicio se vuelve a sincronizar ese usuario (rol ADMIN y password hasheada a partir de `DEFAULT_ADMIN_PASSWORD`).
113+
106114
## Autenticacion
107115

108116
Se implementa autenticacion con JWT en el modulo `auth`.

Backend/app-talen-backend/docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ services:
1414
TYPEORM_SYNC: ${TYPEORM_SYNC:-false}
1515
JWT_SECRET: ${JWT_SECRET}
1616
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN}
17+
SEED_DEFAULT_ADMIN: ${SEED_DEFAULT_ADMIN:-true}
18+
DEFAULT_ADMIN_EMAIL: ${DEFAULT_ADMIN_EMAIL:-admin@talen.local}
19+
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:-AdminTemp2026!}
20+
DEFAULT_ADMIN_FORCE_SYNC: ${DEFAULT_ADMIN_FORCE_SYNC:-false}
1721
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
1822
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
1923
ports:
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
2+
import { ConfigService } from '@nestjs/config';
3+
import { InjectRepository } from '@nestjs/typeorm';
4+
import * as bcrypt from 'bcrypt';
5+
import { QueryFailedError } from 'typeorm';
6+
import { Repository } from 'typeorm';
7+
import { UserRole } from '../../users/domain/user-role.enum';
8+
import { User } from '../../users/infrastructure/entities/user.entity';
9+
10+
@Injectable()
11+
export class AdminBootstrapService implements OnModuleInit {
12+
private readonly logger = new Logger(AdminBootstrapService.name);
13+
14+
constructor(
15+
@InjectRepository(User)
16+
private readonly usersRepository: Repository<User>,
17+
private readonly configService: ConfigService,
18+
) {}
19+
20+
async onModuleInit(): Promise<void> {
21+
const seedEnabled =
22+
this.configService.get<string>('SEED_DEFAULT_ADMIN', 'true') === 'true';
23+
24+
if (!seedEnabled) {
25+
this.logger.log('Default admin seed disabled (SEED_DEFAULT_ADMIN=false)');
26+
return;
27+
}
28+
29+
const email = this.configService
30+
.get<string>('DEFAULT_ADMIN_EMAIL', 'admin@talen.local')
31+
.trim()
32+
.toLowerCase();
33+
const plainPassword = this.configService.get<string>(
34+
'DEFAULT_ADMIN_PASSWORD',
35+
'AdminTemp2026!',
36+
);
37+
const forcePasswordSync =
38+
this.configService.get<string>('DEFAULT_ADMIN_FORCE_SYNC', 'false') ===
39+
'true';
40+
41+
if (!email || !plainPassword) {
42+
this.logger.warn(
43+
'Default admin seed skipped: missing DEFAULT_ADMIN_EMAIL or DEFAULT_ADMIN_PASSWORD',
44+
);
45+
return;
46+
}
47+
48+
const existingUser = await this.usersRepository.findOne({
49+
where: { email },
50+
});
51+
52+
if (!existingUser) {
53+
const hashedPassword = await bcrypt.hash(plainPassword, 10);
54+
const user = this.usersRepository.create({
55+
email,
56+
password: hashedPassword,
57+
role: UserRole.ADMIN,
58+
});
59+
try {
60+
await this.usersRepository.save(user);
61+
} catch (error) {
62+
// If multiple instances boot at the same time, another instance may create the user first.
63+
if (!this.isUniqueViolation(error)) {
64+
throw error;
65+
}
66+
}
67+
this.logger.log(`Default admin created: ${email}`);
68+
return;
69+
}
70+
71+
if (!forcePasswordSync) {
72+
this.logger.log(`Default admin already exists: ${email}`);
73+
return;
74+
}
75+
76+
existingUser.password = await bcrypt.hash(plainPassword, 10);
77+
existingUser.role = UserRole.ADMIN;
78+
await this.usersRepository.save(existingUser);
79+
this.logger.log(`Default admin synchronized: ${email}`);
80+
}
81+
82+
private isUniqueViolation(error: unknown): boolean {
83+
if (!(error instanceof QueryFailedError)) {
84+
return false;
85+
}
86+
87+
const code = (error as QueryFailedError & { code?: string }).code;
88+
return code === '23505';
89+
}
90+
}

Backend/app-talen-backend/src/modules/auth/auth.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AuthController } from './infrastructure/auth.controller';
1313
import { LinkedInStrategy } from './infrastructure/strategies/linkedin.strategy';
1414
import { GoogleStrategy } from './infrastructure/strategies/google.strategy';
1515
import { MailModule } from '../mail/mail.module';
16+
import { AdminBootstrapService } from './application/admin-bootstrap.service';
1617

1718
@Module({
1819
imports: [
@@ -50,6 +51,7 @@ import { MailModule } from '../mail/mail.module';
5051
LinkedInAuthGuard,
5152
LinkedInStrategy,
5253
GoogleStrategy,
54+
AdminBootstrapService,
5355
],
5456
exports: [JwtModule, JwtAuthGuard],
5557
})

Frontend/appTalenFront/vercel.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rewrites": [
3+
{
4+
"source": "/(.*)",
5+
"destination": "/index.html"
6+
}
7+
]
8+
}

vercel.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"rewrites": [
3+
{
4+
"source": "/(.*)",
5+
"destination": "/index.html"
6+
}
7+
]
8+
}

0 commit comments

Comments
 (0)