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.
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,51 +1,51 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { InjectModel } from '@nestjs/mongoose';
3
- import { Model } from 'mongoose';
4
- import { User, UserDocument } from './schemas/user.schema';
5
-
6
- @Injectable()
7
- export class UsersRepository {
8
- constructor(
9
- @InjectModel(User.name) private readonly userModel: Model<UserDocument>,
10
- ) {}
11
-
12
- async findByEmail(email: string): Promise<UserDocument | null> {
13
- return this.userModel
14
- .findOne({ email })
15
- .select('+password +refreshToken')
16
- .exec();
17
- }
18
-
19
- async findById(id: string): Promise<UserDocument | null> {
20
- return this.userModel.findById(id).exec();
21
- }
22
-
23
- async findByIdWithSecrets(id: string): Promise<UserDocument | null> {
24
- return this.userModel.findById(id).select('+refreshToken').exec();
25
- }
26
-
27
- async findByVerificationToken(token: string): Promise<UserDocument | null> {
28
- return this.userModel
29
- .findOne({ emailVerificationToken: token })
30
- .select('+emailVerificationToken')
31
- .exec();
32
- }
33
-
34
- async findByPasswordResetToken(token: string): Promise<UserDocument | null> {
35
- return this.userModel
36
- .findOne({ passwordResetToken: token })
37
- .select('+passwordResetToken')
38
- .exec();
39
- }
40
-
41
- async create(data: Partial<User>): Promise<UserDocument> {
42
- const user = new this.userModel(data);
43
- return user.save();
44
- }
45
-
46
- async update(id: string, data: Partial<User>): Promise<UserDocument | null> {
47
- return this.userModel
48
- .findByIdAndUpdate(id, { $set: data }, { returnDocument: 'after' })
49
- .exec();
50
- }
51
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectModel } from '@nestjs/mongoose';
3
+ import { Model } from 'mongoose';
4
+ import { User, UserDocument } from './schemas/user.schema';
5
+
6
+ @Injectable()
7
+ export class UsersRepository {
8
+ constructor(
9
+ @InjectModel(User.name) private readonly userModel: Model<UserDocument>,
10
+ ) {}
11
+
12
+ async findByEmail(email: string): Promise<UserDocument | null> {
13
+ return this.userModel
14
+ .findOne({ email })
15
+ .select('+password +refreshToken')
16
+ .exec();
17
+ }
18
+
19
+ async findById(id: string): Promise<UserDocument | null> {
20
+ return this.userModel.findById(id).exec();
21
+ }
22
+
23
+ async findByIdWithSecrets(id: string): Promise<UserDocument | null> {
24
+ return this.userModel.findById(id).select('+refreshToken').exec();
25
+ }
26
+
27
+ async findByVerificationToken(token: string): Promise<UserDocument | null> {
28
+ return this.userModel
29
+ .findOne({ emailVerificationToken: token })
30
+ .select('+emailVerificationToken')
31
+ .exec();
32
+ }
33
+
34
+ async findByPasswordResetToken(token: string): Promise<UserDocument | null> {
35
+ return this.userModel
36
+ .findOne({ passwordResetToken: token })
37
+ .select('+passwordResetToken')
38
+ .exec();
39
+ }
40
+
41
+ async create(data: Partial<User>): Promise<UserDocument> {
42
+ const user = new this.userModel(data);
43
+ return user.save();
44
+ }
45
+
46
+ async update(id: string, data: Partial<User>): Promise<UserDocument | null> {
47
+ return this.userModel
48
+ .findByIdAndUpdate(id, { $set: data }, { returnDocument: 'after' })
49
+ .exec();
50
+ }
51
+ }
@@ -1,104 +1,104 @@
1
- import { ConflictException, Injectable } from '@nestjs/common';
2
- import * as bcrypt from 'bcryptjs';
3
- import { User, UserDocument, UserRole } from './schemas/user.schema';
4
- import { UsersRepository } from './users.repository';
5
-
6
- @Injectable()
7
- export class UsersService {
8
- constructor(private readonly usersRepository: UsersRepository) {}
9
-
10
- async create(data: {
11
- name: string;
12
- email: string;
13
- password: string;
14
- workspaceId: string;
15
- role?: UserRole;
16
- }): Promise<UserDocument> {
17
- const existing = await this.usersRepository.findByEmail(data.email);
18
- if (existing) {
19
- throw new ConflictException('Este email ya está registrado.');
20
- }
21
- const hashedPassword = await bcrypt.hash(data.password, 12);
22
- return this.usersRepository.create({ ...data, password: hashedPassword });
23
- }
24
-
25
- async findByEmail(email: string): Promise<UserDocument | null> {
26
- return this.usersRepository.findByEmail(email);
27
- }
28
-
29
- async findById(id: string): Promise<UserDocument | null> {
30
- return this.usersRepository.findById(id);
31
- }
32
-
33
- async updateRefreshToken(
34
- userId: string,
35
- refreshToken: string | null,
36
- ): Promise<void> {
37
- const hashed = refreshToken
38
- ? await bcrypt.hash(refreshToken, 10)
39
- : null;
40
- await this.usersRepository.update(userId, { refreshToken: hashed });
41
- }
42
-
43
- async validateRefreshToken(
44
- userId: string,
45
- token: string,
46
- ): Promise<UserDocument | null> {
47
- const user = await this.usersRepository.findByIdWithSecrets(userId);
48
- if (!user?.refreshToken) return null;
49
- const valid = await bcrypt.compare(token, user.refreshToken);
50
- return valid ? user : null;
51
- }
52
-
53
- async setEmailVerificationToken(
54
- userId: string,
55
- token: string,
56
- expiresAt: Date,
57
- ): Promise<void> {
58
- await this.usersRepository.update(userId, {
59
- emailVerificationToken: token,
60
- emailVerificationTokenExpiresAt: expiresAt,
61
- });
62
- }
63
-
64
- async findByVerificationToken(token: string): Promise<UserDocument | null> {
65
- return this.usersRepository.findByVerificationToken(token);
66
- }
67
-
68
- async markEmailVerified(userId: string): Promise<void> {
69
- await this.usersRepository.update(userId, {
70
- emailVerified: true,
71
- emailVerificationToken: null,
72
- emailVerificationTokenExpiresAt: null,
73
- });
74
- }
75
-
76
- async setPasswordResetToken(
77
- userId: string,
78
- token: string,
79
- expiresAt: Date,
80
- ): Promise<void> {
81
- await this.usersRepository.update(userId, {
82
- passwordResetToken: token,
83
- passwordResetTokenExpiresAt: expiresAt,
84
- });
85
- }
86
-
87
- async findByPasswordResetToken(token: string): Promise<UserDocument | null> {
88
- return this.usersRepository.findByPasswordResetToken(token);
89
- }
90
-
91
- async updatePassword(userId: string, password: string): Promise<void> {
92
- const hashed = await bcrypt.hash(password, 12);
93
- await this.usersRepository.update(userId, {
94
- password: hashed,
95
- passwordResetToken: null,
96
- passwordResetTokenExpiresAt: null,
97
- refreshToken: null,
98
- });
99
- }
100
-
101
- async updateLastLoginAt(userId: string): Promise<void> {
102
- await this.usersRepository.update(userId, { lastLoginAt: new Date() });
103
- }
104
- }
1
+ import { ConflictException, Injectable } from '@nestjs/common';
2
+ import * as bcrypt from 'bcryptjs';
3
+ import { User, UserDocument, UserRole } from './schemas/user.schema';
4
+ import { UsersRepository } from './users.repository';
5
+
6
+ @Injectable()
7
+ export class UsersService {
8
+ constructor(private readonly usersRepository: UsersRepository) {}
9
+
10
+ async create(data: {
11
+ name: string;
12
+ email: string;
13
+ password: string;
14
+ workspaceId: string;
15
+ role?: UserRole;
16
+ }): Promise<UserDocument> {
17
+ const existing = await this.usersRepository.findByEmail(data.email);
18
+ if (existing) {
19
+ throw new ConflictException('Este email ya está registrado.');
20
+ }
21
+ const hashedPassword = await bcrypt.hash(data.password, 12);
22
+ return this.usersRepository.create({ ...data, password: hashedPassword });
23
+ }
24
+
25
+ async findByEmail(email: string): Promise<UserDocument | null> {
26
+ return this.usersRepository.findByEmail(email);
27
+ }
28
+
29
+ async findById(id: string): Promise<UserDocument | null> {
30
+ return this.usersRepository.findById(id);
31
+ }
32
+
33
+ async updateRefreshToken(
34
+ userId: string,
35
+ refreshToken: string | null,
36
+ ): Promise<void> {
37
+ const hashed = refreshToken
38
+ ? await bcrypt.hash(refreshToken, 10)
39
+ : null;
40
+ await this.usersRepository.update(userId, { refreshToken: hashed });
41
+ }
42
+
43
+ async validateRefreshToken(
44
+ userId: string,
45
+ token: string,
46
+ ): Promise<UserDocument | null> {
47
+ const user = await this.usersRepository.findByIdWithSecrets(userId);
48
+ if (!user?.refreshToken) return null;
49
+ const valid = await bcrypt.compare(token, user.refreshToken);
50
+ return valid ? user : null;
51
+ }
52
+
53
+ async setEmailVerificationToken(
54
+ userId: string,
55
+ token: string,
56
+ expiresAt: Date,
57
+ ): Promise<void> {
58
+ await this.usersRepository.update(userId, {
59
+ emailVerificationToken: token,
60
+ emailVerificationTokenExpiresAt: expiresAt,
61
+ });
62
+ }
63
+
64
+ async findByVerificationToken(token: string): Promise<UserDocument | null> {
65
+ return this.usersRepository.findByVerificationToken(token);
66
+ }
67
+
68
+ async markEmailVerified(userId: string): Promise<void> {
69
+ await this.usersRepository.update(userId, {
70
+ emailVerified: true,
71
+ emailVerificationToken: null,
72
+ emailVerificationTokenExpiresAt: null,
73
+ });
74
+ }
75
+
76
+ async setPasswordResetToken(
77
+ userId: string,
78
+ token: string,
79
+ expiresAt: Date,
80
+ ): Promise<void> {
81
+ await this.usersRepository.update(userId, {
82
+ passwordResetToken: token,
83
+ passwordResetTokenExpiresAt: expiresAt,
84
+ });
85
+ }
86
+
87
+ async findByPasswordResetToken(token: string): Promise<UserDocument | null> {
88
+ return this.usersRepository.findByPasswordResetToken(token);
89
+ }
90
+
91
+ async updatePassword(userId: string, password: string): Promise<void> {
92
+ const hashed = await bcrypt.hash(password, 12);
93
+ await this.usersRepository.update(userId, {
94
+ password: hashed,
95
+ passwordResetToken: null,
96
+ passwordResetTokenExpiresAt: null,
97
+ refreshToken: null,
98
+ });
99
+ }
100
+
101
+ async updateLastLoginAt(userId: string): Promise<void> {
102
+ await this.usersRepository.update(userId, { lastLoginAt: new Date() });
103
+ }
104
+ }
@@ -1,26 +1,26 @@
1
- import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
2
- import { HydratedDocument } from 'mongoose';
3
-
4
- export type WorkspaceDocument = HydratedDocument<Workspace>;
5
-
6
- @Schema({ collection: 'workspaces', timestamps: true })
7
- export class Workspace {
8
- @Prop({ required: true, trim: true })
9
- name!: string;
10
-
11
- @Prop({ required: true, unique: true, lowercase: true, trim: true })
12
- slug!: string;
13
-
14
- @Prop({ required: true })
15
- ownerId!: string;
16
-
17
- @Prop({ type: String, enum: ['active', 'suspended'], default: 'active' })
18
- status!: 'active' | 'suspended';
19
-
20
- createdAt!: Date;
21
- updatedAt!: Date;
22
- }
23
-
24
- export const WorkspaceSchema = SchemaFactory.createForClass(Workspace);
25
-
26
- WorkspaceSchema.index({ ownerId: 1 });
1
+ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
2
+ import { HydratedDocument } from 'mongoose';
3
+
4
+ export type WorkspaceDocument = HydratedDocument<Workspace>;
5
+
6
+ @Schema({ collection: 'workspaces', timestamps: true })
7
+ export class Workspace {
8
+ @Prop({ required: true, trim: true })
9
+ name!: string;
10
+
11
+ @Prop({ required: true, unique: true, lowercase: true, trim: true })
12
+ slug!: string;
13
+
14
+ @Prop({ required: true })
15
+ ownerId!: string;
16
+
17
+ @Prop({ type: String, enum: ['active', 'suspended'], default: 'active' })
18
+ status!: 'active' | 'suspended';
19
+
20
+ createdAt!: Date;
21
+ updatedAt!: Date;
22
+ }
23
+
24
+ export const WorkspaceSchema = SchemaFactory.createForClass(Workspace);
25
+
26
+ WorkspaceSchema.index({ ownerId: 1 });
@@ -1,16 +1,16 @@
1
- import { Module } from '@nestjs/common';
2
- import { MongooseModule } from '@nestjs/mongoose';
3
- import { Workspace, WorkspaceSchema } from './schemas/workspace.schema';
4
- import { WorkspacesRepository } from './workspaces.repository';
5
- import { WorkspacesService } from './workspaces.service';
6
-
7
- @Module({
8
- imports: [
9
- MongooseModule.forFeature([
10
- { name: Workspace.name, schema: WorkspaceSchema },
11
- ]),
12
- ],
13
- providers: [WorkspacesService, WorkspacesRepository],
14
- exports: [WorkspacesService],
15
- })
16
- export class WorkspacesModule {}
1
+ import { Module } from '@nestjs/common';
2
+ import { MongooseModule } from '@nestjs/mongoose';
3
+ import { Workspace, WorkspaceSchema } from './schemas/workspace.schema';
4
+ import { WorkspacesRepository } from './workspaces.repository';
5
+ import { WorkspacesService } from './workspaces.service';
6
+
7
+ @Module({
8
+ imports: [
9
+ MongooseModule.forFeature([
10
+ { name: Workspace.name, schema: WorkspaceSchema },
11
+ ]),
12
+ ],
13
+ providers: [WorkspacesService, WorkspacesRepository],
14
+ exports: [WorkspacesService],
15
+ })
16
+ export class WorkspacesModule {}
@@ -1,34 +1,34 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { InjectModel } from '@nestjs/mongoose';
3
- import { Model } from 'mongoose';
4
- import { Workspace, WorkspaceDocument } from './schemas/workspace.schema';
5
-
6
- @Injectable()
7
- export class WorkspacesRepository {
8
- constructor(
9
- @InjectModel(Workspace.name)
10
- private readonly workspaceModel: Model<WorkspaceDocument>,
11
- ) {}
12
-
13
- async findById(id: string): Promise<WorkspaceDocument | null> {
14
- return this.workspaceModel.findById(id).exec();
15
- }
16
-
17
- async findBySlug(slug: string): Promise<WorkspaceDocument | null> {
18
- return this.workspaceModel.findOne({ slug }).exec();
19
- }
20
-
21
- async create(data: Partial<Workspace>): Promise<WorkspaceDocument> {
22
- const workspace = new this.workspaceModel(data);
23
- return workspace.save();
24
- }
25
-
26
- async update(
27
- id: string,
28
- data: Partial<Workspace>,
29
- ): Promise<WorkspaceDocument | null> {
30
- return this.workspaceModel
31
- .findByIdAndUpdate(id, { $set: data }, { returnDocument: 'after' })
32
- .exec();
33
- }
34
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectModel } from '@nestjs/mongoose';
3
+ import { Model } from 'mongoose';
4
+ import { Workspace, WorkspaceDocument } from './schemas/workspace.schema';
5
+
6
+ @Injectable()
7
+ export class WorkspacesRepository {
8
+ constructor(
9
+ @InjectModel(Workspace.name)
10
+ private readonly workspaceModel: Model<WorkspaceDocument>,
11
+ ) {}
12
+
13
+ async findById(id: string): Promise<WorkspaceDocument | null> {
14
+ return this.workspaceModel.findById(id).exec();
15
+ }
16
+
17
+ async findBySlug(slug: string): Promise<WorkspaceDocument | null> {
18
+ return this.workspaceModel.findOne({ slug }).exec();
19
+ }
20
+
21
+ async create(data: Partial<Workspace>): Promise<WorkspaceDocument> {
22
+ const workspace = new this.workspaceModel(data);
23
+ return workspace.save();
24
+ }
25
+
26
+ async update(
27
+ id: string,
28
+ data: Partial<Workspace>,
29
+ ): Promise<WorkspaceDocument | null> {
30
+ return this.workspaceModel
31
+ .findByIdAndUpdate(id, { $set: data }, { returnDocument: 'after' })
32
+ .exec();
33
+ }
34
+ }
@@ -1,42 +1,42 @@
1
- import { Injectable } from '@nestjs/common';
2
- import { Workspace, WorkspaceDocument } from './schemas/workspace.schema';
3
- import { WorkspacesRepository } from './workspaces.repository';
4
-
5
- @Injectable()
6
- export class WorkspacesService {
7
- constructor(
8
- private readonly workspacesRepository: WorkspacesRepository,
9
- ) {}
10
-
11
- async create(ownerId: string, name: string): Promise<WorkspaceDocument> {
12
- const slug = await this.generateUniqueSlug(name);
13
- return this.workspacesRepository.create({ name, slug, ownerId });
14
- }
15
-
16
- async update(
17
- id: string,
18
- data: Partial<Workspace>,
19
- ): Promise<WorkspaceDocument | null> {
20
- return this.workspacesRepository.update(id, data);
21
- }
22
-
23
- async findById(id: string): Promise<WorkspaceDocument | null> {
24
- return this.workspacesRepository.findById(id);
25
- }
26
-
27
- private async generateUniqueSlug(name: string): Promise<string> {
28
- const base = name
29
- .toLowerCase()
30
- .normalize('NFD')
31
- .replace(/\p{M}/gu, '')
32
- .replace(/[^a-z0-9]+/g, '-')
33
- .replace(/^-+|-+$/g, '');
34
-
35
- let slug = base || 'workspace';
36
- let counter = 1;
37
- while (await this.workspacesRepository.findBySlug(slug)) {
38
- slug = `${base}-${counter++}`;
39
- }
40
- return slug;
41
- }
42
- }
1
+ import { Injectable } from '@nestjs/common';
2
+ import { Workspace, WorkspaceDocument } from './schemas/workspace.schema';
3
+ import { WorkspacesRepository } from './workspaces.repository';
4
+
5
+ @Injectable()
6
+ export class WorkspacesService {
7
+ constructor(
8
+ private readonly workspacesRepository: WorkspacesRepository,
9
+ ) {}
10
+
11
+ async create(ownerId: string, name: string): Promise<WorkspaceDocument> {
12
+ const slug = await this.generateUniqueSlug(name);
13
+ return this.workspacesRepository.create({ name, slug, ownerId });
14
+ }
15
+
16
+ async update(
17
+ id: string,
18
+ data: Partial<Workspace>,
19
+ ): Promise<WorkspaceDocument | null> {
20
+ return this.workspacesRepository.update(id, data);
21
+ }
22
+
23
+ async findById(id: string): Promise<WorkspaceDocument | null> {
24
+ return this.workspacesRepository.findById(id);
25
+ }
26
+
27
+ private async generateUniqueSlug(name: string): Promise<string> {
28
+ const base = name
29
+ .toLowerCase()
30
+ .normalize('NFD')
31
+ .replace(/\p{M}/gu, '')
32
+ .replace(/[^a-z0-9]+/g, '-')
33
+ .replace(/^-+|-+$/g, '');
34
+
35
+ let slug = base || 'workspace';
36
+ let counter = 1;
37
+ while (await this.workspacesRepository.findBySlug(slug)) {
38
+ slug = `${base}-${counter++}`;
39
+ }
40
+ return slug;
41
+ }
42
+ }
@@ -1,25 +1,25 @@
1
- import { Test, TestingModule } from '@nestjs/testing';
2
- import { INestApplication } from '@nestjs/common';
3
- import request from 'supertest';
4
- import { App } from 'supertest/types';
5
- import { AppModule } from './../src/app.module';
6
-
7
- describe('AppController (e2e)', () => {
8
- let app: INestApplication<App>;
9
-
10
- beforeEach(async () => {
11
- const moduleFixture: TestingModule = await Test.createTestingModule({
12
- imports: [AppModule],
13
- }).compile();
14
-
15
- app = moduleFixture.createNestApplication();
16
- await app.init();
17
- });
18
-
19
- it('/ (GET)', () => {
20
- return request(app.getHttpServer())
21
- .get('/')
22
- .expect(200)
23
- .expect('Hello World!');
24
- });
25
- });
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { INestApplication } from '@nestjs/common';
3
+ import request from 'supertest';
4
+ import { App } from 'supertest/types';
5
+ import { AppModule } from './../src/app.module';
6
+
7
+ describe('AppController (e2e)', () => {
8
+ let app: INestApplication<App>;
9
+
10
+ beforeEach(async () => {
11
+ const moduleFixture: TestingModule = await Test.createTestingModule({
12
+ imports: [AppModule],
13
+ }).compile();
14
+
15
+ app = moduleFixture.createNestApplication();
16
+ await app.init();
17
+ });
18
+
19
+ it('/ (GET)', () => {
20
+ return request(app.getHttpServer())
21
+ .get('/')
22
+ .expect(200)
23
+ .expect('Hello World!');
24
+ });
25
+ });
@@ -1,9 +1,9 @@
1
- {
2
- "moduleFileExtensions": ["js", "json", "ts"],
3
- "rootDir": ".",
4
- "testEnvironment": "node",
5
- "testRegex": ".e2e-spec.ts$",
6
- "transform": {
7
- "^.+\\.(t|j)s$": "ts-jest"
8
- }
9
- }
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
@@ -1,4 +1,4 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
- }
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }