ar-saas 0.3.2 → 0.4.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 (28) hide show
  1. package/dist/generator.js +2 -6
  2. package/package.json +1 -1
  3. package/templates/backend/.env.example +1 -1
  4. package/templates/backend/package.json +5 -2
  5. package/templates/backend/src/app.module.ts +68 -40
  6. package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +27 -45
  7. package/templates/backend/src/main.ts +50 -51
  8. package/templates/backend/src/modules/auth/auth.controller.ts +162 -158
  9. package/templates/backend/src/modules/auth/auth.service.ts +236 -257
  10. package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +45 -43
  11. package/templates/backend/src/modules/users/users.controller.ts +28 -0
  12. package/templates/backend/src/modules/users/users.module.ts +16 -14
  13. package/templates/backend/src/modules/users/users.repository.ts +57 -51
  14. package/templates/backend/src/modules/users/users.service.ts +130 -104
  15. package/templates/backend/src/modules/workspaces/workspaces.repository.ts +38 -34
  16. package/templates/backend/src/modules/workspaces/workspaces.service.ts +51 -42
  17. package/templates/frontend/package.json +2 -5
  18. package/templates/frontend/pnpm-workspace.yaml +2 -2
  19. package/templates/frontend/src/app/(auth)/layout.tsx +29 -28
  20. package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
  21. package/templates/frontend/src/app/(dashboard)/profile/page.tsx +241 -226
  22. package/templates/frontend/src/app/(dashboard)/settings/page.tsx +155 -156
  23. package/templates/frontend/src/app/(dashboard)/team/page.tsx +179 -178
  24. package/templates/frontend/src/app/layout.tsx +29 -26
  25. package/templates/frontend/src/app/page.tsx +1 -1
  26. package/templates/frontend/src/app/setup/page.tsx +1 -1
  27. package/templates/frontend/src/components/dashboard/header.tsx +5 -3
  28. package/templates/frontend/src/config/site.ts +1 -1
