@kuldi/create-nestjs 1.0.0 → 1.1.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.
- package/README.md +27 -0
- package/package.json +13 -6
- package/src/cli.js +139 -0
- package/template/.editorconfig +12 -0
- package/template/.env.example +23 -0
- package/template/.eslintrc.js +25 -0
- package/template/.prettierrc +8 -0
- package/template/README.md +133 -0
- package/template/nest-cli.json +10 -0
- package/template/package-lock.json +11539 -0
- package/template/package.json +99 -0
- package/template/prisma/migrations/20260625045841_init/migration.sql +73 -0
- package/template/prisma/migrations/migration_lock.toml +3 -0
- package/template/prisma/schema.prisma +75 -0
- package/template/prisma/seeder/data/permission.seed.ts +67 -0
- package/template/prisma/seeder/data/position.seed.ts +21 -0
- package/template/prisma/seeder/data/user.seed.ts +39 -0
- package/template/prisma/seeder/index.ts +56 -0
- package/template/prisma.config.ts +8 -0
- package/template/src/app.module.ts +68 -0
- package/template/src/common/constants/permissions.constant.ts +27 -0
- package/template/src/common/decorators/api-response.decorator.ts +44 -0
- package/template/src/common/decorators/get-user.decorator.ts +11 -0
- package/template/src/common/decorators/permissions.decorator.ts +5 -0
- package/template/src/common/decorators/public.decorator.ts +4 -0
- package/template/src/common/dto/api-response.dto.ts +15 -0
- package/template/src/common/dto/pagination.dto.ts +33 -0
- package/template/src/common/filters/http-exception.filter.ts +54 -0
- package/template/src/common/guards/jwt-auth.guard.ts +32 -0
- package/template/src/common/guards/permissions.guard.ts +53 -0
- package/template/src/common/helpers/function/error-helper.ts +35 -0
- package/template/src/common/interceptors/logging.interceptor.ts +37 -0
- package/template/src/common/interceptors/transform.interceptor.ts +53 -0
- package/template/src/common/prisma/prisma.module.ts +9 -0
- package/template/src/common/prisma/prisma.service.ts +52 -0
- package/template/src/common/utils/password.util.ts +13 -0
- package/template/src/config/app.config.ts +10 -0
- package/template/src/config/database.config.ts +5 -0
- package/template/src/config/env.validation.ts +30 -0
- package/template/src/config/jwt.config.ts +8 -0
- package/template/src/config/swagger.config.ts +6 -0
- package/template/src/main.ts +84 -0
- package/template/src/modules/auth/auth.module.ts +28 -0
- package/template/src/modules/auth/auth.service.ts +173 -0
- package/template/src/modules/auth/controllers/v1/auth.controller.ts +71 -0
- package/template/src/modules/auth/core/dto/auth-response.dto.ts +19 -0
- package/template/src/modules/auth/core/dto/login-response.dto.ts +10 -0
- package/template/src/modules/auth/core/dto/login.dto.ts +15 -0
- package/template/src/modules/auth/core/dto/register.dto.ts +30 -0
- package/template/src/modules/auth/core/interfaces/jwt-payload.interface.ts +7 -0
- package/template/src/modules/auth/core/strategies/jwt.strategy.ts +59 -0
- package/template/src/modules/health/health.controller.ts +29 -0
- package/template/src/modules/health/health.module.ts +7 -0
- package/template/src/modules/users/controllers/v1/users.controller.ts +120 -0
- package/template/src/modules/users/core/dto/change-position.dto.ts +9 -0
- package/template/src/modules/users/core/dto/create-user.dto.ts +35 -0
- package/template/src/modules/users/core/dto/manage-permissions.dto.ts +13 -0
- package/template/src/modules/users/core/dto/update-user.dto.ts +30 -0
- package/template/src/modules/users/core/dto/user-query.dto.ts +22 -0
- package/template/src/modules/users/core/dto/user-response.dto.ts +32 -0
- package/template/src/modules/users/core/entities/user.entity.ts +45 -0
- package/template/src/modules/users/core/helpers/user-transform.helper.ts +31 -0
- package/template/src/modules/users/users.module.ts +10 -0
- package/template/src/modules/users/users.service.ts +344 -0
- package/template/test/app.e2e-spec.ts +40 -0
- package/template/test/jest-e2e.json +9 -0
- package/template/tsconfig.json +30 -0
- package/bin/cli.js +0 -71
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { Exclude } from 'class-transformer';
|
|
3
|
+
|
|
4
|
+
export class UserResponseDto {
|
|
5
|
+
@ApiProperty()
|
|
6
|
+
id: number;
|
|
7
|
+
|
|
8
|
+
@ApiProperty()
|
|
9
|
+
email: string;
|
|
10
|
+
|
|
11
|
+
@ApiProperty({ required: false })
|
|
12
|
+
first_name?: string;
|
|
13
|
+
|
|
14
|
+
@ApiProperty({ required: false })
|
|
15
|
+
last_name?: string;
|
|
16
|
+
|
|
17
|
+
@ApiProperty()
|
|
18
|
+
is_active: boolean;
|
|
19
|
+
|
|
20
|
+
@ApiProperty()
|
|
21
|
+
created_at: Date;
|
|
22
|
+
|
|
23
|
+
@ApiProperty()
|
|
24
|
+
updated_at: Date;
|
|
25
|
+
|
|
26
|
+
@Exclude()
|
|
27
|
+
password: string;
|
|
28
|
+
|
|
29
|
+
constructor(partial: Partial<UserResponseDto>) {
|
|
30
|
+
Object.assign(this, partial);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
2
|
+
import { User as PrismaUser, Position } from '@prisma/client';
|
|
3
|
+
import { Exclude } from 'class-transformer';
|
|
4
|
+
|
|
5
|
+
export class UserEntity implements Partial<PrismaUser> {
|
|
6
|
+
@ApiProperty()
|
|
7
|
+
id: number;
|
|
8
|
+
|
|
9
|
+
@ApiProperty()
|
|
10
|
+
email: string;
|
|
11
|
+
|
|
12
|
+
@Exclude()
|
|
13
|
+
password: string;
|
|
14
|
+
|
|
15
|
+
@ApiProperty()
|
|
16
|
+
first_name: string;
|
|
17
|
+
|
|
18
|
+
@ApiProperty()
|
|
19
|
+
last_name: string;
|
|
20
|
+
|
|
21
|
+
@ApiProperty()
|
|
22
|
+
is_active: boolean;
|
|
23
|
+
|
|
24
|
+
@ApiProperty()
|
|
25
|
+
position_id: number;
|
|
26
|
+
|
|
27
|
+
@ApiPropertyOptional()
|
|
28
|
+
position?: Partial<Position>;
|
|
29
|
+
|
|
30
|
+
@ApiPropertyOptional()
|
|
31
|
+
permissions?: string[];
|
|
32
|
+
|
|
33
|
+
@ApiProperty()
|
|
34
|
+
created_at: Date;
|
|
35
|
+
|
|
36
|
+
@ApiProperty()
|
|
37
|
+
updated_at: Date;
|
|
38
|
+
|
|
39
|
+
@ApiPropertyOptional()
|
|
40
|
+
deleted_at: Date | null;
|
|
41
|
+
|
|
42
|
+
constructor(partial: Partial<UserEntity>) {
|
|
43
|
+
Object.assign(this, partial);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { User, Position, PositionPermission, Permission } from '@prisma/client';
|
|
2
|
+
import { UserEntity } from '../entities/user.entity';
|
|
3
|
+
type UserWithRelations = User & {
|
|
4
|
+
position?: Position & {
|
|
5
|
+
position_permissions?: (PositionPermission & {
|
|
6
|
+
permission: Permission;
|
|
7
|
+
})[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export class UserTransformHelper {
|
|
12
|
+
static toEntity(user: UserWithRelations): UserEntity {
|
|
13
|
+
const permissions = user.position?.position_permissions?.map(
|
|
14
|
+
(pp) => pp.permission.name,
|
|
15
|
+
) || [];
|
|
16
|
+
|
|
17
|
+
return new UserEntity({
|
|
18
|
+
...user,
|
|
19
|
+
permissions,
|
|
20
|
+
position: user.position ? {
|
|
21
|
+
id: user.position.id,
|
|
22
|
+
name: user.position.name,
|
|
23
|
+
description: user.position.description,
|
|
24
|
+
} : undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static toEntities(users: UserWithRelations[]): UserEntity[] {
|
|
29
|
+
return users.map((user) => this.toEntity(user));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { UsersService } from './users.service';
|
|
3
|
+
import { UsersController } from './controllers/v1/users.controller';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
controllers: [UsersController],
|
|
7
|
+
providers: [UsersService],
|
|
8
|
+
exports: [UsersService],
|
|
9
|
+
})
|
|
10
|
+
export class UsersModule {}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
NotFoundException,
|
|
4
|
+
ConflictException,
|
|
5
|
+
BadRequestException,
|
|
6
|
+
} from '@nestjs/common';
|
|
7
|
+
import { PrismaService } from '@common/prisma/prisma.service';
|
|
8
|
+
import { PasswordUtil } from '@common/utils/password.util';
|
|
9
|
+
import { CreateUserDto } from './core/dto/create-user.dto';
|
|
10
|
+
import { UpdateUserDto } from './core/dto/update-user.dto';
|
|
11
|
+
import { ChangePositionDto } from './core/dto/change-position.dto';
|
|
12
|
+
import { ManagePermissionsDto } from './core/dto/manage-permissions.dto';
|
|
13
|
+
import { UserQueryDto } from './core/dto/user-query.dto';
|
|
14
|
+
import { UserEntity } from './core/entities/user.entity';
|
|
15
|
+
import { UserTransformHelper } from './core/helpers/user-transform.helper';
|
|
16
|
+
import { PaginatedResponseDto } from '@common/dto/pagination.dto';
|
|
17
|
+
|
|
18
|
+
@Injectable()
|
|
19
|
+
export class UsersService {
|
|
20
|
+
constructor(private prisma: PrismaService) {}
|
|
21
|
+
|
|
22
|
+
async create(createUserDto: CreateUserDto): Promise<UserEntity> {
|
|
23
|
+
const { email, password, position_id, ...userData } = createUserDto;
|
|
24
|
+
|
|
25
|
+
// Check if email already exists
|
|
26
|
+
const existingUser = await this.prisma.user.findUnique({
|
|
27
|
+
where: { email },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (existingUser) {
|
|
31
|
+
throw new ConflictException('Email already exists');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate position exists
|
|
35
|
+
const position = await this.prisma.position.findUnique({
|
|
36
|
+
where: { id: position_id },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!position) {
|
|
40
|
+
throw new BadRequestException('Invalid position ID');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Hash password
|
|
44
|
+
const hashedPassword = await PasswordUtil.hash(password);
|
|
45
|
+
|
|
46
|
+
// Create user
|
|
47
|
+
const user = await this.prisma.user.create({
|
|
48
|
+
data: {
|
|
49
|
+
email,
|
|
50
|
+
password: hashedPassword,
|
|
51
|
+
position_id,
|
|
52
|
+
...userData,
|
|
53
|
+
},
|
|
54
|
+
include: {
|
|
55
|
+
position: {
|
|
56
|
+
include: {
|
|
57
|
+
position_permissions: {
|
|
58
|
+
include: {
|
|
59
|
+
permission: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return UserTransformHelper.toEntity(user);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async findAll(query: UserQueryDto): Promise<PaginatedResponseDto<UserEntity>> {
|
|
71
|
+
const { page = 1, limit = 10, search, is_active, position_id } = query;
|
|
72
|
+
const skip = (page - 1) * limit;
|
|
73
|
+
|
|
74
|
+
const where: any = {
|
|
75
|
+
deleted_at: null,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (search) {
|
|
79
|
+
where.OR = [
|
|
80
|
+
{ first_name: { contains: search, mode: 'insensitive' } },
|
|
81
|
+
{ last_name: { contains: search, mode: 'insensitive' } },
|
|
82
|
+
{ email: { contains: search, mode: 'insensitive' } },
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (is_active !== undefined) {
|
|
87
|
+
where.is_active = is_active;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (position_id) {
|
|
91
|
+
where.position_id = position_id;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const [users, total] = await Promise.all([
|
|
95
|
+
this.prisma.user.findMany({
|
|
96
|
+
where,
|
|
97
|
+
skip,
|
|
98
|
+
take: limit,
|
|
99
|
+
include: {
|
|
100
|
+
position: {
|
|
101
|
+
include: {
|
|
102
|
+
position_permissions: {
|
|
103
|
+
include: {
|
|
104
|
+
permission: true,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
orderBy: {
|
|
111
|
+
created_at: 'desc',
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
this.prisma.user.count({ where }),
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
if (total === 0) {
|
|
118
|
+
throw new NotFoundException('No users found');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
data: UserTransformHelper.toEntities(users),
|
|
123
|
+
meta: {
|
|
124
|
+
total,
|
|
125
|
+
page,
|
|
126
|
+
limit,
|
|
127
|
+
totalPages: Math.ceil(total / limit),
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async findOne(id: number): Promise<UserEntity> {
|
|
133
|
+
const user = await this.prisma.user.findFirst({
|
|
134
|
+
where: { id, deleted_at: null },
|
|
135
|
+
include: {
|
|
136
|
+
position: {
|
|
137
|
+
include: {
|
|
138
|
+
position_permissions: {
|
|
139
|
+
include: {
|
|
140
|
+
permission: true,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (!user) {
|
|
149
|
+
throw new NotFoundException(`User with ID ${id} not found`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return UserTransformHelper.toEntity(user);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async update(id: number, updateUserDto: UpdateUserDto): Promise<UserEntity> {
|
|
156
|
+
const user = await this.prisma.user.findFirst({
|
|
157
|
+
where: { id, deleted_at: null },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!user) {
|
|
161
|
+
throw new NotFoundException(`User with ID ${id} not found`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { email, password, ...updateData } = updateUserDto;
|
|
165
|
+
|
|
166
|
+
// Check email uniqueness if changing email
|
|
167
|
+
if (email && email !== user.email) {
|
|
168
|
+
const existingUser = await this.prisma.user.findUnique({
|
|
169
|
+
where: { email },
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (existingUser) {
|
|
173
|
+
throw new ConflictException('Email already exists');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Hash password if provided
|
|
178
|
+
let hashedPassword: string | undefined;
|
|
179
|
+
if (password) {
|
|
180
|
+
hashedPassword = await PasswordUtil.hash(password);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const updatedUser = await this.prisma.user.update({
|
|
184
|
+
where: { id },
|
|
185
|
+
data: {
|
|
186
|
+
...updateData,
|
|
187
|
+
...(email && { email }),
|
|
188
|
+
...(hashedPassword && { password: hashedPassword }),
|
|
189
|
+
},
|
|
190
|
+
include: {
|
|
191
|
+
position: {
|
|
192
|
+
include: {
|
|
193
|
+
position_permissions: {
|
|
194
|
+
include: {
|
|
195
|
+
permission: true,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return UserTransformHelper.toEntity(updatedUser);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async remove(id: number): Promise<void> {
|
|
207
|
+
const user = await this.prisma.user.findFirst({
|
|
208
|
+
where: { id, deleted_at: null },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (!user) {
|
|
212
|
+
throw new NotFoundException(`User with ID ${id} not found`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Soft delete
|
|
216
|
+
await this.prisma.user.update({
|
|
217
|
+
where: { id },
|
|
218
|
+
data: { deleted_at: new Date() },
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async changePosition(id: number, changePositionDto: ChangePositionDto): Promise<UserEntity> {
|
|
223
|
+
const user = await this.prisma.user.findFirst({
|
|
224
|
+
where: { id, deleted_at: null },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!user) {
|
|
228
|
+
throw new NotFoundException(`User with ID ${id} not found`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Validate position exists
|
|
232
|
+
const position = await this.prisma.position.findUnique({
|
|
233
|
+
where: { id: changePositionDto.position_id },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (!position) {
|
|
237
|
+
throw new BadRequestException('Invalid position ID');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const updatedUser = await this.prisma.user.update({
|
|
241
|
+
where: { id },
|
|
242
|
+
data: { position_id: changePositionDto.position_id },
|
|
243
|
+
include: {
|
|
244
|
+
position: {
|
|
245
|
+
include: {
|
|
246
|
+
position_permissions: {
|
|
247
|
+
include: {
|
|
248
|
+
permission: true,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return UserTransformHelper.toEntity(updatedUser);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async assignPermissions(
|
|
260
|
+
userId: number,
|
|
261
|
+
managePermissionsDto: ManagePermissionsDto,
|
|
262
|
+
): Promise<UserEntity> {
|
|
263
|
+
const user = await this.prisma.user.findFirst({
|
|
264
|
+
where: { id: userId, deleted_at: null },
|
|
265
|
+
include: { position: true },
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (!user) {
|
|
269
|
+
throw new NotFoundException(`User with ID ${userId} not found`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Validate all permissions exist
|
|
273
|
+
const permissions = await this.prisma.permission.findMany({
|
|
274
|
+
where: { name: { in: managePermissionsDto.permissions } },
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (permissions.length !== managePermissionsDto.permissions.length) {
|
|
278
|
+
throw new BadRequestException('One or more permissions are invalid');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Get current permissions for the position
|
|
282
|
+
const currentPermissions = await this.prisma.positionPermission.findMany({
|
|
283
|
+
where: { position_id: user.position_id },
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const currentPermissionIds = currentPermissions.map((pp) => pp.permission_id);
|
|
287
|
+
const newPermissionIds = permissions.map((p) => p.id);
|
|
288
|
+
|
|
289
|
+
// Find permissions to add
|
|
290
|
+
const permissionsToAdd = newPermissionIds.filter(
|
|
291
|
+
(id) => !currentPermissionIds.includes(id),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// Add new permissions
|
|
295
|
+
if (permissionsToAdd.length > 0) {
|
|
296
|
+
await this.prisma.positionPermission.createMany({
|
|
297
|
+
data: permissionsToAdd.map((permission_id) => ({
|
|
298
|
+
position_id: user.position_id,
|
|
299
|
+
permission_id,
|
|
300
|
+
})),
|
|
301
|
+
skipDuplicates: true,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Return updated user
|
|
306
|
+
return this.findOne(userId);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async revokePermissions(
|
|
310
|
+
userId: number,
|
|
311
|
+
managePermissionsDto: ManagePermissionsDto,
|
|
312
|
+
): Promise<UserEntity> {
|
|
313
|
+
const user = await this.prisma.user.findFirst({
|
|
314
|
+
where: { id: userId, deleted_at: null },
|
|
315
|
+
include: { position: true },
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
if (!user) {
|
|
319
|
+
throw new NotFoundException(`User with ID ${userId} not found`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Validate all permissions exist
|
|
323
|
+
const permissions = await this.prisma.permission.findMany({
|
|
324
|
+
where: { name: { in: managePermissionsDto.permissions } },
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
if (permissions.length !== managePermissionsDto.permissions.length) {
|
|
328
|
+
throw new BadRequestException('One or more permissions are invalid');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const permissionIds = permissions.map((p) => p.id);
|
|
332
|
+
|
|
333
|
+
// Remove permissions
|
|
334
|
+
await this.prisma.positionPermission.deleteMany({
|
|
335
|
+
where: {
|
|
336
|
+
position_id: user.position_id,
|
|
337
|
+
permission_id: { in: permissionIds },
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Return updated user
|
|
342
|
+
return this.findOne(userId);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
2
|
+
import { INestApplication } from '@nestjs/common';
|
|
3
|
+
import * as request from 'supertest';
|
|
4
|
+
import { AppModule } from './../src/app.module';
|
|
5
|
+
|
|
6
|
+
describe('AppController (e2e)', () => {
|
|
7
|
+
let app: INestApplication;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
11
|
+
imports: [AppModule],
|
|
12
|
+
}).compile();
|
|
13
|
+
|
|
14
|
+
app = moduleFixture.createNestApplication();
|
|
15
|
+
await app.init();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('/health (GET)', () => {
|
|
19
|
+
return request(app.getHttpServer())
|
|
20
|
+
.get('/health')
|
|
21
|
+
.expect(200)
|
|
22
|
+
.expect((res) => {
|
|
23
|
+
expect(res.body).toHaveProperty('status', 'ok');
|
|
24
|
+
expect(res.body).toHaveProperty('timestamp');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('/ping (GET)', () => {
|
|
29
|
+
return request(app.getHttpServer())
|
|
30
|
+
.get('/ping')
|
|
31
|
+
.expect(200)
|
|
32
|
+
.expect((res) => {
|
|
33
|
+
expect(res.body).toHaveProperty('message', 'pong');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await app.close();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2021",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"baseUrl": "./",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strictNullChecks": false,
|
|
17
|
+
"noImplicitAny": false,
|
|
18
|
+
"strictBindCallApply": false,
|
|
19
|
+
"forceConsistentCasingInFileNames": false,
|
|
20
|
+
"noFallthroughCasesInSwitch": false,
|
|
21
|
+
"paths": {
|
|
22
|
+
"@common/*": ["src/common/*"],
|
|
23
|
+
"@config/*": ["src/config/*"],
|
|
24
|
+
"@modules/*": ["src/modules/*"]
|
|
25
|
+
},
|
|
26
|
+
"ignoreDeprecations": "6.0"
|
|
27
|
+
},
|
|
28
|
+
"include": ["src/**/*"],
|
|
29
|
+
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
|
30
|
+
}
|
package/bin/cli.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
|
|
7
|
-
const projectName = process.argv[2];
|
|
8
|
-
|
|
9
|
-
if (!projectName) {
|
|
10
|
-
console.error('\n❌ Tolong masukkan nama project!');
|
|
11
|
-
console.error('Contoh: npm create @kuldi/nestjs my-app');
|
|
12
|
-
console.error('Atau gunakan "." untuk install di folder saat ini: npm create @kuldi/nestjs .\n');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Menangani jika user menggunakan titik (.) untuk current directory
|
|
17
|
-
const currentPath = process.cwd();
|
|
18
|
-
const projectPath = projectName === '.' ? currentPath : path.join(currentPath, projectName);
|
|
19
|
-
const gitRepo = 'https://github.com/bhagaskuro/boilerplate-nestJs.git';
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
// Mengecek apakah target directory kosong (jika menggunakan .)
|
|
23
|
-
if (projectName === '.') {
|
|
24
|
-
const files = fs.readdirSync(currentPath);
|
|
25
|
-
if (files.length > 0) {
|
|
26
|
-
console.error('\n❌ Folder saat ini tidak kosong! Harap jalankan di folder kosong agar tidak menimpa file yang ada.\n');
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log(`\n🚀 Mengunduh Kuli Digital NestJS Boilerplate ke ${projectPath}...`);
|
|
32
|
-
|
|
33
|
-
// 1. Clone repository
|
|
34
|
-
// Gunakan git clone ke folder sementara jika current dir agar tidak tabrakan,
|
|
35
|
-
// tapi berhubung git clone butuh folder kosong, aman.
|
|
36
|
-
execSync(`git clone --depth 1 ${gitRepo} "${projectPath}"`, { stdio: 'inherit' });
|
|
37
|
-
|
|
38
|
-
// 2. Masuk ke folder project
|
|
39
|
-
process.chdir(projectPath);
|
|
40
|
-
|
|
41
|
-
// 3. Hapus folder .git bawaan boilerplate
|
|
42
|
-
fs.rmSync(path.join(projectPath, '.git'), { recursive: true, force: true });
|
|
43
|
-
|
|
44
|
-
// Hapus juga folder create-nestjs agar tidak mengotori project user akhir
|
|
45
|
-
const cliFolder = path.join(projectPath, 'create-nestjs');
|
|
46
|
-
if (fs.existsSync(cliFolder)) {
|
|
47
|
-
fs.rmSync(cliFolder, { recursive: true, force: true });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Inisialisasi ulang git baru
|
|
51
|
-
execSync('git init', { stdio: 'ignore' });
|
|
52
|
-
|
|
53
|
-
// 4. Install dependencies
|
|
54
|
-
console.log('\n📦 Meng-install dependencies (ini butuh waktu beberapa menit)...');
|
|
55
|
-
execSync('npm install', { stdio: 'inherit' });
|
|
56
|
-
|
|
57
|
-
console.log('\n✅ Project berhasil dibuat!');
|
|
58
|
-
|
|
59
|
-
if (projectName !== '.') {
|
|
60
|
-
console.log(`\nLangkah selanjutnya:`);
|
|
61
|
-
console.log(` cd ${projectName}`);
|
|
62
|
-
console.log(` npm run start:dev\n`);
|
|
63
|
-
} else {
|
|
64
|
-
console.log(`\nLangkah selanjutnya:`);
|
|
65
|
-
console.log(` npm run start:dev\n`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('\n❌ Gagal membuat project:', error.message);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|