Compare commits
5 Commits
fix/admin/
...
nico/25-fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 223b85a714 | |||
| f1729151b3 | |||
| 8e8c133eea | |||
| 1e7acac193 | |||
| 42dcbcfb22 |
@@ -26,7 +26,24 @@ export async function seedBerita() {
|
|||||||
|
|
||||||
console.log("🔄 Seeding Berita...");
|
console.log("🔄 Seeding Berita...");
|
||||||
|
|
||||||
|
// Build a map of valid kategori IDs
|
||||||
|
const validKategoriIds = new Set<string>();
|
||||||
|
const kategoriList = await prisma.kategoriBerita.findMany({
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
kategoriList.forEach((k) => validKategoriIds.add(k.id));
|
||||||
|
|
||||||
|
console.log(`📋 Found ${validKategoriIds.size} valid kategori IDs in database`);
|
||||||
|
|
||||||
for (const b of beritaJson) {
|
for (const b of beritaJson) {
|
||||||
|
// Validate kategoriBeritaId exists
|
||||||
|
if (!b.kategoriBeritaId || !validKategoriIds.has(b.kategoriBeritaId)) {
|
||||||
|
console.warn(
|
||||||
|
`⚠️ Skipping berita "${b.judul}": Invalid kategoriBeritaId "${b.kategoriBeritaId}"`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let imageId: string | null = null;
|
let imageId: string | null = null;
|
||||||
|
|
||||||
if (b.imageName) {
|
if (b.imageName) {
|
||||||
@@ -44,26 +61,32 @@ export async function seedBerita() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.berita.upsert({
|
try {
|
||||||
where: { id: b.id },
|
await prisma.berita.upsert({
|
||||||
update: {
|
where: { id: b.id },
|
||||||
judul: b.judul,
|
update: {
|
||||||
deskripsi: b.deskripsi,
|
judul: b.judul,
|
||||||
content: b.content,
|
deskripsi: b.deskripsi,
|
||||||
kategoriBeritaId: b.kategoriBeritaId,
|
content: b.content,
|
||||||
imageId,
|
kategoriBeritaId: b.kategoriBeritaId,
|
||||||
},
|
imageId,
|
||||||
create: {
|
},
|
||||||
id: b.id,
|
create: {
|
||||||
judul: b.judul,
|
id: b.id,
|
||||||
deskripsi: b.deskripsi,
|
judul: b.judul,
|
||||||
content: b.content,
|
deskripsi: b.deskripsi,
|
||||||
kategoriBeritaId: b.kategoriBeritaId,
|
content: b.content,
|
||||||
imageId,
|
kategoriBeritaId: b.kategoriBeritaId,
|
||||||
},
|
imageId,
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`✅ Berita seeded: ${b.judul}`);
|
console.log(`✅ Berita seeded: ${b.judul}`);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(
|
||||||
|
`❌ Failed to seed berita "${b.judul}": ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("🎉 Berita seed selesai");
|
console.log("🎉 Berita seed selesai");
|
||||||
|
|||||||
170
prisma/migrations/20260225082505_deploy/migration.sql
Normal file
170
prisma/migrations/20260225082505_deploy/migration.sql
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to alter the column `nama` on the `KategoriPotensi` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(100)`.
|
||||||
|
- You are about to alter the column `name` on the `PotensiDesa` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(255)`.
|
||||||
|
- You are about to alter the column `kategoriId` on the `PotensiDesa` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(36)`.
|
||||||
|
- A unique constraint covering the columns `[nama]` on the table `KategoriPotensi` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[name]` on the table `PotensiDesa` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Made the column `kategoriId` on table `PotensiDesa` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "DataPerpustakaan" DROP CONSTRAINT "DataPerpustakaan_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "DesaDigital" DROP CONSTRAINT "DesaDigital_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "InfoTekno" DROP CONSTRAINT "InfoTekno_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "KegiatanDesa" DROP CONSTRAINT "KegiatanDesa_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "PengaduanMasyarakat" DROP CONSTRAINT "PengaduanMasyarakat_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "PotensiDesa" DROP CONSTRAINT "PotensiDesa_kategoriId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "ProfileDesaImage" DROP CONSTRAINT "ProfileDesaImage_imageId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CaraMemperolehInformasi" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "CaraMemperolehSalinanInformasi" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DaftarInformasiPublik" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DasarHukumPPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DataPerpustakaan" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DesaDigital" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "FormulirPermohonanKeberatan" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "InfoTekno" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "JenisInformasiDiminta" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "JenisKelaminResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "KategoriPotensi" ALTER COLUMN "nama" SET DATA TYPE VARCHAR(100),
|
||||||
|
ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "KategoriPrestasiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "KegiatanDesa" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "LambangDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "MaskotDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PegawaiPPID" ADD COLUMN "deletedAt" TIMESTAMP(3);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PengaduanMasyarakat" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PermohonanInformasiPublik" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PilihanRatingResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PosisiOrganisasiPPID" ADD COLUMN "deletedAt" TIMESTAMP(3);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PotensiDesa" ALTER COLUMN "name" SET DATA TYPE VARCHAR(255),
|
||||||
|
ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT,
|
||||||
|
ALTER COLUMN "kategoriId" SET NOT NULL,
|
||||||
|
ALTER COLUMN "kategoriId" SET DATA TYPE VARCHAR(36);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PrestasiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ProfileDesaImage" ALTER COLUMN "imageId" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ProfilePPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Responden" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "SejarahDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UmurResponden" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "VisiMisiDesa" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "VisiMisiPPID" ALTER COLUMN "deletedAt" DROP NOT NULL,
|
||||||
|
ALTER COLUMN "deletedAt" DROP DEFAULT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "KategoriPotensi_nama_key" ON "KategoriPotensi"("nama");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "PotensiDesa_name_key" ON "PotensiDesa"("name");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ProfileDesaImage" ADD CONSTRAINT "ProfileDesaImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PotensiDesa" ADD CONSTRAINT "PotensiDesa_kategoriId_fkey" FOREIGN KEY ("kategoriId") REFERENCES "KategoriPotensi"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DesaDigital" ADD CONSTRAINT "DesaDigital_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "InfoTekno" ADD CONSTRAINT "InfoTekno_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "PengaduanMasyarakat" ADD CONSTRAINT "PengaduanMasyarakat_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "KegiatanDesa" ADD CONSTRAINT "KegiatanDesa_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "DataPerpustakaan" ADD CONSTRAINT "DataPerpustakaan_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "FileStorage"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
# Please do not edit this file manually
|
# Please do not edit this file manually
|
||||||
# It should be added in your version-control system (e.g., Git)
|
# It should be added in your version-control system (e.g., Git)
|
||||||
provider = "postgresql"
|
provider = "postgresql"
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function ListKategoriBerita({ search }: { search: string }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<TableTr>
|
<TableTr>
|
||||||
<TableTd colSpan={4}>
|
<TableTd colSpan={3}> {/* ✅ Match column count (3 columns) */}
|
||||||
<Center py={24}>
|
<Center py={24}>
|
||||||
<Text c="dimmed" fz="sm" lh={1.4}>
|
<Text c="dimmed" fz="sm" lh={1.4}>
|
||||||
Tidak ada data kategori berita yang cocok
|
Tidak ada data kategori berita yang cocok
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ function ListBerita({ search }: { search: string }) {
|
|||||||
<Pagination
|
<Pagination
|
||||||
value={page}
|
value={page}
|
||||||
onChange={(newPage) => {
|
onChange={(newPage) => {
|
||||||
load(newPage, 10);
|
load(newPage, 10, debouncedSearch); // ✅ Include search parameter
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
total={totalPages}
|
total={totalPages}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function Page() {
|
|||||||
fz={{ base: 'md', md: 'lg' }}
|
fz={{ base: 'md', md: 'lg' }}
|
||||||
lh={{ base: 1.4, md: 1.4 }}
|
lh={{ base: 1.4, md: 1.4 }}
|
||||||
>
|
>
|
||||||
{perbekel.nama || "I.B. Surya Prabhawa Manuaba, S.H., M.H."}
|
I.B. Surya Prabhawa Manuaba, S.H., M.H.
|
||||||
</Text>
|
</Text>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { authStore } from "@/store/authStore";
|
|
||||||
import { useDarkMode } from "@/state/darkModeStore";
|
|
||||||
import { themeTokens, getActiveStateStyles } from "@/utils/themeTokens";
|
|
||||||
import { DarkModeToggle } from "@/components/admin/DarkModeToggle";
|
import { DarkModeToggle } from "@/components/admin/DarkModeToggle";
|
||||||
|
import { useDarkMode } from "@/state/darkModeStore";
|
||||||
|
import { authStore } from "@/store/authStore";
|
||||||
|
import { themeTokens } from "@/utils/themeTokens";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
AppShell,
|
AppShell,
|
||||||
|
|||||||
@@ -2,15 +2,49 @@ import prisma from "@/lib/prisma";
|
|||||||
import { Context } from "elysia";
|
import { Context } from "elysia";
|
||||||
|
|
||||||
export default async function kategoriBeritaDelete(context: Context) {
|
export default async function kategoriBeritaDelete(context: Context) {
|
||||||
const id = context.params.id as string;
|
try {
|
||||||
|
const id = context.params?.id as string;
|
||||||
|
|
||||||
await prisma.kategoriBerita.delete({
|
if (!id) {
|
||||||
where: { id },
|
return Response.json({
|
||||||
});
|
success: false,
|
||||||
|
message: "ID tidak boleh kosong",
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
// ✅ Cek apakah kategori masih digunakan oleh berita
|
||||||
status: 200,
|
const beritaCount = await prisma.berita.count({
|
||||||
success: true,
|
where: {
|
||||||
message: "Sukses Menghapus kategori berita",
|
kategoriBeritaId: id,
|
||||||
};
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (beritaCount > 0) {
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: `Kategori tidak dapat dihapus karena masih digunakan oleh ${beritaCount} berita`,
|
||||||
|
}, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Soft delete (bukan hard delete)
|
||||||
|
await prisma.kategoriBerita.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Kategori berita berhasil dihapus",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Delete kategori error:", error);
|
||||||
|
return Response.json({
|
||||||
|
success: false,
|
||||||
|
message: "Gagal menghapus kategori: " + (error instanceof Error ? error.message : 'Unknown error'),
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -23,10 +23,9 @@ export default async function findUnique(
|
|||||||
|
|
||||||
// ✅ Filter by isActive and deletedAt
|
// ✅ Filter by isActive and deletedAt
|
||||||
const data = await prisma.potensiDesa.findFirst({
|
const data = await prisma.potensiDesa.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
image: true,
|
image: true,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export default async function kategoriPotensiDelete(context: Context) {
|
|||||||
where: {
|
where: {
|
||||||
kategoriId: id,
|
kategoriId: id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { requireAuth } from "@/lib/api-auth";
|
import { requireAuth } from "@/lib/api-auth";
|
||||||
|
|
||||||
export default async function sejarahDesaFindFirst(request: Request) {
|
export default async function sejarahDesaFindFirst() {
|
||||||
// ✅ Authentication check
|
// ✅ Authentication check
|
||||||
const headers = new Headers(request.url);
|
const authResult = await requireAuth();
|
||||||
const authResult = await requireAuth({ headers });
|
|
||||||
if (!authResult.authenticated) {
|
if (!authResult.authenticated) {
|
||||||
return authResult.response;
|
return authResult.response;
|
||||||
}
|
}
|
||||||
@@ -12,9 +11,8 @@ export default async function sejarahDesaFindFirst(request: Request) {
|
|||||||
try {
|
try {
|
||||||
// Get the first active record
|
// Get the first active record
|
||||||
const data = await prisma.sejarahDesa.findFirst({
|
const data = await prisma.sejarahDesa.findFirst({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null
|
|
||||||
},
|
},
|
||||||
orderBy: { createdAt: 'asc' } // Get the oldest one first
|
orderBy: { createdAt: 'asc' } // Get the oldest one first
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ const SejarahDesa = new Elysia({
|
|||||||
prefix: "/sejarah",
|
prefix: "/sejarah",
|
||||||
tags: ["Desa/Profile"],
|
tags: ["Desa/Profile"],
|
||||||
})
|
})
|
||||||
.get("/first", async (context) => {
|
.get("/first", async () => {
|
||||||
const response = await sejarahDesaFindFirst(new Request(context.request));
|
const response = await sejarahDesaFindFirst();
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.get("/:id", async (context) => {
|
.get("/:id", async (context) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Context } from "elysia";
|
|||||||
|
|
||||||
export default async function sejarahDesaUpdate(context: Context) {
|
export default async function sejarahDesaUpdate(context: Context) {
|
||||||
// ✅ Authentication check
|
// ✅ Authentication check
|
||||||
const authResult = await requireAuth(context);
|
const authResult = await requireAuth();
|
||||||
if (!authResult.authenticated) {
|
if (!authResult.authenticated) {
|
||||||
return authResult.response;
|
return authResult.response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,17 @@ fs.mkdir(UPLOAD_DIR_IMAGE, {
|
|||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
const corsConfig = {
|
const corsConfig = {
|
||||||
origin: "*",
|
origin: [
|
||||||
methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[],
|
"http://localhost:3000",
|
||||||
allowedHeaders: "*",
|
"http://localhost:3001",
|
||||||
|
"https://cld-dkr-desa-darmasaba-stg.wibudev.com",
|
||||||
|
"https://cld-dkr-staging-desa-darmasaba.wibudev.com",
|
||||||
|
"*", // Allow all origins in development
|
||||||
|
],
|
||||||
|
methods: ["GET", "POST", "PATCH", "DELETE", "PUT", "OPTIONS"] as HTTPMethod[],
|
||||||
|
allowedHeaders: ["Content-Type", "Authorization", "*"],
|
||||||
exposedHeaders: "*",
|
exposedHeaders: "*",
|
||||||
maxAge: 5,
|
maxAge: 86400, // 24 hours
|
||||||
credentials: true,
|
credentials: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useDarkMode } from '@/state/darkModeStore';
|
import { useDarkMode } from '@/state/darkModeStore';
|
||||||
import { themeTokens } from '@/utils/themeTokens';
|
import { themeTokens } from '@/utils/themeTokens';
|
||||||
import { Paper, Box, BoxProps, Divider, DividerProps } from '@mantine/core';
|
import { Box, BoxProps, Divider, DividerProps, Paper } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +22,6 @@ import React from 'react';
|
|||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Unified Card Component
|
// Unified Card Component
|
||||||
* ============================================================================
|
|
||||||
|
|
||||||
interface UnifiedCardProps extends BoxProps {
|
interface UnifiedCardProps extends BoxProps {
|
||||||
withBorder?: boolean;
|
withBorder?: boolean;
|
||||||
@@ -63,12 +62,18 @@ export function UnifiedCard({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getShadow = () => {
|
||||||
|
if (shadow === 'none') return 'none';
|
||||||
|
return tokens.shadows[shadow];
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
withBorder={withBorder}
|
withBorder={withBorder}
|
||||||
bg={tokens.colors.bg.card}
|
bg={tokens.colors.bg.card}
|
||||||
p={getPadding()}
|
p={getPadding()}
|
||||||
radius={tokens.radius.lg} // 12-16px sesuai spec
|
radius={tokens.radius.lg} // 12-16px sesuai spec
|
||||||
|
shadow={getShadow()}
|
||||||
style={{
|
style={{
|
||||||
borderColor: tokens.colors.border.default,
|
borderColor: tokens.colors.border.default,
|
||||||
transition: hoverable
|
transition: hoverable
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { themeTokens, getResponsiveFz } from '@/utils/themeTokens';
|
|||||||
import { Text, Title, Box, BoxProps } from '@mantine/core';
|
import { Text, Title, Box, BoxProps } from '@mantine/core';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
type TextTruncate = 'end' | 'start' | boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unified Typography Components
|
* Unified Typography Components
|
||||||
*
|
*
|
||||||
@@ -73,7 +75,7 @@ export function UnifiedTitle({
|
|||||||
const getColor = () => {
|
const getColor = () => {
|
||||||
if (color === 'primary') return tokens.colors.text.primary;
|
if (color === 'primary') return tokens.colors.text.primary;
|
||||||
if (color === 'secondary') return tokens.colors.text.secondary;
|
if (color === 'secondary') return tokens.colors.text.secondary;
|
||||||
if (color === 'brand') return tokens.colors.brand;
|
if (color === 'brand') return tokens.colors.text.brand;
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,8 +111,14 @@ interface UnifiedTextProps {
|
|||||||
align?: 'left' | 'center' | 'right';
|
align?: 'left' | 'center' | 'right';
|
||||||
color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string;
|
color?: 'primary' | 'secondary' | 'tertiary' | 'muted' | 'brand' | 'link' | string;
|
||||||
lineClamp?: number;
|
lineClamp?: number;
|
||||||
truncate?: 'start' | 'end' | 'middle' | boolean;
|
truncate?: TextTruncate;
|
||||||
span?: boolean;
|
span?: boolean;
|
||||||
|
mt?: string;
|
||||||
|
mb?: string;
|
||||||
|
ml?: string;
|
||||||
|
mr?: string;
|
||||||
|
mx?: string;
|
||||||
|
my?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +131,12 @@ export function UnifiedText({
|
|||||||
lineClamp,
|
lineClamp,
|
||||||
truncate,
|
truncate,
|
||||||
span = false,
|
span = false,
|
||||||
|
mt,
|
||||||
|
mb,
|
||||||
|
ml,
|
||||||
|
mr,
|
||||||
|
mx,
|
||||||
|
my,
|
||||||
style,
|
style,
|
||||||
}: UnifiedTextProps) {
|
}: UnifiedTextProps) {
|
||||||
const { isDark } = useDarkMode();
|
const { isDark } = useDarkMode();
|
||||||
@@ -163,7 +177,7 @@ export function UnifiedText({
|
|||||||
case 'muted':
|
case 'muted':
|
||||||
return tokens.colors.text.muted;
|
return tokens.colors.text.muted;
|
||||||
case 'brand':
|
case 'brand':
|
||||||
return tokens.colors.brand;
|
return tokens.colors.text.brand;
|
||||||
case 'link':
|
case 'link':
|
||||||
return tokens.colors.text.link;
|
return tokens.colors.text.link;
|
||||||
default:
|
default:
|
||||||
@@ -177,7 +191,7 @@ export function UnifiedText({
|
|||||||
|
|
||||||
if (span) {
|
if (span) {
|
||||||
return (
|
return (
|
||||||
<Text.Span
|
<Text
|
||||||
ta={align}
|
ta={align}
|
||||||
fz={typo.fz}
|
fz={typo.fz}
|
||||||
fw={fw}
|
fw={fw}
|
||||||
@@ -185,10 +199,16 @@ export function UnifiedText({
|
|||||||
c={textColor}
|
c={textColor}
|
||||||
lineClamp={lineClamp}
|
lineClamp={lineClamp}
|
||||||
truncate={truncate}
|
truncate={truncate}
|
||||||
|
mt={mt}
|
||||||
|
mb={mb}
|
||||||
|
ml={ml}
|
||||||
|
mr={mr}
|
||||||
|
mx={mx}
|
||||||
|
my={my}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text.Span>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,6 +221,12 @@ export function UnifiedText({
|
|||||||
c={textColor}
|
c={textColor}
|
||||||
lineClamp={lineClamp}
|
lineClamp={lineClamp}
|
||||||
truncate={truncate}
|
truncate={truncate}
|
||||||
|
mt={mt}
|
||||||
|
mb={mb}
|
||||||
|
ml={ml}
|
||||||
|
mr={mr}
|
||||||
|
mx={mx}
|
||||||
|
my={my}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Authentication helper untuk API endpoints
|
* Authentication helper untuk API endpoints
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* import { requireAuth } from "@/lib/api-auth";
|
* import { requireAuth } from "@/lib/api-auth";
|
||||||
*
|
*
|
||||||
* export default async function myEndpoint(context: Context) {
|
* export default async function myEndpoint() {
|
||||||
* const authResult = await requireAuth(context);
|
* const authResult = await requireAuth();
|
||||||
* if (!authResult.authenticated) {
|
* if (!authResult.authenticated) {
|
||||||
* return authResult.response;
|
* return authResult.response;
|
||||||
* }
|
* }
|
||||||
@@ -13,24 +13,24 @@
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getSession } from "@/lib/session";
|
import { getSession, SessionData } from "@/lib/session";
|
||||||
|
|
||||||
export type AuthResult =
|
export type AuthResult =
|
||||||
| { authenticated: true; user: any }
|
| { authenticated: true; user: NonNullable<SessionData["user"]> }
|
||||||
| { authenticated: false; response: Response };
|
| { authenticated: false; response: Response };
|
||||||
|
|
||||||
export async function requireAuth(context: any): Promise<AuthResult> {
|
export async function requireAuth(): Promise<AuthResult> {
|
||||||
try {
|
try {
|
||||||
// Cek session dari cookies
|
// Cek session dari cookies
|
||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
|
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
return {
|
return {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
response: new Response(JSON.stringify({
|
response: new Response(JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: "Unauthorized - Silakan login terlebih dahulu"
|
message: "Unauthorized - Silakan login terlebih dahulu"
|
||||||
}), {
|
}), {
|
||||||
status: 401,
|
status: 401,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
})
|
})
|
||||||
@@ -44,7 +44,7 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
|||||||
response: new Response(JSON.stringify({
|
response: new Response(JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: "Akun Anda tidak aktif. Hubungi administrator."
|
message: "Akun Anda tidak aktif. Hubungi administrator."
|
||||||
}), {
|
}), {
|
||||||
status: 403,
|
status: 403,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
})
|
})
|
||||||
@@ -55,14 +55,13 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
|||||||
authenticated: true,
|
authenticated: true,
|
||||||
user: session.user
|
user: session.user
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("Auth error:", error);
|
|
||||||
return {
|
return {
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
response: new Response(JSON.stringify({
|
response: new Response(JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
message: "Authentication error"
|
message: "Authentication error"
|
||||||
}), {
|
}), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
})
|
})
|
||||||
@@ -74,11 +73,11 @@ export async function requireAuth(context: any): Promise<AuthResult> {
|
|||||||
* Optional auth - tidak error jika tidak authenticated
|
* Optional auth - tidak error jika tidak authenticated
|
||||||
* Berguna untuk endpoint yang bisa diakses public atau private
|
* Berguna untuk endpoint yang bisa diakses public atau private
|
||||||
*/
|
*/
|
||||||
export async function optionalAuth(context: any): Promise<any> {
|
export async function optionalAuth(): Promise<NonNullable<SessionData["user"]> | null> {
|
||||||
try {
|
try {
|
||||||
const session = await getSession();
|
const session = await getSession();
|
||||||
return session?.user || null;
|
return session?.user || null;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,10 @@ export const themeTokens = (isDark: boolean = false): ThemeTokens => {
|
|||||||
hoverSoft: 'rgba(25, 113, 194, 0.03)',
|
hoverSoft: 'rgba(25, 113, 194, 0.03)',
|
||||||
hoverMedium: 'rgba(25, 113, 194, 0.05)',
|
hoverMedium: 'rgba(25, 113, 194, 0.05)',
|
||||||
activeAccent: 'rgba(25, 113, 194, 0.1)',
|
activeAccent: 'rgba(25, 113, 194, 0.1)',
|
||||||
|
success: '#22c55e',
|
||||||
|
warning: '#facc15',
|
||||||
|
error: '#ef4444',
|
||||||
|
info: '#38bdf8',
|
||||||
};
|
};
|
||||||
|
|
||||||
const current = isDark ? darkColors : lightColors;
|
const current = isDark ? darkColors : lightColors;
|
||||||
@@ -381,3 +385,7 @@ export const getActiveStateStyles = (isActive: boolean, isDark: boolean = false)
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user