Test AUTH - 30 Jul
This commit is contained in:
1
auth
Submodule
1
auth
Submodule
Submodule auth added at 51d749567a
@@ -14,8 +14,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cubejs-client/core": "^0.31.0",
|
"@cubejs-client/core": "^0.31.0",
|
||||||
|
"@elysiajs/cookie": "^0.8.0",
|
||||||
"@elysiajs/cors": "^1.2.0",
|
"@elysiajs/cors": "^1.2.0",
|
||||||
"@elysiajs/eden": "^1.3.2",
|
"@elysiajs/eden": "^1.3.2",
|
||||||
|
"@elysiajs/jwt": "^1.3.2",
|
||||||
"@elysiajs/static": "^1.3.0",
|
"@elysiajs/static": "^1.3.0",
|
||||||
"@elysiajs/stream": "^1.1.0",
|
"@elysiajs/stream": "^1.1.0",
|
||||||
"@elysiajs/swagger": "^1.2.0",
|
"@elysiajs/swagger": "^1.2.0",
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.16",
|
||||||
"add": "^2.0.6",
|
"add": "^2.0.6",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
"bcryptjs": "^3.0.2",
|
||||||
"bun": "^1.2.2",
|
"bun": "^1.2.2",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
"framer-motion": "^12.23.5",
|
"framer-motion": "^12.23.5",
|
||||||
"get-port": "^7.1.0",
|
"get-port": "^7.1.0",
|
||||||
"jotai": "^2.12.3",
|
"jotai": "^2.12.3",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"list": "^2.0.19",
|
"list": "^2.0.19",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -78,15 +82,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.1.6",
|
"eslint-config-next": "15.1.6",
|
||||||
|
"parcel": "^2.6.2",
|
||||||
"postcss": "^8.5.1",
|
"postcss": "^8.5.1",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"typescript": "^5",
|
"typescript": "^5"
|
||||||
"parcel": "^2.6.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1987,27 +1987,49 @@ model JenisProgramYangDiselenggarakan {
|
|||||||
|
|
||||||
// ========================================= PERPUSTAKAAN ========================================= //
|
// ========================================= PERPUSTAKAAN ========================================= //
|
||||||
model DataPerpustakaan {
|
model DataPerpustakaan {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
judul String
|
judul String
|
||||||
deskripsi String @db.Text
|
deskripsi String @db.Text
|
||||||
kategori KategoriBuku @relation(fields: [kategoriId], references: [id])
|
kategori KategoriBuku @relation(fields: [kategoriId], references: [id])
|
||||||
kategoriId String
|
kategoriId String
|
||||||
image FileStorage @relation(fields: [imageId], references: [id])
|
image FileStorage @relation(fields: [imageId], references: [id])
|
||||||
imageId String
|
imageId String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
model KategoriBuku {
|
model KategoriBuku {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime @default(now())
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
DataPerpustakaan DataPerpustakaan[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
email String @unique
|
||||||
|
password String
|
||||||
|
role Role @relation(fields: [roleId], references: [id])
|
||||||
|
roleId String
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Role {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
deletedAt DateTime @default(now())
|
deletedAt DateTime @default(now())
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
DataPerpustakaan DataPerpustakaan[]
|
User User[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================= DATA PENDIDIKAN ========================================= //
|
// ========================================= DATA PENDIDIKAN ========================================= //
|
||||||
|
|||||||
220
src/app/admin/(dashboard)/_state/user/user-state.ts
Normal file
220
src/app/admin/(dashboard)/_state/user/user-state.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import { proxy } from "valtio";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
import ApiFetch from "@/lib/api-fetch";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const userSchema = z.object({
|
||||||
|
nama: z.string().min(1, "Nama harus diisi"),
|
||||||
|
email: z.string().email("Email tidak valid"),
|
||||||
|
password: z.string().min(6, "Password minimal 6 karakter"),
|
||||||
|
roleId: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
nama: "",
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
roleId: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const userState = proxy({
|
||||||
|
create: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async create(isAdmin: boolean = false) {
|
||||||
|
const valid = userSchema.safeParse(userState.create.form);
|
||||||
|
if (!valid.success) {
|
||||||
|
const err = valid.error.issues.map((i) => i.message).join(", ");
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
userState.create.loading = true;
|
||||||
|
const res = await ApiFetch.api.user[
|
||||||
|
isAdmin ? "create" : "register"
|
||||||
|
].post(userState.create.form);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("User berhasil dibuat");
|
||||||
|
userState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(res.data?.message || "Gagal membuat user");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat membuat user");
|
||||||
|
} finally {
|
||||||
|
userState.create.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
login: {
|
||||||
|
form: { email: "", password: "" },
|
||||||
|
loading: false,
|
||||||
|
async submit() {
|
||||||
|
try {
|
||||||
|
userState.login.loading = true;
|
||||||
|
const res = await ApiFetch.api.user.login.post(userState.login.form);
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Login berhasil");
|
||||||
|
const token = res.data?.data?.token;
|
||||||
|
if (typeof token === "string") {
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error(res.data?.message || "Login gagal");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat login");
|
||||||
|
} finally {
|
||||||
|
userState.login.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
register: {
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async submit() {
|
||||||
|
const valid = userSchema.safeParse(userState.register.form);
|
||||||
|
if (!valid.success) {
|
||||||
|
const err = valid.error.issues.map(i => i.message).join(", ");
|
||||||
|
return toast.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
userState.register.loading = true;
|
||||||
|
const res = await ApiFetch.api.user.register.post(userState.register.form);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Registrasi berhasil, silakan login");
|
||||||
|
userState.register.form = { ...defaultForm }; // Reset form
|
||||||
|
} else {
|
||||||
|
toast.error(res.data?.message || "Gagal registrasi");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat registrasi");
|
||||||
|
} finally {
|
||||||
|
userState.register.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findMany: {
|
||||||
|
data: [] as Prisma.UserGetPayload<{ include: { role: true } }>[],
|
||||||
|
loading: false,
|
||||||
|
async load() {
|
||||||
|
userState.findMany.loading = true;
|
||||||
|
const res = await ApiFetch.api.user.findMany.get();
|
||||||
|
if (res.status === 200) {
|
||||||
|
userState.findMany.data = res.data?.data ?? [];
|
||||||
|
}
|
||||||
|
userState.findMany.loading = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
findUnique: {
|
||||||
|
data: null as Prisma.UserGetPayload<{ include: { role: true } }> | null,
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
userState.findUnique.loading = true;
|
||||||
|
const res = await fetch(`/api/user/findUnique/${id}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 200) {
|
||||||
|
userState.findUnique.data = data.data ?? null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data user");
|
||||||
|
} finally {
|
||||||
|
userState.findUnique.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
update: {
|
||||||
|
id: "",
|
||||||
|
form: { ...defaultForm },
|
||||||
|
loading: false,
|
||||||
|
async load(id: string) {
|
||||||
|
try {
|
||||||
|
userState.update.loading = true;
|
||||||
|
const res = await fetch(`/api/user/findUnique/${id}`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 200) {
|
||||||
|
const user = data.data;
|
||||||
|
userState.update.id = user.id;
|
||||||
|
userState.update.form = {
|
||||||
|
nama: user.nama,
|
||||||
|
email: user.email,
|
||||||
|
password: "",
|
||||||
|
roleId: user.roleId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat mengambil data user");
|
||||||
|
} finally {
|
||||||
|
userState.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
try {
|
||||||
|
userState.update.loading = true;
|
||||||
|
const res = await fetch(`/api/user/update/${userState.update.id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(userState.update.form),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("Berhasil update user");
|
||||||
|
userState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(data?.message || "Gagal update user");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat update user");
|
||||||
|
} finally {
|
||||||
|
userState.update.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
loading: false,
|
||||||
|
async submit(id: string) {
|
||||||
|
try {
|
||||||
|
userState.delete.loading = true;
|
||||||
|
const res = await fetch(`/api/user/del/${id}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.status === 200) {
|
||||||
|
toast.success("User berhasil dihapus");
|
||||||
|
userState.findMany.load();
|
||||||
|
} else {
|
||||||
|
toast.error(data?.message || "Gagal hapus user");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("Terjadi kesalahan saat hapus user");
|
||||||
|
} finally {
|
||||||
|
userState.delete.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default userState;
|
||||||
51
src/app/api/[[...slugs]]/_lib/user/create.ts
Normal file
51
src/app/api/[[...slugs]]/_lib/user/create.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { Context } from "elysia";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
|
||||||
|
type FormCreateUser = {
|
||||||
|
nama: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
roleId: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function userCreate(context: Context) {
|
||||||
|
const body = (await context.body) as FormCreateUser;
|
||||||
|
|
||||||
|
if (!body.nama || !body.email || !body.password || !body.roleId) {
|
||||||
|
throw new Error("Semua field wajib diisi");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cek apakah email sudah terdaftar
|
||||||
|
const existing = await prisma.user.findUnique({
|
||||||
|
where: { email: body.email },
|
||||||
|
});
|
||||||
|
if (existing) {
|
||||||
|
throw new Error("Email sudah terdaftar");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password sebelum simpan
|
||||||
|
const hashedPassword = await bcrypt.hash(body.password, 10);
|
||||||
|
|
||||||
|
const result = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
email: body.email,
|
||||||
|
password: hashedPassword,
|
||||||
|
roleId: body.roleId,
|
||||||
|
isActive: body.isActive ?? true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "User berhasil dibuat",
|
||||||
|
data: result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating user:", error);
|
||||||
|
throw new Error("Gagal membuat user: " + (error as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/app/api/[[...slugs]]/_lib/user/del.ts
Normal file
28
src/app/api/[[...slugs]]/_lib/user/del.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// /api/user/delete.ts
|
||||||
|
import prisma from '@/lib/prisma';
|
||||||
|
import { Context } from 'elysia';
|
||||||
|
|
||||||
|
export default async function userDelete(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deleted = await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'User berhasil dinonaktifkan',
|
||||||
|
data: deleted,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Gagal menghapus user',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/app/api/[[...slugs]]/_lib/user/findMany.ts
Normal file
28
src/app/api/[[...slugs]]/_lib/user/findMany.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function userFindMany() {
|
||||||
|
try {
|
||||||
|
const data = await prisma.user.findMany({
|
||||||
|
include: {
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Success get all user",
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Find many error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message:
|
||||||
|
"Gagal mengambil data: " +
|
||||||
|
(error instanceof Error ? error.message : "Unknown error"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/app/api/[[...slugs]]/_lib/user/findUnique.ts
Normal file
31
src/app/api/[[...slugs]]/_lib/user/findUnique.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import prisma from '@/lib/prisma';
|
||||||
|
import { Context } from 'elysia';
|
||||||
|
|
||||||
|
export default async function userFindUnique(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return { success: false, message: 'User tidak ditemukan' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'Berhasil mendapatkan user',
|
||||||
|
data: user,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Gagal mengambil data user',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/app/api/[[...slugs]]/_lib/user/index.ts
Normal file
57
src/app/api/[[...slugs]]/_lib/user/index.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { Elysia, t } from "elysia";
|
||||||
|
|
||||||
|
// Import semua handler
|
||||||
|
import userCreate from "./create";
|
||||||
|
import userFindMany from "./findMany";
|
||||||
|
import userFindUnique from "./findUnique";
|
||||||
|
import userUpdate from "./updt";
|
||||||
|
import userDelete from "./del"; // `delete` nggak boleh jadi nama file JS langsung, jadi biasanya `del.ts`
|
||||||
|
import userLogin from "./login";
|
||||||
|
import userRegister from "./register";
|
||||||
|
|
||||||
|
const User = new Elysia({ prefix: "/api/user" })
|
||||||
|
.post("/register", userRegister, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
email: t.String(),
|
||||||
|
password: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.post("/login", userLogin, {
|
||||||
|
body: t.Object({
|
||||||
|
email: t.String(),
|
||||||
|
password: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.post("/create", userCreate, {
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
email: t.String(),
|
||||||
|
password: t.String(),
|
||||||
|
roleId: t.String(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.get("/findMany", userFindMany)
|
||||||
|
.get("/findUnique/:id", userFindUnique)
|
||||||
|
.put(
|
||||||
|
"/update/:id",
|
||||||
|
async (context) => {
|
||||||
|
const response = await userUpdate(context);
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
nama: t.String(),
|
||||||
|
email: t.String(),
|
||||||
|
password: t.String(),
|
||||||
|
roleId: t.String(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.put("/del/:id", userDelete, {
|
||||||
|
params: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
}),
|
||||||
|
}); // pakai PUT untuk soft delete
|
||||||
|
|
||||||
|
export default User;
|
||||||
81
src/app/api/[[...slugs]]/_lib/user/login.ts
Normal file
81
src/app/api/[[...slugs]]/_lib/user/login.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { Context } from "elysia";
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
// ENV atau secret key untuk token
|
||||||
|
const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key"; // ganti di env production
|
||||||
|
|
||||||
|
type LoginForm = {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function userLogin(context: Context) {
|
||||||
|
const body = (await context.body) as LoginForm;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Cari user berdasarkan email
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email: body.email },
|
||||||
|
include: { role: true }, // include role untuk otorisasi
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Jika tidak ada user
|
||||||
|
if (!user) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Email tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Cek apakah user aktif
|
||||||
|
if (!user.isActive) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Akun tidak aktif",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verifikasi password
|
||||||
|
const isMatch = await bcrypt.compare(body.password, user.password);
|
||||||
|
if (!isMatch) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Password salah",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Buat JWT token
|
||||||
|
const token = jwt.sign(
|
||||||
|
{
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role.name,
|
||||||
|
},
|
||||||
|
JWT_SECRET,
|
||||||
|
{ expiresIn: "7d" } // expire 7 hari
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Kirim response
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Login berhasil",
|
||||||
|
data: {
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
nama: user.nama,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role.name,
|
||||||
|
},
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Login error:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Terjadi kesalahan saat login",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/app/api/[[...slugs]]/_lib/user/register.ts
Normal file
43
src/app/api/[[...slugs]]/_lib/user/register.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { Context } from "elysia";
|
||||||
|
|
||||||
|
export default async function userRegister(context: Context) {
|
||||||
|
const body = (await context.body) as {
|
||||||
|
nama: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingUser = await prisma.user.findUnique({
|
||||||
|
where: { email: body.email },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Email sudah terdaftar",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await prisma.role.findFirst({ where: { name: "warga" } });
|
||||||
|
|
||||||
|
if (!role) throw new Error("Role warga tidak ditemukan");
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(body.password, 10);
|
||||||
|
|
||||||
|
const user = await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
nama: body.nama,
|
||||||
|
email: body.email,
|
||||||
|
password: hashedPassword,
|
||||||
|
roleId: role.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil daftar sebagai warga",
|
||||||
|
data: user,
|
||||||
|
};
|
||||||
|
}
|
||||||
35
src/app/api/[[...slugs]]/_lib/user/updt.ts
Normal file
35
src/app/api/[[...slugs]]/_lib/user/updt.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// /api/user/update.ts
|
||||||
|
import prisma from '@/lib/prisma';
|
||||||
|
import { Context } from 'elysia';
|
||||||
|
|
||||||
|
export default async function userUpdate(context: Context) {
|
||||||
|
const { id } = context.params as { id: string };
|
||||||
|
const body = await context.body as {
|
||||||
|
nama?: string;
|
||||||
|
email?: string;
|
||||||
|
password?: string;
|
||||||
|
roleId?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updated = await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
...body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: 'User berhasil diupdate',
|
||||||
|
data: updated,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: 'Gagal mengupdate user',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ import Inovasi from "./_lib/inovasi";
|
|||||||
import Lingkungan from "./_lib/lingkungan";
|
import Lingkungan from "./_lib/lingkungan";
|
||||||
import LandingPage from "./_lib/landing_page";
|
import LandingPage from "./_lib/landing_page";
|
||||||
import Pendidikan from "./_lib/pendidikan";
|
import Pendidikan from "./_lib/pendidikan";
|
||||||
|
import User from "./_lib/user";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ROOT = process.cwd();
|
const ROOT = process.cwd();
|
||||||
@@ -89,6 +91,8 @@ const ApiServer = new Elysia()
|
|||||||
.use(Inovasi)
|
.use(Inovasi)
|
||||||
.use(Lingkungan)
|
.use(Lingkungan)
|
||||||
.use(Pendidikan)
|
.use(Pendidikan)
|
||||||
|
.use(User)
|
||||||
|
|
||||||
.onError(({ code }) => {
|
.onError(({ code }) => {
|
||||||
if (code === "NOT_FOUND") {
|
if (code === "NOT_FOUND") {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user