@flusys/nestjs-auth 1.0.0-rc → 2.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.
Files changed (58) hide show
  1. package/README.md +530 -396
  2. package/cjs/controllers/authentication.controller.js +160 -28
  3. package/cjs/controllers/user-permission.controller.js +49 -136
  4. package/cjs/docs/auth-swagger.config.js +14 -7
  5. package/cjs/dtos/authentication.dto.js +0 -30
  6. package/cjs/dtos/index.js +0 -1
  7. package/cjs/interceptors/clear-token.interceptor.js +8 -18
  8. package/cjs/interceptors/set-token.interceptor.js +8 -55
  9. package/cjs/interfaces/auth-email-provider.interface.js +1 -1
  10. package/cjs/modules/auth.module.js +63 -129
  11. package/cjs/services/auth-config.service.js +6 -29
  12. package/cjs/services/auth-datasource.provider.js +8 -20
  13. package/cjs/services/auth-email.service.js +72 -87
  14. package/cjs/services/authentication.service.js +211 -355
  15. package/cjs/services/branch.service.js +9 -5
  16. package/cjs/services/user-permission.service.js +43 -71
  17. package/cjs/services/user.service.js +18 -47
  18. package/cjs/strategies/jwt.strategy.js +44 -44
  19. package/controllers/authentication.controller.d.ts +5 -3
  20. package/controllers/user-permission.controller.d.ts +5 -4
  21. package/dtos/authentication.dto.d.ts +0 -4
  22. package/dtos/index.d.ts +0 -1
  23. package/entities/index.d.ts +3 -3
  24. package/fesm/controllers/authentication.controller.js +162 -30
  25. package/fesm/controllers/user-permission.controller.js +50 -137
  26. package/fesm/docs/auth-swagger.config.js +14 -7
  27. package/fesm/dtos/authentication.dto.js +0 -30
  28. package/fesm/dtos/index.js +0 -1
  29. package/fesm/interceptors/clear-token.interceptor.js +8 -18
  30. package/fesm/interceptors/set-token.interceptor.js +8 -55
  31. package/fesm/interfaces/auth-email-provider.interface.js +1 -1
  32. package/fesm/modules/auth.module.js +63 -129
  33. package/fesm/services/auth-config.service.js +6 -29
  34. package/fesm/services/auth-datasource.provider.js +8 -20
  35. package/fesm/services/auth-email.service.js +72 -87
  36. package/fesm/services/authentication.service.js +211 -355
  37. package/fesm/services/branch.service.js +9 -5
  38. package/fesm/services/user-permission.service.js +44 -72
  39. package/fesm/services/user.service.js +18 -47
  40. package/fesm/strategies/jwt.strategy.js +44 -44
  41. package/interceptors/clear-token.interceptor.d.ts +0 -1
  42. package/interceptors/set-token.interceptor.d.ts +1 -2
  43. package/interfaces/authentication.interface.d.ts +0 -4
  44. package/modules/auth.module.d.ts +6 -2
  45. package/package.json +3 -3
  46. package/services/auth-config.service.d.ts +2 -7
  47. package/services/auth-datasource.provider.d.ts +2 -4
  48. package/services/auth-email.service.d.ts +7 -4
  49. package/services/authentication.service.d.ts +14 -8
  50. package/services/user-permission.service.d.ts +6 -8
  51. package/services/user.service.d.ts +1 -1
  52. package/strategies/jwt.strategy.d.ts +4 -1
  53. package/cjs/dtos/user-company-permission.dto.js +0 -110
  54. package/cjs/guards/auth-throttler.guard.js +0 -53
  55. package/dtos/user-company-permission.dto.d.ts +0 -10
  56. package/fesm/dtos/user-company-permission.dto.js +0 -98
  57. package/fesm/guards/auth-throttler.guard.js +0 -43
  58. package/guards/auth-throttler.guard.d.ts +0 -7
@@ -26,14 +26,15 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { CurrentUser } from '@flusys/nestjs-shared/decorators';
29
+ import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
29
30
  import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
