lapeh 1.0.10 → 2.0.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.
@@ -26,7 +26,7 @@ export async function register(req: Request, res: Response) {
26
26
  if (existing) {
27
27
  sendError(res, 409, "Email already used", {
28
28
  field: "email",
29
- message: "Email sudah terdaftar, silakan gunakan email lain",
29
+ message: "Email is already registered, please use another email",
30
30
  });
31
31
  return;
32
32
  }
@@ -55,7 +55,7 @@ export async function register(req: Request, res: Response) {
55
55
  });
56
56
  }
57
57
 
58
- sendSuccess(res, 200, "Registrasi berhasil", {
58
+ sendSuccess(res, 200, "Registration successful", {
59
59
  id: user.id.toString(),
60
60
  email: user.email,
61
61
  name: user.name,
@@ -76,7 +76,7 @@ export async function login(req: Request, res: Response) {
76
76
  include: {
77
77
  user_roles: {
78
78
  include: {
79
- roles: true,
79
+ role: true,
80
80
  },
81
81
  },
82
82
  },
@@ -84,7 +84,7 @@ export async function login(req: Request, res: Response) {
84
84
  if (!user) {
85
85
  sendError(res, 401, "Email not registered", {
86
86
  field: "email",
87
- message: "Email belum terdaftar, silakan registrasi terlebih dahulu",
87
+ message: "Email is not registered, please register first",
88
88
  });
89
89
  return;
90
90
  }
@@ -92,7 +92,7 @@ export async function login(req: Request, res: Response) {
92
92
  if (!ok) {
93
93
  sendError(res, 401, "Invalid credentials", {
94
94
  field: "password",
95
- message: "Password yang Anda masukkan salah",
95
+ message: "The password you entered is incorrect",
96
96
  });
97
97
  return;
98
98
  }
@@ -102,8 +102,8 @@ export async function login(req: Request, res: Response) {
102
102
  return;
103
103
  }
104
104
  const primaryUserRole =
105
- user.user_roles && user.user_roles.length > 0 && user.user_roles[0].roles
106
- ? user.user_roles[0].roles.slug
105
+ user.user_roles && user.user_roles.length > 0 && user.user_roles[0].role
106
+ ? user.user_roles[0].role.slug
107
107
  : "user";
108
108
  const accessExpiresInSeconds = ACCESS_TOKEN_EXPIRES_IN_SECONDS;
109
109
  const accessExpiresAt = new Date(
@@ -124,7 +124,7 @@ export async function login(req: Request, res: Response) {
124
124
  secret,
125
125
  { expiresIn: refreshExpiresInSeconds }
126
126
  );
127
- sendSuccess(res, 200, "Login berhasil", {
127
+ sendSuccess(res, 200, "Login successful", {
128
128
  token,
129
129
  refreshToken,
130
130
  expiresIn: accessExpiresInSeconds,
@@ -145,7 +145,7 @@ export async function me(req: Request, res: Response) {
145
145
  include: {
146
146
  user_roles: {
147
147
  include: {
148
- roles: true,
148
+ role: true,
149
149
  },
150
150
  },
151
151
  },
@@ -159,14 +159,14 @@ export async function me(req: Request, res: Response) {
159
159
  ...rest,
160
160
  id: user.id.toString(),
161
161
  role:
162
- user.user_roles && user.user_roles.length > 0 && user.user_roles[0].roles
163
- ? user.user_roles[0].roles.slug
162
+ user.user_roles && user.user_roles.length > 0 && user.user_roles[0].role
163
+ ? user.user_roles[0].role.slug
164
164
  : "user",
165
165
  });
166
166
  }
167
167
 
168
168
  export async function logout(req: Request, res: Response) {
169
- sendSuccess(res, 200, "Logout berhasil", null);
169
+ sendSuccess(res, 200, "Logout successful", null);
170
170
  }
171
171
 
172
172
  export async function refreshToken(req: Request, res: Response) {
@@ -198,7 +198,7 @@ export async function refreshToken(req: Request, res: Response) {
198
198
  include: {
199
199
  user_roles: {
200
200
  include: {
201
- roles: true,
201
+ role: true,
202
202
  },
203
203
  },
204
204
  },
@@ -208,8 +208,8 @@ export async function refreshToken(req: Request, res: Response) {
208
208
  return;
209
209
  }
210
210
  const primaryUserRole =
211
- user.user_roles && user.user_roles.length > 0 && user.user_roles[0].roles
212
- ? user.user_roles[0].roles.slug
211
+ user.user_roles && user.user_roles.length > 0 && user.user_roles[0].role
212
+ ? user.user_roles[0].role.slug
213
213
  : "user";
214
214
  const accessExpiresInSeconds = ACCESS_TOKEN_EXPIRES_IN_SECONDS;
215
215
  const accessExpiresAt = new Date(
@@ -243,7 +243,7 @@ export async function updateAvatar(req: Request, res: Response) {
243
243
  path: string;
244
244
  } | null;
245
245
  if (!file) {
246
- sendError(res, 400, "Avatar file wajib diupload");
246
+ sendError(res, 400, "Avatar file is required");
247
247
  return;
248
248
  }
249
249
  const userId = BigInt(payload.userId);
@@ -259,7 +259,7 @@ export async function updateAvatar(req: Request, res: Response) {
259
259
  },
260
260
  });
261
261
  const { password, remember_token, ...rest } = updated as any;
262
- sendSuccess(res, 200, "Avatar berhasil diperbarui", {
262
+ sendSuccess(res, 200, "Avatar updated successfully", {
263
263
  ...rest,
264
264
  id: updated.id.toString(),
265
265
  });
@@ -289,7 +289,7 @@ export async function updatePassword(req: Request, res: Response) {
289
289
  if (!ok) {
290
290
  sendError(res, 401, "Invalid credentials", {
291
291
  field: "currentPassword",
292
- message: "Password saat ini tidak sesuai",
292
+ message: "Current password is incorrect",
293
293
  });
294
294
  return;
295
295
  }
@@ -301,7 +301,7 @@ export async function updatePassword(req: Request, res: Response) {
301
301
  updated_at: new Date(),
302
302
  },
303
303
  });
304
- sendSuccess(res, 200, "Password berhasil diperbarui", null);
304
+ sendSuccess(res, 200, "Password updated successfully", null);
305
305
  }
306
306
 
307
307
  export async function updateProfile(req: Request, res: Response) {
@@ -327,7 +327,7 @@ export async function updateProfile(req: Request, res: Response) {
327
327
  if (existing) {
328
328
  sendError(res, 409, "Email already used", {
329
329
  field: "email",
330
- message: "Email sudah terdaftar, silakan gunakan email lain",
330
+ message: "Email is already registered, please use another email",
331
331
  });
332
332
  return;
333
333
  }
@@ -340,7 +340,7 @@ export async function updateProfile(req: Request, res: Response) {
340
340
  },
341
341
  });
342
342
  const { password, remember_token, ...rest } = updated as any;
343
- sendSuccess(res, 200, "Profil berhasil diperbarui", {
343
+ sendSuccess(res, 200, "Profile updated successfully", {
344
344
  ...rest,
345
345
  id: updated.id.toString(),
346
346
  });
@@ -0,0 +1,132 @@
1
+
2
+ import { Request, Response } from "express";
3
+ import { prisma } from "../prisma";
4
+ import { sendSuccess, sendError } from "../utils/response";
5
+ import { createPetSchema, updatePetSchema } from "../schema/pet-schema";
6
+ import { getPagination, buildPaginationMeta } from "../utils/pagination";
7
+
8
+ export async function index(req: Request, res: Response) {
9
+ const { page, perPage, skip, take } = getPagination(req.query);
10
+ const search = req.query.search as string;
11
+
12
+ const where: any = {};
13
+ if (search) {
14
+ where.OR = [
15
+ { name: { contains: search, mode: "insensitive" } },
16
+ { species: { contains: search, mode: "insensitive" } },
17
+ ];
18
+ }
19
+
20
+ const [data, total] = await Promise.all([
21
+ prisma.pets.findMany({
22
+ where,
23
+ skip,
24
+ take,
25
+ orderBy: { created_at: "desc" },
26
+ }),
27
+ prisma.pets.count({ where }),
28
+ ]);
29
+
30
+ const serialized = data.map((item: any) => ({
31
+ ...item,
32
+ id: item.id.toString(),
33
+ }));
34
+
35
+ const meta = buildPaginationMeta(page, perPage, total);
36
+
37
+ sendSuccess(res, 200, "Pets retrieved successfully", {
38
+ data: serialized,
39
+ meta,
40
+ });
41
+ }
42
+
43
+ export async function show(req: Request, res: Response) {
44
+ const { id } = req.params;
45
+ const pet = await prisma.pets.findUnique({
46
+ where: { id: BigInt(id) },
47
+ });
48
+
49
+ if (!pet) {
50
+ sendError(res, 404, "Pet not found");
51
+ return;
52
+ }
53
+
54
+ sendSuccess(res, 200, "Pet retrieved successfully", {
55
+ ...pet,
56
+ id: pet.id.toString(),
57
+ });
58
+ }
59
+
60
+ export async function store(req: Request, res: Response) {
61
+ const parsed = createPetSchema.safeParse(req.body);
62
+ if (!parsed.success) {
63
+ const errors = parsed.error.flatten().fieldErrors;
64
+ sendError(res, 422, "Validation error", errors);
65
+ return;
66
+ }
67
+
68
+ const pet = await prisma.pets.create({
69
+ data: {
70
+ ...parsed.data,
71
+ created_at: new Date(),
72
+ updated_at: new Date(),
73
+ },
74
+ });
75
+
76
+ sendSuccess(res, 201, "Pet created successfully", {
77
+ ...pet,
78
+ id: pet.id.toString(),
79
+ });
80
+ }
81
+
82
+ export async function update(req: Request, res: Response) {
83
+ const { id } = req.params;
84
+ const parsed = updatePetSchema.safeParse(req.body);
85
+
86
+ if (!parsed.success) {
87
+ const errors = parsed.error.flatten().fieldErrors;
88
+ sendError(res, 422, "Validation error", errors);
89
+ return;
90
+ }
91
+
92
+ const existing = await prisma.pets.findUnique({
93
+ where: { id: BigInt(id) },
94
+ });
95
+
96
+ if (!existing) {
97
+ sendError(res, 404, "Pet not found");
98
+ return;
99
+ }
100
+
101
+ const updated = await prisma.pets.update({
102
+ where: { id: BigInt(id) },
103
+ data: {
104
+ ...parsed.data,
105
+ updated_at: new Date(),
106
+ },
107
+ });
108
+
109
+ sendSuccess(res, 200, "Pet updated successfully", {
110
+ ...updated,
111
+ id: updated.id.toString(),
112
+ });
113
+ }
114
+
115
+ export async function destroy(req: Request, res: Response) {
116
+ const { id } = req.params;
117
+
118
+ const existing = await prisma.pets.findUnique({
119
+ where: { id: BigInt(id) },
120
+ });
121
+
122
+ if (!existing) {
123
+ sendError(res, 404, "Pet not found");
124
+ return;
125
+ }
126
+
127
+ await prisma.pets.delete({
128
+ where: { id: BigInt(id) },
129
+ });
130
+
131
+ sendSuccess(res, 200, "Pet deleted successfully", null);
132
+ }
@@ -0,0 +1,163 @@
1
+ model cache {
2
+ key String @id @db.VarChar(255)
3
+ value String @db.Text
4
+ expiration Int
5
+ }
6
+
7
+ model cache_locks {
8
+ key String @id @db.VarChar(255)
9
+ owner String @db.VarChar(255)
10
+ expiration Int
11
+ }
12
+
13
+ model failed_jobs {
14
+ id BigInt @id @default(autoincrement())
15
+ uuid String @unique @db.VarChar(255)
16
+ connection String @db.Text
17
+ queue String @db.Text
18
+ payload String @db.Text
19
+ exception String @db.Text
20
+ failed_at DateTime @default(now()) @db.Timestamp(0)
21
+ }
22
+
23
+ model job_batches {
24
+ id String @id @db.VarChar(255)
25
+ name String @db.VarChar(255)
26
+ total_jobs Int
27
+ pending_jobs Int
28
+ failed_jobs Int
29
+ failed_job_ids String @db.Text
30
+ options String? @db.Text
31
+ cancelled_at Int?
32
+ created_at Int
33
+ finished_at Int?
34
+ }
35
+
36
+ model jobs {
37
+ id BigInt @id @default(autoincrement())
38
+ queue String @db.VarChar(255)
39
+ payload String @db.Text
40
+ attempts Int @db.SmallInt
41
+ reserved_at Int?
42
+ available_at Int
43
+ created_at Int
44
+
45
+ @@index([queue])
46
+ }
47
+
48
+ model migrations {
49
+ id Int @id @default(autoincrement())
50
+ migration String @db.VarChar(255)
51
+ batch Int
52
+ }
53
+
54
+ model password_reset_tokens {
55
+ email String @id @db.VarChar(255)
56
+ token String @db.VarChar(255)
57
+ created_at DateTime? @db.Timestamp(0)
58
+ }
59
+
60
+ model personal_access_tokens {
61
+ id BigInt @id @default(autoincrement())
62
+ tokenable_type String @db.VarChar(255)
63
+ tokenable_id BigInt
64
+ name String @db.Text
65
+ token String @unique @db.VarChar(64)
66
+ abilities String? @db.Text
67
+ last_used_at DateTime? @db.Timestamp(0)
68
+ expires_at DateTime? @db.Timestamp(0)
69
+ created_at DateTime? @db.Timestamp(0)
70
+ updated_at DateTime? @db.Timestamp(0)
71
+
72
+ @@index([expires_at])
73
+ @@index([tokenable_type, tokenable_id])
74
+ }
75
+
76
+ model sessions {
77
+ id String @id @db.VarChar(255)
78
+ user_id BigInt?
79
+ ip_address String? @db.VarChar(45)
80
+ user_agent String? @db.Text
81
+ payload String @db.Text
82
+ last_activity Int
83
+
84
+ @@index([last_activity])
85
+ @@index([user_id])
86
+ }
87
+
88
+ model users {
89
+ id BigInt @id @default(autoincrement())
90
+ uuid String @unique @db.Uuid
91
+ name String @db.VarChar(255)
92
+ email String @unique @db.VarChar(255)
93
+ avatar String? @db.VarChar(255)
94
+ avatar_url String? @db.VarChar(255)
95
+ email_verified_at DateTime? @db.Timestamp(0)
96
+ password String @db.VarChar(255)
97
+ remember_token String? @db.VarChar(100)
98
+ created_at DateTime? @db.Timestamp(0)
99
+ updated_at DateTime? @db.Timestamp(0)
100
+
101
+ user_roles user_roles[]
102
+ user_permissions user_permissions[]
103
+ }
104
+
105
+ model roles {
106
+ id BigInt @id @default(autoincrement())
107
+ name String @db.VarChar(255)
108
+ slug String @unique @db.VarChar(255)
109
+ description String? @db.Text
110
+ created_at DateTime? @db.Timestamp(0)
111
+ updated_at DateTime? @db.Timestamp(0)
112
+
113
+ user_roles user_roles[]
114
+ role_permissions role_permissions[]
115
+ }
116
+
117
+ model permissions {
118
+ id BigInt @id @default(autoincrement())
119
+ name String @db.VarChar(255)
120
+ slug String @unique @db.VarChar(255)
121
+ description String? @db.Text
122
+ created_at DateTime? @db.Timestamp(0)
123
+ updated_at DateTime? @db.Timestamp(0)
124
+
125
+ role_permissions role_permissions[]
126
+ user_permissions user_permissions[]
127
+ }
128
+
129
+ model user_roles {
130
+ id BigInt @id @default(autoincrement())
131
+ user_id BigInt
132
+ role_id BigInt
133
+ created_at DateTime? @db.Timestamp(0)
134
+
135
+ user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
136
+ role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
137
+
138
+ @@unique([user_id, role_id])
139
+ }
140
+
141
+ model role_permissions {
142
+ id BigInt @id @default(autoincrement())
143
+ role_id BigInt
144
+ permission_id BigInt
145
+ created_at DateTime? @db.Timestamp(0)
146
+
147
+ role roles @relation(fields: [role_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
148
+ permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
149
+
150
+ @@unique([role_id, permission_id])
151
+ }
152
+
153
+ model user_permissions {
154
+ id BigInt @id @default(autoincrement())
155
+ user_id BigInt
156
+ permission_id BigInt
157
+ created_at DateTime? @db.Timestamp(0)
158
+
159
+ user users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
160
+ permission permissions @relation(fields: [permission_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
161
+
162
+ @@unique([user_id, permission_id])
163
+ }
@@ -0,0 +1,9 @@
1
+
2
+ model pets {
3
+ id BigInt @id @default(autoincrement())
4
+ name String @db.VarChar(255)
5
+ species String @db.VarChar(100)
6
+ age Int @db.SmallInt
7
+ created_at DateTime? @db.Timestamp(0)
8
+ updated_at DateTime? @db.Timestamp(0)
9
+ }
package/src/prisma.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PrismaClient } from "../generated/prisma/client";
1
+ import { PrismaClient } from "../generated/prisma";
2
2
  import { PrismaPg } from "@prisma/adapter-pg";
3
3
  import { PrismaMariaDb } from "@prisma/adapter-mariadb";
4
4
 
@@ -0,0 +1,12 @@
1
+ import { Router } from "express";
2
+ import * as PetController from "../controllers/petController";
3
+
4
+ const router = Router();
5
+
6
+ router.get("/", PetController.index);
7
+ router.get("/:id", PetController.show);
8
+ router.post("/", PetController.store);
9
+ router.put("/:id", PetController.update);
10
+ router.delete("/:id", PetController.destroy);
11
+
12
+ export default router;
@@ -3,60 +3,60 @@ import z from "zod";
3
3
  export const registerSchema = z
4
4
  .object({
5
5
  email: z
6
- .string({ required_error: "Email wajib diisi" })
7
- .email("Format email tidak valid"),
6
+ .string({ required_error: "Email is required" })
7
+ .email("Invalid email format"),
8
8
  name: z
9
- .string({ required_error: "Nama wajib diisi" })
10
- .min(1, "Nama wajib diisi"),
9
+ .string({ required_error: "Name is required" })
10
+ .min(1, "Name is required"),
11
11
  password: z
12
- .string({ required_error: "Password wajib diisi" })
13
- .min(4, "Password minimal 4 karakter"),
12
+ .string({ required_error: "Password is required" })
13
+ .min(4, "Password must be at least 4 characters"),
14
14
  confirmPassword: z
15
- .string({ required_error: "Konfirmasi password wajib diisi" })
16
- .min(4, "Konfirmasi password minimal 4 karakter"),
15
+ .string({ required_error: "Confirm password is required" })
16
+ .min(4, "Confirm password must be at least 4 characters"),
17
17
  })
18
18
  .refine((data) => data.password === data.confirmPassword, {
19
19
  path: ["confirmPassword"],
20
- message: "Konfirmasi password tidak sama",
20
+ message: "Confirm password does not match",
21
21
  });
22
22
 
23
23
  export const loginSchema = z.object({
24
24
  email: z
25
- .string({ required_error: "Email wajib diisi" })
26
- .email("Format email tidak valid"),
25
+ .string({ required_error: "Email is required" })
26
+ .email("Invalid email format"),
27
27
  password: z
28
- .string({ required_error: "Password wajib diisi" })
29
- .min(4, "Password minimal 4 karakter"),
28
+ .string({ required_error: "Password is required" })
29
+ .min(4, "Password must be at least 4 characters"),
30
30
  });
31
31
 
32
32
  export const refreshSchema = z.object({
33
33
  refreshToken: z
34
- .string({ required_error: "Refresh token wajib diisi" })
35
- .min(1, "Refresh token wajib diisi"),
34
+ .string({ required_error: "Refresh token is required" })
35
+ .min(1, "Refresh token is required"),
36
36
  });
37
37
 
38
38
  export const updatePasswordSchema = z
39
39
  .object({
40
40
  currentPassword: z
41
- .string({ required_error: "Password saat ini wajib diisi" })
42
- .min(4, "Password saat ini minimal 4 karakter"),
41
+ .string({ required_error: "Current password is required" })
42
+ .min(4, "Current password must be at least 4 characters"),
43
43
  newPassword: z
44
- .string({ required_error: "Password baru wajib diisi" })
45
- .min(4, "Password baru minimal 4 karakter"),
44
+ .string({ required_error: "New password is required" })
45
+ .min(4, "New password must be at least 4 characters"),
46
46
  confirmPassword: z
47
- .string({ required_error: "Konfirmasi password wajib diisi" })
48
- .min(4, "Konfirmasi password minimal 4 karakter"),
47
+ .string({ required_error: "Confirm password is required" })
48
+ .min(4, "Confirm password must be at least 4 characters"),
49
49
  })
50
50
  .refine((data) => data.newPassword === data.confirmPassword, {
51
51
  path: ["confirmPassword"],
52
- message: "Konfirmasi password tidak sama",
52
+ message: "Confirm password does not match",
53
53
  });
54
54
 
55
55
  export const updateProfileSchema = z.object({
56
56
  name: z
57
- .string({ required_error: "Nama wajib diisi" })
58
- .min(1, "Nama wajib diisi"),
57
+ .string({ required_error: "Name is required" })
58
+ .min(1, "Name is required"),
59
59
  email: z
60
- .string({ required_error: "Email wajib diisi" })
61
- .email("Format email tidak valid"),
60
+ .string({ required_error: "Email is required" })
61
+ .email("Invalid email format"),
62
62
  });
@@ -0,0 +1,14 @@
1
+
2
+ import { z } from "zod";
3
+
4
+ export const createPetSchema = z.object({
5
+ name: z.string({ message: "Name is required" }),
6
+ species: z.string({ message: "Species is required" }),
7
+ age: z.number({ message: "Age is required" }).int().positive(),
8
+ });
9
+
10
+ export const updatePetSchema = z.object({
11
+ name: z.string().optional(),
12
+ species: z.string().optional(),
13
+ age: z.number().int().positive().optional(),
14
+ });
package/src/server.ts CHANGED
@@ -3,6 +3,7 @@ import cors from "cors";
3
3
  import helmet from "helmet";
4
4
  import { authRouter } from "./routes/auth";
5
5
  import { rbacRouter } from "./routes/rbac";
6
+ import petRouter from "./routes/pets";
6
7
  import { visitorCounter } from "./middleware/visitor";
7
8
  import { errorHandler } from "./middleware/error";
8
9
 
@@ -28,5 +29,6 @@ app.use(visitorCounter);
28
29
 
29
30
  app.use("/api/auth", authRouter);
30
31
  app.use("/api/rbac", rbacRouter);
32
+ app.use("/api/pets", petRouter);
31
33
 
32
34
  app.use(errorHandler);