Tambah seeder di bagian landing page
This commit is contained in:
30
prisma/data/fetchWithRetry.ts
Normal file
30
prisma/data/fetchWithRetry.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
export default async function fetchWithRetry(
|
||||||
|
url: string,
|
||||||
|
retries = 3,
|
||||||
|
timeoutMs = 20000
|
||||||
|
) {
|
||||||
|
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, { signal: controller.signal });
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`⚠️ Download attempt ${attempt} failed`);
|
||||||
|
|
||||||
|
if (attempt === retries) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unreachable");
|
||||||
|
}
|
||||||
@@ -1,137 +1,120 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "cmff0rr4z0002vn0twp333m2",
|
"id": "cmk27746i0000vnso2aspwf9g",
|
||||||
"name": "S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
"name": "Eqlrr1W-pK8ShMGqgPGL3-desktop.webp",
|
||||||
"realName": "bares.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/S6RIjFaPvdQm3oq4rM4X9-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff0tnf00003vn0t3kgzi0u0",
|
|
||||||
"name": "_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
|
||||||
"realName": "bicara-darma.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/_pVNEmThU5ICGa8gv3gh_-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff0uykf0004vn0trmmxpgfh",
|
|
||||||
"name": "bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
|
||||||
"realName": "daves.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/bv6rdKvjxkkjUSGLQ0lvB-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff0z34f0005vn0tjtvq519p",
|
|
||||||
"name": "Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
|
||||||
"realName": "mangan.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/Z4hWaV04CvoE20MjccQsV-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff38cyq000bvn0t9f01cz3f",
|
|
||||||
"name": "LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
|
||||||
"realName": "gelah-melah.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/LvLAtOqWojx4sn6NjJWB9-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff0zqvd0007vn0tv6o5hjcq",
|
|
||||||
"name": "gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
|
||||||
"realName": "inovasi-desa-darmasaba.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/gR2mcvAQVgJ2-rM5coYJj-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff1013m0008vn0th7t0d64d",
|
|
||||||
"name": "JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
|
||||||
"realName": "pdkt.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/JpL-9F8-IGztMn8E2ce02-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff10cwq0009vn0tse8dzu3j",
|
|
||||||
"name": "bxAk4AsGbJTC705_IVdes-desktop.webp",
|
|
||||||
"realName": "sajjiana-dharma-raksaka.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/bxAk4AsGbJTC705_IVdes-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff2w5ly000avn0telhct71k",
|
|
||||||
"name": "Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
|
||||||
"realName": "perbekel.png",
|
"realName": "perbekel.png",
|
||||||
"path": "uploads/images",
|
"path": "uploads/images",
|
||||||
"mimeType": "image/webp",
|
"mimeType": "image/webp",
|
||||||
"link": "/api/fileStorage/findUnique/Vbj_osnMJUkGEQGDTLwV--desktop.webp",
|
"link": "/api/fileStorage/findUnique/Eqlrr1W-pK8ShMGqgPGL3-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"id": "cmk20mg320000vnevxy0k73fr",
|
||||||
|
"name": "thpgPSJkBxUIRajZt3AVo-desktop.webp",
|
||||||
|
"realName": "bares.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/thpgPSJkBxUIRajZt3AVo-desktop.webp",
|
||||||
"category": "image"
|
"category": "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmff3joae0000vn6h8sgs0ilg",
|
"id": "cmk20nqmu0001vnevfte29rk0",
|
||||||
"name": "7hox9spUxj56hY_EBYLnj-desktop.webp",
|
"name": "ubna9N6r7RgVWN5plO5mq-desktop.webp",
|
||||||
|
"realName": "bicara-darma.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/ubna9N6r7RgVWN5plO5mq-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk228urs0007vnevi5b66bqn",
|
||||||
|
"name": "Z4i2RRnnlHq2iWj94ldyo-desktop.webp",
|
||||||
|
"realName": "daves.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/Z4i2RRnnlHq2iWj94ldyo-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk20nyen0002vnevd0hfr3u8",
|
||||||
|
"name": "y4yaE4XdUP1TSUGhWPW9h-desktop.webp",
|
||||||
|
"realName": "mangan.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/y4yaE4XdUP1TSUGhWPW9h-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk20o7mf0003vnevohrksm1d",
|
||||||
|
"name": "Vr7CoaYDpk2dIkHx9PxRj-desktop.webp",
|
||||||
|
"realName": "gelah-melah.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/Vr7CoaYDpk2dIkHx9PxRj-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk20of8m0004vnev9ujy5o0l",
|
||||||
|
"name": "ceoB_sg-HOzljN8j_2nZA-desktop.webp",
|
||||||
|
"realName": "inovasi-desa-darmasaba.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/ceoB_sg-HOzljN8j_2nZA-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk20omzq0005vnevgi6f4edu",
|
||||||
|
"name": "vOy5YVUXfHXfiFOHylIN7-desktop.webp",
|
||||||
|
"realName": "pdkt.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/vOy5YVUXfHXfiFOHylIN7-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk20pf3d0006vnev3mkoqpyy",
|
||||||
|
"name": "gE_qcqIbY0mqI6FV9V4CL-desktop.webp",
|
||||||
|
"realName": "sajjiana-dharma-raksaka.png",
|
||||||
|
"path": "uploads/images",
|
||||||
|
"mimeType": "image/webp",
|
||||||
|
"link": "/api/fileStorage/findUnique/gE_qcqIbY0mqI6FV9V4CL-desktop.webp",
|
||||||
|
"category": "image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk2cgqgm0003vn96jun52pik",
|
||||||
|
"name": "q1G995W7cLkC_qquLTlKN-desktop.webp",
|
||||||
"realName": "youtube.png",
|
"realName": "youtube.png",
|
||||||
"path": "uploads/images",
|
"path": "uploads/images",
|
||||||
"mimeType": "image/webp",
|
"mimeType": "image/webp",
|
||||||
"link": "/api/fileStorage/findUnique/7hox9spUxj56hY_EBYLnj-desktop.webp",
|
"link": "/api/fileStorage/findUnique/q1G995W7cLkC_qquLTlKN-desktop.webp",
|
||||||
"category": "image"
|
"category": "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmff3ll130001vn6hkhls3f5y",
|
"id": "cmk2cmr000006vn96qepq6gvl",
|
||||||
"name": "ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
"name": "I6mlQ4nRmPX26gm79C_rM-desktop.webp",
|
||||||
"realName": "gmail.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/ChihV7_1eS-AGtSg9UwMv-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff3mtat0002vn6hs8vyyhdd",
|
|
||||||
"name": "z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
|
||||||
"realName": "facebook.png",
|
"realName": "facebook.png",
|
||||||
"path": "uploads/images",
|
"path": "uploads/images",
|
||||||
"mimeType": "image/webp",
|
"mimeType": "image/webp",
|
||||||
"link": "/api/fileStorage/findUnique/z8v9ZREwOJHKGIRYauROt-desktop.webp",
|
"link": "/api/fileStorage/findUnique/I6mlQ4nRmPX26gm79C_rM-desktop.webp",
|
||||||
"category": "image"
|
"category": "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmff3nv180003vn6h5jvedidq",
|
"id": "cmk2cpeba0009vn966jcrpf3u",
|
||||||
"name": "BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
"name": "WArLC_yvU33MjoqEnQeQ1-desktop.webp",
|
||||||
"realName": "telephone-call.png",
|
|
||||||
"path": "uploads/images",
|
|
||||||
"mimeType": "image/webp",
|
|
||||||
"link": "/api/fileStorage/findUnique/BLjMxTKoCNE31uOURR3IU-desktop.webp",
|
|
||||||
"category": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cmff3oouh0004vn6hd94brzv9",
|
|
||||||
"name": "hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
|
||||||
"realName": "instagram.png",
|
"realName": "instagram.png",
|
||||||
"path": "uploads/images",
|
"path": "uploads/images",
|
||||||
"mimeType": "image/webp",
|
"mimeType": "image/webp",
|
||||||
"link": "/api/fileStorage/findUnique/hkJYAeTNWK_vYaYS20w3I-desktop.webp",
|
"link": "/api/fileStorage/findUnique/WArLC_yvU33MjoqEnQeQ1-desktop.webp",
|
||||||
"category": "image"
|
"category": "image"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmff3q12g0005vn6h5ojov2qa",
|
"id": "cmk2crcl1000cvn96j8pmgmo5",
|
||||||
"name": "6XEoZ9SFu59COpil03Gya-desktop.webp",
|
"name": "D3RPbNiaNSCjacLjeR_qO-desktop.webp",
|
||||||
"realName": "tiktok.png",
|
"realName": "tiktok.png",
|
||||||
"path": "uploads/images",
|
"path": "uploads/images",
|
||||||
"mimeType": "image/webp",
|
"mimeType": "image/webp",
|
||||||
"link": "/api/fileStorage/findUnique/6XEoZ9SFu59COpil03Gya-desktop.webp",
|
"link": "/api/fileStorage/findUnique/D3RPbNiaNSCjacLjeR_qO-desktop.webp",
|
||||||
"category": "image"
|
"category": "image"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,24 +3,24 @@
|
|||||||
"id": "cmds9023u0008vnbe3oxmhwyf",
|
"id": "cmds9023u0008vnbe3oxmhwyf",
|
||||||
"name": "Desa Darmasaba",
|
"name": "Desa Darmasaba",
|
||||||
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg",
|
"iconUrl": "https://www.youtube.com/channel/UCtPw9WOQO7d2HIKzKgel4Xg",
|
||||||
"imageId": "cmff3joae0000vn6h8sgs0ilg"
|
"imageId": "cmk2cgqgm0003vn96jun52pik"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds90oul000bvnbe2bqkptoi",
|
"id": "cmds90oul000bvnbe2bqkptoi",
|
||||||
"name": "Pemerintah Desa Darmasaba",
|
"name": "Pemerintah Desa Darmasaba",
|
||||||
"iconUrl": "https://www.facebook.com/DarmasabaDesaku",
|
"iconUrl": "https://www.facebook.com/DarmasabaDesaku",
|
||||||
"imageId": "cmff3mtat0002vn6hs8vyyhdd"
|
"imageId": "cmk2cmr000006vn96qepq6gvl"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds91i4e000evnbe8gtf1gub",
|
"id": "cmds91i4e000evnbe8gtf1gub",
|
||||||
"name": "ddarmasaba",
|
"name": "ddarmasaba",
|
||||||
"iconUrl": "https://www.instagram.com/ddarmasaba/",
|
"iconUrl": "https://www.instagram.com/ddarmasaba/",
|
||||||
"imageId": "cmff3oouh0004vn6hd94brzv9"
|
"imageId": "cmk2cpeba0009vn966jcrpf3u"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmds92de5000hvnbemlu6sq5x",
|
"id": "cmds92de5000hvnbemlu6sq5x",
|
||||||
"name": "desa.darmasaba",
|
"name": "desa.darmasaba",
|
||||||
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc",
|
"iconUrl": "https://www.tiktok.com/@desa.darmasaba?is_from_webapp=1&sender_device=pc",
|
||||||
"imageId": "cmff3q12g0005vn6h5ojov2qa"
|
"imageId": "cmk2crcl1000cvn96j8pmgmo5"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"id": "edit",
|
"id": "edit",
|
||||||
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
"name": "I.B Surya Prabhawa Manuaba, S.H., M.H.",
|
||||||
"position": "Perbekel Darmasaba periode 2021-2027",
|
"position": "Perbekel Darmasaba periode 2021-2027",
|
||||||
"imageId": "cmff2w5ly000avn0telhct71k"
|
"imageId": "cmk2a2dl6001nvngck1n0k8qc"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,48 +4,55 @@
|
|||||||
"name": "Dmangan",
|
"name": "Dmangan",
|
||||||
"description": "Darmasaba Aman Pangan",
|
"description": "Darmasaba Aman Pangan",
|
||||||
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024",
|
"link": "https://darmasaba.desa.id/berita/61452-kader-d-mangan-berhasil-meraih-prestasi-dalam-ajang-lomba-banjar-bali-quis-bbq-tahun-2024",
|
||||||
"imageId" : "cmff0z34f0005vn0tjtvq519p"
|
"imageId" : "cmk20nyen0002vnevd0hfr3u8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
"id": "cmdr76nqk0008vn5rdddvcxnr",
|
||||||
"name": "Bicara Darmasaba",
|
"name": "Bicara Darmasaba",
|
||||||
"description": "Bicara Darmasaba",
|
"description": "Bicara Darmasaba",
|
||||||
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba",
|
"link": "https://darmasaba.desa.id/berita/42506-bicara-darmasaba",
|
||||||
"imageId" : "cmff0tnf00003vn0t3kgzi0u0"
|
"imageId" : "cmk20nqmu0001vnevfte29rk0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
"id": "cmdr77vbw000bvn5rvpmoq31s",
|
||||||
"name": "Bares",
|
"name": "Bares",
|
||||||
"description": "Darmasaba Recycling Stock/Exchange",
|
"description": "Darmasaba Recycling Stock/Exchange",
|
||||||
"link": "http://darmasaba.desa.id/berita/56722-bares",
|
"link": "http://darmasaba.desa.id/berita/56722-bares",
|
||||||
"imageId" : "cmff0rr4z0002vn0twp333m2"
|
"imageId" : "cmk20mg320000vnevxy0k73fr"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7bxtp000evn5rmy85wihx",
|
"id": "cmdr7bxtp000evn5rmy85wihx",
|
||||||
"name": "Sajjana Dharma Raksaka",
|
"name": "Sajjana Dharma Raksaka",
|
||||||
"description": "Sajjana Dharma Raksaka",
|
"description": "Sajjana Dharma Raksaka",
|
||||||
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf",
|
"link": "https://ppid.badungkab.go.id/storage/dokumen/5RS9dldGkrgzMQq6bKdZsqsVRHI8gffWv4PGfb3r.pdf",
|
||||||
"imageId" : "cmff10cwq0009vn0tse8dzu3j"
|
"imageId" : "cmk20pf3d0006vnev3mkoqpyy"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
"id": "cmdr7dlnk000hvn5r9lur3z35",
|
||||||
"name": "PDKT",
|
"name": "PDKT",
|
||||||
"description": "Perangkat Desa Kuat Teknologi",
|
"description": "Perangkat Desa Kuat Teknologi",
|
||||||
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t",
|
"link": "https://darmasaba.desa.id/berita/53752-p-d-k-t",
|
||||||
"imageId" : "cmff1013m0008vn0th7t0d64d"
|
"imageId" : "cmk20omzq0005vnevgi6f4edu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
"id": "cmdr7ftob000mvn5rfhgdtg8v",
|
||||||
"name": "GM",
|
"name": "GM",
|
||||||
"description": "Galah Melah",
|
"description": "Galah Melah",
|
||||||
"link": "https://darmasaba.desa.id/berita/52880-galah-melah",
|
"link": "https://darmasaba.desa.id/berita/52880-galah-melah",
|
||||||
"imageId" : "cmff38cyq000bvn0t9f01cz3f"
|
"imageId" : "cmk20o7mf0003vnevohrksm1d"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "cmdr7glue000pvn5r6onzslju",
|
"id": "cmdr7glue000pvn5r6onzslju",
|
||||||
"name": "Inovasi Desa Darmasaba",
|
"name": "Inovasi Desa Darmasaba",
|
||||||
"description": "Inovasi Desa Darmasaba",
|
"description": "Inovasi Desa Darmasaba",
|
||||||
"link": "https://darmasaba.desa.id/produk-lokal-desa",
|
"link": "https://darmasaba.desa.id/produk-lokal-desa",
|
||||||
"imageId" : "cmff0zqvd0007vn0tv6o5hjcq"
|
"imageId" : "cmk20of8m0004vnev9ujy5o0l"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cmk228ust0009vnev5p8i377o",
|
||||||
|
"name": "Davest",
|
||||||
|
"description": "<p>DAVEST (Darmasaba Investment) merupakan program inovasi Desa Darmasaba yang bertujuan mempromosikan potensi investasi desa secara terintegrasi melalui media digital dan pendampingan langsung. Program ini menjadi sarana penghubung antara pemerintah desa, pelaku usaha, dan investor dalam rangka mendorong pertumbuhan ekonomi desa yang berkelanjutan.</p><p>DAVEST menyajikan informasi potensi unggulan desa seperti sektor UMKM, pariwisata, ekonomi kreatif, serta peluang investasi berbasis sumber daya lokal dengan prinsip transparansi dan kemudahan akses informasi.</p><p>Di tahun 2024 ini Davest (Darmasaba Village Festival) akan diadakan lagi, dengan berbagai kegiatan pemerdayaan, edukasi dan hiburan yang tentunya lebih waahhhh dari dua tahun lalu. Untuk memantapkan hal tersebut, Pemdes Darmasaba melakukan rapat koordinasi (rakor) Davest 2024 yang dipimpin langsung oleh Perbekel Darmasaba I. B. Surya Prabhawa Manuaba, S.H.,M.H. pada hari Senin (22/1/2024) bertempat di Ruang Shanti Gosana Kantor Perbekel Darmasaba.</p><hr><h3>Tujuan Program</h3><ul><li><p>Meningkatkan daya tarik investasi di Desa Darmasaba</p></li><li><p>Mempromosikan potensi unggulan desa secara profesional</p></li><li><p>Mendorong pertumbuhan ekonomi dan penciptaan lapangan kerja</p></li><li><p>Mendukung visi Desa Darmasaba sebagai desa inovatif dan berdaya saing</p></li></ul><hr><h3>Sasaran Program</h3><ul><li><p>Calon investor lokal dan regional</p></li><li><p>Pelaku UMKM dan kelompok usaha desa</p></li><li><p>Masyarakat Desa Darmasaba</p></li></ul><hr><h3>Bentuk Inovasi</h3><ul><li><p>Inovasi ekonomi desa</p></li><li><p>Inovasi digital</p></li><li><p>Inovasi tata kelola pelayanan investasi</p></li></ul><hr><h3>Ruang Lingkup Kegiatan</h3><ul><li><p>Penyusunan profil potensi investasi desa</p></li><li><p>Digitalisasi informasi investasi desa</p></li><li><p>Promosi peluang investasi melalui media online</p></li><li><p>Fasilitasi komunikasi antara investor dan desa</p></li><li><p>Pendampingan awal investasi berbasis desa</p></li></ul>",
|
||||||
|
"link": "https://darmasaba.desa.id/berita/55862-rakor-davest-2024",
|
||||||
|
"imageId" : "cmk228urs0007vnevi5b66bqn"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
11
prisma/data/resolveImageId.ts
Normal file
11
prisma/data/resolveImageId.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import safeImageId from "./safeImageId";
|
||||||
|
|
||||||
|
export default async function resolveImageIdForSeed(
|
||||||
|
existingImageId: string | null | undefined,
|
||||||
|
seedImageId: string | null | undefined
|
||||||
|
) {
|
||||||
|
if (existingImageId) return existingImageId;
|
||||||
|
|
||||||
|
// ✅ Skip validasi saat seed
|
||||||
|
return await safeImageId(seedImageId, true);
|
||||||
|
}
|
||||||
24
prisma/data/safeImageId.ts
Normal file
24
prisma/data/safeImageId.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
export default async function safeImageId(
|
||||||
|
imageId?: string | null,
|
||||||
|
skipValidation = false // ✅ tambah param
|
||||||
|
) {
|
||||||
|
if (!imageId) return null;
|
||||||
|
|
||||||
|
if (skipValidation) {
|
||||||
|
console.log(`⚠️ Skipping validation for ${imageId} (seed mode)`);
|
||||||
|
return imageId; // langsung return tanpa cek DB
|
||||||
|
}
|
||||||
|
|
||||||
|
const exists = await prisma.fileStorage.findUnique({
|
||||||
|
where: { id: imageId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
console.warn(`⚠️ imageId ${imageId} not found in FileStorage`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
142
prisma/migrations/20260106072549_nico_6_jan2025/migration.sql
Normal file
142
prisma/migrations/20260106072549_nico_6_jan2025/migration.sql
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `dokterdanTenagaMedisId` on the `FasilitasKesehatan` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `tarifDanLayananId` on the `FasilitasKesehatan` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `UserSession` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `permissions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "FasilitasKesehatan" DROP CONSTRAINT "FasilitasKesehatan_dokterdanTenagaMedisId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "FasilitasKesehatan" DROP CONSTRAINT "FasilitasKesehatan_tarifDanLayananId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "User" DROP CONSTRAINT "User_roleId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "UserSession" DROP CONSTRAINT "UserSession_userId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "DokterdanTenagaMedis" ADD COLUMN "jadwalLibur" TEXT,
|
||||||
|
ADD COLUMN "jamBukaLibur" TEXT,
|
||||||
|
ADD COLUMN "jamBukaOperasional" TEXT,
|
||||||
|
ADD COLUMN "jamTutupLibur" TEXT,
|
||||||
|
ADD COLUMN "jamTutupOperasional" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "FasilitasKesehatan" DROP COLUMN "dokterdanTenagaMedisId",
|
||||||
|
DROP COLUMN "tarifDanLayananId";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "MediaSosial" ADD COLUMN "icon" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "roles" ALTER COLUMN "permissions" DROP NOT NULL;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "User";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "UserSession";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "permissions";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"nomor" TEXT NOT NULL,
|
||||||
|
"roleId" TEXT NOT NULL DEFAULT '2',
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"sessionInvalid" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"lastLogin" TIMESTAMP(3),
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"permissions" JSONB,
|
||||||
|
|
||||||
|
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "user_sessions" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"token" TEXT NOT NULL,
|
||||||
|
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
"active" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "user_sessions_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserMenuAccess" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"menuId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserMenuAccess_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_Tarif" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_Tarif_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_Dokter" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_Dokter_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "users_nomor_key" ON "users"("nomor");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "user_sessions_userId_idx" ON "user_sessions"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "user_sessions_token_idx" ON "user_sessions"("token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "UserMenuAccess_userId_menuId_key" ON "UserMenuAccess"("userId", "menuId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_Tarif_B_index" ON "_Tarif"("B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_Dokter_B_index" ON "_Dokter"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "users" ADD CONSTRAINT "users_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "user_sessions" ADD CONSTRAINT "user_sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserMenuAccess" ADD CONSTRAINT "UserMenuAccess_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_Tarif" ADD CONSTRAINT "_Tarif_A_fkey" FOREIGN KEY ("A") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_Tarif" ADD CONSTRAINT "_Tarif_B_fkey" FOREIGN KEY ("B") REFERENCES "TarifDanLayanan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_Dokter" ADD CONSTRAINT "_Dokter_A_fkey" FOREIGN KEY ("A") REFERENCES "DokterdanTenagaMedis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_Dokter" ADD CONSTRAINT "_Dokter_B_fkey" FOREIGN KEY ("B") REFERENCES "FasilitasKesehatan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -1,30 +1,63 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// helpers/safeSeedUnique.ts
|
import prisma from "@/lib/prisma";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
type SafeSeedOptions = {
|
||||||
|
skipUpdate?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
// prisma/safeseedUnique.ts
|
||||||
* Helper generic buat seed dengan upsert aman
|
|
||||||
*/
|
|
||||||
export async function safeSeedUnique<T extends keyof PrismaClient>(
|
export async function safeSeedUnique<T extends keyof PrismaClient>(
|
||||||
model: T,
|
model: T,
|
||||||
where: Record<string, any>,
|
where: Record<string, any>,
|
||||||
data: Record<string, any>
|
data: Record<string, any>,
|
||||||
|
options: SafeSeedOptions = {}
|
||||||
) {
|
) {
|
||||||
const m = prisma[model];
|
const m = prisma[model] as any;
|
||||||
|
if (!m) throw new Error(`Model ${String(model)} tidak ditemukan`);
|
||||||
if (!m) throw new Error(`Model ${String(model)} tidak ditemukan di PrismaClient`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error upsert dynamic
|
// Pastikan `where` berisi field yang benar-benar unique (misal: `id`)
|
||||||
await m.upsert({
|
const result = await m.upsert({
|
||||||
where,
|
where,
|
||||||
update: data,
|
update: options.skipUpdate ? {} : data,
|
||||||
create: { ...where, ...data },
|
create: data, // ✅ Jangan duplikasi `where` ke `create`
|
||||||
});
|
});
|
||||||
console.log(`✅ Seeded ${String(model)} -> ${JSON.stringify(where)}`);
|
console.log(`✅ Seed ${String(model)}:`, where);
|
||||||
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`❌ Gagal seed ${String(model)} -> ${JSON.stringify(where)}`, err);
|
console.error(`❌ Gagal seed ${String(model)}:`, where, err);
|
||||||
|
throw err; // ✅ Rethrow agar seeding berhenti jika kritis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
// import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
// const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
// type SafeSeedOptions = {
|
||||||
|
// skipUpdate?: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export async function safeSeedUnique<T extends keyof PrismaClient>(
|
||||||
|
// model: T,
|
||||||
|
// where: Record<string, any>,
|
||||||
|
// data: Record<string, any>,
|
||||||
|
// options: SafeSeedOptions = {}
|
||||||
|
// ) {
|
||||||
|
// const m = prisma[model] as any;
|
||||||
|
// if (!m) throw new Error(`Model ${String(model)} tidak ditemukan`);
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// await m.upsert({
|
||||||
|
// where,
|
||||||
|
// update: options.skipUpdate ? {} : data,
|
||||||
|
// create: { ...where, ...data },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// console.log(`✅ Seed ${String(model)}:`, where);
|
||||||
|
// } catch (err) {
|
||||||
|
// console.error(`❌ Gagal seed ${String(model)}:`, where, err);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|||||||
177
prisma/seed.ts
177
prisma/seed.ts
@@ -60,8 +60,37 @@ import jenjangPendidikan from "./data/pendidikan/info-sekolah/jenjang-pendidikan
|
|||||||
import seedAssets from "./seed_assets";
|
import seedAssets from "./seed_assets";
|
||||||
import users from "./data/user/users.json";
|
import users from "./data/user/users.json";
|
||||||
import { safeSeedUnique } from "./safeseedUnique";
|
import { safeSeedUnique } from "./safeseedUnique";
|
||||||
|
import safeImageId from "./data/safeImageId";
|
||||||
|
import resolveImageIdForSeed from "./data/resolveImageId";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
// seed assets
|
||||||
|
await prisma.fileStorage.deleteMany();
|
||||||
|
console.log("🗑️ Cleared existing fileStorage records");
|
||||||
|
await seedAssets();
|
||||||
|
|
||||||
|
// // =========== FILE STORAGE ===========
|
||||||
|
console.log("🔄 Seeding file storage...");
|
||||||
|
for (const f of fileStorage) {
|
||||||
|
await safeSeedUnique(
|
||||||
|
"fileStorage",
|
||||||
|
{ name: f.name },
|
||||||
|
{
|
||||||
|
id: f.id,
|
||||||
|
name: f.name,
|
||||||
|
realName: f.realName,
|
||||||
|
path: f.path,
|
||||||
|
mimeType: f.mimeType,
|
||||||
|
link: f.link,
|
||||||
|
category: f.category,
|
||||||
|
deletedAt: null,
|
||||||
|
isActive: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ File storage seeded");
|
||||||
|
|
||||||
console.log("🔄 Seeding roles...");
|
console.log("🔄 Seeding roles...");
|
||||||
|
|
||||||
for (const r of roles) {
|
for (const r of roles) {
|
||||||
@@ -131,112 +160,119 @@ import { safeSeedUnique } from "./safeseedUnique";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("✅ Users seeding completed");
|
console.log("✅ Users seeding completed");
|
||||||
|
|
||||||
// =========== FILE STORAGE ===========
|
|
||||||
console.log("🔄 Seeding file storage...");
|
|
||||||
for (const f of fileStorage) {
|
|
||||||
try {
|
|
||||||
await prisma.fileStorage.upsert({
|
|
||||||
where: { id: f.id },
|
|
||||||
update: {
|
|
||||||
name: f.name,
|
|
||||||
realName: f.realName,
|
|
||||||
path: f.path,
|
|
||||||
mimeType: f.mimeType,
|
|
||||||
link: f.link,
|
|
||||||
category: f.category,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: f.id,
|
|
||||||
name: f.name,
|
|
||||||
realName: f.realName,
|
|
||||||
path: f.path,
|
|
||||||
mimeType: f.mimeType,
|
|
||||||
link: f.link,
|
|
||||||
category: f.category,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`❌ Failed to seed file storage ${f.name}:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log("✅ File storage seeded");
|
|
||||||
// =========== LANDING PAGE ===========
|
// =========== LANDING PAGE ===========
|
||||||
// =========== SUBMENU PROFILE ===========
|
// =========== SUBMENU PROFILE ===========
|
||||||
// =========== PROFILE PEJABAT DESA ===========
|
// =========== PROFILE PEJABAT DESA ===========
|
||||||
|
// In your seed.ts file, update the PejabatDesa seeding section to:
|
||||||
|
console.log("🔄 Seeding Pejabat Desa...");
|
||||||
for (const p of profilePejabatDesa) {
|
for (const p of profilePejabatDesa) {
|
||||||
await prisma.pejabatDesa.upsert({
|
try {
|
||||||
where: { id: p.id },
|
// First, verify the image exists
|
||||||
update: {
|
|
||||||
name: p.name,
|
|
||||||
position: p.position,
|
|
||||||
imageId: p.imageId,
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
id: p.id,
|
|
||||||
name: p.name,
|
|
||||||
position: p.position,
|
|
||||||
imageId: p.imageId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
"✅ profilePejabatDesa seeded without imageId (editable later via UI)"
|
|
||||||
);
|
|
||||||
|
|
||||||
// =========== PROGRAM INOVASI ===========
|
|
||||||
for (const p of programInovasi) {
|
|
||||||
let imageId: string | null = null;
|
|
||||||
|
|
||||||
if (p.imageId) {
|
if (p.imageId) {
|
||||||
const imageExists = await prisma.fileStorage.findUnique({
|
const imageExists = await prisma.fileStorage.findUnique({
|
||||||
where: { id: p.imageId },
|
where: { id: p.imageId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (imageExists) {
|
if (!imageExists) {
|
||||||
imageId = p.imageId;
|
|
||||||
} else {
|
|
||||||
console.warn(
|
console.warn(
|
||||||
`⚠️ imageId ${p.imageId} tidak ditemukan untuk ProgramInovasi ${p.name}`
|
`⚠️ Image not found for PejabatDesa ${p.name}, skipping...`
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await safeSeedUnique(
|
||||||
|
"pejabatDesa",
|
||||||
|
{ id: p.id },
|
||||||
|
{
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
position: p.position,
|
||||||
|
imageId: p.imageId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(`✅ Seeded Pejabat Desa -> ${p.name}`);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`❌ Failed to seed Pejabat Desa ${p.name}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("✅ Pejabat Desa seeding completed");
|
||||||
|
|
||||||
|
// =========== PROGRAM INOVASI ===========
|
||||||
|
// Add this section after the other seed operations in seed.ts
|
||||||
|
console.log("🔄 Seeding Program Inovasi...");
|
||||||
|
|
||||||
|
for (const p of programInovasi) {
|
||||||
|
const existing = await prisma.programInovasi.findUnique({
|
||||||
|
where: { id: p.id },
|
||||||
|
select: { imageId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
let imageId = existing?.imageId; // Pertahankan existing
|
||||||
|
|
||||||
|
// Kalau belum ada imageId, cari berdasarkan name/realName
|
||||||
|
if (!imageId && p.imageId) {
|
||||||
|
// ✅ Cari langsung berdasarkan ID yang ada di p.imageId
|
||||||
|
const fileRecord = await prisma.fileStorage.findUnique({
|
||||||
|
where: { id: p.imageId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fileRecord) {
|
||||||
|
imageId = fileRecord.id;
|
||||||
|
console.log(
|
||||||
|
`✅ Found file by ID: ${fileRecord.name} (${fileRecord.id})`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.warn(`⚠️ File with ID ${p.imageId} not found for ${p.name}`);
|
||||||
|
imageId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await prisma.programInovasi.upsert({
|
await prisma.programInovasi.upsert({
|
||||||
where: { id: p.id },
|
where: { id: p.id },
|
||||||
update: {
|
update: {
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
link: p.link,
|
link: p.link,
|
||||||
imageId: p.imageId,
|
imageId,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
description: p.description,
|
||||||
link: p.link,
|
link: p.link,
|
||||||
imageId: p.imageId,
|
imageId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("program inovasi success ...");
|
|
||||||
|
|
||||||
// =========== MEDIA SOSIAL ===========
|
// =========== MEDIA SOSIAL ===========
|
||||||
for (const p of mediaSosial) {
|
for (const m of mediaSosial) {
|
||||||
|
const existing = await prisma.mediaSosial.findUnique({
|
||||||
|
where: { id: m.id },
|
||||||
|
select: { imageId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageId = await resolveImageIdForSeed(existing?.imageId, m.imageId);
|
||||||
|
|
||||||
await prisma.mediaSosial.upsert({
|
await prisma.mediaSosial.upsert({
|
||||||
where: { id: p.id },
|
where: { id: m.id },
|
||||||
update: {
|
update: {
|
||||||
name: p.name,
|
name: m.name,
|
||||||
iconUrl: p.iconUrl,
|
iconUrl: m.iconUrl,
|
||||||
imageId: p.imageId,
|
// ⛔ JANGAN overwrite imageId sembarangan
|
||||||
|
imageId,
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
id: p.id,
|
id: m.id,
|
||||||
name: p.name,
|
name: m.name,
|
||||||
iconUrl: p.iconUrl,
|
iconUrl: m.iconUrl,
|
||||||
imageId: p.imageId,
|
imageId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("media sosial success ...");
|
console.log("media sosial success ...");
|
||||||
|
|
||||||
// =========== SUBMENU DESA ANTI KORUPSI ===========
|
// =========== SUBMENU DESA ANTI KORUPSI ===========
|
||||||
@@ -1245,9 +1281,6 @@ import { safeSeedUnique } from "./safeseedUnique";
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log("✅ Jenjang Pendidikan seeded successfully");
|
console.log("✅ Jenjang Pendidikan seeded successfully");
|
||||||
|
|
||||||
// seed assets
|
|
||||||
await seedAssets();
|
|
||||||
})()
|
})()
|
||||||
.then(() => prisma.$disconnect())
|
.then(() => prisma.$disconnect())
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
// prisma/seedAssets.ts
|
// prisma/seedAssets.ts
|
||||||
|
import prisma from "@/lib/prisma";
|
||||||
|
import AdmZip from "adm-zip";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import fetch from "node-fetch";
|
import fetchWithRetry from "./data/fetchWithRetry";
|
||||||
import AdmZip from "adm-zip";
|
|
||||||
import prisma from "@/lib/prisma";
|
|
||||||
|
|
||||||
const UPLOADS_DIR =
|
const UPLOADS_DIR =
|
||||||
process.env.WIBU_UPLOAD_DIR || path.join(process.cwd(), "uploads");
|
process.env.WIBU_UPLOAD_DIR || path.join(process.cwd(), "uploads");
|
||||||
@@ -18,7 +19,10 @@ function detectCategory(filename: string): "image" | "document" | "other" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- Helper: recursive walk dir ---
|
// --- Helper: recursive walk dir ---
|
||||||
async function walkDir(dir: string, fileList: string[] = []): Promise<string[]> {
|
async function walkDir(
|
||||||
|
dir: string,
|
||||||
|
fileList: string[] = []
|
||||||
|
): Promise<string[]> {
|
||||||
const entries = await fs.readdir(dir, { withFileTypes: true });
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@@ -41,18 +45,45 @@ export default async function seedAssets() {
|
|||||||
|
|
||||||
// 1. Download zip
|
// 1. Download zip
|
||||||
const url =
|
const url =
|
||||||
"https://cld-dkr-makuro-seafile.wibudev.com/f/ffd5a548a04f47939474/?dl=1";
|
"https://cld-dkr-makuro-seafile.wibudev.com/f/90dd12c9713e42379fcd/?dl=1";
|
||||||
const res = await fetch(url);
|
const res = await fetchWithRetry(url, 3, 20000);
|
||||||
if (!res.ok) throw new Error(`Gagal download assets: ${res.statusText}`);
|
|
||||||
|
// Validasi content-type
|
||||||
|
const contentType = res.headers.get("content-type");
|
||||||
|
if (!contentType?.includes("zip")) {
|
||||||
|
throw new Error(`Invalid content-type (${contentType}). Expected ZIP file`);
|
||||||
|
}
|
||||||
|
|
||||||
const buffer = Buffer.from(await res.arrayBuffer());
|
const buffer = Buffer.from(await res.arrayBuffer());
|
||||||
|
|
||||||
|
// Validasi ukuran file
|
||||||
|
if (buffer.length < 100) {
|
||||||
|
throw new Error("Downloaded ZIP is empty or corrupted");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validasi signature ZIP ("PK")
|
||||||
|
if (buffer.toString("utf8", 0, 2) !== "PK") {
|
||||||
|
throw new Error("Invalid ZIP signature (PK not found)");
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Extract zip ke folder tmp
|
// 2. Extract zip ke folder tmp
|
||||||
const extractDir = path.join(process.cwd(), "tmp_assets");
|
const extractDir = path.join(process.cwd(), "tmp_assets");
|
||||||
await fs.rm(extractDir, { recursive: true, force: true });
|
await fs.rm(extractDir, { recursive: true, force: true });
|
||||||
await fs.mkdir(extractDir, { recursive: true });
|
await fs.mkdir(extractDir, { recursive: true });
|
||||||
|
|
||||||
const zip = new AdmZip(buffer);
|
let zip: AdmZip;
|
||||||
|
|
||||||
|
try {
|
||||||
|
zip = new AdmZip(buffer);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Failed to parse ZIP file (corrupted or invalid)");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
zip.extractAllTo(extractDir, true);
|
zip.extractAllTo(extractDir, true);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Failed to extract ZIP contents");
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Cari semua file valid (recursive)
|
// 3. Cari semua file valid (recursive)
|
||||||
const files = await walkDir(extractDir);
|
const files = await walkDir(extractDir);
|
||||||
@@ -84,7 +115,27 @@ export default async function seedAssets() {
|
|||||||
await fs.copyFile(filePath, targetPath);
|
await fs.copyFile(filePath, targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Simpan ke DB
|
const existing = await prisma.fileStorage.findUnique({
|
||||||
|
where: { name: finalName },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
// Restore kalau soft deleted
|
||||||
|
await prisma.fileStorage.update({
|
||||||
|
where: { name: finalName },
|
||||||
|
data: {
|
||||||
|
path: targetPath,
|
||||||
|
realName: entryName,
|
||||||
|
mimeType,
|
||||||
|
link: `/uploads/${category}/${finalName}`,
|
||||||
|
category,
|
||||||
|
deletedAt: null,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`♻️ restored: ${category}/${finalName}`);
|
||||||
|
} else {
|
||||||
await prisma.fileStorage.create({
|
await prisma.fileStorage.create({
|
||||||
data: {
|
data: {
|
||||||
name: finalName,
|
name: finalName,
|
||||||
@@ -96,6 +147,9 @@ export default async function seedAssets() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`📂 created: ${category}/${finalName}`);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`📂 saved: ${category}/${finalName}`);
|
console.log(`📂 saved: ${category}/${finalName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +157,8 @@ export default async function seedAssets() {
|
|||||||
await fs.rm(extractDir, { recursive: true, force: true });
|
await fs.rm(extractDir, { recursive: true, force: true });
|
||||||
|
|
||||||
console.log("✅ Selesai seed assets!");
|
console.log("✅ Selesai seed assets!");
|
||||||
|
console.log("DB URL (asset):", process.env.DATABASE_URL);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Auto run kalau dipanggil langsung ---
|
// --- Auto run kalau dipanggil langsung ---
|
||||||
|
|||||||
Reference in New Issue
Block a user