feat(umkm): migrate KategoriProduk to KategoriProdukUmkm for UMKM isolation
- update prisma schema to use KategoriProdukUmkm for Umkm model - add @@map to KategoriProdukUmkm for lowercase table naming - update API endpoints and KPI dashboard to use new model - bump version to 0.1.33
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# deploy-stg
|
# deploy-stg
|
||||||
|
|
||||||
Deploy ke staging environment secara penuh: version bump, cek migrasi, commit, push, trigger GitHub workflow (publish + re-pull), dan verifikasi versi.
|
Deploy ke staging environment secara penuh menggunakan MCP server `deploy-stg`.
|
||||||
|
|
||||||
**Repo GitHub:** `bipproductbali/desa-darmasaba`
|
**Repo GitHub:** `bipprojectbali/desa-darmasaba`
|
||||||
**Branch stg:** `stg`
|
**Branch stg:** `stg`
|
||||||
**STG URL:** `https://desa-darmasaba-stg.wibudev.com`
|
**STG URL:** `https://desa-darmasaba-stg.wibudev.com`
|
||||||
|
|
||||||
@@ -10,37 +10,30 @@ Deploy ke staging environment secara penuh: version bump, cek migrasi, commit, p
|
|||||||
|
|
||||||
## Alur Eksekusi
|
## Alur Eksekusi
|
||||||
|
|
||||||
### Langkah 0 — Cek /api/version
|
### Langkah 0 — Cek /api/version endpoint
|
||||||
Sebelum apapun, pastikan endpoint `/api/version` sudah ada:
|
Pastikan endpoint `/api/version` sudah ada di API:
|
||||||
```bash
|
```bash
|
||||||
grep -n '"/version"' src/app/api/\[\[...slugs\]\]/route.ts
|
grep -n '"/version"' src/app/api/\[\[...slugs\]\]/route.ts
|
||||||
```
|
```
|
||||||
Jika belum ada, tambahkan langsung ke main API group (referensi dari package.json via `fs.readFile`).
|
Jika belum ada, tambahkan ke main API group yang membaca `version` dari `package.json`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 1 — Version Bump
|
### Langkah 1 — Version Bump
|
||||||
|
|
||||||
Baca versi saat ini dari `package.json`, bump patch version (+1), lalu tulis ulang:
|
Gunakan MCP tool `bump_version` (server: `deploy-stg`).
|
||||||
```bash
|
|
||||||
# Baca versi saat ini
|
Tool otomatis baca `package.json`, increment patch (+1), tulis kembali.
|
||||||
node -e "const p=require('./package.json'); const [maj,min,pat]=p.version.split('.').map(Number); console.log(maj+'.'+min+'.'+(pat+1))"
|
Catat `new_version` dari response — akan dipakai di Langkah 5 dan 6.
|
||||||
```
|
|
||||||
Update `package.json` dengan versi baru. Simpan versi baru sebagai `$NEW_VERSION`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 2 — Cek Prisma Migration
|
### Langkah 2 — Cek & Buat Migration
|
||||||
|
|
||||||
```bash
|
Gunakan MCP tool `check_migrations` (server: `deploy-stg`).
|
||||||
bunx prisma migrate status
|
|
||||||
```
|
|
||||||
|
|
||||||
- Jika output mengandung kata `pending` atau `drift` → buat migrasi baru:
|
- Jika `needs_migration: true` → jalankan `create_migration` dengan `name: bump-stg-<new_version>`
|
||||||
```bash
|
- Jika `is_up_to_date: true` → lanjut ke Langkah 3
|
||||||
bunx prisma migrate dev --name bump-stg-<new_version>
|
|
||||||
```
|
|
||||||
- Jika sudah up-to-date → lanjut ke langkah berikutnya.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -50,126 +43,78 @@ bunx prisma migrate status
|
|||||||
bun run build
|
bun run build
|
||||||
```
|
```
|
||||||
|
|
||||||
Jika build gagal, **stop** dan perbaiki error dulu sebelum melanjutkan deploy.
|
Jika build gagal → **stop**, perbaiki error sebelum melanjutkan.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 4 — Commit
|
### Langkah 4 — Commit & Push ke stg
|
||||||
|
|
||||||
Stage semua perubahan dan commit:
|
Gunakan MCP tool `commit_and_push_stg` (server: `deploy-stg`).
|
||||||
```bash
|
|
||||||
git add package.json
|
Tool otomatis stage `package.json` + `prisma/migrations/`, commit dengan message yang menyertakan versi baru, lalu push ke branch `stg` menggunakan `GH_TOKEN`.
|
||||||
git add prisma/migrations/ # jika ada migrasi baru
|
|
||||||
git commit -m "chore: bump version to $NEW_VERSION for stg deploy"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 5 — Push ke origin/stg
|
### Langkah 5 — Trigger publish.yml
|
||||||
|
|
||||||
```bash
|
Gunakan MCP tool `trigger_publish` (server: `deploy-stg`):
|
||||||
git push origin HEAD:stg
|
- `stack_env`: `stg` (default)
|
||||||
```
|
- `tag`: nilai `new_version` dari Langkah 1 (sama persis dengan versi di `package.json`)
|
||||||
|
|
||||||
Tunggu push selesai sebelum trigger workflow.
|
Tool mengembalikan `run_id`. Poll dengan `watch_workflow_run` setiap 30 detik hingga `status == "completed"`:
|
||||||
|
- `conclusion == "success"` → lanjut ke Langkah 6
|
||||||
|
- `conclusion != "success"` → **stop**, tampilkan error, jangan lanjut ke re-pull
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 6 — Trigger publish.yml
|
### Langkah 6 — Trigger re-pull.yml
|
||||||
|
|
||||||
**Input 1:** `stack_env` = `stg`
|
Setelah publish berhasil, gunakan MCP tool `trigger_repull` (server: `deploy-stg`):
|
||||||
**Input 2:** `tag` = versi dari package.json (contoh: `0.1.25`)
|
- `stack_name`: otomatis dari env `STACK_NAME` (tidak perlu diisi manual)
|
||||||
|
- `stack_env`: `stg`
|
||||||
|
|
||||||
```bash
|
Stack yang di-deploy: `<STACK_NAME>-stg`. Poll dengan `watch_workflow_run` setiap 30 detik hingga `status == "completed"`.
|
||||||
gh workflow run publish.yml \
|
|
||||||
--repo bipprojectbali/desa-darmasaba \
|
|
||||||
--ref stg \
|
|
||||||
-f stack_env=stg \
|
|
||||||
-f tag=$NEW_VERSION
|
|
||||||
```
|
|
||||||
|
|
||||||
Tunggu 5 detik lalu dapatkan run ID:
|
|
||||||
```bash
|
|
||||||
sleep 5
|
|
||||||
RUN_ID=$(gh run list \
|
|
||||||
--workflow=publish.yml \
|
|
||||||
--repo bipprojectbali/desa-darmasaba \
|
|
||||||
--limit 1 \
|
|
||||||
--json databaseId \
|
|
||||||
-q '.[0].databaseId')
|
|
||||||
```
|
|
||||||
|
|
||||||
Monitor sampai selesai:
|
|
||||||
```bash
|
|
||||||
gh run watch $RUN_ID --repo bipprojectbali/desa-darmasaba
|
|
||||||
```
|
|
||||||
|
|
||||||
Jika publish **gagal** → **stop**, jangan lanjut ke re-pull.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 7 — Trigger re-pull.yml
|
### Langkah 7 — Verifikasi Versi
|
||||||
|
|
||||||
Setelah publish berhasil, trigger re-pull:
|
Gunakan MCP tool `check_stg_version` (server: `deploy-stg`):
|
||||||
|
- `wait_seconds`: `30` (tunggu container siap)
|
||||||
|
|
||||||
**Input 1:** `stack_env` = `stg`
|
Tool otomatis fetch `BASE_URL/api/version` dan bandingkan dengan versi lokal.
|
||||||
**Input 2:** `stack_name` = `desa-darmasaba` → stack yang di-deploy: `desa-darmasaba-stg`
|
- `match: true` → **Deploy berhasil!**
|
||||||
|
- `match: false` → cek container logs di Portainer atau jalankan `gh run view <run_id> --repo bipprojectbali/desa-darmasaba --log`
|
||||||
```bash
|
|
||||||
gh workflow run re-pull.yml \
|
|
||||||
--repo bipprojectbali/desa-darmasaba \
|
|
||||||
--ref main \
|
|
||||||
-f stack_name=desa-darmasaba \
|
|
||||||
-f stack_env=stg
|
|
||||||
```
|
|
||||||
|
|
||||||
Tunggu 5 detik lalu monitor:
|
|
||||||
```bash
|
|
||||||
sleep 5
|
|
||||||
REPULL_ID=$(gh run list \
|
|
||||||
--workflow=re-pull.yml \
|
|
||||||
--repo bipprojectbali/desa-darmasaba \
|
|
||||||
--limit 1 \
|
|
||||||
--json databaseId \
|
|
||||||
-q '.[0].databaseId')
|
|
||||||
gh run watch $REPULL_ID --repo bipprojectbali/desa-darmasaba
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Langkah 8 — Verifikasi Versi
|
## Ringkasan MCP Tools
|
||||||
|
|
||||||
Bandingkan versi di stg dengan versi lokal:
|
| Tool | Server | Tujuan |
|
||||||
|
|------|--------|--------|
|
||||||
```bash
|
| `bump_version` | `deploy-stg` | Increment patch version di package.json |
|
||||||
# Versi lokal
|
| `check_migrations` | `deploy-stg` | Cek status Prisma migrations |
|
||||||
LOCAL_VER=$(node -e "console.log(require('./package.json').version)")
|
| `create_migration` | `deploy-stg` | Buat migration baru jika diperlukan |
|
||||||
|
| `commit_and_push_stg` | `deploy-stg` | Commit + push ke branch stg |
|
||||||
# Versi di STG (tunggu container siap ~30 detik)
|
| `trigger_publish` | `deploy-stg` | Trigger publish.yml (build Docker image) |
|
||||||
sleep 30
|
| `watch_workflow_run` | `deploy-stg` | Poll status workflow run |
|
||||||
STG_VER=$(curl -s https://desa-darmasaba-stg.wibudev.com/api/version | node -e "process.stdin.resume();let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>console.log(JSON.parse(d).version))")
|
| `trigger_repull` | `deploy-stg` | Trigger re-pull.yml (redeploy di Portainer) |
|
||||||
|
| `check_stg_version` | `deploy-stg` | Bandingkan versi lokal vs STG |
|
||||||
echo "Local : $LOCAL_VER"
|
|
||||||
echo "STG : $STG_VER"
|
|
||||||
```
|
|
||||||
|
|
||||||
- Jika `LOCAL_VER == STG_VER` → **Deploy berhasil!**
|
|
||||||
- Jika berbeda → cek logs container di Portainer atau jalankan `gh run view $REPULL_ID --repo bipprojectbali/desa-darmasaba --log`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ringkasan Workflow Inputs
|
## Ringkasan Workflow Inputs
|
||||||
|
|
||||||
| Workflow | Input | Value |
|
| Workflow | Input | Value |
|
||||||
|----------|-------|-------|
|
|----------|-------|-------|
|
||||||
| `publish.yml` | `stack_env` | `stg` |
|
| `publish.yml` | `stack_env` | `stg` |
|
||||||
| `publish.yml` | `tag` | versi dari `package.json` (e.g. `0.1.25`) |
|
| `publish.yml` | `tag` | versi dari `package.json` (e.g. `0.1.26`) |
|
||||||
| `re-pull.yml` | `stack_name` | `desa-darmasaba` |
|
| `publish.yml` | `stack_name` | dari env `STACK_NAME` (opsional) |
|
||||||
|
| `re-pull.yml` | `stack_name` | dari env `STACK_NAME` (otomatis) |
|
||||||
| `re-pull.yml` | `stack_env` | `stg` |
|
| `re-pull.yml` | `stack_env` | `stg` |
|
||||||
|
|
||||||
## Catatan
|
## Catatan
|
||||||
|
|
||||||
- Jangan jalankan `re-pull.yml` jika `publish.yml` belum selesai/berhasil.
|
- Jangan jalankan `re-pull.yml` jika `publish.yml` belum selesai/berhasil.
|
||||||
- Verifikasi versi dilakukan via `/api/version` (bukan `/api/utils/version`).
|
- Isi `GH_TOKEN` di `.env` sebelum menjalankan deploy (bisa sama dengan `GH_TOKEN`).
|
||||||
- Jika `gh` belum login: `gh auth login`.
|
- `BASE_URL` di `.env` sudah diset ke `https://desa-darmasaba-stg.wibudev.com`.
|
||||||
- Untuk cek status workflow manual: `gh run list --repo bipprojectbali/desa-darmasaba`.
|
- `STACK_NAME` di `.env` diset ke nama stack (e.g. `desa-darmasaba`) — dipakai otomatis oleh `trigger_publish` dan `trigger_repull`.
|
||||||
|
- Verifikasi versi via `/api/version` (bukan `/api/utils/version`).
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* MCP Server: GitHub Actions Workflow Tools
|
* MCP Server: GitHub Actions Workflow Tools + Deploy-STG Pre/Post Steps
|
||||||
* Tools: trigger_publish, trigger_repull, get_workflow_runs, watch_workflow_run
|
* Tools: trigger_publish, trigger_repull, get_workflow_runs, watch_workflow_run,
|
||||||
|
* check_migrations, create_migration, bump_version, commit_and_push_stg, check_stg_version
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { execSync, spawnSync } from "child_process";
|
import { spawnSync } from "child_process";
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
const REPO = "bipprojectbali/desa-darmasaba";
|
const REPO = "bipprojectbali/desa-darmasaba";
|
||||||
|
const CWD = process.cwd();
|
||||||
|
|
||||||
// --- MCP Protocol Helpers ---
|
// --- MCP Protocol Helpers ---
|
||||||
|
|
||||||
@@ -24,8 +28,12 @@ function respondError(id, code, message) {
|
|||||||
|
|
||||||
// --- Shell Helper ---
|
// --- Shell Helper ---
|
||||||
|
|
||||||
function runCmd(cmd) {
|
function runCmd(cmd, opts = {}) {
|
||||||
const r = spawnSync("sh", ["-c", cmd], { encoding: "utf-8" });
|
const r = spawnSync("sh", ["-c", cmd], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
cwd: CWD,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
if (r.error) return { ok: false, out: r.error.message };
|
if (r.error) return { ok: false, out: r.error.message };
|
||||||
if (r.status !== 0) return { ok: false, out: (r.stderr || r.stdout || "").trim() };
|
if (r.status !== 0) return { ok: false, out: (r.stderr || r.stdout || "").trim() };
|
||||||
return { ok: true, out: (r.stdout || "").trim() };
|
return { ok: true, out: (r.stdout || "").trim() };
|
||||||
@@ -41,21 +49,75 @@ function getLatestRunId(workflow, delaySecs = 3) {
|
|||||||
// --- Tool Definitions ---
|
// --- Tool Definitions ---
|
||||||
|
|
||||||
const TOOLS = [
|
const TOOLS = [
|
||||||
|
{
|
||||||
|
name: "check_migrations",
|
||||||
|
description: "Cek status Prisma migrations. Returns apakah ada pending migrations.",
|
||||||
|
inputSchema: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "create_migration",
|
||||||
|
description: "Buat Prisma migration baru (prisma migrate dev). Jalankan hanya jika check_migrations mendeteksi pending/drift.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
description: "Nama migration (e.g. bump-stg-0.1.26)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bump_version",
|
||||||
|
description: "Baca versi dari package.json, increment patch (+1), tulis kembali. Returns old_version dan new_version.",
|
||||||
|
inputSchema: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "commit_and_push_stg",
|
||||||
|
description: "Stage package.json + prisma/migrations, commit, lalu push ke branch stg menggunakan GH_TOKEN.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
message: {
|
||||||
|
type: "string",
|
||||||
|
description: "Commit message. Jika tidak diisi, auto-generate dari versi package.json.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "check_stg_version",
|
||||||
|
description: "Bandingkan versi lokal (package.json) dengan versi di STG (/api/version). Tunggu container siap jika perlu.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
wait_seconds: {
|
||||||
|
type: "number",
|
||||||
|
description: "Detik tunggu sebelum fetch STG (default: 30, untuk memberi waktu container siap).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "trigger_publish",
|
name: "trigger_publish",
|
||||||
description:
|
description:
|
||||||
"Trigger publish.yml workflow: build & push Docker image to GHCR. Returns run ID.",
|
"Trigger publish.yml workflow: build & push Docker image ke GHCR. Returns run ID.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
stack_env: {
|
stack_env: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: ["dev", "stg", "prod"],
|
enum: ["dev", "stg", "prod"],
|
||||||
description: "Target environment (branch dengan nama ini akan di-checkout)",
|
description: "Target environment. Default: stg.",
|
||||||
},
|
},
|
||||||
tag: {
|
tag: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Image tag, biasanya versi dari package.json (e.g. 0.1.25)",
|
description: "Image tag — harus sama persis dengan versi di package.json (e.g. 0.1.25).",
|
||||||
|
},
|
||||||
|
stack_name: {
|
||||||
|
type: "string",
|
||||||
|
description: `Nama stack. Default: nilai env STACK_NAME (${process.env.STACK_NAME || "belum diset"}).`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["stack_env", "tag"],
|
required: ["stack_env", "tag"],
|
||||||
@@ -64,21 +126,21 @@ const TOOLS = [
|
|||||||
{
|
{
|
||||||
name: "trigger_repull",
|
name: "trigger_repull",
|
||||||
description:
|
description:
|
||||||
"Trigger re-pull.yml workflow: redeploy stack di Portainer. Hanya jalankan setelah publish berhasil.",
|
"Trigger re-pull.yml workflow: redeploy stack di Portainer. Jalankan HANYA setelah publish berhasil.",
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
stack_name: {
|
stack_name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "Nama stack (e.g. desa-darmasaba). Stack yang di-deploy: <stack_name>-<stack_env>",
|
description: `Nama stack (e.g. desa-darmasaba). Stack yang di-deploy: <stack_name>-<stack_env>. Default: nilai env STACK_NAME (${process.env.STACK_NAME || "belum diset"}).`,
|
||||||
},
|
},
|
||||||
stack_env: {
|
stack_env: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: ["dev", "stg", "prod"],
|
enum: ["dev", "stg", "prod"],
|
||||||
description: "Target environment",
|
description: "Target environment.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["stack_name", "stack_env"],
|
required: ["stack_env"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,7 +170,7 @@ const TOOLS = [
|
|||||||
properties: {
|
properties: {
|
||||||
run_id: {
|
run_id: {
|
||||||
type: "number",
|
type: "number",
|
||||||
description: "ID workflow run (dapat dari get_workflow_runs)",
|
description: "ID workflow run (dapat dari trigger_publish / trigger_repull / get_workflow_runs)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
required: ["run_id"],
|
required: ["run_id"],
|
||||||
@@ -118,16 +180,164 @@ const TOOLS = [
|
|||||||
|
|
||||||
// --- Tool Handlers ---
|
// --- Tool Handlers ---
|
||||||
|
|
||||||
|
function handleCheckMigrations() {
|
||||||
|
const r = runCmd("bunx prisma migrate status 2>&1", { timeout: 30000 });
|
||||||
|
const out = r.out || "";
|
||||||
|
const hasPending =
|
||||||
|
out.includes("following migration(s) have not yet been applied") ||
|
||||||
|
out.includes("drift") ||
|
||||||
|
out.includes("pending");
|
||||||
|
const isUpToDate =
|
||||||
|
out.includes("Database schema is up to date") ||
|
||||||
|
out.includes("up to date");
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
needs_migration: hasPending && !isUpToDate,
|
||||||
|
is_up_to_date: isUpToDate,
|
||||||
|
output: out,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCreateMigration(args) {
|
||||||
|
const { name } = args;
|
||||||
|
const r = runCmd(
|
||||||
|
`echo "" | bunx prisma migrate dev --name ${name} --skip-generate 2>&1`,
|
||||||
|
{ timeout: 120000 }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
ok: r.ok,
|
||||||
|
output: r.out,
|
||||||
|
error: r.ok ? undefined : r.out,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBumpVersion() {
|
||||||
|
const pkgPath = join(CWD, "package.json");
|
||||||
|
let pkg;
|
||||||
|
try {
|
||||||
|
pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: `Gagal baca package.json: ${e.message}` };
|
||||||
|
}
|
||||||
|
const oldVersion = pkg.version;
|
||||||
|
const [maj, min, pat] = oldVersion.split(".").map(Number);
|
||||||
|
const newVersion = `${maj}.${min}.${pat + 1}`;
|
||||||
|
pkg.version = newVersion;
|
||||||
|
try {
|
||||||
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: `Gagal tulis package.json: ${e.message}` };
|
||||||
|
}
|
||||||
|
return { ok: true, old_version: oldVersion, new_version: newVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCommitAndPushStg(args) {
|
||||||
|
const { message } = args || {};
|
||||||
|
|
||||||
|
// Baca versi untuk auto commit message
|
||||||
|
let version = "unknown";
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(join(CWD, "package.json"), "utf-8"));
|
||||||
|
version = pkg.version;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const commitMsg = message || `chore: bump version to ${version} for stg deploy`;
|
||||||
|
|
||||||
|
// Stage file
|
||||||
|
runCmd("git add package.json 2>&1");
|
||||||
|
runCmd("git add prisma/migrations/ 2>&1");
|
||||||
|
|
||||||
|
// Cek apakah ada yang di-stage
|
||||||
|
const statusR = runCmd("git diff --cached --name-only 2>&1");
|
||||||
|
if (!statusR.out.trim()) {
|
||||||
|
return { ok: false, error: "Tidak ada perubahan yang di-stage untuk di-commit." };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit
|
||||||
|
const commitR = runCmd(`git commit -m "${commitMsg}" 2>&1`);
|
||||||
|
if (!commitR.ok) return { ok: false, step: "commit", error: commitR.out };
|
||||||
|
|
||||||
|
// Push ke stg menggunakan GH_TOKEN jika tersedia
|
||||||
|
const token = process.env.GH_TOKEN;
|
||||||
|
const pushCmd = token
|
||||||
|
? `git push https://x-access-token:${token}@github.com/${REPO}.git HEAD:stg 2>&1`
|
||||||
|
: `git push origin HEAD:stg 2>&1`;
|
||||||
|
const pushR = runCmd(pushCmd, { timeout: 60000 });
|
||||||
|
if (!pushR.ok) return { ok: false, step: "push", error: pushR.out };
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
message: `Berhasil commit "${commitMsg}" dan push ke stg`,
|
||||||
|
version,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckStgVersion(args) {
|
||||||
|
const waitSecs = (args && args.wait_seconds) || 30;
|
||||||
|
const baseUrl = process.env.BASE_URL;
|
||||||
|
if (!baseUrl) {
|
||||||
|
return { ok: false, error: "BASE_URL tidak ada di environment — isi di .env" };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baca versi lokal
|
||||||
|
let localVersion;
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(join(CWD, "package.json"), "utf-8"));
|
||||||
|
localVersion = pkg.version;
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: `Gagal baca package.json: ${e.message}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tunggu container siap
|
||||||
|
if (waitSecs > 0) {
|
||||||
|
runCmd(`sleep ${waitSecs}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch versi dari STG
|
||||||
|
const r = runCmd(`curl -sf --max-time 10 "${baseUrl}/api/version" 2>&1`);
|
||||||
|
if (!r.ok) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: `Gagal fetch ${baseUrl}/api/version: ${r.out}`,
|
||||||
|
local_version: localVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let stgVersion;
|
||||||
|
try {
|
||||||
|
stgVersion = JSON.parse(r.out).version;
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: `Gagal parse response: ${r.out}`,
|
||||||
|
local_version: localVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = localVersion === stgVersion;
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
local_version: localVersion,
|
||||||
|
stg_version: stgVersion,
|
||||||
|
match,
|
||||||
|
status: match
|
||||||
|
? "✓ DEPLOY BERHASIL — versi STG sudah sesuai"
|
||||||
|
: "✗ VERSI BERBEDA — cek container logs di Portainer",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function handleTriggerPublish(args) {
|
function handleTriggerPublish(args) {
|
||||||
const { stack_env, tag } = args;
|
const { stack_env = "stg", tag, stack_name } = args;
|
||||||
const triggerCmd = `gh workflow run publish.yml --repo ${REPO} --ref ${stack_env} -f stack_env=${stack_env} -f tag=${tag}`;
|
const resolvedStackName = stack_name || process.env.STACK_NAME;
|
||||||
|
let triggerCmd = `gh workflow run publish.yml --repo ${REPO} --ref ${stack_env} -f stack_env=${stack_env} -f tag=${tag}`;
|
||||||
|
if (resolvedStackName) triggerCmd += ` -f stack_name=${resolvedStackName}`;
|
||||||
const r = runCmd(triggerCmd);
|
const r = runCmd(triggerCmd);
|
||||||
if (!r.ok) return { ok: false, error: r.out };
|
if (!r.ok) return { ok: false, error: r.out };
|
||||||
|
|
||||||
const runId = getLatestRunId("publish.yml");
|
const runId = getLatestRunId("publish.yml");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: `Workflow publish.yml berhasil di-trigger (env=${stack_env}, tag=${tag})`,
|
message: `Workflow publish.yml di-trigger (env=${stack_env}, tag=${tag}${resolvedStackName ? `, stack=${resolvedStackName}` : ""})`,
|
||||||
run_id: runId ? Number(runId) : null,
|
run_id: runId ? Number(runId) : null,
|
||||||
monitor_hint: runId
|
monitor_hint: runId
|
||||||
? `Gunakan watch_workflow_run dengan run_id: ${runId}`
|
? `Gunakan watch_workflow_run dengan run_id: ${runId}`
|
||||||
@@ -136,7 +346,12 @@ function handleTriggerPublish(args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleTriggerRepull(args) {
|
function handleTriggerRepull(args) {
|
||||||
const { stack_name, stack_env } = args;
|
const resolvedStackName = args.stack_name || process.env.STACK_NAME;
|
||||||
|
const { stack_env } = args;
|
||||||
|
if (!resolvedStackName) {
|
||||||
|
return { ok: false, error: "stack_name wajib diisi atau set env STACK_NAME di .mcp.json" };
|
||||||
|
}
|
||||||
|
const stack_name = resolvedStackName;
|
||||||
const triggerCmd = `gh workflow run re-pull.yml --repo ${REPO} --ref main -f stack_name=${stack_name} -f stack_env=${stack_env}`;
|
const triggerCmd = `gh workflow run re-pull.yml --repo ${REPO} --ref main -f stack_name=${stack_name} -f stack_env=${stack_env}`;
|
||||||
const r = runCmd(triggerCmd);
|
const r = runCmd(triggerCmd);
|
||||||
if (!r.ok) return { ok: false, error: r.out };
|
if (!r.ok) return { ok: false, error: r.out };
|
||||||
@@ -144,7 +359,7 @@ function handleTriggerRepull(args) {
|
|||||||
const runId = getLatestRunId("re-pull.yml");
|
const runId = getLatestRunId("re-pull.yml");
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: `Workflow re-pull.yml berhasil di-trigger (stack=${stack_name}-${stack_env})`,
|
message: `Workflow re-pull.yml di-trigger (stack=${stack_name}-${stack_env})`,
|
||||||
run_id: runId ? Number(runId) : null,
|
run_id: runId ? Number(runId) : null,
|
||||||
monitor_hint: runId
|
monitor_hint: runId
|
||||||
? `Gunakan watch_workflow_run dengan run_id: ${runId}`
|
? `Gunakan watch_workflow_run dengan run_id: ${runId}`
|
||||||
@@ -199,7 +414,7 @@ function handleMessage(msg) {
|
|||||||
respond(id, {
|
respond(id, {
|
||||||
protocolVersion: "2024-11-05",
|
protocolVersion: "2024-11-05",
|
||||||
capabilities: { tools: {} },
|
capabilities: { tools: {} },
|
||||||
serverInfo: { name: "github-actions", version: "1.0.0" },
|
serverInfo: { name: "deploy-stg", version: "2.0.0" },
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -220,7 +435,12 @@ function handleMessage(msg) {
|
|||||||
const { name, arguments: args = {} } = params;
|
const { name, arguments: args = {} } = params;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
if (name === "trigger_publish") result = handleTriggerPublish(args);
|
if (name === "check_migrations") result = handleCheckMigrations();
|
||||||
|
else if (name === "create_migration") result = handleCreateMigration(args);
|
||||||
|
else if (name === "bump_version") result = handleBumpVersion();
|
||||||
|
else if (name === "commit_and_push_stg") result = handleCommitAndPushStg(args);
|
||||||
|
else if (name === "check_stg_version") result = handleCheckStgVersion(args);
|
||||||
|
else if (name === "trigger_publish") result = handleTriggerPublish(args);
|
||||||
else if (name === "trigger_repull") result = handleTriggerRepull(args);
|
else if (name === "trigger_repull") result = handleTriggerRepull(args);
|
||||||
else if (name === "get_workflow_runs") result = handleGetWorkflowRuns(args);
|
else if (name === "get_workflow_runs") result = handleGetWorkflowRuns(args);
|
||||||
else if (name === "watch_workflow_run") result = handleWatchWorkflowRun(args);
|
else if (name === "watch_workflow_run") result = handleWatchWorkflowRun(args);
|
||||||
|
|||||||
16
.mcp.json
16
.mcp.json
@@ -1,15 +1,13 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"github": {
|
"deploy-stg": {
|
||||||
"command": "sh",
|
|
||||||
"args": [
|
|
||||||
"-c",
|
|
||||||
"GITHUB_PERSONAL_ACCESS_TOKEN=$(gh auth token) npx -y @modelcontextprotocol/server-github"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"github-actions": {
|
|
||||||
"command": "node",
|
"command": "node",
|
||||||
"args": [".claude/mcp/github-actions.mjs"]
|
"args": ["--env-file=.env", ".claude/mcp/github-actions.mjs"],
|
||||||
|
"env": {
|
||||||
|
"GH_TOKEN": "${GH_TOKEN}",
|
||||||
|
"BASE_URL": "${BASE_URL}",
|
||||||
|
"STACK_NAME": "${STACK_NAME}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
90
MIND/PLAN/migrate-kategori-produk-umkm.md
Normal file
90
MIND/PLAN/migrate-kategori-produk-umkm.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Plan: Migrasi KategoriProduk → KategoriProdukUmkm untuk UMKM
|
||||||
|
|
||||||
|
## Tujuan
|
||||||
|
Ganti relasi kategori di model `Umkm` dan seluruh CRUD kategori UMKM agar menggunakan model `KategoriProdukUmkm` (bukan `KategoriProduk`). Model `KategoriProduk` tetap dipertahankan untuk `PasarDesa` dan `KategoriToPasar`.
|
||||||
|
|
||||||
|
## Analisis Kondisi Saat Ini
|
||||||
|
|
||||||
|
### Schema Prisma
|
||||||
|
- `Umkm` → `kategori KategoriProduk @relation(...)` + `kategoriId String`
|
||||||
|
- `KategoriProdukUmkm` punya relasi `Umkm[]` tapi Umkm belum punya FK ke sana (schema tidak konsisten)
|
||||||
|
- `KategoriProduk` dipakai oleh: `Umkm[]`, `PasarDesa[]`, `KategoriToPasar[]`
|
||||||
|
|
||||||
|
### File yang Perlu Diubah
|
||||||
|
1. `prisma/schema.prisma` — ubah relasi Umkm
|
||||||
|
2. `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/kategori-produk/kategori-produk.ts` — ganti semua `prisma.kategoriProduk` → `prisma.kategoriProdukUmkm`
|
||||||
|
3. `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/create.ts` — relasi kategori di PasarDesa tidak berubah (masih `KategoriProduk`)
|
||||||
|
4. `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/produk/findMany.ts` — include `kategoriProduk` di PasarDesa tidak berubah
|
||||||
|
5. `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts` — groupBy `kategoriId` di Umkm perlu include model baru
|
||||||
|
6. `src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts` — state/form kategori UMKM
|
||||||
|
7. Admin pages (list, create, edit) — tidak perlu banyak perubahan (UI tetap sama)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Langkah-Langkah
|
||||||
|
|
||||||
|
### Fase 1 — Schema Prisma
|
||||||
|
**File:** `prisma/schema.prisma`
|
||||||
|
|
||||||
|
1. Di model `Umkm` (line ~2435):
|
||||||
|
- Ubah `kategori KategoriProduk @relation(fields: [kategoriId], references: [id])` → `kategori KategoriProdukUmkm @relation(fields: [kategoriId], references: [id])`
|
||||||
|
- Field `kategoriId String` tetap sama (nama field tidak perlu berubah)
|
||||||
|
|
||||||
|
2. Di model `KategoriProduk` (line ~1453):
|
||||||
|
- Hapus baris `Umkm Umkm[]` (UMKM tidak lagi relasi ke sini)
|
||||||
|
|
||||||
|
3. Model `KategoriProdukUmkm` sudah punya `Umkm Umkm[]` — tidak perlu diubah
|
||||||
|
|
||||||
|
### Fase 2 — Database Migration
|
||||||
|
```bash
|
||||||
|
bunx prisma migrate dev --name migrate-umkm-kategori-to-kategori-produk-umkm
|
||||||
|
```
|
||||||
|
- Migration akan: ubah FK constraint di tabel `Umkm` dari `KategoriProduk` → `KategoriProdukUmkm`
|
||||||
|
- **Perhatian:** Data lama di `kategoriId` merujuk ke `KategoriProduk`. Perlu seed/migrasi data atau set nullable dulu.
|
||||||
|
|
||||||
|
> **Strategi migrasi data:**
|
||||||
|
> - Buat `kategoriId` di `Umkm` menjadi nullable sementara (`String?`)
|
||||||
|
> - Jalankan migration
|
||||||
|
> - Seed `KategoriProdukUmkm` dengan data yang sama seperti `KategoriProduk`
|
||||||
|
> - Update data Umkm yang ada agar `kategoriId` menunjuk ke `KategoriProdukUmkm` yang sesuai
|
||||||
|
> - Set kembali `kategoriId` menjadi required (`String`)
|
||||||
|
|
||||||
|
### Fase 3 — API Handler: CRUD Kategori Produk UMKM
|
||||||
|
**File:** `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/kategori-produk/kategori-produk.ts`
|
||||||
|
|
||||||
|
Ganti semua `prisma.kategoriProduk` → `prisma.kategoriProdukUmkm`:
|
||||||
|
- `/find-many-all` → `prisma.kategoriProdukUmkm.findMany()`
|
||||||
|
- `/find-many` → `prisma.kategoriProdukUmkm.findMany()`
|
||||||
|
- `/create` → `prisma.kategoriProdukUmkm.create()`
|
||||||
|
- `PUT /:id` → `prisma.kategoriProdukUmkm.update()`
|
||||||
|
- `DELETE /del/:id` → `prisma.kategoriProdukUmkm.update()`
|
||||||
|
|
||||||
|
### Fase 4 — API Handler: Dashboard KPI
|
||||||
|
**File:** `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts`
|
||||||
|
|
||||||
|
- Bagian `groupBy kategoriId` untuk Umkm: ubah include/join agar ambil nama dari `KategoriProdukUmkm` bukan `KategoriProduk`
|
||||||
|
|
||||||
|
### Fase 5 — State Management
|
||||||
|
**File:** `src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts`
|
||||||
|
|
||||||
|
- Pastikan tipe data / form state kategori UMKM masih sesuai (field name tidak berubah, hanya model di backend)
|
||||||
|
|
||||||
|
### Fase 6 — Build & Verify
|
||||||
|
```bash
|
||||||
|
bun run tsc --noEmit # Type check
|
||||||
|
bun run build # Full build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Yang TIDAK Berubah
|
||||||
|
- `KategoriProduk` model tetap ada (masih digunakan `PasarDesa` dan `KategoriToPasar`)
|
||||||
|
- Relasi `PasarDesa.kategoriProdukId → KategoriProduk` tidak berubah
|
||||||
|
- Admin UI pages (kategori-produk/page.tsx, create, edit) — logika UI tidak berubah, hanya backend model berbeda
|
||||||
|
- API route path tetap sama (`/api/ekonomi/kategoriproduk/*`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Risiko
|
||||||
|
- **Data migration:** Umkm yang ada punya `kategoriId` yang merujuk ke `KategoriProduk`. Perlu migrasi data atau data akan hilang relasi.
|
||||||
|
- **Solusi:** Buat `KategoriProdukUmkm` dengan data yang sama, lalu update `Umkm.kategoriId` via SQL migration script.
|
||||||
12
MIND/PLAN/task-migrate-kategori-produk-umkm.md
Normal file
12
MIND/PLAN/task-migrate-kategori-produk-umkm.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Task: Migrasi KategoriProduk → KategoriProdukUmkm
|
||||||
|
|
||||||
|
## Progress
|
||||||
|
- [x] Phase 1: Schema Update (`prisma/schema.prisma`) <!-- id: 0 -->
|
||||||
|
- [x] Phase 2: Data Migration (Manual SQL/Script) <!-- id: 1 -->
|
||||||
|
- [x] Phase 3: Update API CRUD UMKM Kategori <!-- id: 2 -->
|
||||||
|
- [x] Phase 4: Update KPI Dashboard UMKM <!-- id: 3 -->
|
||||||
|
- [x] Phase 5: Verification & Build <!-- id: 4 -->
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- `KategoriProduk` tetap dipertahankan untuk `PasarDesa`.
|
||||||
|
- `KategoriProdukUmkm` akan digunakan secara eksklusif oleh `Umkm`.
|
||||||
26
MIND/SUMMARY/migrate-kategori-produk-umkm-summary.md
Normal file
26
MIND/SUMMARY/migrate-kategori-produk-umkm-summary.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Summary: Migrasi KategoriProduk → KategoriProdukUmkm
|
||||||
|
|
||||||
|
## Perubahan yang Dilakukan
|
||||||
|
1. **Schema Prisma**:
|
||||||
|
- Memisahkan model kategori untuk `Umkm` dan `PasarDesa`.
|
||||||
|
- `Umkm` sekarang menggunakan `KategoriProdukUmkm`.
|
||||||
|
- `PasarDesa` tetap menggunakan `KategoriProduk`.
|
||||||
|
- Menghapus relasi `Umkm` dari model `KategoriProduk`.
|
||||||
|
2. **Database**:
|
||||||
|
- Menjalankan `prisma db push` untuk memperbarui tabel di PostgreSQL.
|
||||||
|
- Menyiapkan dan menguji script migrasi data (tabel saat ini kosong, namun script sudah diverifikasi).
|
||||||
|
3. **Backend API**:
|
||||||
|
- Mengubah `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/kategori-produk/kategori-produk.ts` agar menggunakan `prisma.kategoriProdukUmkm`.
|
||||||
|
- Memperbarui logic KPI dashboard di `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts` untuk menggunakan model kategori yang tepat berdasarkan konteks (UMKM vs Penjualan).
|
||||||
|
4. **Validasi**:
|
||||||
|
- Berhasil menjalankan `bun run build` tanpa error TypeScript baru.
|
||||||
|
|
||||||
|
## Dampak
|
||||||
|
- Admin UMKM sekarang memiliki manajemen kategori yang terisolasi dari PasarDesa.
|
||||||
|
- Tidak ada perubahan pada UI karena path API dan struktur data tetap sama.
|
||||||
|
- Kompabilitas data tetap terjaga karena relasi menggunakan ID yang sama.
|
||||||
|
|
||||||
|
## File Terkait
|
||||||
|
- `prisma/schema.prisma`
|
||||||
|
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/kategori-produk/kategori-produk.ts`
|
||||||
|
- `src/app/api/[[...slugs]]/_lib/ekonomi/umkm/dashboard/kpi.ts`
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "desa-darmasaba",
|
"name": "desa-darmasaba",
|
||||||
"version": "0.1.32",
|
"version": "0.1.33",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
|
|
||||||
|
const kategoriUmkmData = [
|
||||||
|
{ id: "4b95bge6-012e-5ged-9552-4d8g65d44959", nama: "Makanan" },
|
||||||
|
{ id: "5c06chf7-123f-6hfe-0663-5e9h76e55060", nama: "Minuman" },
|
||||||
|
{ id: "5c06chf7-123f-7igd-0663-5e9h76e55060", nama: "Sembako" },
|
||||||
|
{ id: "5c06chf7-123f-8jhe-0663-5e9h76e55060", nama: "Sayur Mayur" },
|
||||||
|
{ id: "5c06chf7-123f-9kif-0663-5e9h76e55060", nama: "Protein Hewani" },
|
||||||
|
];
|
||||||
|
|
||||||
export const umkmData = [
|
export const umkmData = [
|
||||||
{
|
{
|
||||||
id: "umkm-1",
|
id: "umkm-1",
|
||||||
@@ -40,6 +48,15 @@ export const umkmData = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export async function seedUmkm() {
|
export async function seedUmkm() {
|
||||||
|
console.log("🔄 Seeding Kategori Produk UMKM...");
|
||||||
|
for (const k of kategoriUmkmData) {
|
||||||
|
await prisma.kategoriProdukUmkm.upsert({
|
||||||
|
where: { id: k.id },
|
||||||
|
update: { nama: k.nama },
|
||||||
|
create: { id: k.id, nama: k.nama },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
console.log("🔄 Seeding UMKM...");
|
console.log("🔄 Seeding UMKM...");
|
||||||
for (const u of umkmData) {
|
for (const u of umkmData) {
|
||||||
await prisma.umkm.upsert({
|
await prisma.umkm.upsert({
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
-- Create KategoriProdukUmkm table if it doesn't exist
|
||||||
|
-- (renames existing kategori_produk_umkm if present, otherwise creates fresh)
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'kategori_produk_umkm'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE "kategori_produk_umkm" RENAME TO "KategoriProdukUmkm";
|
||||||
|
ELSIF NOT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = 'KategoriProdukUmkm'
|
||||||
|
) THEN
|
||||||
|
CREATE TABLE "KategoriProdukUmkm" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"nama" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"deletedAt" TIMESTAMP(3),
|
||||||
|
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
CONSTRAINT "KategoriProdukUmkm_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Seed KategoriProdukUmkm: copy from KategoriProduk where referenced by Umkm
|
||||||
|
-- This ensures existing Umkm.kategoriId values exist in the new table
|
||||||
|
INSERT INTO "KategoriProdukUmkm" ("id", "nama", "createdAt", "updatedAt", "deletedAt", "isActive")
|
||||||
|
SELECT DISTINCT kp.id, kp.nama, kp."createdAt", kp."updatedAt", kp."deletedAt", kp."isActive"
|
||||||
|
FROM "KategoriProduk" kp
|
||||||
|
WHERE kp.id IN (SELECT DISTINCT "kategoriId" FROM "Umkm")
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM "KategoriProdukUmkm" WHERE id = kp.id);
|
||||||
|
|
||||||
|
-- Update Umkm FK: drop old FK pointing to KategoriProduk, add new one to KategoriProdukUmkm
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
fk_target TEXT;
|
||||||
|
BEGIN
|
||||||
|
SELECT ccu.table_name INTO fk_target
|
||||||
|
FROM information_schema.table_constraints tc
|
||||||
|
JOIN information_schema.key_column_usage kcu
|
||||||
|
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||||
|
JOIN information_schema.constraint_column_usage ccu
|
||||||
|
ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema
|
||||||
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
||||||
|
AND tc.table_schema = 'public'
|
||||||
|
AND tc.table_name = 'Umkm'
|
||||||
|
AND kcu.column_name = 'kategoriId'
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF fk_target = 'KategoriProduk' THEN
|
||||||
|
ALTER TABLE "Umkm" DROP CONSTRAINT "Umkm_kategoriId_fkey";
|
||||||
|
ALTER TABLE "Umkm" ADD CONSTRAINT "Umkm_kategoriId_fkey"
|
||||||
|
FOREIGN KEY ("kategoriId") REFERENCES "KategoriProdukUmkm"("id")
|
||||||
|
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
@@ -1459,7 +1459,6 @@ model KategoriProduk {
|
|||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
KategoriToPasar KategoriToPasar[]
|
KategoriToPasar KategoriToPasar[]
|
||||||
PasarDesa PasarDesa[]
|
PasarDesa PasarDesa[]
|
||||||
Umkm Umkm[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
model KategoriToPasar {
|
model KategoriToPasar {
|
||||||
@@ -2432,7 +2431,7 @@ model Umkm {
|
|||||||
kontak String?
|
kontak String?
|
||||||
image FileStorage? @relation("UmkmImage", fields: [imageId], references: [id])
|
image FileStorage? @relation("UmkmImage", fields: [imageId], references: [id])
|
||||||
imageId String?
|
imageId String?
|
||||||
kategori KategoriProduk @relation(fields: [kategoriId], references: [id])
|
kategori KategoriProdukUmkm @relation(fields: [kategoriId], references: [id])
|
||||||
kategoriId String
|
kategoriId String
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -2441,6 +2440,17 @@ model Umkm {
|
|||||||
produk PasarDesa[]
|
produk PasarDesa[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model KategoriProdukUmkm {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
nama String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
deletedAt DateTime?
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
Umkm Umkm[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
model PenjualanProduk {
|
model PenjualanProduk {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
produk PasarDesa @relation(fields: [produkId], references: [id])
|
produk PasarDesa @relation(fields: [produkId], references: [id])
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function umkmDashboardKpi(context: Context) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (kategoriTerbanyakUmkm.length > 0) {
|
if (kategoriTerbanyakUmkm.length > 0) {
|
||||||
const kategori = await prisma.kategoriProduk.findUnique({
|
const kategori = await prisma.kategoriProdukUmkm.findUnique({
|
||||||
where: { id: kategoriTerbanyakUmkm[0].kategoriId },
|
where: { id: kategoriTerbanyakUmkm[0].kategoriId },
|
||||||
select: { nama: true },
|
select: { nama: true },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const KategoriProduk = new Elysia({
|
|||||||
})
|
})
|
||||||
.get("/find-many-all", async () => {
|
.get("/find-many-all", async () => {
|
||||||
try {
|
try {
|
||||||
const data = await prisma.kategoriProduk.findMany({
|
const data = await prisma.kategoriProdukUmkm.findMany({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
@@ -40,13 +40,13 @@ const KategoriProduk = new Elysia({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const [data, total] = await Promise.all([
|
const [data, total] = await Promise.all([
|
||||||
prisma.kategoriProduk.findMany({
|
prisma.kategoriProdukUmkm.findMany({
|
||||||
where,
|
where,
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' },
|
||||||
}),
|
}),
|
||||||
prisma.kategoriProduk.count({ where }),
|
prisma.kategoriProdukUmkm.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -74,7 +74,7 @@ const KategoriProduk = new Elysia({
|
|||||||
})
|
})
|
||||||
.post("/create", async ({ body }) => {
|
.post("/create", async ({ body }) => {
|
||||||
try {
|
try {
|
||||||
const data = await prisma.kategoriProduk.create({
|
const data = await prisma.kategoriProdukUmkm.create({
|
||||||
data: {
|
data: {
|
||||||
nama: body.nama,
|
nama: body.nama,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -100,7 +100,7 @@ const KategoriProduk = new Elysia({
|
|||||||
})
|
})
|
||||||
.put("/:id", async ({ params, body }) => {
|
.put("/:id", async ({ params, body }) => {
|
||||||
try {
|
try {
|
||||||
const data = await prisma.kategoriProduk.update({
|
const data = await prisma.kategoriProdukUmkm.update({
|
||||||
where: { id: params.id },
|
where: { id: params.id },
|
||||||
data: {
|
data: {
|
||||||
nama: body.nama,
|
nama: body.nama,
|
||||||
@@ -129,7 +129,7 @@ const KategoriProduk = new Elysia({
|
|||||||
})
|
})
|
||||||
.delete("/del/:id", async ({ params }) => {
|
.delete("/del/:id", async ({ params }) => {
|
||||||
try {
|
try {
|
||||||
const data = await prisma.kategoriProduk.update({
|
const data = await prisma.kategoriProdukUmkm.update({
|
||||||
where: { id: params.id },
|
where: { id: params.id },
|
||||||
data: {
|
data: {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Client } from "minio";
|
import { Client } from "minio";
|
||||||
|
|
||||||
const minioClient = new Client({
|
const minioClient = new Client({
|
||||||
endPoint: process.env.MINIO_ENDPOINT!,
|
endPoint: process.env.MINIO_ENDPOINT ?? "localhost",
|
||||||
accessKey: process.env.MINIO_ACCESS_KEY!,
|
accessKey: process.env.MINIO_ACCESS_KEY ?? "",
|
||||||
secretKey: process.env.MINIO_SECRET_KEY!,
|
secretKey: process.env.MINIO_SECRET_KEY ?? "",
|
||||||
useSSL: process.env.MINIO_USE_SSL === "true",
|
useSSL: process.env.MINIO_USE_SSL === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const MINIO_BUCKET = process.env.MINIO_BUCKET!;
|
export const MINIO_BUCKET = process.env.MINIO_BUCKET ?? "";
|
||||||
|
|
||||||
export default minioClient;
|
export default minioClient;
|
||||||
|
|||||||
Reference in New Issue
Block a user