diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..00c8fb40 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI Pipeline + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + services: + # Menjalankan PostgreSQL sebagai service di GitHub Actions + postgres: + image: postgres:14 + env: + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + + steps: + # Checkout kode sumber + - name: Checkout code + uses: actions/checkout@v3 + + # Setup Bun + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + + # Install dependencies + - name: Install dependencies + run: bun install + + # Konfigurasi environment variable untuk PostgreSQL dan variabel tambahan + - name: Set up environment variables + run: | + echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}?schema=public" >> .env + echo "PORT=${{ secrets.PORT }}" >> .env + echo "NEXT_PUBLIC_WIBU_URL=${{ secrets.NEXT_PUBLIC_WIBU_URL }}" >> .env + echo "WIBU_UPLOAD_DIR=${{ secrets.WIBU_UPLOAD_DIR }}" >> .env + + # Migrasi database menggunakan Prisma + - name: Apply Prisma schema to database + run: bun prisma db push + + # Seed database (opsional) + - name: Seed database + run: bun prisma db seed + + # Build project + - name: Build project + run: bun run build \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a520..157b8563 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,10 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# uploads +/uploads + +# cache +/cache + diff --git a/bun.lockb b/bun.lockb index c555ee57..624c5ecf 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/compressed_pdf.pdf b/compressed_pdf.pdf deleted file mode 100644 index 4319680e..00000000 Binary files a/compressed_pdf.pdf and /dev/null differ diff --git a/find-port.ts b/find-port.ts new file mode 100644 index 00000000..887273b3 --- /dev/null +++ b/find-port.ts @@ -0,0 +1,68 @@ +import getPort, { portNumbers } from 'get-port'; + +/** + * Mencari port yang tersedia dalam rentang tertentu. + * @param params - Parameter opsional untuk mencari port. + * @param params.count - Jumlah port yang dibutuhkan (default: 1). + * @param params.portStart - Awal rentang port (default: 3000). + * @param params.portEnd - Akhir rentang port (default: 6000). + * @param params.exclude - Daftar port yang harus dikecualikan. + * @returns Array port yang tersedia atau null jika tidak ada port yang cukup. + */ +async function findPort(params?: { count?: number, portStart?: number, portEnd?: number, exclude?: number[] }) { + const { count = 1, portStart = 3000, portEnd = 6000, exclude = [] } = params || {}; + + // Gabungkan port yang dikecualikan + const listPort = [...exclude]; // Hapus .flat() karena tidak diperlukan + const usedPorts = Array.from(new Set(listPort)) as number[]; + + // Validasi input + if (count <= 0) { + throw new Error('Count harus lebih besar dari 0'); + } + if (count > (portEnd - portStart + 1)) { + throw new Error(`Count tidak boleh lebih besar dari range port (${portEnd - portStart + 1})`); + } + if (portStart >= portEnd) { + throw new Error('portStart harus lebih kecil dari portEnd'); + } + if (portStart < 0 || portEnd > 65535) { + throw new Error('Port harus berada dalam rentang 0-65535'); + } + + // Optimasi pencarian port + const availablePorts = new Set(); + const portRange = portNumbers(portStart, portEnd); + const usedPortsSet = new Set(usedPorts); + + for (const port of portRange) { + if (availablePorts.size >= count) break; + + // Skip jika port sudah digunakan + if (usedPortsSet.has(port)) continue; + + try { + const availablePort = await getPort({ + port, + exclude: [...usedPorts, ...Array.from(availablePorts)], + }); + + // Pastikan port yang diperiksa berada dalam rentang yang ditentukan + if (availablePort === port && availablePort >= portStart && availablePort <= portEnd) { + availablePorts.add(port); + } + } catch (error) { + console.warn(`Gagal memeriksa port ${port}:`, error); + continue; // Lanjutkan ke port berikutnya + } + } + + // Jika tidak cukup port yang tersedia, lempar error + if (availablePorts.size < count) { + throw new Error('Tidak cukup port yang tersedia dalam rentang yang diberikan'); + } + + return Array.from(availablePorts); +} + +export default findPort; diff --git a/package.json b/package.json index f2c08e20..5d93f96e 100644 --- a/package.json +++ b/package.json @@ -15,26 +15,31 @@ "@elysiajs/swagger": "^1.2.0", "@mantine/carousel": "^7.16.2", "@mantine/core": "^7.16.2", + "@mantine/dropzone": "^7.17.0", "@mantine/hooks": "^7.16.2", + "@paljs/types": "^8.1.0", "@prisma/client": "^6.3.1", + "@tabler/icons-react": "^3.30.0", "@types/bun": "^1.2.2", "@types/lodash": "^4.17.15", "add": "^2.0.6", "animate.css": "^4.1.1", - "compress-pdf": "^0.5.2", + "bun": "^1.2.2", "elysia": "^1.2.12", "embla-carousel-autoplay": "^8.5.2", "embla-carousel-react": "^7.1.0", "framer-motion": "^12.4.1", + "get-port": "^7.1.0", "lodash": "^4.17.21", "motion": "^12.4.1", + "nanoid": "^5.1.0", "next": "15.1.6", "next-view-transitions": "^0.3.4", + "p-limit": "^6.2.0", "prisma": "^6.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-multi-carousel": "^2.8.5", - "react-scroll-motion": "^0.3.5", + "react-simple-toasts": "^6.1.0", "readdirp": "^4.1.1", "swr": "^2.3.2", "valtio": "^2.1.3" diff --git a/prisma/seed.ts b/prisma/seed.ts index 909ab864..6fa88d8c 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -33,7 +33,16 @@ import prisma from '@/lib/prisma'; } console.log("potensi success ...") +})().then(() => prisma.$disconnect()).catch((e) => { + console.error(e) + prisma.$disconnect() +}); +process.on('exit', () => { + prisma.$disconnect() +}) - -})(); \ No newline at end of file +process.on('SIGINT', () => { + prisma.$disconnect() + process.exit(0) +}) \ No newline at end of file diff --git a/public/assets/images/spash.png b/public/assets/images/spash.png new file mode 100644 index 00000000..c3cfb585 Binary files /dev/null and b/public/assets/images/spash.png differ diff --git a/public/no-image.jpg b/public/no-image.jpg new file mode 100644 index 00000000..8f8d872a Binary files /dev/null and b/public/no-image.jpg differ diff --git a/src/app/_com/SpashScreen.tsx b/src/app/_com/SpashScreen.tsx new file mode 100644 index 00000000..69d22304 --- /dev/null +++ b/src/app/_com/SpashScreen.tsx @@ -0,0 +1,41 @@ +"use client"; + +import colors from "@/con/colors"; +import images from "@/con/images"; +import { Flex, Image, Paper, Stack, Text } from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; + +export default function SpashScreen() { + useShallowEffect(() => { + const timeout = setTimeout(() => { + window.location.href = "/darmasaba"; + }, 3000); + return () => clearTimeout(timeout); + }, []); + return ( + + + + darmasaba + + Pemerintah Desa + + DARMASABA + + + + + + ); +} diff --git a/src/app/admin/_com/AdminNav.tsx b/src/app/admin/_com/AdminNav.tsx new file mode 100644 index 00000000..63cd3bf5 --- /dev/null +++ b/src/app/admin/_com/AdminNav.tsx @@ -0,0 +1,18 @@ +"use client"; +import { Button, Stack } from "@mantine/core"; +import { Link } from "next-view-transitions"; + +export default function AdminNav() { + return ( + + + + + + + ); +} diff --git a/src/app/admin/_com/ListImage.tsx b/src/app/admin/_com/ListImage.tsx new file mode 100644 index 00000000..e8009b4f --- /dev/null +++ b/src/app/admin/_com/ListImage.tsx @@ -0,0 +1,139 @@ +"use client"; +import stateListImage from "@/state/state-list-image"; +import { + ActionIcon, + Box, + Button, + Group, + Image, + Pagination, + Paper, + SimpleGrid, + Stack, + Text, + TextInput, +} from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; +import { IconSearch, IconX } from "@tabler/icons-react"; +import { motion } from "framer-motion"; +import { useState } from "react"; +import toast from "react-simple-toasts"; +import { useSnapshot } from "valtio"; + +export default function ListImage() { + const { list, total } = useSnapshot(stateListImage); + const [loading, setLoading] = useState(false); + + useShallowEffect(() => { + // get url + console.log(window.location.origin); + stateListImage.load(); + }, []); + + let timeOut: NodeJS.Timer; + return ( + + + } + rightSection={ + { + stateListImage.load(); + }} + > + + + } + placeholder="Cari" + onChange={(e) => { + if (timeOut) clearTimeout(timeOut); + timeOut = setTimeout(() => { + stateListImage.load({ search: e.target.value }); + }, 200); + }} + /> + + + {list && + list.map((v, k) => { + return ( + + + { + // copy to clipboard + navigator.clipboard.writeText(v.url); + toast("Berhasil disalin"); + }} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.8 }} + > + {v.name} + + + + {v.name} + + + + + + + + + + + ); + })} + + {total && ( + { + stateListImage.page = e; + stateListImage.load(); + }} + /> + )} + + ); +} diff --git a/src/app/admin/_com/UploadCsv.tsx b/src/app/admin/_com/UploadCsv.tsx new file mode 100644 index 00000000..44e53730 --- /dev/null +++ b/src/app/admin/_com/UploadCsv.tsx @@ -0,0 +1,49 @@ +"use client"; +import ApiFetch from "@/lib/api-fetch"; +import { Group, Stack, Text } from "@mantine/core"; +import { Dropzone } from "@mantine/dropzone"; +import { useState } from "react"; +import toast from "react-simple-toasts"; + +export default function UploadCsv() { + return ( + + + + ); +} + +function DropUpload() { + const [loading, setLoading] = useState(false); + + return ( + + + { + if (droppedFiles.length < 0) { + return toast("Tidak ada file yang diunggah"); + } + + setLoading(true); + for (const file of droppedFiles) { + await ApiFetch.api["upl-csv-single"].post({ + name: file.name, + file, + }); + } + setLoading(false); + }} + > + + Drop Csv here + + + + + ); +} diff --git a/src/app/admin/_com/UploadImage.tsx b/src/app/admin/_com/UploadImage.tsx new file mode 100644 index 00000000..6405439d --- /dev/null +++ b/src/app/admin/_com/UploadImage.tsx @@ -0,0 +1,34 @@ +"use client"; +import ApiFetch from "@/lib/api-fetch"; +import stateListImage from "@/state/state-list-image"; +import { Stack, Text } from "@mantine/core"; +import { Dropzone, IMAGE_MIME_TYPE } from "@mantine/dropzone"; +import { useState } from "react"; + +export default function UploadImage() { + const [loading, setLoading] = useState(false); + return ( + + { + setLoading(true); + for (const file of droppedFiles) { + await ApiFetch.api["upl-img-single"].post({ + file: file, + name: file.name, + }); + } + + setLoading(false); + stateListImage.load(); + }} + > + + Drop images here + + + + ); +} diff --git a/src/app/admin/csv/page.tsx b/src/app/admin/csv/page.tsx new file mode 100644 index 00000000..ffc804b0 --- /dev/null +++ b/src/app/admin/csv/page.tsx @@ -0,0 +1,10 @@ +import { Stack } from "@mantine/core"; +import UploadCsv from "../_com/UploadCsv"; + +export default function Page() { + return ( + + + + ); +} diff --git a/src/app/admin/images/page.tsx b/src/app/admin/images/page.tsx new file mode 100644 index 00000000..462d9701 --- /dev/null +++ b/src/app/admin/images/page.tsx @@ -0,0 +1,12 @@ +import { Stack } from "@mantine/core"; +import ListImage from "../_com/ListImage"; +import UploadImage from "../_com/UploadImage"; + +export default function Page() { + return ( + + + + + ); +} diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx new file mode 100644 index 00000000..b073cdf1 --- /dev/null +++ b/src/app/admin/layout.tsx @@ -0,0 +1,11 @@ +import { Stack } from "@mantine/core"; +import AdminNav from "./_com/AdminNav"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + ); +} diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx new file mode 100644 index 00000000..bf9b4ecd --- /dev/null +++ b/src/app/admin/page.tsx @@ -0,0 +1,9 @@ +import { Container, Stack } from "@mantine/core"; + +export default function Page() { + return ( + + admin + + ); +} diff --git a/src/app/animate/page.tsx b/src/app/animate/page.tsx deleted file mode 100644 index ffb20690..00000000 --- a/src/app/animate/page.tsx +++ /dev/null @@ -1,128 +0,0 @@ -"use client" - -import { - motion, - MotionValue, - useScroll, - useSpring, - useTransform, -} from "motion/react" -import { useRef } from "react" - -function useParallax(value: MotionValue, distance: number) { - return useTransform(value, [0, 1], [-distance, distance]) -} - -function Image({ id }: { id: number }) { - const ref = useRef(null) - const { scrollYProgress } = useScroll({ target: ref }) - const y = useParallax(scrollYProgress, 300) - - return ( -
-
- A London skyscraper -
- {`#00${id}`} -
- ) -} - -export default function Parallax() { - const { scrollYProgress } = useScroll() - const scaleX = useSpring(scrollYProgress, { - stiffness: 100, - damping: 30, - restDelta: 0.001, - }) - - return ( -
- {[1, 2, 3, 4, 5].map((image) => ( - - ))} - - -
- ) -} - -/** - * ============== Styles ================ - */ - -function StyleSheet() { - return ( - - ) -} diff --git a/src/app/api/[[...slugs]]/_lib/img-del.ts b/src/app/api/[[...slugs]]/_lib/img-del.ts new file mode 100644 index 00000000..22b0c7f2 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/img-del.ts @@ -0,0 +1,20 @@ +import fs from "fs/promises"; +import path from "path"; + +async function imgDel({ + name, + UPLOAD_DIR_IMAGE, +}: { + name: string; + UPLOAD_DIR_IMAGE: string; +}) { + try { + await fs.unlink(path.join(UPLOAD_DIR_IMAGE, name)); + return "ok"; + } catch (error) { + console.log(error); + return "error"; + } +} + +export default imgDel; diff --git a/src/app/api/[[...slugs]]/_lib/img.ts b/src/app/api/[[...slugs]]/_lib/img.ts new file mode 100644 index 00000000..4752afbf --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/img.ts @@ -0,0 +1,61 @@ +import fs from "fs/promises"; +import path from "path"; +import sharp from "sharp"; + +async function img({ + name, + UPLOAD_DIR_IMAGE, + ROOT, + size, +}: { + name: string; + UPLOAD_DIR_IMAGE: string; + ROOT: string; + size?: number; // Ukuran opsional (tidak ada default) +}) { + const completeName = path.basename(name); // Nama file lengkap + const ext = path.extname(name).toLowerCase(); // Ekstensi file dalam huruf kecil + // const fileNameWithoutExt = path.basename(name, ext); // Nama file tanpa ekstensi + + // Default image jika terjadi kesalahan + const noImage = path.join(ROOT, "public/no-image.jpg"); + + // Validasi ekstensi file + if (![".jpg", ".jpeg", ".png"].includes(ext)) { + console.warn(`Ekstensi file tidak didukung: ${ext}`); + return new Response(await fs.readFile(noImage), { + headers: { "Content-Type": "image/jpeg" }, + }); + } + + try { + // Path ke file asli + const filePath = path.join(UPLOAD_DIR_IMAGE, completeName); + + // Periksa apakah file ada + await fs.stat(filePath); + + // Metadata gambar asli + const metadata = await sharp(filePath).metadata(); + + // Proses resize menggunakan sharp + const resizedImageBuffer = await sharp(filePath) + .resize(size || metadata.width) // Gunakan size jika diberikan, jika tidak gunakan width asli + .toBuffer(); + + return new Response(resizedImageBuffer, { + headers: { + "Cache-Control": "public, max-age=3600, stale-while-revalidate=600", + "Content-Type": "image/jpeg", + }, + }); + } catch (error) { + console.error(`Gagal memproses file: ${name}`, error); + // Jika file tidak ditemukan atau gagal diproses, kembalikan default image + return new Response(await fs.readFile(noImage), { + headers: { "Content-Type": "image/jpeg" }, + }); + } +} + +export default img; diff --git a/src/app/api/[[...slugs]]/_lib/imgs.ts b/src/app/api/[[...slugs]]/_lib/imgs.ts new file mode 100644 index 00000000..5b3470f7 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/imgs.ts @@ -0,0 +1,30 @@ +import fs from "fs/promises"; + +async function imgs({ + search = "", + page = 1, + count = 20, + UPLOAD_DIR_IMAGE, +}: { + search?: string; + page?: number; + count?: number; + UPLOAD_DIR_IMAGE: string; +}) { + const files = await fs.readdir(UPLOAD_DIR_IMAGE); + + return files + .filter( + (file) => + file.endsWith(".jpg") || file.endsWith(".png") || file.endsWith(".jpeg") + ) + .filter((file) => file.includes(search)) + .slice((page - 1) * count, page * count) + .map((file) => ({ + name: file, + url: `/api/img/${file}`, + total: files.length, + })); +} + +export default imgs; diff --git a/src/app/api/[[...slugs]]/_lib/upl-csv-single.ts b/src/app/api/[[...slugs]]/_lib/upl-csv-single.ts new file mode 100644 index 00000000..c39f9ab2 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/upl-csv-single.ts @@ -0,0 +1,12 @@ +export async function uplCsvSingle({ + fileName, + file, +}: { + fileName: string; + file: File; +}) { + const textFile = await file.text(); + console.log(fileName, textFile); + + return "ok"; +} diff --git a/src/app/api/[[...slugs]]/_lib/upl-csv.ts b/src/app/api/[[...slugs]]/_lib/upl-csv.ts new file mode 100644 index 00000000..c46ee621 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/upl-csv.ts @@ -0,0 +1,16 @@ +async function uplCsv({ files }: { files: File[] }) { + if (!Array.isArray(files) || files.length === 0) { + throw new Error("Tidak ada file yang diunggah"); + } + + for (const file of files) { + const textFile = await file.text(); + const fileName = file.name; + + console.log(textFile, fileName); + } + + return "ok"; +} + +export default uplCsv; diff --git a/src/app/api/[[...slugs]]/_lib/upl-img-single.ts b/src/app/api/[[...slugs]]/_lib/upl-img-single.ts new file mode 100644 index 00000000..a8d7c7a2 --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/upl-img-single.ts @@ -0,0 +1,32 @@ +import { nanoid } from "nanoid"; +import fs from "fs/promises"; +import path from "path"; +import _ from "lodash"; + +export async function uplImgSingle({ + fileName, + file, + UPLOAD_DIR_IMAGE, +}: { + fileName: string; + file: File; + UPLOAD_DIR_IMAGE: string; +}) { + if (!fileName || typeof fileName !== "string" || fileName.trim() === "") { + console.warn(`Nama file tidak valid: ${fileName}`); + fileName = nanoid() + ".jpg"; + } + const ext = path.extname(fileName).toLowerCase(); + const fileNameWithoutExt = path.basename(fileName, ext); + const fileNameKebabCase = _.kebabCase(fileNameWithoutExt) + ext; + + try { + const buffer = Buffer.from(await file.arrayBuffer()); + const filePath = path.join(UPLOAD_DIR_IMAGE, fileNameKebabCase); + await fs.writeFile(filePath, buffer); + return filePath; + } catch (error) { + console.log(error); + return "error"; + } +} diff --git a/src/app/api/[[...slugs]]/_lib/upl-img.ts b/src/app/api/[[...slugs]]/_lib/upl-img.ts new file mode 100644 index 00000000..9fdf482e --- /dev/null +++ b/src/app/api/[[...slugs]]/_lib/upl-img.ts @@ -0,0 +1,52 @@ +import path from "path"; +import fs from "fs/promises"; +import { nanoid } from "nanoid"; + +async function uplImg({ + files, + UPLOAD_DIR_IMAGE, +}: { + files: File[]; + UPLOAD_DIR_IMAGE: string; +}) { + // Validasi input + if (!Array.isArray(files) || files.length === 0) { + throw new Error("Tidak ada file yang diunggah"); + } + + for (const file of files) { + let fileName = file.name; + + // Validasi nama file + if (!fileName || typeof fileName !== "string" || fileName.trim() === "") { + console.warn(`Nama file tidak valid: ${fileName}`); + fileName = nanoid() + ".jpg"; + } + + // Sanitasi nama file untuk mencegah path traversal + const sanitizedFileName = sanitizeFileName(fileName); + + try { + // Konversi file ke buffer + const buffer = Buffer.from(await file.arrayBuffer()); + + // Tulis file ke direktori uploads + const filePath = path.join(UPLOAD_DIR_IMAGE, sanitizedFileName); + await fs.writeFile(filePath, buffer); + + console.log(`File berhasil diunggah: ${sanitizedFileName}`); + } catch (error) { + console.error(`Gagal mengunggah file ${fileName}:`, error); + throw new Error(`Gagal mengunggah file: ${fileName}`); + } + } + + return "ok"; +} + +// Fungsi untuk membersihkan nama file dari karakter yang tidak aman +function sanitizeFileName(fileName: string): string { + return fileName.replace(/[^a-zA-Z0-9._\-]/g, "_"); +} + +export default uplImg; diff --git a/src/app/api/[[...slugs]]/route.ts b/src/app/api/[[...slugs]]/route.ts index c78076ac..a84cb826 100644 --- a/src/app/api/[[...slugs]]/route.ts +++ b/src/app/api/[[...slugs]]/route.ts @@ -1,32 +1,171 @@ import prisma from "@/lib/prisma"; import cors, { HTTPMethod } from "@elysiajs/cors"; import swagger from "@elysiajs/swagger"; -import { Elysia } from "elysia"; +import { Elysia, t } from "elysia"; import getPotensi from "./_lib/get-potensi"; +import img from "./_lib/img"; +import fs from "fs/promises"; +import path from "path"; +import uplImg from "./_lib/upl-img"; +import imgs from "./_lib/imgs"; +import uplCsv from "./_lib/upl-csv"; +import imgDel from "./_lib/img-del"; +import { uplImgSingle } from "./_lib/upl-img-single"; +import { uplCsvSingle } from "./_lib/upl-csv-single"; +const ROOT = process.cwd(); + +if (!process.env.WIBU_UPLOAD_DIR) + throw new Error("WIBU_UPLOAD_DIR is not defined"); + +const UPLOAD_DIR = path.join(ROOT, process.env.WIBU_UPLOAD_DIR); +const UPLOAD_DIR_IMAGE = path.join(UPLOAD_DIR, "image"); + +// create uploads dir +fs.mkdir(UPLOAD_DIR, { + recursive: true, +}).catch(() => {}); + +// create image uploads dir +fs.mkdir(UPLOAD_DIR_IMAGE, { + recursive: true, +}).catch(() => {}); const corsConfig = { - origin: "*", - methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[], - allowedHeaders: "*", - exposedHeaders: "*", - maxAge: 5, - credentials: true, + origin: "*", + methods: ["GET", "POST", "PATCH", "DELETE", "PUT"] as HTTPMethod[], + allowedHeaders: "*", + exposedHeaders: "*", + maxAge: 5, + credentials: true, }; - async function layanan() { - const data = await prisma.layanan.findMany(); - return { data }; + const data = await prisma.layanan.findMany(); + return { data }; } const ApiServer = new Elysia() - .use(swagger({ path: "/api/docs" })) - .use(cors(corsConfig)) - .group("/api", app => app - .get("/layanan", layanan) - .get("/potensi", getPotensi) - ) - - + .use(swagger({ path: "/api/docs" })) + .use(cors(corsConfig)) + .onError(({ code }) => { + if (code === "NOT_FOUND") { + return { + status: 404, + body: "Route not found :(", + }; + } + }) + .group("/api", (app) => + app + .get("/layanan", layanan) + .get("/potensi", getPotensi) + .get( + "/img/:name", + ({ params, query }) => { + return img({ + name: params.name, + UPLOAD_DIR_IMAGE, + ROOT, + size: query.size, + }); + }, + { + params: t.Object({ + name: t.String(), + }), + query: t.Optional( + t.Object({ + size: t.Optional(t.Number()), + }) + ), + } + ) + .delete( + "/img/:name", + ({ params }) => { + return imgDel({ + name: params.name, + UPLOAD_DIR_IMAGE, + }); + }, + { + params: t.Object({ + name: t.String(), + }), + } + ) + .get( + "/imgs", + ({ query }) => { + return imgs({ + search: query.search, + page: query.page, + count: query.count, + UPLOAD_DIR_IMAGE, + }); + }, + { + query: t.Optional( + t.Object({ + page: t.Number({ default: 1 }), + count: t.Number({ default: 10 }), + search: t.String({ default: "" }), + }) + ), + } + ) + .post( + "/upl-img", + ({ body }) => { + console.log(body.title); + return uplImg({ files: body.files, UPLOAD_DIR_IMAGE }); + }, + { + body: t.Object({ + title: t.String(), + files: t.Files({ multiple: true }), + }), + } + ) + .post( + "/upl-img-single", + ({ body }) => { + return uplImgSingle({ + fileName: body.name, + file: body.file, + UPLOAD_DIR_IMAGE, + }); + }, + { + body: t.Object({ + name: t.String(), + file: t.File(), + }), + } + ) + .post( + "/upl-csv-single", + ({ body }) => { + return uplCsvSingle({ fileName: body.name, file: body.file }); + }, + { + body: t.Object({ + name: t.String(), + file: t.File(), + }), + } + ) + .post( + "/upl-csv", + ({ body }) => { + return uplCsv({ files: body.files }); + }, + { + body: t.Object({ + files: t.Files(), + }), + } + ) + ); export const GET = ApiServer.handle; export const POST = ApiServer.handle; @@ -34,5 +173,4 @@ export const PATCH = ApiServer.handle; export const DELETE = ApiServer.handle; export const PUT = ApiServer.handle; -export type AppServer = typeof ApiServer - +export type AppServer = typeof ApiServer; diff --git a/src/app/darmasaba/(pages)/desa/[sub]/page.tsx b/src/app/darmasaba/(pages)/desa/[sub]/page.tsx new file mode 100644 index 00000000..eeeb31a4 --- /dev/null +++ b/src/app/darmasaba/(pages)/desa/[sub]/page.tsx @@ -0,0 +1,8 @@ + + +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return
+ {sub} +
+} \ No newline at end of file diff --git a/src/app/desa/page.tsx b/src/app/darmasaba/(pages)/desa/page.tsx similarity index 100% rename from src/app/desa/page.tsx rename to src/app/darmasaba/(pages)/desa/page.tsx diff --git a/src/app/darmasaba/(pages)/ekonomi/[sub]/page.tsx b/src/app/darmasaba/(pages)/ekonomi/[sub]/page.tsx new file mode 100644 index 00000000..a340d73b --- /dev/null +++ b/src/app/darmasaba/(pages)/ekonomi/[sub]/page.tsx @@ -0,0 +1,5 @@ +export default function Page() { + return
+ ekonomi +
+} \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/ekonomi/page.tsx b/src/app/darmasaba/(pages)/ekonomi/page.tsx new file mode 100644 index 00000000..71ce5a0b --- /dev/null +++ b/src/app/darmasaba/(pages)/ekonomi/page.tsx @@ -0,0 +1,8 @@ +import { Stack } from "@mantine/core"; + +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return + {sub} + +} \ No newline at end of file diff --git a/src/app/darmasaba/(pages)/inovasi/[sub]/page.tsx b/src/app/darmasaba/(pages)/inovasi/[sub]/page.tsx new file mode 100644 index 00000000..49ba22d7 --- /dev/null +++ b/src/app/darmasaba/(pages)/inovasi/[sub]/page.tsx @@ -0,0 +1,6 @@ +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return
+ {sub} +
+} \ No newline at end of file diff --git a/src/app/inovasi/page.tsx b/src/app/darmasaba/(pages)/inovasi/page.tsx similarity index 100% rename from src/app/inovasi/page.tsx rename to src/app/darmasaba/(pages)/inovasi/page.tsx diff --git a/src/app/darmasaba/(pages)/keamanan/[sub]/page.tsx b/src/app/darmasaba/(pages)/keamanan/[sub]/page.tsx new file mode 100644 index 00000000..ff84e5a3 --- /dev/null +++ b/src/app/darmasaba/(pages)/keamanan/[sub]/page.tsx @@ -0,0 +1,8 @@ +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return ( +
+

{sub}

+
+ ); +} \ No newline at end of file diff --git a/src/app/keamanan/page.tsx b/src/app/darmasaba/(pages)/keamanan/page.tsx similarity index 100% rename from src/app/keamanan/page.tsx rename to src/app/darmasaba/(pages)/keamanan/page.tsx diff --git a/src/app/darmasaba/(pages)/kesehatan/[sub]/page.tsx b/src/app/darmasaba/(pages)/kesehatan/[sub]/page.tsx new file mode 100644 index 00000000..2721c2b1 --- /dev/null +++ b/src/app/darmasaba/(pages)/kesehatan/[sub]/page.tsx @@ -0,0 +1,8 @@ +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return ( +
+ {sub} +
+ ); +} \ No newline at end of file diff --git a/src/app/kesehatan/page.tsx b/src/app/darmasaba/(pages)/kesehatan/page.tsx similarity index 100% rename from src/app/kesehatan/page.tsx rename to src/app/darmasaba/(pages)/kesehatan/page.tsx diff --git a/src/app/darmasaba/(pages)/lingkungan/[sub]/page.tsx b/src/app/darmasaba/(pages)/lingkungan/[sub]/page.tsx new file mode 100644 index 00000000..70b0d940 --- /dev/null +++ b/src/app/darmasaba/(pages)/lingkungan/[sub]/page.tsx @@ -0,0 +1,8 @@ + + +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return
+ {sub} +
+} \ No newline at end of file diff --git a/src/app/lingkungan/page.tsx b/src/app/darmasaba/(pages)/lingkungan/page.tsx similarity index 100% rename from src/app/lingkungan/page.tsx rename to src/app/darmasaba/(pages)/lingkungan/page.tsx diff --git a/src/app/darmasaba/(pages)/module/[sub]/page.tsx b/src/app/darmasaba/(pages)/module/[sub]/page.tsx new file mode 100644 index 00000000..951cdbed --- /dev/null +++ b/src/app/darmasaba/(pages)/module/[sub]/page.tsx @@ -0,0 +1,6 @@ +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return
+ {sub} +
+} \ No newline at end of file diff --git a/src/app/module/page.tsx b/src/app/darmasaba/(pages)/module/page.tsx similarity index 100% rename from src/app/module/page.tsx rename to src/app/darmasaba/(pages)/module/page.tsx diff --git a/src/app/darmasaba/(pages)/pendidikan/[sub]/page.tsx b/src/app/darmasaba/(pages)/pendidikan/[sub]/page.tsx new file mode 100644 index 00000000..ff84e5a3 --- /dev/null +++ b/src/app/darmasaba/(pages)/pendidikan/[sub]/page.tsx @@ -0,0 +1,8 @@ +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return ( +
+

{sub}

+
+ ); +} \ No newline at end of file diff --git a/src/app/pendidikan/page.tsx b/src/app/darmasaba/(pages)/pendidikan/page.tsx similarity index 100% rename from src/app/pendidikan/page.tsx rename to src/app/darmasaba/(pages)/pendidikan/page.tsx diff --git a/src/app/darmasaba/(tambahan)/layanan/[sub]/page.tsx b/src/app/darmasaba/(tambahan)/layanan/[sub]/page.tsx new file mode 100644 index 00000000..535f2fba --- /dev/null +++ b/src/app/darmasaba/(tambahan)/layanan/[sub]/page.tsx @@ -0,0 +1,10 @@ +import { Stack } from "@mantine/core"; + +export default async function Page({ params }: { params: Promise<{ sub: string }> }) { + const { sub } = await params + return ( + + {sub} + + ) +} \ No newline at end of file diff --git a/src/app/darmasaba/(tambahan)/layanan/_com/BackButto.tsx b/src/app/darmasaba/(tambahan)/layanan/_com/BackButto.tsx new file mode 100644 index 00000000..80b90b42 --- /dev/null +++ b/src/app/darmasaba/(tambahan)/layanan/_com/BackButto.tsx @@ -0,0 +1,14 @@ +'use client' + +import { ActionIcon } from "@mantine/core"; +import { IconArrowLeft } from "@tabler/icons-react"; +import { useTransitionRouter } from 'next-view-transitions'; + +export default function BackButton() { + const router = useTransitionRouter() + return ( + router.back()}> + + + ); +} \ No newline at end of file diff --git a/src/app/ekonomi/page.tsx b/src/app/darmasaba/(tambahan)/layanan/page.tsx similarity index 86% rename from src/app/ekonomi/page.tsx rename to src/app/darmasaba/(tambahan)/layanan/page.tsx index 6599ef48..055c01a5 100644 --- a/src/app/ekonomi/page.tsx +++ b/src/app/darmasaba/(tambahan)/layanan/page.tsx @@ -2,6 +2,6 @@ import { Stack } from "@mantine/core"; export default function Page() { return - ekonomi + layanan } \ No newline at end of file diff --git a/src/app/darmasaba/(tambahan)/penghargaan/page.tsx b/src/app/darmasaba/(tambahan)/penghargaan/page.tsx new file mode 100644 index 00000000..5d2a6fbe --- /dev/null +++ b/src/app/darmasaba/(tambahan)/penghargaan/page.tsx @@ -0,0 +1,5 @@ +export default function Page() { + return
+ penghargaan +
+} \ No newline at end of file diff --git a/src/com/Footer.tsx b/src/app/darmasaba/_com/Footer.tsx similarity index 73% rename from src/com/Footer.tsx rename to src/app/darmasaba/_com/Footer.tsx index c605978f..9f498093 100644 --- a/src/com/Footer.tsx +++ b/src/app/darmasaba/_com/Footer.tsx @@ -1,8 +1,9 @@ +import colors from "@/con/colors"; import { Stack, Container, Center, Text } from "@mantine/core"; function Footer() { - return + return
Footer diff --git a/src/com/LoadDataFirstClient.tsx b/src/app/darmasaba/_com/LoadDataFirstClient.tsx similarity index 100% rename from src/com/LoadDataFirstClient.tsx rename to src/app/darmasaba/_com/LoadDataFirstClient.tsx diff --git a/src/com/MainLayout.tsx b/src/app/darmasaba/_com/MainLayout.tsx similarity index 56% rename from src/com/MainLayout.tsx rename to src/app/darmasaba/_com/MainLayout.tsx index aa7b3971..52024a4c 100644 --- a/src/com/MainLayout.tsx +++ b/src/app/darmasaba/_com/MainLayout.tsx @@ -1,16 +1,21 @@ -import { Space, Stack } from "@mantine/core"; -import { Navbar } from "./Navbar"; +import colors from "@/con/colors"; +import { Box, Space, Stack } from "@mantine/core"; import Footer from "./Footer"; +import { Navbar } from "./Navbar"; export function MainLayout({ children }: { children: React.ReactNode }) { return ( - + - {children} + + {children} +