ar-saas 0.3.1 → 0.3.3

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 (114) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +338 -314
  3. package/dist/cli.js +19 -0
  4. package/dist/generator.js +166 -55
  5. package/package.json +52 -50
  6. package/templates/backend/.env.example +67 -67
  7. package/templates/backend/.prettierrc +4 -4
  8. package/templates/backend/README.md +249 -168
  9. package/templates/backend/eslint.config.mjs +35 -35
  10. package/templates/backend/nest-cli.json +8 -8
  11. package/templates/backend/package-lock.json +10979 -10979
  12. package/templates/backend/package.json +88 -88
  13. package/templates/backend/src/app.controller.spec.ts +24 -24
  14. package/templates/backend/src/app.controller.ts +15 -15
  15. package/templates/backend/src/app.module.ts +40 -40
  16. package/templates/backend/src/app.service.ts +11 -11
  17. package/templates/backend/src/common/base/base.repository.ts +221 -221
  18. package/templates/backend/src/common/base/base.schema.ts +24 -24
  19. package/templates/backend/src/common/decorators/cookie.decorator.ts +9 -9
  20. package/templates/backend/src/common/decorators/current-user.decorator.ts +20 -20
  21. package/templates/backend/src/common/decorators/workspace-id.decorator.ts +14 -14
  22. package/templates/backend/src/common/filters/global-exception.filter.ts +61 -61
  23. package/templates/backend/src/common/guards/jwt-auth.guard.ts +5 -5
  24. package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +45 -45
  25. package/templates/backend/src/main.ts +51 -51
  26. package/templates/backend/src/modules/auth/auth.controller.ts +158 -158
  27. package/templates/backend/src/modules/auth/auth.module.ts +20 -20
  28. package/templates/backend/src/modules/auth/auth.service.ts +257 -257
  29. package/templates/backend/src/modules/auth/dto/forgot-password.dto.ts +9 -9
  30. package/templates/backend/src/modules/auth/dto/login.dto.ts +14 -14
  31. package/templates/backend/src/modules/auth/dto/refresh-token.dto.ts +12 -12
  32. package/templates/backend/src/modules/auth/dto/register.dto.ts +26 -26
  33. package/templates/backend/src/modules/auth/dto/reset-password.dto.ts +16 -16
  34. package/templates/backend/src/modules/auth/dto/verify-email.dto.ts +9 -9
  35. package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +43 -43
  36. package/templates/backend/src/modules/mail/mail.module.ts +9 -9
  37. package/templates/backend/src/modules/mail/mail.service.ts +141 -141
  38. package/templates/backend/src/modules/users/schemas/user.schema.ts +54 -54
  39. package/templates/backend/src/modules/users/users.module.ts +14 -14
  40. package/templates/backend/src/modules/users/users.repository.ts +51 -51
  41. package/templates/backend/src/modules/users/users.service.ts +104 -104
  42. package/templates/backend/src/modules/workspaces/schemas/workspace.schema.ts +26 -26
  43. package/templates/backend/src/modules/workspaces/workspaces.module.ts +16 -16
  44. package/templates/backend/src/modules/workspaces/workspaces.repository.ts +34 -34
  45. package/templates/backend/src/modules/workspaces/workspaces.service.ts +42 -42
  46. package/templates/backend/test/app.e2e-spec.ts +25 -25
  47. package/templates/backend/test/jest-e2e.json +9 -9
  48. package/templates/backend/tsconfig.build.json +4 -4
  49. package/templates/backend/tsconfig.json +26 -26
  50. package/templates/frontend/.env.local.example +1 -1
  51. package/templates/frontend/README.md +152 -0
  52. package/templates/frontend/components.json +20 -20
  53. package/templates/frontend/eslint.config.mjs +14 -14
  54. package/templates/frontend/next.config.ts +5 -5
  55. package/templates/frontend/package-lock.json +6722 -6722
  56. package/templates/frontend/package.json +48 -48
  57. package/templates/frontend/pnpm-lock.yaml +5012 -5012
  58. package/templates/frontend/pnpm-workspace.yaml +3 -3
  59. package/templates/frontend/postcss.config.mjs +7 -7
  60. package/templates/frontend/src/app/(auth)/forgot-password/page.tsx +84 -84
  61. package/templates/frontend/src/app/(auth)/layout.tsx +28 -28
  62. package/templates/frontend/src/app/(auth)/login/page.tsx +111 -111
  63. package/templates/frontend/src/app/(auth)/register/page.tsx +161 -161
  64. package/templates/frontend/src/app/(auth)/reset-password/page.tsx +120 -120
  65. package/templates/frontend/src/app/(auth)/verify-email/page.tsx +78 -78
  66. package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
  67. package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +105 -105
  68. package/templates/frontend/src/app/(dashboard)/layout.tsx +38 -38
  69. package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -226
  70. package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -156
  71. package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -178
  72. package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -127
  73. package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -118
  74. package/templates/frontend/src/app/globals.css +81 -81
  75. package/templates/frontend/src/app/layout.tsx +26 -26
  76. package/templates/frontend/src/app/page.tsx +5 -45
  77. package/templates/frontend/src/app/setup/page.tsx +371 -275
  78. package/templates/frontend/src/components/dashboard/header.tsx +89 -89
  79. package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -71
  80. package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -34
  81. package/templates/frontend/src/components/landing/faq.tsx +39 -39
  82. package/templates/frontend/src/components/landing/features.tsx +54 -54
  83. package/templates/frontend/src/components/landing/footer.tsx +76 -76
  84. package/templates/frontend/src/components/landing/hero.tsx +72 -72
  85. package/templates/frontend/src/components/landing/navbar.tsx +78 -78
  86. package/templates/frontend/src/components/landing/pricing.tsx +90 -90
  87. package/templates/frontend/src/components/ui/accordion.tsx +52 -52
  88. package/templates/frontend/src/components/ui/avatar.tsx +46 -46
  89. package/templates/frontend/src/components/ui/badge.tsx +30 -30
  90. package/templates/frontend/src/components/ui/button.tsx +52 -52
  91. package/templates/frontend/src/components/ui/card.tsx +50 -50
  92. package/templates/frontend/src/components/ui/checkbox.tsx +27 -27
  93. package/templates/frontend/src/components/ui/dialog.tsx +100 -100
  94. package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -173
  95. package/templates/frontend/src/components/ui/form.tsx +158 -158
  96. package/templates/frontend/src/components/ui/input.tsx +21 -21
  97. package/templates/frontend/src/components/ui/label.tsx +22 -22
  98. package/templates/frontend/src/components/ui/separator.tsx +25 -25
  99. package/templates/frontend/src/components/ui/skeleton.tsx +7 -7
  100. package/templates/frontend/src/components/ui/switch.tsx +28 -28
  101. package/templates/frontend/src/components/ui/tabs.tsx +54 -54
  102. package/templates/frontend/src/components/ui/textarea.tsx +20 -20
  103. package/templates/frontend/src/components/ui/toast.tsx +109 -109
  104. package/templates/frontend/src/components/ui/toaster.tsx +30 -30
  105. package/templates/frontend/src/config/site.ts +197 -197
  106. package/templates/frontend/src/hooks/use-toast.ts +116 -116
  107. package/templates/frontend/src/lib/api/auth.ts +39 -39
  108. package/templates/frontend/src/lib/api/client.ts +66 -66
  109. package/templates/frontend/src/lib/hooks/use-auth.ts +1 -1
  110. package/templates/frontend/src/lib/utils.ts +6 -6
  111. package/templates/frontend/src/providers/auth-provider.tsx +60 -60
  112. package/templates/frontend/src/types/api.ts +12 -12
  113. package/templates/frontend/src/types/auth.ts +27 -27
  114. package/templates/frontend/tsconfig.json +23 -23
