ar-saas 0.3.0 → 0.3.2
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/LICENSE +21 -21
- package/README.md +338 -314
- package/dist/cli.js +19 -0
- package/dist/generator.js +166 -55
- package/package.json +52 -50
- package/templates/backend/.env.example +67 -67
- package/templates/backend/.prettierrc +4 -4
- package/templates/backend/README.md +249 -168
- package/templates/backend/eslint.config.mjs +35 -35
- package/templates/backend/nest-cli.json +8 -8
- package/templates/backend/package-lock.json +10979 -10979
- package/templates/backend/package.json +88 -88
- package/templates/backend/src/app.controller.spec.ts +24 -24
- package/templates/backend/src/app.controller.ts +15 -15
- package/templates/backend/src/app.module.ts +40 -40
- package/templates/backend/src/app.service.ts +11 -11
- package/templates/backend/src/common/base/base.repository.ts +221 -221
- package/templates/backend/src/common/base/base.schema.ts +24 -24
- package/templates/backend/src/common/decorators/cookie.decorator.ts +9 -9
- package/templates/backend/src/common/decorators/current-user.decorator.ts +20 -20
- package/templates/backend/src/common/decorators/workspace-id.decorator.ts +14 -14
- package/templates/backend/src/common/filters/global-exception.filter.ts +61 -61
- package/templates/backend/src/common/guards/jwt-auth.guard.ts +5 -5
- package/templates/backend/src/common/interceptors/workspace-tenant.interceptor.ts +45 -45
- package/templates/backend/src/main.ts +51 -51
- package/templates/backend/src/modules/auth/auth.controller.ts +158 -158
- package/templates/backend/src/modules/auth/auth.module.ts +20 -20
- package/templates/backend/src/modules/auth/auth.service.ts +257 -257
- package/templates/backend/src/modules/auth/dto/forgot-password.dto.ts +9 -9
- package/templates/backend/src/modules/auth/dto/login.dto.ts +14 -14
- package/templates/backend/src/modules/auth/dto/refresh-token.dto.ts +12 -12
- package/templates/backend/src/modules/auth/dto/register.dto.ts +26 -26
- package/templates/backend/src/modules/auth/dto/reset-password.dto.ts +16 -16
- package/templates/backend/src/modules/auth/dto/verify-email.dto.ts +9 -9
- package/templates/backend/src/modules/auth/strategies/jwt.strategy.ts +43 -43
- package/templates/backend/src/modules/mail/mail.module.ts +9 -9
- package/templates/backend/src/modules/mail/mail.service.ts +141 -141
- package/templates/backend/src/modules/users/schemas/user.schema.ts +54 -54
- package/templates/backend/src/modules/users/users.module.ts +14 -14
- package/templates/backend/src/modules/users/users.repository.ts +51 -51
- package/templates/backend/src/modules/users/users.service.ts +104 -104
- package/templates/backend/src/modules/workspaces/schemas/workspace.schema.ts +26 -26
- package/templates/backend/src/modules/workspaces/workspaces.module.ts +16 -16
- package/templates/backend/src/modules/workspaces/workspaces.repository.ts +34 -34
- package/templates/backend/src/modules/workspaces/workspaces.service.ts +42 -42
- package/templates/backend/test/app.e2e-spec.ts +25 -25
- package/templates/backend/test/jest-e2e.json +9 -9
- package/templates/backend/tsconfig.build.json +4 -4
- package/templates/backend/tsconfig.json +26 -26
- package/templates/frontend/.env.local.example +1 -1
- package/templates/frontend/README.md +152 -0
- package/templates/frontend/components.json +20 -20
- package/templates/frontend/eslint.config.mjs +14 -14
- package/templates/frontend/next.config.ts +5 -5
- package/templates/frontend/package-lock.json +6722 -6722
- package/templates/frontend/package.json +48 -48
- package/templates/frontend/pnpm-lock.yaml +5012 -5012
- package/templates/frontend/pnpm-workspace.yaml +3 -3
- package/templates/frontend/postcss.config.mjs +7 -7
- package/templates/frontend/src/app/(auth)/forgot-password/page.tsx +84 -84
- package/templates/frontend/src/app/(auth)/layout.tsx +28 -28
- package/templates/frontend/src/app/(auth)/login/page.tsx +111 -111
- package/templates/frontend/src/app/(auth)/register/page.tsx +161 -161
- package/templates/frontend/src/app/(auth)/reset-password/page.tsx +120 -120
- package/templates/frontend/src/app/(auth)/verify-email/page.tsx +78 -78
- package/templates/frontend/src/app/(dashboard)/billing/page.tsx +111 -111
- package/templates/frontend/src/app/(dashboard)/dashboard/page.tsx +105 -105
- package/templates/frontend/src/app/(dashboard)/layout.tsx +38 -38
- package/templates/frontend/src/app/(dashboard)/profile/page.tsx +226 -226
- package/templates/frontend/src/app/(dashboard)/settings/page.tsx +156 -156
- package/templates/frontend/src/app/(dashboard)/team/page.tsx +178 -178
- package/templates/frontend/src/app/(legal)/privacy/page.tsx +127 -127
- package/templates/frontend/src/app/(legal)/terms/page.tsx +118 -118
- package/templates/frontend/src/app/globals.css +81 -81
- package/templates/frontend/src/app/layout.tsx +26 -26
- package/templates/frontend/src/app/page.tsx +5 -45
- package/templates/frontend/src/app/setup/page.tsx +371 -275
- package/templates/frontend/src/components/dashboard/header.tsx +89 -89
- package/templates/frontend/src/components/dashboard/sidebar.tsx +71 -71
- package/templates/frontend/src/components/dashboard/stat-card.tsx +34 -34
- package/templates/frontend/src/components/landing/faq.tsx +39 -39
- package/templates/frontend/src/components/landing/features.tsx +54 -54
- package/templates/frontend/src/components/landing/footer.tsx +76 -76
- package/templates/frontend/src/components/landing/hero.tsx +72 -72
- package/templates/frontend/src/components/landing/navbar.tsx +78 -78
- package/templates/frontend/src/components/landing/pricing.tsx +90 -90
- package/templates/frontend/src/components/ui/accordion.tsx +52 -52
- package/templates/frontend/src/components/ui/avatar.tsx +46 -46
- package/templates/frontend/src/components/ui/badge.tsx +30 -30
- package/templates/frontend/src/components/ui/button.tsx +52 -52
- package/templates/frontend/src/components/ui/card.tsx +50 -50
- package/templates/frontend/src/components/ui/checkbox.tsx +27 -27
- package/templates/frontend/src/components/ui/dialog.tsx +100 -100
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +173 -173
- package/templates/frontend/src/components/ui/form.tsx +158 -158
- package/templates/frontend/src/components/ui/input.tsx +21 -21
- package/templates/frontend/src/components/ui/label.tsx +22 -22
- package/templates/frontend/src/components/ui/separator.tsx +25 -25
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -7
- package/templates/frontend/src/components/ui/switch.tsx +28 -28
- package/templates/frontend/src/components/ui/tabs.tsx +54 -54
- package/templates/frontend/src/components/ui/textarea.tsx +20 -20
- package/templates/frontend/src/components/ui/toast.tsx +109 -109
- package/templates/frontend/src/components/ui/toaster.tsx +30 -30
- package/templates/frontend/src/config/site.ts +197 -197
- package/templates/frontend/src/hooks/use-toast.ts +116 -116
- package/templates/frontend/src/lib/api/auth.ts +39 -39
- package/templates/frontend/src/lib/api/client.ts +66 -66
- package/templates/frontend/src/lib/hooks/use-auth.ts +1 -1
- package/templates/frontend/src/lib/utils.ts +6 -6
- package/templates/frontend/src/providers/auth-provider.tsx +60 -60
- package/templates/frontend/src/types/api.ts +12 -12
- package/templates/frontend/src/types/auth.ts +27 -27
- 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
|
+
);
|