30
31
  import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
31
32
  import { Body, Controller, HttpCode, HttpStatus, Inject, Optional, Post, Req, UnauthorizedException, UseGuards, UseInterceptors } from '@nestjs/common';
32
- import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
33
+ import { ApiBearerAuth, ApiBody, ApiExtraModels, ApiHeader, ApiOperation, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
33
34
  import { SkipThrottle, Throttle } from '@nestjs/throttler';
34
35
  import { Request } from 'express';
35
- import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
36
- import { MessageResponseDto } from '@flusys/nestjs-shared/dtos';
36
+ import { DEFAULT_COOKIE_NAME } from '../config';
37
+ import { ChangePasswordDto, CompanySelectionResponseDto, ForgotPasswordDto, LoginDto, LoginResponseDto, LoginResponseRequiresEmailVerificationDto, MeResponseDto, RefreshTokenDto, RegistrationDto, RegistrationResponseDto, ResendVerificationDto, ResetPasswordDto, VerifyEmailDto } from '../dtos/authentication.dto';
37
38
  import { ClearToken } from '../interceptors/clear-token.interceptor';
38
39
  import { SetToken } from '../interceptors/set-token.interceptor';
39
40
  import { AuthConfigService } from '../services/auth-config.service';
@@ -44,8 +45,20 @@ export class AuthenticationController {
44
45
  const frontendUrl = this.authConfig?.getFrontendUrl();
45
46
  return frontendUrl ? `${frontendUrl}${path}` : `${req.protocol}://${req.get('host')}${path}`;
46
47
  }
48
+ get emailService() {
49
+ if (!this.authEmailService) {
50
+ throw new Error('Email service is not configured');
51
+ }
52
+ return this.authEmailService;
53
+ }
54
+ get isEmailEnabled() {
55
+ return !!this.authEmailService;
56
+ }
47
57
  async login(loginDto) {
48
58
  const result = await this.authService.login(loginDto.email, loginDto.password);
59
+ if ('requiresEmailVerification' in result) {
60
+ return result;
61
+ }
49
62
  if ('requiresSelection' in result) {
50
63
  return result;
51
64
  }
@@ -54,14 +67,15 @@ export class AuthenticationController {
54
67
  /** Register new user. If company feature enabled, can join/create company. */ async register(req, registrationDto) {
55
68
  const verifyUrlBase = this.buildAuthUrl(req, '/auth/verify-email');
56
69
  const result = await this.authService.register(registrationDto, verifyUrlBase);
57
- return {
58
- ...result,
59
- companyFeatureEnabled: this.authConfig?.isCompanyFeatureEnabled() ?? false
60
- };
70
+ return result;
61
71
  }
62
- /** Refresh access token. Browser uses HTTP-only cookie, API/Mobile uses request body. */ async refreshToken(req, refreshTokenDto) {
63
- // Get refresh token from cookie (browser) or body (API/mobile)
64
- const cookieName = this.authConfig?.getRefreshTokenCookieName() ?? 'fsn_refresh_token';
72
+ /**
73
+ * Refresh access token.
74
+ * - **Browser**: Prefers HTTP-only cookie, falls back to body if cookie missing.
75
+ * - **API/Mobile**: Must send `refreshToken` in request body. Set header `x-client-type: mobile` or `x-client-type: api`.
76
+ */ async refreshToken(req, refreshTokenDto) {
77
+ const cookieName = this.authConfig?.getRefreshTokenCookieName() ?? DEFAULT_COOKIE_NAME;
78
+ // Always check cookie first, then body - handles Browser, SSR, and Mobile/API
65
79
  const refreshToken = req.cookies?.[cookieName] || refreshTokenDto.refreshToken;
66
80
  if (!refreshToken) {
67
81
  throw new UnauthorizedException('Refresh token is required');
@@ -86,46 +100,42 @@ export class AuthenticationController {
86
100
  };
87
101
  }
88
102
  async forgotPassword(req, dto) {
89
- if (!this.authEmailService) {
103
+ if (!this.isEmailEnabled) {
90
104
  return {
91
105
  success: false,
92
106
  message: 'Email service is not configured'
93
107
  };
94
108
  }
95
- return this.authEmailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
109
+ return this.emailService.forgotPassword(dto.email, this.buildAuthUrl(req, '/auth/reset-password'));
96
110
  }
97
111
  async resetPassword(dto) {
98
- if (!this.authEmailService) {
112
+ if (!this.isEmailEnabled) {
99
113
  return {
100
114
  success: false,
101
115
  message: 'Email service is not configured'
102
116
  };
103
117
  }
104
- return this.authEmailService.resetPassword(dto.token, dto.newPassword);
118
+ return this.emailService.resetPassword(dto.token, dto.newPassword);
105
119
  }
106
120
  async resendVerification(req, dto) {
107
- if (!this.authEmailService) {
121
+ if (!this.isEmailEnabled) {
108
122
  return {
109
123
  success: false,
110
124
  message: 'Email service is not configured'
111
125
  };
112
126
  }
113
- return this.authEmailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
127
+ return this.emailService.resendVerificationEmail(dto.email, this.buildAuthUrl(req, '/auth/verify-email'));
114
128
  }
115
129
  /** Verify email - Verify email using token. Handles both pending registration and existing user verification. */ async verifyEmail(dto) {
116
- if (!this.authEmailService) {
130
+ if (!this.isEmailEnabled) {
117
131
  return {
118
132
  success: false,
119
133
  message: 'Email service is not configured'
120
134
  };
121
135
  }
122
- const result = await this.authEmailService.verifyEmail(dto.token);
136
+ const result = await this.emailService.verifyEmail(dto.token);
123
137
  if (result.type === 'pending') {
124
- const registrationResult = await this.authService.createUserFromPendingRegistration(result.pending);
125
- return {
126
- ...registrationResult,
127
- companyFeatureEnabled: this.authConfig?.isCompanyFeatureEnabled() ?? false
128
- };
138
+ return this.authService.createUserFromPendingRegistration(result.pending);
129
139
  }
130
140
  return {
131
141
  success: true,
@@ -157,13 +167,102 @@ _ts_decorate([
157
167
  ApiBody({
158
168
  type: LoginDto
159
169
  }),
170
+ ApiExtraModels(LoginResponseDto, CompanySelectionResponseDto, LoginResponseRequiresEmailVerificationDto),
160
171
  ApiResponse({
161
172
  status: 200,
162
- type: LoginResponseDto
163
- }),
164
- ApiResponse({
165
- status: 200,
166
- type: CompanySelectionResponseDto
173
+ description: 'Login successful, requires company selection, or requires email verification',
174
+ content: {
175
+ 'application/json': {
176
+ schema: {
177
+ oneOf: [
178
+ {
179
+ $ref: getSchemaPath(LoginResponseDto)
180
+ },
181
+ {
182
+ $ref: getSchemaPath(CompanySelectionResponseDto)
183
+ },
184
+ {
185
+ $ref: getSchemaPath(LoginResponseRequiresEmailVerificationDto)
186
+ }
187
+ ]
188
+ },
189
+ examples: {
190
+ loginSuccess: {
191
+ summary: 'Login Success',
192
+ description: 'Successful login with tokens (company/branch included when company feature enabled)',
193
+ value: {
194
+ accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
195
+ refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
196
+ user: {
197
+ id: '550e8400-e29b-41d4-a716-446655440000',
198
+ email: 'john@example.com',
199
+ name: 'John Doe'
200
+ }
201
+ }
202
+ },
203
+ loginSuccessWithCompany: {
204
+ summary: 'Login Success (Company Feature)',
205
+ description: 'Login with company/branch - only when company feature is enabled',
206
+ value: {
207
+ accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
208
+ refreshToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
209
+ user: {
210
+ id: '550e8400-e29b-41d4-a716-446655440000',
211
+ email: 'john@example.com',
212
+ name: 'John Doe'
213
+ },
214
+ company: {
215
+ id: '550e8400-e29b-41d4-a716-446655440001',
216
+ name: 'Acme Corporation',
217
+ slug: 'acme-corp'
218
+ },
219
+ branch: {
220
+ id: '550e8400-e29b-41d4-a716-446655440002',
221
+ name: 'Main Branch',
222
+ slug: 'main-branch'
223
+ }
224
+ }
225
+ },
226
+ companySelection: {
227
+ summary: 'Company Selection Required (Company Feature)',
228
+ description: 'User has multiple companies/branches - only when company feature is enabled',
229
+ value: {
230
+ requiresSelection: true,
231
+ sessionId: 'sess_abc123def456',
232
+ expiresAt: '2024-12-18T15:30:00.000Z',
233
+ user: {
234
+ id: '550e8400-e29b-41d4-a716-446655440000',
235
+ email: 'john@example.com',
236
+ name: 'John Doe'
237
+ },
238
+ companies: [
239
+ {
240
+ id: '550e8400-e29b-41d4-a716-446655440001',
241
+ name: 'Company A',
242
+ slug: 'company-a',
243
+ branches: [
244
+ {
245
+ id: '550e8400-e29b-41d4-a716-446655440010',
246
+ name: 'Branch 1',
247
+ slug: 'branch-1'
248
+ }
249
+ ]
250
+ }
251
+ ]
252
+ }
253
+ },
254
+ emailVerification: {
255
+ summary: 'Email Verification Required',
256
+ description: 'User must verify email before logging in',
257
+ value: {
258
+ requiresEmailVerification: true,
259
+ email: 'john@example.com',
260
+ message: 'Please verify your email address before logging in.'
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
167
266
  }),
168
267
  _ts_param(0, Body()),
169
268
  _ts_metadata("design:type", Function),
@@ -211,10 +310,43 @@ _ts_decorate([
211
310
  }
212
311
  }),
213
312
  ApiOperation({
214
- summary: 'Refresh access token'
313
+ summary: 'Refresh access token',
314
+ description: `
315
+ **Browser clients**: Refresh token is read from HTTP-only cookie (automatically set on login/register).
316
+ Falls back to request body if cookie is not available.
317
+
318
+ **API/Mobile clients**: Must send \`refreshToken\` in request body.
319
+ Set header \`x-client-type: mobile\` or \`x-client-type: api\` to identify as non-browser client.
320
+
321
+ **Client detection**: Uses \`x-client-type\` header first, then falls back to User-Agent detection.
322
+ `
323
+ }),
324
+ ApiHeader({
325
+ name: 'x-client-type',
326
+ required: false,
327
+ description: 'Client type: "browser"/"web" for cookie-based auth, "mobile"/"api" for body-based auth',
328
+ example: 'mobile'
215
329
  }),
216
330
  ApiBody({
217
- type: RefreshTokenDto
331
+ type: RefreshTokenDto,
332
+ description: 'Required for API/Mobile clients. Browser clients prefer HTTP-only cookie with body fallback.'
333
+ }),
334
+ ApiResponse({
335
+ status: 200,
336
+ description: 'New access token',
337
+ schema: {
338
+ type: 'object',
339
+ properties: {
340
+ accessToken: {
341
+ type: 'string',
342
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
343
+ }
344
+ }
345
+ }
346
+ }),
347
+ ApiResponse({
348
+ status: 401,
349
+ description: 'Invalid or missing refresh token'
218
350
  }),
219
351
  _ts_param(0, Req()),
220
352
  _ts_param(1, Body()),
@@ -25,129 +25,74 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
+ import { JwtAuthGuard, RequirePermission, USER_PERMISSIONS } from '@flusys/nestjs-shared';
28
29
  import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
29
30
  import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
30
- import { JwtAuthGuard, RequirePermission, USER_PERMISSIONS } from '@flusys/nestjs-shared';
31
- import { AssignUserCompanyDto, AssignUserBranchDto, UserCompaniesResponseDto, UserBranchesResponseDto } from '../dtos/user-permission.dto';
31
+ import { AssignUserBranchDto, AssignUserCompanyDto, UserBranchesResponseDto, UserCompaniesResponseDto } from '../dtos/user-permission.dto';
32
+ import { PermissionType } from '../entities/user-company-permission.entity';
32
33
  import { UserPermissionService } from '../services/user-permission.service';
33
34
  export class UserPermissionController {
34
35
  async assignUserCompany(dto) {
35
- let addedCount = 0;
36
- let removedCount = 0;
37
- for (const item of dto.items){
38
- if (item.isAdd) {
39
- await this.userPermissionService.assignUserToCompany({
40
- userId: dto.userId,
41
- companyId: item.targetId
42
- });
43
- addedCount++;
44
- } else {
45
- await this.userPermissionService.revokeCompanyAccess(dto.userId, item.targetId);
46
- removedCount++;
47
- }
48
- }
49
- return {
50
- success: true,
51
- message: `Processed ${addedCount} additions and ${removedCount} removals`
52
- };
36
+ return this.processBatchPermissions(dto.userId, dto.items, PermissionType.COMPANY);
53
37
  }
54
38
  async getUserCompanies(userId) {
55
- const permissions = await this.userPermissionService.findUserCompanies(userId);
56
- const data = permissions.map((permission)=>({
57
- id: permission.id,
58
- userId: permission.userId,
59
- companyId: permission.company?.id || permission.targetId,
60
- companyName: permission.company?.name || '',
61
- companySlug: permission.company?.slug || ''
62
- }));
39
+ const permissions = await this.permissionService.findUserCompanies(userId);
63
40
  return {
64
41
  success: true,
65
42
  message: 'User companies retrieved successfully',
66
- data
43
+ data: permissions.map((p)=>({
44
+ id: p.id,
45
+ userId: p.userId,
46
+ companyId: p.company?.id || p.targetId,
47
+ companyName: p.company?.name || '',
48
+ companySlug: p.company?.slug || ''
49
+ }))
67
50
  };
68
51
  }
69
52
  async assignUserBranch(dto) {
70
- let addedCount = 0;
71
- let removedCount = 0;
72
- for (const item of dto.items){
73
- if (item.isAdd) {
74
- await this.userPermissionService.assignUserToBranch({
75
- userId: dto.userId,
76
- branchId: item.targetId
77
- });
78
- addedCount++;
79
- } else {
80
- await this.userPermissionService.revokeBranchAccess(dto.userId, item.targetId);
81
- removedCount++;
82
- }
83
- }
53
+ return this.processBatchPermissions(dto.userId, dto.items, PermissionType.BRANCH);
54
+ }
55
+ async getUserBranches(userId, companyId) {
56
+ const permissions = await this.permissionService.findUserBranches(userId, companyId);
84
57
  return {
85
58
  success: true,
86
- message: `Processed ${addedCount} additions and ${removedCount} removals`
59
+ message: 'User branches retrieved successfully',
60
+ data: permissions.map((p)=>({
61
+ id: p.id,
62
+ userId: p.userId,
63
+ branchId: p.branch?.id || p.targetId,
64
+ branchName: p.branch?.name || '',
65
+ branchSlug: p.branch?.slug || '',
66
+ companyId: p.branch?.companyId || ''
67
+ }))
87
68
  };
88
69
  }
89
- async getUserBranches(userId) {
90
- const permissions = await this.userPermissionService.findUserBranches(userId);
91
- const data = permissions.map((permission)=>({
92
- id: permission.id,
93
- userId: permission.userId,
94
- branchId: permission.branch?.id || permission.targetId,
95
- branchName: permission.branch?.name || '',
96
- branchSlug: permission.branch?.slug || '',
97
- companyId: permission.branch?.companyId || ''
98
- }));
70
+ /** Process batch permission assignments/revocations */ async processBatchPermissions(userId, items, type) {
71
+ let added = 0, removed = 0;
72
+ for (const item of items){
73
+ if (item.isAdd) {
74
+ await this.permissionService.assignPermission(userId, item.targetId, type);
75
+ added++;
76
+ } else {
77
+ await this.permissionService.revokePermission(userId, item.targetId, type);
78
+ removed++;
79
+ }
80
+ }
99
81
  return {
100
82
  success: true,
101
- message: 'User branches retrieved successfully',
102
- data
83
+ message: `Processed ${added} additions and ${removed} removals`
103
84
  };
104
85
  }
105
- constructor(userPermissionService){
106
- _define_property(this, "userPermissionService", void 0);
107
- this.userPermissionService = userPermissionService;
86
+ constructor(permissionService){
87
+ _define_property(this, "permissionService", void 0);
88
+ this.permissionService = permissionService;
108
89
  }
109
90
  }
110
91
  _ts_decorate([
111
92
  Post('user-company/assign'),
112
93
  RequirePermission(USER_PERMISSIONS.UPDATE),
113
94
  ApiOperation({
114
- summary: 'Batch assign/revoke user-company permissions',
115
- description: `
116
- Batch operation to assign or revoke user permissions to companies.
117
-
118
- **Example: Add company permissions**
119
- \`\`\`json
120
- {
121
- "userId": "user-id",
122
- "items": [
123
- { "targetId": "company-id-1", "isAdd": true },
124
- { "targetId": "company-id-2", "isAdd": true }
125
- ]
126
- }
127
- \`\`\`
128
-
129
- **Example: Remove company permissions**
130
- \`\`\`json
131
- {
132
- "userId": "user-id",
133
- "items": [
134
- { "targetId": "company-id-1", "isAdd": false },
135
- { "targetId": "company-id-2", "isAdd": false }
136
- ]
137
- }
138
- \`\`\`
139
-
140
- **Example: Mix add and remove**
141
- \`\`\`json
142
- {
143
- "userId": "user-id",
144
- "items": [
145
- { "targetId": "company-id-1", "isAdd": true },
146
- { "targetId": "company-id-2", "isAdd": false }
147
- ]
148
- }
149
- \`\`\`
150
- `
95
+ summary: 'Batch assign/revoke user-company permissions'
151
96
  }),
152
97
  ApiResponse({
153
98
  status: 200,
@@ -167,8 +112,7 @@ _ts_decorate([
167
112
  Post('get-user-company'),
168
113
  RequirePermission(USER_PERMISSIONS.READ),
169
114
  ApiOperation({
170
- summary: 'Get user companies',
171
- description: 'Returns all companies the user has access to.'
115
+ summary: 'Get user companies'
172
116
  }),
173
117
  ApiBody({
174
118
  schema: {
@@ -197,43 +141,7 @@ _ts_decorate([
197
141
  Post('user-branch/assign'),
198
142
  RequirePermission(USER_PERMISSIONS.UPDATE),
199
143
  ApiOperation({
200
- summary: 'Batch assign/revoke user-branch permissions',
201
- description: `
202
- Batch operation to assign or revoke user permissions to branches.
203
-
204
- **Example: Add branch permissions**
205
- \`\`\`json
206
- {
207
- "userId": "user-id",
208
- "items": [
209
- { "targetId": "branch-id-1", "isAdd": true },
210
- { "targetId": "branch-id-2", "isAdd": true }
211
- ]
212
- }
213
- \`\`\`
214
-
215
- **Example: Remove branch permissions**
216
- \`\`\`json
217
- {
218
- "userId": "user-id",
219
- "items": [
220
- { "targetId": "branch-id-1", "isAdd": false },
221
- { "targetId": "branch-id-2", "isAdd": false }
222
- ]
223
- }
224
- \`\`\`
225
-
226
- **Example: Mix add and remove**
227
- \`\`\`json
228
- {
229
- "userId": "user-id",
230
- "items": [
231
- { "targetId": "branch-id-1", "isAdd": true },
232
- { "targetId": "branch-id-2", "isAdd": false }
233
- ]
234
- }
235
- \`\`\`
236
- `
144
+ summary: 'Batch assign/revoke user-branch permissions'
237
145
  }),
238
146
  ApiResponse({
239
147
  status: 200,
@@ -253,14 +161,17 @@ _ts_decorate([
253
161
  Post('get-user-branch'),
254
162
  RequirePermission(USER_PERMISSIONS.READ),
255
163
  ApiOperation({
256
- summary: 'Get user branches',
257
- description: 'Returns all branches the user has access to.'
164
+ summary: 'Get user branches'
258
165
  }),
259
166
  ApiBody({
260
167
  schema: {
261
168
  properties: {
262
169
  userId: {
263
170
  type: 'string'
171
+ },
172
+ companyId: {
173
+ type: 'string',
174
+ description: 'Optional company ID to filter branches'
264
175
  }
265
176
  },
266
177
  required: [
@@ -273,8 +184,10 @@ _ts_decorate([
273
184
  type: UserBranchesResponseDto
274
185
  }),
275
186
  _ts_param(0, Body('userId')),
187
+ _ts_param(1, Body('companyId')),
276
188
  _ts_metadata("design:type", Function),
277
189
  _ts_metadata("design:paramtypes", [
190
+ String,
278
191
  String
279
192
  ]),
280
193
  _ts_metadata("design:returntype", Promise)
@@ -4,6 +4,16 @@
4
4
  'User Permissions',
5
5
  'Company Selection'
6
6
  ];
7
+ /** Examples to exclude when company feature is disabled */ const COMPANY_EXAMPLE_EXCLUSIONS = [
8
+ {
9
+ pathPattern: '/auth/login',
10
+ method: 'post',
11
+ examples: [
12
+ 'loginSuccessWithCompany',
13
+ 'companySelection'
14
+ ]
15
+ }
16
+ ];
7
17
  /**
8
18
  * Schema properties to exclude when company feature is disabled
9
19
  *
@@ -48,16 +58,14 @@
48
58
  schemaName: 'RegistrationResponseDto',
49
59
  properties: [
50
60
  'company',
51
- 'branch',
52
- 'companyFeatureEnabled'
61
+ 'branch'
53
62
  ]
54
63
  },
55
64
  {
56
65
  schemaName: 'LoginResponseDto',
57
66
  properties: [
58
67
  'company',
59
- 'branch',
60
- 'companyFeatureEnabled'
68
+ 'branch'
61
69
  ]
62
70
  },
63
71
  {
@@ -92,7 +100,6 @@
92
100
  'id',
93
101
  'name',
94
102
  'slug',
95
- 'isActive',
96
103
  'logoId'
97
104
  ]
98
105
  },
@@ -102,7 +109,6 @@
102
109
  'id',
103
110
  'name',
104
111
  'slug',
105
- 'isActive',
106
112
  'logoId',
107
113
  'branches'
108
114
  ]
@@ -401,6 +407,7 @@ POST /auth/login → { accessToken, refreshToken, user }
401
407
  path: 'api/docs/auth',
402
408
  bearerAuth: true,
403
409
  excludeTags,
404
- excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS
410
+ excludeSchemaProperties: enableCompanyFeature ? undefined : COMPANY_SCHEMA_EXCLUSIONS,
411
+ excludeExamples: enableCompanyFeature ? undefined : COMPANY_EXAMPLE_EXCLUSIONS
405
412
  };
406
413
  }
@@ -361,7 +361,6 @@ _ts_decorate([
361
361
  _define_property(this, "id", void 0);
362
362
  _define_property(this, "name", void 0);
363
363
  _define_property(this, "slug", void 0);
364
- _define_property(this, "isActive", void 0);
365
364
  _define_property(this, "logoId", void 0);
366
365
  }
367
366
  }
@@ -383,12 +382,6 @@ _ts_decorate([
383
382
  }),
384
383
  _ts_metadata("design:type", String)
385
384
  ], BranchSelectionInfoDto.prototype, "slug", void 0);
386
- _ts_decorate([
387
- ApiProperty({
388
- example: true
389
- }),
390
- _ts_metadata("design:type", Boolean)
391
- ], BranchSelectionInfoDto.prototype, "isActive", void 0);
392
385
  _ts_decorate([
393
386
  ApiPropertyOptional({
394
387
  example: '550e8400-e29b-41d4-a716-446655440077',
@@ -402,7 +395,6 @@ _ts_decorate([
402
395
  _define_property(this, "id", void 0);
403
396
  _define_property(this, "name", void 0);
404
397
  _define_property(this, "slug", void 0);
405
- _define_property(this, "isActive", void 0);
406
398
  _define_property(this, "logoId", void 0);
407
399
  _define_property(this, "branches", void 0);
408
400
  }
@@ -425,12 +417,6 @@ _ts_decorate([
425
417
  }),
426
418
  _ts_metadata("design:type", String)
427
419
  ], CompanyWithBranchesDto.prototype, "slug", void 0);
428
- _ts_decorate([
429
- ApiProperty({
430
- example: true
431
- }),
432
- _ts_metadata("design:type", Boolean)
433
- ], CompanyWithBranchesDto.prototype, "isActive", void 0);
434
420
  _ts_decorate([
435
421
  ApiPropertyOptional({
436
422
  example: '550e8400-e29b-41d4-a716-446655440088',
@@ -455,7 +441,6 @@ _ts_decorate([
455
441
  _define_property(this, "user", void 0);
456
442
  _define_property(this, "company", void 0);
457
443
  _define_property(this, "branch", void 0);
458
- _define_property(this, "companyFeatureEnabled", void 0);
459
444
  }
460
445
  }
461
446
  _ts_decorate([
@@ -491,13 +476,6 @@ _ts_decorate([
491
476
  }),
492
477
  _ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
493
478
  ], LoginResponseDto.prototype, "branch", void 0);
494
- _ts_decorate([
495
- ApiPropertyOptional({
496
- example: true,
497
- description: 'Whether company feature is enabled'
498
- }),
499
- _ts_metadata("design:type", Boolean)
500
- ], LoginResponseDto.prototype, "companyFeatureEnabled", void 0);
501
479
  /** Registration Response DTO - Returned on successful registration */ export class RegistrationResponseDto {
502
480
  constructor(){
503
481
  _define_property(this, "accessToken", void 0);
@@ -505,7 +483,6 @@ _ts_decorate([
505
483
  _define_property(this, "user", void 0);
506
484
  _define_property(this, "company", void 0);
507
485
  _define_property(this, "branch", void 0);
508
- _define_property(this, "companyFeatureEnabled", void 0);
509
486
  _define_property(this, "requiresEmailVerification", void 0);
510
487
  }
511
488
  }
@@ -542,13 +519,6 @@ _ts_decorate([
542
519
  }),
543
520
  _ts_metadata("design:type", typeof BranchInfoDto === "undefined" ? Object : BranchInfoDto)
544
521
  ], RegistrationResponseDto.prototype, "branch", void 0);
545
- _ts_decorate([
546
- ApiPropertyOptional({
547
- example: true,
548
- description: 'Whether company feature is enabled'
549
- }),
550
- _ts_metadata("design:type", Boolean)
551
- ], RegistrationResponseDto.prototype, "companyFeatureEnabled", void 0);
552
522
  _ts_decorate([
553
523
  ApiPropertyOptional({
554
524
  example: true,
@@ -1,6 +1,5 @@
1
1
  export * from './authentication.dto';
2
2
  export * from './company-branch.dto';
3
3
  export * from './company.dto';
4
- export * from './user-company-permission.dto';
5
4
  export * from './user.dto';
6
5
  export * from './user-permission.dto';