@@ -1,221 +1,221 @@
1
- import {
2
- BadRequestException,
3
- InternalServerErrorException,
4
- } from '@nestjs/common';
5
- import { Model, Document, Types } from 'mongoose';
6
- import type { QueryFilter, UpdateQuery, PipelineStage } from 'mongoose';
7
-
8
- export interface PaginatedResult<T> {
9
- data: T[];
10
- total: number;
11
- page: number;
12
- limit: number;
13
- totalPages: number;
14
- }
15
-
16
- export class BaseRepository<T extends Document> {
17
- constructor(protected readonly model: Model<T>) {}
18
-
19
- protected toObjectId(id: string): Types.ObjectId {
20
- if (!Types.ObjectId.isValid(id)) {
21
- throw new BadRequestException(`ID inválido: ${id}`);
22
- }
23
- return new Types.ObjectId(id);
24
- }
25
-
26
- async findAll(
27
- workspaceId: string,
28
- options?: {
29
- filter?: Record<string, unknown>;
30
- limit?: number;
31
- skip?: number;
32
- sort?: Record<string, 1 | -1>;
33
- },
34
- ): Promise<T[]> {
35
- const query = this.buildBaseQuery(workspaceId, options?.filter);
36
- return this.model
37
- .find(query)
38
- .limit(options?.limit ?? 100)
39
- .skip(options?.skip ?? 0)
40
- .sort(options?.sort ?? { createdAt: -1 })
41
- .lean()
42
- .exec() as unknown as T[];
43
- }
44
-
45
- async findById(workspaceId: string, id: string): Promise<T | null> {
46
- return this.model
47
- .findOne({
48
- _id: this.toObjectId(id),
49
- workspaceId: this.toObjectId(workspaceId),
50
- deletedAt: null,
51
- })
52
- .lean()
53
- .exec() as unknown as T | null;
54
- }
55
-
56
- async findOne(
57
- workspaceId: string,
58
- filter: Record<string, unknown>,
59
- ): Promise<T | null> {
60
- return this.model
61
- .findOne({
62
- ...filter,
63
- workspaceId: this.toObjectId(workspaceId),
64
- deletedAt: null,
65
- })
66
- .lean()
67
- .exec() as unknown as T | null;
68
- }
69
-
70
- async create(data: Partial<T>): Promise<T> {
71
- try {
72
- const created = await this.model.create(data);
73
- return created.toObject() as unknown as T;
74
- } catch (error: unknown) {
75
- this.handleMongoError(error);
76
- throw error;
77
- }
78
- }
79
-
80
- async update(
81
- workspaceId: string,
82
- id: string,
83
- data: UpdateQuery<T>,
84
- ): Promise<T | null> {
85
- return this.model
86
- .findOneAndUpdate(
87
- {
88
- _id: this.toObjectId(id),
89
- workspaceId: this.toObjectId(workspaceId),
90
- deletedAt: null,
91
- },
92
- { $set: data },
93
- { new: true, runValidators: true },
94
- )
95
- .lean()
96
- .exec() as unknown as T | null;
97
- }
98
-
99
- async softDelete(workspaceId: string, id: string): Promise<T | null> {
100
- return this.model
101
- .findOneAndUpdate(
102
- {
103
- _id: this.toObjectId(id),
104
- workspaceId: this.toObjectId(workspaceId),
105
- deletedAt: null,
106
- },
107
- { $set: { deletedAt: new Date() } },
108
- { new: true },
109
- )
110
- .lean()
111
- .exec() as unknown as T | null;
112
- }
113
-
114
- async restore(workspaceId: string, id: string): Promise<T | null> {
115
- return this.model
116
- .findOneAndUpdate(
117
- {
118
- _id: this.toObjectId(id),
119
- workspaceId: this.toObjectId(workspaceId),
120
- deletedAt: { $ne: null },
121
- },
122
- { $set: { deletedAt: null } },
123
- { new: true },
124
- )
125
- .lean()
126
- .exec() as unknown as T | null;
127
- }
128
-
129
- async count(
130
- workspaceId: string,
131
- filter?: Record<string, unknown>,
132
- ): Promise<number> {
133
- const query = this.buildBaseQuery(workspaceId, filter);
134
- return this.model.countDocuments(query).exec();
135
- }
136
-
137
- async exists(
138
- workspaceId: string,
139
- filter: Record<string, unknown>,
140
- ): Promise<boolean> {
141
- const count = await this.model.countDocuments({
142
- ...filter,
143
- workspaceId: this.toObjectId(workspaceId),
144
- deletedAt: null,
145
- });
146
- return count > 0;
147
- }
148
-
149
- async paginate(
150
- workspaceId: string,
151
- options: {
152
- page?: number;
153
- limit?: number;
154
- filter?: Record<string, unknown>;
155
- sort?: Record<string, 1 | -1>;
156
- },
157
- ): Promise<PaginatedResult<T>> {
158
- const page = options.page ?? 1;
159
- const limit = options.limit ?? 20;
160
- const skip = (page - 1) * limit;
161
- const query = this.buildBaseQuery(workspaceId, options.filter);
162
-
163
- const [data, total] = await Promise.all([
164
- this.model
165
- .find(query)
166
- .sort(options.sort ?? { createdAt: -1 })
167
- .skip(skip)
168
- .limit(limit)
169
- .lean()
170
- .exec(),
171
- this.model.countDocuments(query).exec(),
172
- ]);
173
-
174
- return {
175
- data: data as unknown as T[],
176
- total,
177
- page,
178
- limit,
179
- totalPages: Math.ceil(total / limit),
180
- };
181
- }
182
-
183
- async aggregate(pipeline: PipelineStage[]): Promise<unknown[]> {
184
- return this.model.aggregate(pipeline).exec();
185
- }
186
-
187
- private buildBaseQuery(
188
- workspaceId: string,
189
- additionalFilter?: Record<string, unknown>,
190
- ): QueryFilter<T> {
191
- return {
192
- workspaceId: this.toObjectId(workspaceId),
193
- deletedAt: null,
194
- ...additionalFilter,
195
- };
196
- }
197
-
198
- private handleMongoError(error: unknown): never {
199
- const mongoError = error as {
200
- code?: number;
201
- keyValue?: Record<string, unknown>;
202
- };
203
-
204
- if (mongoError.code === 11000) {
205
- const fields = Object.keys(mongoError.keyValue ?? {}).join(', ');
206
- throw new BadRequestException(
207
- `Ya existe un registro con los mismos valores en: ${fields}`,
208
- );
209
- }
210
-
211
- if (mongoError.code === 121) {
212
- throw new BadRequestException(
213
- 'Error de validación del schema de MongoDB',
214
- );
215
- }
216
-
217
- throw new InternalServerErrorException(
218
- 'Error inesperado en la base de datos',
219
- );
220
- }
221
- }
1
+ import {
2
+ BadRequestException,
3
+ InternalServerErrorException,
4
+ } from '@nestjs/common';
5
+ import { Model, Document, Types } from 'mongoose';
6
+ import type { QueryFilter, UpdateQuery, PipelineStage } from 'mongoose';
7
+
8
+ export interface PaginatedResult<T> {
9
+ data: T[];
10
+ total: number;
11
+ page: number;
12
+ limit: number;
13
+ totalPages: number;
14
+ }
15
+
16
+ export class BaseRepository<T extends Document> {
17
+ constructor(protected readonly model: Model<T>) {}
18
+
19
+ protected toObjectId(id: string): Types.ObjectId {
20
+ if (!Types.ObjectId.isValid(id)) {
21
+ throw new BadRequestException(`ID inválido: ${id}`);
22
+ }
23
+ return new Types.ObjectId(id);
24
+ }
25
+
26
+ async findAll(
27
+ workspaceId: string,
28
+ options?: {
29
+ filter?: Record<string, unknown>;
30
+ limit?: number;
31
+ skip?: number;
32
+ sort?: Record<string, 1 | -1>;
33
+ },
34
+ ): Promise<T[]> {
35
+ const query = this.buildBaseQuery(workspaceId, options?.filter);
36
+ return this.model
37
+ .find(query)
38
+ .limit(options?.limit ?? 100)
39
+ .skip(options?.skip ?? 0)
40
+ .sort(options?.sort ?? { createdAt: -1 })
41
+ .lean()
42
+ .exec() as unknown as T[];
43
+ }
44
+
45
+ async findById(workspaceId: string, id: string): Promise<T | null> {
46
+ return this.model
47
+ .findOne({
48
+ _id: this.toObjectId(id),
49
+ workspaceId: this.toObjectId(workspaceId),
50
+ deletedAt: null,
51
+ })
52
+ .lean()
53
+ .exec() as unknown as T | null;
54
+ }
55
+
56
+ async findOne(
57
+ workspaceId: string,
58
+ filter: Record<string, unknown>,
59
+ ): Promise<T | null> {
60
+ return this.model
61
+ .findOne({
62
+ ...filter,
63
+ workspaceId: this.toObjectId(workspaceId),
64
+ deletedAt: null,
65
+ })
66
+ .lean()
67
+ .exec() as unknown as T | null;
68
+ }
69
+
70
+ async create(data: Partial<T>): Promise<T> {
71
+ try {
72
+ const created = await this.model.create(data);
73
+ return created.toObject() as unknown as T;
74
+ } catch (error: unknown) {
75
+ this.handleMongoError(error);
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ async update(
81
+ workspaceId: string,
82
+ id: string,
83
+ data: UpdateQuery<T>,
84
+ ): Promise<T | null> {
85
+ return this.model
86
+ .findOneAndUpdate(
87
+ {
88
+ _id: this.toObjectId(id),
89
+ workspaceId: this.toObjectId(workspaceId),
90
+ deletedAt: null,
91
+ },
92
+ { $set: data },
93
+ { new: true, runValidators: true },
94
+ )
95
+ .lean()
96
+ .exec() as unknown as T | null;
97
+ }
98
+
99
+ async softDelete(workspaceId: string, id: string): Promise<T | null> {
100
+ return this.model
101
+ .findOneAndUpdate(
102
+ {
103
+ _id: this.toObjectId(id),
104
+ workspaceId: this.toObjectId(workspaceId),
105
+ deletedAt: null,
106
+ },
107
+ { $set: { deletedAt: new Date() } },
108
+ { new: true },
109
+ )
110
+ .lean()
111
+ .exec() as unknown as T | null;
112
+ }
113
+
114
+ async restore(workspaceId: string, id: string): Promise<T | null> {
115
+ return this.model
116
+ .findOneAndUpdate(
117
+ {
118
+ _id: this.toObjectId(id),
119
+ workspaceId: this.toObjectId(workspaceId),
120
+ deletedAt: { $ne: null },
121
+ },
122
+ { $set: { deletedAt: null } },
123
+ { new: true },
124
+ )
125
+ .lean()
126
+ .exec() as unknown as T | null;
127
+ }
128
+
129
+ async count(
130
+ workspaceId: string,
131
+ filter?: Record<string, unknown>,
132
+ ): Promise<number> {
133
+ const query = this.buildBaseQuery(workspaceId, filter);
134
+ return this.model.countDocuments(query).exec();
135
+ }
136
+
137
+ async exists(
138
+ workspaceId: string,
139
+ filter: Record<string, unknown>,
140
+ ): Promise<boolean> {
141
+ const count = await this.model.countDocuments({
142
+ ...filter,
143
+ workspaceId: this.toObjectId(workspaceId),
144
+ deletedAt: null,
145
+ });
146
+ return count > 0;
147
+ }
148
+
149
+ async paginate(
150
+ workspaceId: string,
151
+ options: {
152
+ page?: number;
153
+ limit?: number;
154
+ filter?: Record<string, unknown>;
155
+ sort?: Record<string, 1 | -1>;
156
+ },
157
+ ): Promise<PaginatedResult<T>> {
158
+ const page = options.page ?? 1;
159
+ const limit = options.limit ?? 20;
160
+ const skip = (page - 1) * limit;
161
+ const query = this.buildBaseQuery(workspaceId, options.filter);
162
+
163
+ const [data, total] = await Promise.all([
164
+ this.model
165
+ .find(query)
166
+ .sort(options.sort ?? { createdAt: -1 })
167
+ .skip(skip)
168
+ .limit(limit)
169
+ .lean()
170
+ .exec(),
171
+ this.model.countDocuments(query).exec(),
172
+ ]);
173
+
174
+ return {
175
+ data: data as unknown as T[],
176
+ total,
177
+ page,
178
+ limit,
179
+ totalPages: Math.ceil(total / limit),
180
+ };
181
+ }
182
+
183
+ async aggregate(pipeline: PipelineStage[]): Promise<unknown[]> {
184
+ return this.model.aggregate(pipeline).exec();
185
+ }
186
+
187
+ private buildBaseQuery(
188
+ workspaceId: string,
189
+ additionalFilter?: Record<string, unknown>,
190
+ ): QueryFilter<T> {
191
+ return {
192
+ workspaceId: this.toObjectId(workspaceId),
193
+ deletedAt: null,
194
+ ...additionalFilter,
195
+ };
196
+ }
197
+
198
+ private handleMongoError(error: unknown): never {
199
+ const mongoError = error as {
200
+ code?: number;
201
+ keyValue?: Record<string, unknown>;
202
+ };
203
+
204
+ if (mongoError.code === 11000) {
205
+ const fields = Object.keys(mongoError.keyValue ?? {}).join(', ');
206
+ throw new BadRequestException(
207
+ `Ya existe un registro con los mismos valores en: ${fields}`,
208
+ );
209
+ }
210
+
211
+ if (mongoError.code === 121) {
212
+ throw new BadRequestException(
213
+ 'Error de validación del schema de MongoDB',
214
+ );
215
+ }
216
+
217
+ throw new InternalServerErrorException(
218
+ 'Error inesperado en la base de datos',
219
+ );
220
+ }
221
+ }
@@ -1,24 +1,24 @@
1
- import { Prop, Schema } from '@nestjs/mongoose';
2
- import { Types } from 'mongoose';
3
-
4
- @Schema({ timestamps: true })
5
- export class BaseSchema {
6
- @Prop({
7
- type: Types.ObjectId,
8
- required: true,
9
- index: true,
10
- })
11
- workspaceId!: Types.ObjectId;
12
-
13
- @Prop({
14
- type: Types.ObjectId,
15
- required: true,
16
- })
17
- createdBy!: Types.ObjectId;
18
-
19
- @Prop({
20
- type: Date,
21
- default: null,
22
- })
23
- deletedAt!: Date | null;
24
- }
1
+ import { Prop, Schema } from '@nestjs/mongoose';
2
+ import { Types } from 'mongoose';
3
+
4
+ @Schema({ timestamps: true })
5
+ export class BaseSchema {
6
+ @Prop({
7
+ type: Types.ObjectId,
8
+ required: true,
9
+ index: true,
10
+ })
11
+ workspaceId!: Types.ObjectId;
12
+
13
+ @Prop({
14
+ type: Types.ObjectId,
15
+ required: true,
16
+ })
17
+ createdBy!: Types.ObjectId;
18
+
19
+ @Prop({
20
+ type: Date,
21
+ default: null,
22
+ })
23
+ deletedAt!: Date | null;
24
+ }
@@ -1,9 +1,9 @@
1
- import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
- import { Request } from 'express';
3
-
4
- export const Cookie = createParamDecorator(
5
- (name: string, ctx: ExecutionContext): string | undefined => {
6
- const request = ctx.switchToHttp().getRequest<Request>();
7
- return (request.cookies as Record<string, string>)?.[name];
8
- },
9
- );
1
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
+ import { Request } from 'express';
3
+
4
+ export const Cookie = createParamDecorator(
5
+ (name: string, ctx: ExecutionContext): string | undefined => {
6
+ const request = ctx.switchToHttp().getRequest<Request>();
7
+ return (request.cookies as Record<string, string>)?.[name];
8
+ },
9
+ );
@@ -1,20 +1,20 @@
1
- import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
-
3
- export interface TokenPayload {
4
- userId: string;
5
- email: string;
6
- workspaceId: string;
7
- role: string;
8
- }
9
-
10
- export const CurrentUser = createParamDecorator(
11
- (_data: unknown, ctx: ExecutionContext): TokenPayload => {
12
- const request = ctx.switchToHttp().getRequest();
13
- if (!request.user) {
14
- throw new Error(
15
- 'Usuario no encontrado en el request. ¿Falta el AuthGuard?',
16
- );
17
- }
18
- return request.user as TokenPayload;
19
- },
20
- );
1
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
+
3
+ export interface TokenPayload {
4
+ userId: string;
5
+ email: string;
6
+ workspaceId: string;
7
+ role: string;
8
+ }
9
+
10
+ export const CurrentUser = createParamDecorator(
11
+ (_data: unknown, ctx: ExecutionContext): TokenPayload => {
12
+ const request = ctx.switchToHttp().getRequest();
13
+ if (!request.user) {
14
+ throw new Error(
15
+ 'Usuario no encontrado en el request. ¿Falta el AuthGuard?',
16
+ );
17
+ }
18
+ return request.user as TokenPayload;
19
+ },
20
+ );
@@ -1,14 +1,14 @@
1
- import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
- import { TenantRequest } from '../interceptors/workspace-tenant.interceptor';
3
-
4
- export const WorkspaceId = createParamDecorator(
5
- (_data: unknown, ctx: ExecutionContext): string => {
6
- const request = ctx.switchToHttp().getRequest<TenantRequest>();
7
- if (!request.workspaceId) {
8
- throw new Error(
9
- 'workspaceId no encontrado en el request. ¿Falta el WorkspaceTenantInterceptor?',
10
- );
11
- }
12
- return request.workspaceId;
13
- },
14
- );
1
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
2
+ import { TenantRequest } from '../interceptors/workspace-tenant.interceptor';
3
+
4
+ export const WorkspaceId = createParamDecorator(
5
+ (_data: unknown, ctx: ExecutionContext): string => {
6
+ const request = ctx.switchToHttp().getRequest<TenantRequest>();
7
+ if (!request.workspaceId) {
8
+ throw new Error(
9
+ 'workspaceId no encontrado en el request. ¿Falta el WorkspaceTenantInterceptor?',
10
+ );
11
+ }
12
+ return request.workspaceId;
13
+ },
14
+ );