@@ -1,257 +1,236 @@
1
- import {
2
- BadRequestException,
3
- Injectable,
4
- InternalServerErrorException,
5
- UnauthorizedException,
6
- } from '@nestjs/common';
7
- import { ConfigService } from '@nestjs/config';
8
- import { JwtService } from '@nestjs/jwt';
9
- import * as crypto from 'crypto';
10
- import * as bcrypt from 'bcryptjs';
11
- import type { StringValue } from 'ms';
12
- import { MailService } from '../mail/mail.service';
13
- import { UsersService } from '../users/users.service';
14
- import { UserDocument } from '../users/schemas/user.schema';
15
- import { WorkspacesService } from '../workspaces/workspaces.service';
16
- import { ForgotPasswordDto } from './dto/forgot-password.dto';
17
- import { LoginDto } from './dto/login.dto';
18
- import { RegisterDto } from './dto/register.dto';
19
- import { ResetPasswordDto } from './dto/reset-password.dto';
20
-
21
- interface TokenPair {
22
- accessToken: string;
23
- refreshToken: string;
24
- }
25
-
26
- @Injectable()
27
- export class AuthService {
28
- constructor(
29
- private readonly usersService: UsersService,
30
- private readonly workspacesService: WorkspacesService,
31
- private readonly mailService: MailService,
32
- private readonly jwtService: JwtService,
33
- private readonly configService: ConfigService,
34
- ) {}
35
-
36
- // ─── Register ──────────────────────────────────────────────────────────────
37
-
38
- async register(dto: RegisterDto): Promise<{ message: string }> {
39
- // 1. Create workspace with placeholder ownerId (user doesn't exist yet)
40
- const workspace = await this.workspacesService.create('pending', dto.name);
41
- const workspaceId = String(workspace._id);
42
-
43
- // 2. Create user linked to the workspace
44
- const user = await this.usersService.create({
45
- name: dto.name,
46
- email: dto.email,
47
- password: dto.password,
48
- workspaceId,
49
- });
50
- const userId = String(user._id);
51
-
52
- // 3. Set the real ownerId on the workspace
53
- await this.workspacesService.update(workspaceId, { ownerId: userId });
54
-
55
- // 4. Generate email verification token (expires in 24h)
56
- const token = this.generateSecureToken();
57
- const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
58
- await this.usersService.setEmailVerificationToken(userId, token, expiresAt);
59
-
60
- // 5. Send verification email
61
- await this.mailService.sendVerificationEmail(dto.email, dto.name, token);
62
-
63
- return { message: 'Verification email sent' };
64
- }
65
-
66
- // ─── Verify Email ───────────────────────────────────────────────────────────
67
-
68
- async verifyEmail(token: string): Promise<{ message: string }> {
69
- const user = await this.usersService.findByVerificationToken(token);
70
- if (!user) {
71
- throw new BadRequestException('Token inválido o expirado.');
72
- }
73
- if (
74
- user.emailVerificationTokenExpiresAt &&
75
- user.emailVerificationTokenExpiresAt < new Date()
76
- ) {
77
- throw new BadRequestException(
78
- 'El enlace de verificación expiró. Solicitá uno nuevo.',
79
- );
80
- }
81
- await this.usersService.markEmailVerified(String(user._id));
82
- return { message: 'Email verified' };
83
- }
84
-
85
- // ─── Login ──────────────────────────────────────────────────────────────────
86
-
87
- async login(
88
- dto: LoginDto,
89
- ): Promise<{ user: UserDocument } & TokenPair> {
90
- const user = await this.usersService.findByEmail(dto.email);
91
- if (!user?.password) {
92
- throw new UnauthorizedException('Email o contraseña incorrectos.');
93
- }
94
-
95
- const valid = await bcrypt.compare(dto.password, user.password);
96
- if (!valid) {
97
- throw new UnauthorizedException('Email o contraseña incorrectos.');
98
- }
99
-
100
- if (!user.emailVerified) {
101
- throw new UnauthorizedException(
102
- 'Por favor verificá tu email antes de ingresar.',
103
- );
104
- }
105
-
106
- const userId = String(user._id);
107
- const tokens = await this.generateTokens(
108
- userId,
109
- user.email,
110
- user.workspaceId,
111
- user.role,
112
- );
113
-
114
- await Promise.all([
115
- this.usersService.updateRefreshToken(userId, tokens.refreshToken),
116
- this.usersService.updateLastLoginAt(userId),
117
- ]);
118
-
119
- // Re-fetch without secrets for the response
120
- const cleanUser = await this.usersService.findById(userId);
121
- if (!cleanUser) {
122
- throw new InternalServerErrorException('Error al obtener el usuario.');
123
- }
124
-
125
- return { user: cleanUser, ...tokens };
126
- }
127
-
128
- // ─── Refresh Token ──────────────────────────────────────────────────────────
129
-
130
- async refresh(token: string): Promise<TokenPair> {
131
- let payload: { sub: string };
132
- try {
133
- payload = await this.jwtService.verifyAsync<{ sub: string }>(token, {
134
- secret: this.configService.getOrThrow<string>('JWT_REFRESH_SECRET'),
135
- });
136
- } catch {
137
- throw new UnauthorizedException('Refresh token inválido.');
138
- }
139
-
140
- const user = await this.usersService.validateRefreshToken(
141
- payload.sub,
142
- token,
143
- );
144
- if (!user) {
145
- throw new UnauthorizedException('Refresh token inválido.');
146
- }
147
-
148
- const userId = String(user._id);
149
- const tokens = await this.generateTokens(
150
- userId,
151
- user.email,
152
- user.workspaceId,
153
- user.role,
154
- );
155
-
156
- await this.usersService.updateRefreshToken(userId, tokens.refreshToken);
157
- return tokens;
158
- }
159
-
160
- // ─── Logout ─────────────────────────────────────────────────────────────────
161
-
162
- async logout(userId: string): Promise<void> {
163
- await this.usersService.updateRefreshToken(userId, null);
164
- }
165
-
166
- // ─── Forgot Password ────────────────────────────────────────────────────────
167
-
168
- async forgotPassword(
169
- dto: ForgotPasswordDto,
170
- ): Promise<{ message: string }> {
171
- const user = await this.usersService.findByEmail(dto.email);
172
-
173
- // Always return the same response to avoid leaking email existence
174
- if (user?.emailVerified) {
175
- const token = this.generateSecureToken();
176
- const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
177
- await this.usersService.setPasswordResetToken(
178
- String(user._id),
179
- token,
180
- expiresAt,
181
- );
182
- await this.mailService.sendPasswordResetEmail(
183
- dto.email,
184
- user.name,
185
- token,
186
- );
187
- }
188
-
189
- return { message: 'Reset email sent' };
190
- }
191
-
192
- // ─── Reset Password ─────────────────────────────────────────────────────────
193
-
194
- async resetPassword(dto: ResetPasswordDto): Promise<{ message: string }> {
195
- const user = await this.usersService.findByPasswordResetToken(dto.token);
196
- if (!user) {
197
- throw new BadRequestException('Token inválido o expirado.');
198
- }
199
- if (
200
- user.passwordResetTokenExpiresAt &&
201
- user.passwordResetTokenExpiresAt < new Date()
202
- ) {
203
- throw new BadRequestException(
204
- 'El enlace de restablecimiento expiró. Solicitá uno nuevo.',
205
- );
206
- }
207
-
208
- await this.usersService.updatePassword(String(user._id), dto.newPassword);
209
- return { message: 'Password reset successful' };
210
- }
211
-
212
- // ─── Get Me ─────────────────────────────────────────────────────────────────
213
-
214
- async getMe(userId: string): Promise<UserDocument> {
215
- const user = await this.usersService.findById(userId);
216
- if (!user) {
217
- throw new UnauthorizedException('Usuario no encontrado.');
218
- }
219
- return user;
220
- }
221
-
222
- // ─── Private helpers ────────────────────────────────────────────────────────
223
-
224
- private generateSecureToken(): string {
225
- return crypto.randomBytes(32).toString('hex');
226
- }
227
-
228
- private async generateTokens(
229
- userId: string,
230
- email: string,
231
- workspaceId: string,
232
- role: string,
233
- ): Promise<TokenPair> {
234
- const accessPayload = { sub: userId, email, workspaceId, role };
235
- const refreshPayload = { sub: userId };
236
-
237
- const accessExpiresIn = (
238
- this.configService.get<string>('JWT_ACCESS_EXPIRES_IN') ?? '15m'
239
- ) as StringValue;
240
- const refreshExpiresIn = (
241
- this.configService.get<string>('JWT_REFRESH_EXPIRES_IN') ?? '7d'
242
- ) as StringValue;
243
-
244
- const [accessToken, refreshToken] = await Promise.all([
245
- this.jwtService.signAsync(accessPayload, {
246
- secret: this.configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
247
- expiresIn: accessExpiresIn,
248
- }),
249
- this.jwtService.signAsync(refreshPayload, {
250
- secret: this.configService.getOrThrow<string>('JWT_REFRESH_SECRET'),
251
- expiresIn: refreshExpiresIn,
252
- }),
253
- ]);
254
-
255
- return { accessToken, refreshToken };
256
- }
257
- }
1
+ import {
2
+ BadRequestException,
3
+ Injectable,
4
+ InternalServerErrorException,
5
+ UnauthorizedException,
6
+ } from '@nestjs/common';
7
+ import { ConfigService } from '@nestjs/config';
8
+ import { JwtService } from '@nestjs/jwt';
9
+ import * as crypto from 'crypto';
10
+ import * as bcrypt from 'bcryptjs';
11
+ import type { StringValue } from 'ms';
12
+ import { MailService } from '../mail/mail.service';
13
+ import { UsersService } from '../users/users.service';
14
+ import { UserDocument } from '../users/schemas/user.schema';
15
+ import { WorkspacesService } from '../workspaces/workspaces.service';
16
+ import { ForgotPasswordDto } from './dto/forgot-password.dto';
17
+ import { LoginDto } from './dto/login.dto';
18
+ import { RegisterDto } from './dto/register.dto';
19
+ import { ResetPasswordDto } from './dto/reset-password.dto';
20
+
21
+ interface TokenPair {
22
+ accessToken: string;
23
+ refreshToken: string;
24
+ }
25
+
26
+ @Injectable()
27
+ export class AuthService {
28
+ constructor(
29
+ private readonly usersService: UsersService,
30
+ private readonly workspacesService: WorkspacesService,
31
+ private readonly mailService: MailService,
32
+ private readonly jwtService: JwtService,
33
+ private readonly configService: ConfigService,
34
+ ) {}
35
+
36
+ async register(dto: RegisterDto): Promise<{ message: string }> {
37
+ const workspace = await this.workspacesService.create('pending', dto.name);
38
+ const workspaceId = String(workspace._id);
39
+
40
+ try {
41
+ const user = await this.usersService.create({
42
+ name: dto.name,
43
+ email: dto.email,
44
+ password: dto.password,
45
+ workspaceId,
46
+ });
47
+ const userId = String(user._id);
48
+
49
+ await this.workspacesService.update(workspaceId, { ownerId: userId });
50
+
51
+ const token = this.generateSecureToken();
52
+ const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
53
+ await this.usersService.setEmailVerificationToken(userId, token, expiresAt);
54
+ await this.mailService.sendVerificationEmail(dto.email, dto.name, token);
55
+
56
+ return { message: 'Verification email sent' };
57
+ } catch (error) {
58
+ await this.workspacesService.delete(workspaceId);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ async verifyEmail(token: string): Promise<{ message: string }> {
64
+ const user = await this.usersService.findByVerificationToken(token);
65
+ if (!user) {
66
+ throw new BadRequestException('Token inválido o expirado.');
67
+ }
68
+ if (
69
+ user.emailVerificationTokenExpiresAt &&
70
+ user.emailVerificationTokenExpiresAt < new Date()
71
+ ) {
72
+ throw new BadRequestException(
73
+ 'El enlace de verificación expiró. Solicitá uno nuevo.',
74
+ );
75
+ }
76
+ await this.usersService.markEmailVerified(String(user._id));
77
+ return { message: 'Email verified' };
78
+ }
79
+
80
+ async login(
81
+ dto: LoginDto,
82
+ ): Promise<{ user: UserDocument } & TokenPair> {
83
+ const user = await this.usersService.findByEmailWithPassword(dto.email);
84
+ if (!user?.password) {
85
+ throw new UnauthorizedException('Email o contraseña incorrectos.');
86
+ }
87
+
88
+ const valid = await bcrypt.compare(dto.password, user.password);
89
+ if (!valid) {
90
+ throw new UnauthorizedException('Email o contraseña incorrectos.');
91
+ }
92
+
93
+ if (!user.emailVerified) {
94
+ throw new UnauthorizedException(
95
+ 'Por favor verificá tu email antes de ingresar.',
96
+ );
97
+ }
98
+
99
+ const userId = String(user._id);
100
+ const tokens = await this.generateTokens(
101
+ userId,
102
+ user.email,
103
+ user.workspaceId,
104
+ user.role,
105
+ );
106
+
107
+ await Promise.all([
108
+ this.usersService.updateRefreshToken(userId, tokens.refreshToken),
109
+ this.usersService.updateLastLoginAt(userId),
110
+ ]);
111
+
112
+ const cleanUser = await this.usersService.findById(userId);
113
+ if (!cleanUser) {
114
+ throw new InternalServerErrorException('Error al obtener el usuario.');
115
+ }
116
+
117
+ return { user: cleanUser, ...tokens };
118
+ }
119
+
120
+ async refresh(token: string): Promise<TokenPair> {
121
+ let payload: { sub: string };
122
+ try {
123
+ payload = await this.jwtService.verifyAsync<{ sub: string }>(token, {
124
+ secret: this.configService.getOrThrow<string>('JWT_REFRESH_SECRET'),
125
+ });
126
+ } catch {
127
+ throw new UnauthorizedException('Refresh token inválido.');
128
+ }
129
+
130
+ const user = await this.usersService.validateRefreshToken(
131
+ payload.sub,
132
+ token,
133
+ );
134
+ if (!user) {
135
+ throw new UnauthorizedException('Refresh token inválido.');
136
+ }
137
+
138
+ const userId = String(user._id);
139
+ const tokens = await this.generateTokens(
140
+ userId,
141
+ user.email,
142
+ user.workspaceId,
143
+ user.role,
144
+ );
145
+
146
+ await this.usersService.updateRefreshToken(userId, tokens.refreshToken);
147
+ return tokens;
148
+ }
149
+
150
+ async logout(userId: string): Promise<void> {
151
+ await this.usersService.updateRefreshToken(userId, null);
152
+ }
153
+
154
+ async forgotPassword(
155
+ dto: ForgotPasswordDto,
156
+ ): Promise<{ message: string }> {
157
+ const user = await this.usersService.findByEmail(dto.email);
158
+
159
+ if (user?.emailVerified) {
160
+ const token = this.generateSecureToken();
161
+ const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
162
+ await this.usersService.setPasswordResetToken(
163
+ String(user._id),
164
+ token,
165
+ expiresAt,
166
+ );
167
+ await this.mailService.sendPasswordResetEmail(
168
+ dto.email,
169
+ user.name,
170
+ token,
171
+ );
172
+ }
173
+
174
+ return { message: 'Reset email sent' };
175
+ }
176
+
177
+ async resetPassword(dto: ResetPasswordDto): Promise<{ message: string }> {
178
+ const user = await this.usersService.findByPasswordResetToken(dto.token);
179
+ if (!user) {
180
+ throw new BadRequestException('Token inválido o expirado.');
181
+ }
182
+ if (
183
+ user.passwordResetTokenExpiresAt &&
184
+ user.passwordResetTokenExpiresAt < new Date()
185
+ ) {
186
+ throw new BadRequestException(
187
+ 'El enlace de restablecimiento expiró. Solicitá uno nuevo.',
188
+ );
189
+ }
190
+
191
+ await this.usersService.updatePassword(String(user._id), dto.newPassword);
192
+ return { message: 'Password reset successful' };
193
+ }
194
+
195
+ async getMe(userId: string): Promise<UserDocument> {
196
+ const user = await this.usersService.findById(userId);
197
+ if (!user) {
198
+ throw new UnauthorizedException('Usuario no encontrado.');
199
+ }
200
+ return user;
201
+ }
202
+
203
+ private generateSecureToken(): string {
204
+ return crypto.randomBytes(32).toString('hex');
205
+ }
206
+
207
+ private async generateTokens(
208
+ userId: string,
209
+ email: string,
210
+ workspaceId: string,
211
+ role: string,
212
+ ): Promise<TokenPair> {
213
+ const accessPayload = { sub: userId, email, workspaceId, role };
214
+ const refreshPayload = { sub: userId };
215
+
216
+ const accessExpiresIn = (
217
+ this.configService.get<string>('JWT_ACCESS_EXPIRES_IN') ?? '15m'
218
+ ) as StringValue;
219
+ const refreshExpiresIn = (
220
+ this.configService.get<string>('JWT_REFRESH_EXPIRES_IN') ?? '7d'
221
+ ) as StringValue;
222
+
223
+ const [accessToken, refreshToken] = await Promise.all([
224
+ this.jwtService.signAsync(accessPayload, {
225
+ secret: this.configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
226
+ expiresIn: accessExpiresIn,
227
+ }),
228
+ this.jwtService.signAsync(refreshPayload, {
229
+ secret: this.configService.getOrThrow<string>('JWT_REFRESH_SECRET'),
230
+ expiresIn: refreshExpiresIn,
231
+ }),
232
+ ]);
233
+
234
+ return { accessToken, refreshToken };
235
+ }
236
+ }
@@ -1,43 +1,45 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { ConfigService } from '@nestjs/config';
3
- import { PassportStrategy } from '@nestjs/passport';
4
- import { Request } from 'express';
5
- import { ExtractJwt, Strategy } from 'passport-jwt';
6
-
7
- export interface JwtPayload {
8
- sub: string;
9
- email: string;
10
- workspaceId: string;
11
- role: string;
12
- }
13
-
14
- export interface TokenUser {
15
- userId: string;
16
- email: string;
17
- workspaceId: string;
18
- role: string;
19
- }
20
-
21
- @Injectable()
22
- export class JwtStrategy extends PassportStrategy(Strategy) {
23
- constructor(configService: ConfigService) {
24
- super({
25
- jwtFromRequest: ExtractJwt.fromExtractors([
26
- (req: Request): string | null =>
27
- (req?.cookies as Record<string, string>)?.access_token ?? null,
28
- ExtractJwt.fromAuthHeaderAsBearerToken(),
29
- ]),
30
- ignoreExpiration: false,
31
- secretOrKey: configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
32
- });
33
- }
34
-
35
- validate(payload: JwtPayload): TokenUser {
36
- return {
37
- userId: payload.sub,
38
- email: payload.email,
39
- workspaceId: payload.workspaceId,
40
- role: payload.role,
41
- };
42
- }
43
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { PassportStrategy } from '@nestjs/passport';
4
+ import { Request } from 'express';
5
+ import { ExtractJwt, Strategy } from 'passport-jwt';
6
+
7
+ export interface JwtPayload {
8
+ sub: string;
9
+ email: string;
10
+ workspaceId: string;
11
+ role: string;
12
+ }
13
+
14
+ export interface TokenUser {
15
+ userId: string;
16
+ email: string;
17
+ workspaceId: string;
18
+ role: string;
19
+ }
20
+
21
+ @Injectable()
22
+ export class JwtStrategy extends PassportStrategy(Strategy) {
23
+ constructor(configService: ConfigService) {
24
+ super({
25
+ jwtFromRequest: ExtractJwt.fromExtractors([
26
+ (req: Request): string | null =>
27
+ (req?.cookies as Record<string, string>)?.access_token ?? null,
28
+ ExtractJwt.fromAuthHeaderAsBearerToken(),
29
+ ]),
30
+ ignoreExpiration: false,
31
+ secretOrKey: configService.getOrThrow<string>('JWT_ACCESS_SECRET'),
32
+ passReqToCallback: true,
33
+ });
34
+ }
35
+
36
+ validate(req: Request, payload: JwtPayload): TokenUser {
37
+ (req as Record<string, unknown>).workspaceId = payload.workspaceId;
38
+ return {
39
+ userId: payload.sub,
40
+ email: payload.email,
41
+ workspaceId: payload.workspaceId,
42
+ role: payload.role,
43
+ };
44
+ }
45
+ }
@@ -0,0 +1,28 @@
1
+ import { Body, Controller, HttpCode, HttpStatus, Patch, UseGuards } from '@nestjs/common';
2
+ import { ApiOperation, ApiTags } from '@nestjs/swagger';
3
+ import { CurrentUser } from '../../common/decorators/current-user.decorator';
4
+ import type { TokenPayload } from '../../common/decorators/current-user.decorator';
5
+ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
6
+ import { UsersService } from './users.service';
7
+
8
+ class UpdateUserDto {
9
+ name?: string;
10
+ email?: string;
11
+ }
12
+
13
+ @ApiTags('Users')
14
+ @Controller('users')
15
+ export class UsersController {
16
+ constructor(private readonly usersService: UsersService) {}
17
+
18
+ @Patch('me')
19
+ @HttpCode(HttpStatus.OK)
20
+ @UseGuards(JwtAuthGuard)
21
+ @ApiOperation({ summary: 'Actualizar perfil del usuario autenticado' })
22
+ async updateMe(
23
+ @CurrentUser() user: TokenPayload,
24
+ @Body() dto: UpdateUserDto,
25
+ ) {
26
+ return this.usersService.updateMe(user.userId, dto);
27
+ }
28
+ }
@@ -1,14 +1,16 @@
1
- import { Module } from '@nestjs/common';
2
- import { MongooseModule } from '@nestjs/mongoose';
3
- import { User, UserSchema } from './schemas/user.schema';
4
- import { UsersRepository } from './users.repository';
5
- import { UsersService } from './users.service';
6
-
7
- @Module({
8
- imports: [
9
- MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
10
- ],
11
- providers: [UsersService, UsersRepository],
12
- exports: [UsersService],
13
- })
14
- export class UsersModule {}
1
+ import { Module } from '@nestjs/common';
2
+ import { MongooseModule } from '@nestjs/mongoose';
3
+ import { User, UserSchema } from './schemas/user.schema';
4
+ import { UsersController } from './users.controller';
5
+ import { UsersRepository } from './users.repository';
6
+ import { UsersService } from './users.service';
7
+
8
+ @Module({
9
+ imports: [
10
+ MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
11
+ ],
12
+ controllers: [UsersController],
13
+ providers: [UsersService, UsersRepository],
14
+ exports: [UsersService],
15
+ })
16
+ export class UsersModule {}