lapeh 2.3.7 → 2.3.9

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.
@@ -0,0 +1,194 @@
1
+ import { Request, Response } from "express";
2
+ import { prisma } from "@lapeh/core/database";
3
+ import { sendSuccess, sendError, sendFastSuccess } from "@lapeh/utils/response";
4
+ import { getPagination, buildPaginationMeta } from "@lapeh/utils/pagination";
5
+ import { Validator } from "@lapeh/utils/validator";
6
+ import {
7
+ getSerializer,
8
+ createResponseSchema,
9
+ createPaginatedResponseSchema,
10
+ } from "@lapeh/core/serializer";
11
+
12
+ // 1. Definisikan Schema Output untuk performa tinggi
13
+ const petSchema = {
14
+ type: "object",
15
+ properties: {
16
+ id: { type: "string" }, // BigInt dikonversi ke string
17
+ name: { type: "string" },
18
+ species: { type: "string" },
19
+ age: { type: "integer" },
20
+ created_at: { type: "string", format: "date-time" },
21
+ updated_at: { type: "string", format: "date-time" },
22
+ },
23
+ };
24
+
25
+ // 2. Compile Serializer
26
+ // Untuk Single Item
27
+ const petSerializer = getSerializer(
28
+ "pet-single",
29
+ createResponseSchema(petSchema)
30
+ );
31
+
32
+ // Untuk List Item (Paginated)
33
+ const petListSerializer = getSerializer(
34
+ "pet-list",
35
+ createPaginatedResponseSchema(petSchema)
36
+ );
37
+
38
+ export async function index(req: Request, res: Response) {
39
+ const { page, perPage, skip, take } = getPagination(req.query);
40
+ const search = req.query.search as string;
41
+
42
+ const where: any = {};
43
+ if (search) {
44
+ where.OR = [
45
+ { name: { contains: search, mode: "insensitive" } },
46
+ { species: { contains: search, mode: "insensitive" } },
47
+ ];
48
+ }
49
+
50
+ const [data, total] = await Promise.all([
51
+ prisma.pets.findMany({
52
+ where,
53
+ skip,
54
+ take,
55
+ orderBy: { created_at: "desc" },
56
+ }),
57
+ prisma.pets.count({ where }),
58
+ ]);
59
+
60
+ // Kita perlu convert BigInt ke string sebelum masuk serializer
61
+ // Karena fast-json-stringify mengharapkan tipe data yang sesuai dengan schema
62
+ const serialized = data.map((item: any) => ({
63
+ ...item,
64
+ id: item.id.toString(),
65
+ }));
66
+
67
+ const meta = buildPaginationMeta(page, perPage, total);
68
+
69
+ // Gunakan sendFastSuccess untuk performa maksimal
70
+ // Struktur data disesuaikan dengan createPaginatedResponseSchema: { data: [], meta: {} }
71
+ sendFastSuccess(res, 200, petListSerializer, {
72
+ status: "success",
73
+ message: "Pets retrieved successfully",
74
+ data: {
75
+ data: serialized,
76
+ meta,
77
+ },
78
+ });
79
+ }
80
+
81
+ export async function show(req: Request, res: Response) {
82
+ const { id } = req.params;
83
+ const pet = await prisma.pets.findUnique({
84
+ where: { id: BigInt(id) },
85
+ });
86
+
87
+ if (!pet) {
88
+ sendError(res, 404, "Pet not found");
89
+ return;
90
+ }
91
+
92
+ // Gunakan sendFastSuccess
93
+ sendFastSuccess(res, 200, petSerializer, {
94
+ status: "success",
95
+ message: "Pet retrieved successfully",
96
+ data: {
97
+ ...pet,
98
+ id: pet.id.toString(),
99
+ },
100
+ });
101
+ }
102
+
103
+ export async function store(req: Request, res: Response) {
104
+ const validator = Validator.make(req.body || {}, {
105
+ name: "required|string",
106
+ species: "required|string",
107
+ age: "required|integer|min:1",
108
+ });
109
+
110
+ if (await validator.fails()) {
111
+ sendError(res, 422, "Validation error", validator.errors());
112
+ return;
113
+ }
114
+
115
+ const validatedData = await validator.validated();
116
+ const pet = await prisma.pets.create({
117
+ data: {
118
+ ...validatedData,
119
+ created_at: new Date(),
120
+ updated_at: new Date(),
121
+ },
122
+ });
123
+
124
+ // Gunakan sendFastSuccess
125
+ sendFastSuccess(res, 201, petSerializer, {
126
+ status: "success",
127
+ message: "Pet created successfully",
128
+ data: {
129
+ ...pet,
130
+ id: pet.id.toString(),
131
+ },
132
+ });
133
+ }
134
+
135
+ export async function update(req: Request, res: Response) {
136
+ const { id } = req.params;
137
+ const validator = Validator.make(req.body || {}, {
138
+ name: "string",
139
+ species: "string",
140
+ age: "integer|min:1",
141
+ });
142
+
143
+ if (await validator.fails()) {
144
+ sendError(res, 422, "Validation error", validator.errors());
145
+ return;
146
+ }
147
+
148
+ const existing = await prisma.pets.findUnique({
149
+ where: { id: BigInt(id) },
150
+ });
151
+
152
+ if (!existing) {
153
+ sendError(res, 404, "Pet not found");
154
+ return;
155
+ }
156
+
157
+ const validatedData = await validator.validated();
158
+ const updated = await prisma.pets.update({
159
+ where: { id: BigInt(id) },
160
+ data: {
161
+ ...validatedData,
162
+ updated_at: new Date(),
163
+ },
164
+ });
165
+
166
+ // Gunakan sendFastSuccess
167
+ sendFastSuccess(res, 200, petSerializer, {
168
+ status: "success",
169
+ message: "Pet updated successfully",
170
+ data: {
171
+ ...updated,
172
+ id: updated.id.toString(),
173
+ },
174
+ });
175
+ }
176
+
177
+ export async function destroy(req: Request, res: Response) {
178
+ const { id } = req.params;
179
+
180
+ const existing = await prisma.pets.findUnique({
181
+ where: { id: BigInt(id) },
182
+ });
183
+
184
+ if (!existing) {
185
+ sendError(res, 404, "Pet not found");
186
+ return;
187
+ }
188
+
189
+ await prisma.pets.delete({
190
+ where: { id: BigInt(id) },
191
+ });
192
+
193
+ sendSuccess(res, 200, "Pet deleted successfully", null);
194
+ }
@@ -0,0 +1,478 @@
1
+ import { Request, Response } from "express";
2
+ import { prisma } from "@lapeh/core/database";
3
+ import { sendError, sendFastSuccess } from "@lapeh/utils/response";
4
+ import { Validator } from "@lapeh/utils/validator";
5
+ import { z } from "zod";
6
+ import { getSerializer, createResponseSchema } from "@lapeh/core/serializer";
7
+
8
+ // --- Serializers ---
9
+
10
+ const roleSchema = {
11
+ type: "object",
12
+ properties: {
13
+ id: { type: "string" },
14
+ name: { type: "string" },
15
+ slug: { type: "string" },
16
+ description: { type: "string", nullable: true },
17
+ created_at: { type: "string", format: "date-time", nullable: true },
18
+ updated_at: { type: "string", format: "date-time", nullable: true },
19
+ },
20
+ };
21
+
22
+ const permissionSchema = {
23
+ type: "object",
24
+ properties: {
25
+ id: { type: "string" },
26
+ name: { type: "string" },
27
+ slug: { type: "string" },
28
+ description: { type: "string", nullable: true },
29
+ created_at: { type: "string", format: "date-time", nullable: true },
30
+ updated_at: { type: "string", format: "date-time", nullable: true },
31
+ },
32
+ };
33
+
34
+ const roleSerializer = getSerializer("role", createResponseSchema(roleSchema));
35
+ const roleListSerializer = getSerializer(
36
+ "role-list",
37
+ createResponseSchema({ type: "array", items: roleSchema })
38
+ );
39
+
40
+ const permissionSerializer = getSerializer(
41
+ "permission",
42
+ createResponseSchema(permissionSchema)
43
+ );
44
+ const permissionListSerializer = getSerializer(
45
+ "permission-list",
46
+ createResponseSchema({ type: "array", items: permissionSchema })
47
+ );
48
+
49
+ const voidSerializer = getSerializer(
50
+ "void",
51
+ createResponseSchema({ type: "null" })
52
+ );
53
+
54
+ // --- Controllers ---
55
+
56
+ export async function createRole(req: Request, res: Response) {
57
+ const validator = Validator.make(req.body || {}, {
58
+ name: "required|string|min:1",
59
+ slug: "required|string|min:1|unique:roles,slug",
60
+ description: "string",
61
+ });
62
+
63
+ if (await validator.fails()) {
64
+ sendError(res, 422, "Validation error", validator.errors());
65
+ return;
66
+ }
67
+
68
+ const { name, slug, description } = await validator.validated();
69
+ // Manual unique check removed as it is handled by validator
70
+
71
+ const role = await prisma.roles.create({
72
+ data: {
73
+ name,
74
+ slug,
75
+ description: description || null,
76
+ created_at: new Date(),
77
+ updated_at: new Date(),
78
+ },
79
+ });
80
+ sendFastSuccess(res, 201, roleSerializer, {
81
+ status: "success",
82
+ message: "Role created",
83
+ data: { ...role, id: role.id.toString() },
84
+ });
85
+ }
86
+
87
+ export async function listRoles(_req: Request, res: Response) {
88
+ const roles = await prisma.roles.findMany({
89
+ orderBy: { id: "asc" },
90
+ });
91
+ const serialized = roles.map((r: any) => ({ ...r, id: r.id.toString() }));
92
+ sendFastSuccess(res, 200, roleListSerializer, {
93
+ status: "success",
94
+ message: "Roles list",
95
+ data: serialized,
96
+ });
97
+ }
98
+
99
+ export async function updateRole(req: Request, res: Response) {
100
+ const { id } = req.params;
101
+ const roleId = BigInt(id);
102
+
103
+ const validator = Validator.make(req.body || {}, {
104
+ name: "string",
105
+ slug: `string|unique:roles,slug,${id}`,
106
+ description: "string",
107
+ });
108
+
109
+ if (await validator.fails()) {
110
+ sendError(res, 422, "Validation error", validator.errors());
111
+ return;
112
+ }
113
+ const { name, slug, description } = await validator.validated();
114
+
115
+ const role = await prisma.roles.findUnique({ where: { id: roleId } });
116
+ if (!role) {
117
+ sendError(res, 404, "Role not found");
118
+ return;
119
+ }
120
+ // Manual unique check removed as it is handled by validator
121
+ const updated = await prisma.roles.update({
122
+ where: { id: roleId },
123
+ data: {
124
+ name: name ?? role.name,
125
+ slug: slug ?? role.slug,
126
+ description: description ?? role.description,
127
+ updated_at: new Date(),
128
+ },
129
+ });
130
+ sendFastSuccess(res, 200, roleSerializer, {
131
+ status: "success",
132
+ message: "Role updated",
133
+ data: { ...updated, id: updated.id.toString() },
134
+ });
135
+ }
136
+
137
+ export async function deleteRole(req: Request, res: Response) {
138
+ const { id } = req.params;
139
+ const roleId = BigInt(id);
140
+ const role = await prisma.roles.findUnique({ where: { id: roleId } });
141
+ if (!role) {
142
+ sendError(res, 404, "Role not found");
143
+ return;
144
+ }
145
+ await prisma.role_permissions.deleteMany({ where: { role_id: roleId } });
146
+ await prisma.user_roles.deleteMany({ where: { role_id: roleId } });
147
+ await prisma.roles.delete({ where: { id: roleId } });
148
+ sendFastSuccess(res, 200, voidSerializer, {
149
+ status: "success",
150
+ message: "Role deleted",
151
+ data: null,
152
+ });
153
+ }
154
+
155
+ export async function createPermission(req: Request, res: Response) {
156
+ const validator = Validator.make(req.body || {}, {
157
+ name: "required|string|min:1",
158
+ slug: "required|string|min:1|unique:permissions,slug",
159
+ description: "string",
160
+ });
161
+
162
+ if (await validator.fails()) {
163
+ sendError(res, 422, "Validation error", validator.errors());
164
+ return;
165
+ }
166
+ const { name, slug, description } = await validator.validated();
167
+ // Manual unique check removed as it is handled by validator
168
+
169
+ const permission = await prisma.permissions.create({
170
+ data: {
171
+ name,
172
+ slug,
173
+ description: description || null,
174
+ created_at: new Date(),
175
+ updated_at: new Date(),
176
+ },
177
+ });
178
+ sendFastSuccess(res, 201, permissionSerializer, {
179
+ status: "success",
180
+ message: "Permission created",
181
+ data: { ...permission, id: permission.id.toString() },
182
+ });
183
+ }
184
+
185
+ export async function listPermissions(_req: Request, res: Response) {
186
+ const permissions = await prisma.permissions.findMany({
187
+ orderBy: { id: "asc" },
188
+ });
189
+ const serialized = permissions.map((p: any) => ({
190
+ ...p,
191
+ id: p.id.toString(),
192
+ }));
193
+ sendFastSuccess(res, 200, permissionListSerializer, {
194
+ status: "success",
195
+ message: "Permissions list",
196
+ data: serialized,
197
+ });
198
+ }
199
+
200
+ export async function updatePermission(req: Request, res: Response) {
201
+ const { id } = req.params;
202
+ const permissionId = BigInt(id);
203
+
204
+ const validator = Validator.make(req.body || {}, {
205
+ name: "string",
206
+ slug: `string|unique:permissions,slug,${id}`,
207
+ description: "string",
208
+ });
209
+
210
+ if (await validator.fails()) {
211
+ sendError(res, 422, "Validation error", validator.errors());
212
+ return;
213
+ }
214
+ const { name, slug, description } = await validator.validated();
215
+
216
+ const permission = await prisma.permissions.findUnique({
217
+ where: { id: permissionId },
218
+ });
219
+ if (!permission) {
220
+ sendError(res, 404, "Permission not found");
221
+ return;
222
+ }
223
+ // Manual unique check removed as it is handled by validator
224
+ const updated = await prisma.permissions.update({
225
+ where: { id: permissionId },
226
+ data: {
227
+ name: name ?? permission.name,
228
+ slug: slug ?? permission.slug,
229
+ description: description ?? permission.description,
230
+ updated_at: new Date(),
231
+ },
232
+ });
233
+ sendFastSuccess(res, 200, permissionSerializer, {
234
+ status: "success",
235
+ message: "Permission updated",
236
+ data: { ...updated, id: updated.id.toString() },
237
+ });
238
+ }
239
+
240
+ export async function deletePermission(req: Request, res: Response) {
241
+ const { id } = req.params;
242
+ const permissionId = BigInt(id);
243
+ const permission = await prisma.permissions.findUnique({
244
+ where: { id: permissionId },
245
+ });
246
+ if (!permission) {
247
+ sendError(res, 404, "Permission not found");
248
+ return;
249
+ }
250
+ await prisma.role_permissions.deleteMany({
251
+ where: { permission_id: permissionId },
252
+ });
253
+ await prisma.user_permissions.deleteMany({
254
+ where: { permission_id: permissionId },
255
+ });
256
+ await prisma.permissions.delete({ where: { id: permissionId } });
257
+ sendFastSuccess(res, 200, voidSerializer, {
258
+ status: "success",
259
+ message: "Permission deleted",
260
+ data: null,
261
+ });
262
+ }
263
+
264
+ export async function assignRoleToUser(req: Request, res: Response) {
265
+ const validator = Validator.make(req.body || {}, {
266
+ userId: z.string().min(1, "userId wajib diisi"),
267
+ roleId: z.string().min(1, "roleId wajib diisi"),
268
+ });
269
+
270
+ if (await validator.fails()) {
271
+ sendError(res, 422, "Validation error", validator.errors());
272
+ return;
273
+ }
274
+ const { userId, roleId } = await validator.validated();
275
+
276
+ const user = await prisma.users.findUnique({
277
+ where: { id: BigInt(userId) },
278
+ });
279
+ if (!user) {
280
+ sendError(res, 404, "User not found");
281
+ return;
282
+ }
283
+ const role = await prisma.roles.findUnique({
284
+ where: { id: BigInt(roleId) },
285
+ });
286
+ if (!role) {
287
+ sendError(res, 404, "Role not found");
288
+ return;
289
+ }
290
+ await prisma.user_roles.upsert({
291
+ where: {
292
+ user_id_role_id: {
293
+ user_id: BigInt(userId),
294
+ role_id: BigInt(roleId),
295
+ },
296
+ },
297
+ create: {
298
+ user_id: BigInt(userId),
299
+ role_id: BigInt(roleId),
300
+ created_at: new Date(),
301
+ },
302
+ update: {},
303
+ });
304
+ sendFastSuccess(res, 200, voidSerializer, {
305
+ status: "success",
306
+ message: "Role assigned to user",
307
+ data: null,
308
+ });
309
+ }
310
+
311
+ export async function removeRoleFromUser(req: Request, res: Response) {
312
+ const validator = Validator.make(req.body || {}, {
313
+ userId: z.string().min(1, "userId wajib diisi"),
314
+ roleId: z.string().min(1, "roleId wajib diisi"),
315
+ });
316
+
317
+ if (await validator.fails()) {
318
+ sendError(res, 422, "Validation error", validator.errors());
319
+ return;
320
+ }
321
+ const { userId, roleId } = await validator.validated();
322
+
323
+ await prisma.user_roles.deleteMany({
324
+ where: {
325
+ user_id: BigInt(userId),
326
+ role_id: BigInt(roleId),
327
+ },
328
+ });
329
+ sendFastSuccess(res, 200, voidSerializer, {
330
+ status: "success",
331
+ message: "Role removed from user",
332
+ data: null,
333
+ });
334
+ }
335
+
336
+ export async function assignPermissionToRole(req: Request, res: Response) {
337
+ const validator = Validator.make(req.body || {}, {
338
+ roleId: z.string().min(1, "roleId wajib diisi"),
339
+ permissionId: z.string().min(1, "permissionId wajib diisi"),
340
+ });
341
+
342
+ if (await validator.fails()) {
343
+ sendError(res, 422, "Validation error", validator.errors());
344
+ return;
345
+ }
346
+ const { roleId, permissionId } = await validator.validated();
347
+
348
+ const role = await prisma.roles.findUnique({
349
+ where: { id: BigInt(roleId) },
350
+ });
351
+ if (!role) {
352
+ sendError(res, 404, "Role not found");
353
+ return;
354
+ }
355
+ const permission = await prisma.permissions.findUnique({
356
+ where: { id: BigInt(permissionId) },
357
+ });
358
+ if (!permission) {
359
+ sendError(res, 404, "Permission not found");
360
+ return;
361
+ }
362
+ await prisma.role_permissions.upsert({
363
+ where: {
364
+ role_id_permission_id: {
365
+ role_id: BigInt(roleId),
366
+ permission_id: BigInt(permissionId),
367
+ },
368
+ },
369
+ create: {
370
+ role_id: BigInt(roleId),
371
+ permission_id: BigInt(permissionId),
372
+ created_at: new Date(),
373
+ },
374
+ update: {},
375
+ });
376
+ sendFastSuccess(res, 200, voidSerializer, {
377
+ status: "success",
378
+ message: "Permission assigned to role",
379
+ data: null,
380
+ });
381
+ }
382
+
383
+ export async function removePermissionFromRole(req: Request, res: Response) {
384
+ const validator = Validator.make(req.body || {}, {
385
+ roleId: z.string().min(1, "roleId wajib diisi"),
386
+ permissionId: z.string().min(1, "permissionId wajib diisi"),
387
+ });
388
+
389
+ if (await validator.fails()) {
390
+ sendError(res, 422, "Validation error", validator.errors());
391
+ return;
392
+ }
393
+ const { roleId, permissionId } = await validator.validated();
394
+
395
+ await prisma.role_permissions.deleteMany({
396
+ where: {
397
+ role_id: BigInt(roleId),
398
+ permission_id: BigInt(permissionId),
399
+ },
400
+ });
401
+ sendFastSuccess(res, 200, voidSerializer, {
402
+ status: "success",
403
+ message: "Permission removed from role",
404
+ data: null,
405
+ });
406
+ }
407
+
408
+ export async function assignPermissionToUser(req: Request, res: Response) {
409
+ const validator = Validator.make(req.body || {}, {
410
+ userId: z.string().min(1, "userId wajib diisi"),
411
+ permissionId: z.string().min(1, "permissionId wajib diisi"),
412
+ });
413
+
414
+ if (await validator.fails()) {
415
+ sendError(res, 422, "Validation error", validator.errors());
416
+ return;
417
+ }
418
+ const { userId, permissionId } = await validator.validated();
419
+
420
+ const user = await prisma.users.findUnique({
421
+ where: { id: BigInt(userId) },
422
+ });
423
+ if (!user) {
424
+ sendError(res, 404, "User not found");
425
+ return;
426
+ }
427
+ const permission = await prisma.permissions.findUnique({
428
+ where: { id: BigInt(permissionId) },
429
+ });
430
+ if (!permission) {
431
+ sendError(res, 404, "Permission not found");
432
+ return;
433
+ }
434
+ await prisma.user_permissions.upsert({
435
+ where: {
436
+ user_id_permission_id: {
437
+ user_id: BigInt(userId),
438
+ permission_id: BigInt(permissionId),
439
+ },
440
+ },
441
+ create: {
442
+ user_id: BigInt(userId),
443
+ permission_id: BigInt(permissionId),
444
+ created_at: new Date(),
445
+ },
446
+ update: {},
447
+ });
448
+ sendFastSuccess(res, 200, voidSerializer, {
449
+ status: "success",
450
+ message: "Permission assigned to user",
451
+ data: null,
452
+ });
453
+ }
454
+
455
+ export async function removePermissionFromUser(req: Request, res: Response) {
456
+ const validator = Validator.make(req.body || {}, {
457
+ userId: z.string().min(1, "userId wajib diisi"),
458
+ permissionId: z.string().min(1, "permissionId wajib diisi"),
459
+ });
460
+
461
+ if (await validator.fails()) {
462
+ sendError(res, 422, "Validation error", validator.errors());
463
+ return;
464
+ }
465
+ const { userId, permissionId } = await validator.validated();
466
+
467
+ await prisma.user_permissions.deleteMany({
468
+ where: {
469
+ user_id: BigInt(userId),
470
+ permission_id: BigInt(permissionId),
471
+ },
472
+ });
473
+ sendFastSuccess(res, 200, voidSerializer, {
474
+ status: "success",
475
+ message: "Permission removed from user",
476
+ data: null,
477
+ });
478
+ }