Compare commits

...

5 Commits

Author SHA1 Message Date
09c7fd8f3a fix: fileStorage path issues between local and staging environments
- Store relative paths in database instead of absolute paths
- Reconstruct absolute paths at runtime using UPLOAD_DIR env var
- Add VOLUME for /app/uploads in Dockerfile for persistent storage
- Create uploads directory with proper permissions in Docker
- Add error handling for missing UPLOAD_DIR in findUniq.ts
- Simplify GitHub workflow memory in QWEN.md (manual handling)

This fixes the 500 errors on staging for file create/delete operations
caused by environment-specific absolute paths stored in database.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-14 10:57:51 +08:00
656ffcc561 bump: version 0.1.7 -> 0.1.8 - add kependudukan migration
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:41:16 +08:00
76ffa662c5 fix(database): add migration for kependudukan tables
- Add DataBanjar, DistribusiAgama, DistribusiUmur, MigrasiPenduduk, DinamikaPenduduk tables
- Add indexes for performance (tahun, isActive)
- Add JenisMigrasi enum (MASUK, KELUAR)
- Fixes: Error 500 on all CRUD kependudukan endpoints in staging

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:40:53 +08:00
46423409fd bump: version 0.1.6 -> 0.1.7 - auto migration on startup
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:01:08 +08:00
2edf5e9b11 fix(deployment): add auto database migration on container startup
- Create docker-entrypoint.sh to run prisma migrate deploy before app start
- Update Dockerfile to use entrypoint script
- Ensures database schema is always up-to-date after deployment
- Fixes: CRUD kependudukan error 500 di staging karena tabel belum dibuat

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-13 17:00:53 +08:00
8 changed files with 148 additions and 23 deletions

View File

@@ -59,9 +59,16 @@ COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
# Create uploads directory with proper permissions
RUN mkdir -p /app/uploads && chown nextjs:nodejs /app/uploads
USER nextjs
# Persistent storage for uploaded files
VOLUME ["/app/uploads"]
EXPOSE 3000
CMD ["bun", "start"]
CMD ["/app/docker-entrypoint.sh"]

19
QWEN.md
View File

@@ -232,21 +232,4 @@ Common issues and solutions:
6. Verify responsive design on different screen sizes
## Qwen Added Memories
- **GitHub Workflow Execution**: Project ini memiliki 3 workflow GitHub Action:
1. `publish.yml` - Build & push Docker image ke GHCR (manual trigger, butuh input: stack_env + tag)
2. `re-pull.yml` - Re-pull Docker image di Portainer (manual trigger, butuh input: stack_name + stack_env)
3. `docker-publish.yml` - Auto build & push saat ada tag versi v*
Workflow bisa dijalankan via GitHub CLI: `gh workflow run <nama.yml> -f param=value --ref branch`
Setelah commit ke branch deployment (dev/stg/prod), otomatis trigger workflow publish + re-pull untuk deploy ke server.
- **Deployment Workflow Sistematis**:
1. **Version Bump** - Update `version` di `package.json` sebelum deploy (ikuti semver: major.minor.patch)
2. **Commit** - Commit perubahan + version bump dengan pesan yang jelas
3. **Push ke Branch** - Push ke branch target (biasanya `stg` untuk staging atau `prod` untuk production)
4. **Trigger Publish** - Jalankan `gh workflow run publish.yml --ref <branch> -f stack_env=<env> -f tag=<version>`
5. **Trigger Re-Pull** - Jalankan `gh workflow run re-pull.yml -f stack_name=desa-darmasaba -f stack_env=<env>`
6. **Verifikasi** - Cek workflow berhasil dan aplikasi berjalan
Branch deployment: `stg` (staging) atau `prod` (production)
Version format di package.json: `"version": "major.minor.patch"`
- **GitHub Workflows**: Project ini memiliki workflow GitHub Action untuk deployment. User akan menangani workflow secara manual di GitHub.

13
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
set -e
echo "🔄 Running database migrations..."
cd /app
bunx prisma migrate deploy || {
echo "❌ Migration failed!"
exit 1
}
echo "✅ Migrations completed successfully"
echo "🚀 Starting application..."
exec bun start

View File

@@ -1,6 +1,6 @@
{
"name": "desa-darmasaba",
"version": "0.1.6",
"version": "0.1.8",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,112 @@
-- CreateTable
CREATE TABLE "DataBanjar" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"penduduk" INTEGER NOT NULL,
"kk" INTEGER NOT NULL,
"miskin" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DataBanjar_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DistribusiAgama" (
"id" TEXT NOT NULL,
"agama" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DistribusiAgama_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DistribusiUmur" (
"id" TEXT NOT NULL,
"rentangUmur" TEXT NOT NULL,
"jumlah" INTEGER NOT NULL,
"tahun" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DistribusiUmur_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "MigrasiPenduduk" (
"id" TEXT NOT NULL,
"nama" TEXT NOT NULL,
"jenis" "JenisMigrasi" NOT NULL,
"tanggal" TIMESTAMP(3) NOT NULL,
"asal" TEXT,
"tujuan" TEXT,
"alasan" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "MigrasiPenduduk_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DinamikaPenduduk" (
"id" TEXT NOT NULL,
"tahun" INTEGER NOT NULL,
"kelahiran" INTEGER NOT NULL,
"kematian" INTEGER NOT NULL,
"masuk" INTEGER NOT NULL,
"keluar" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),
"isActive" BOOLEAN NOT NULL DEFAULT true,
CONSTRAINT "DinamikaPenduduk_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "DataBanjar_tahun_idx" ON "DataBanjar"("tahun");
-- CreateIndex
CREATE INDEX "DataBanjar_isActive_idx" ON "DataBanjar"("isActive");
-- CreateIndex
CREATE INDEX "DistribusiAgama_tahun_idx" ON "DistribusiAgama"("tahun");
-- CreateIndex
CREATE INDEX "DistribusiAgama_isActive_idx" ON "DistribusiAgama"("isActive");
-- CreateIndex
CREATE INDEX "DistribusiUmur_tahun_idx" ON "DistribusiUmur"("tahun");
-- CreateIndex
CREATE INDEX "DistribusiUmur_isActive_idx" ON "DistribusiUmur"("isActive");
-- CreateIndex
CREATE INDEX "MigrasiPenduduk_tanggal_idx" ON "MigrasiPenduduk"("tanggal");
-- CreateIndex
CREATE INDEX "MigrasiPenduduk_isActive_idx" ON "MigrasiPenduduk"("isActive");
-- CreateIndex
CREATE INDEX "DinamikaPenduduk_tahun_idx" ON "DinamikaPenduduk"("tahun");
-- CreateIndex
CREATE INDEX "DinamikaPenduduk_isActive_idx" ON "DinamikaPenduduk"("isActive");
-- CreateIndex
CREATE UNIQUE INDEX "DinamikaPenduduk_tahun_key" ON "DinamikaPenduduk"("tahun");
-- CreateEnum
CREATE TYPE "JenisMigrasi" AS ENUM ('MASUK', 'KELUAR');

View File

@@ -79,7 +79,7 @@ const fileStorageCreate = async (context: Context) => {
data: {
name: finalName,
realName: file.name,
path: rootPath,
path: pathName, // Store relative path (e.g., "images", "audio", "documents")
mimeType: finalMimeType,
category,
link: `/api/fileStorage/findUnique/${finalName}`,

View File

@@ -36,7 +36,7 @@ const fileStorageDelete = async (context: Context) => {
};
}
const filePath = path.join(file.path, file.name);
const filePath = path.join(UPLOAD_DIR, file.path, file.name);
try {
// Hapus file dari filesystem

View File

@@ -3,6 +3,8 @@ import { Context } from "elysia";
import fs from "fs/promises";
import path from "path";
const UPLOAD_DIR = process.env.WIBU_UPLOAD_DIR;
const fileStorageFindUnique = async (context: Context) => {
const { name } = context.params;
@@ -20,9 +22,17 @@ const fileStorageFindUnique = async (context: Context) => {
};
}
if (!UPLOAD_DIR) {
context.set.status = "Internal Server Error";
return {
status: 500,
message: "UPLOAD_DIR is not defined",
};
}
console.log(data);
const file = await fs.readFile(path.join(data.path, data.name));
const file = await fs.readFile(path.join(UPLOAD_DIR, data.path, data.name));
context.set.headers = {
"Content-Type": data.mimeType,
"Content-Length": file.length,