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>
This commit is contained in:
@@ -61,8 +61,14 @@ COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
|||||||
COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./
|
COPY --from=builder --chown=nextjs:nodejs /app/next.config.* ./
|
||||||
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
|
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
|
USER nextjs
|
||||||
|
|
||||||
|
# Persistent storage for uploaded files
|
||||||
|
VOLUME ["/app/uploads"]
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["/app/docker-entrypoint.sh"]
|
CMD ["/app/docker-entrypoint.sh"]
|
||||||
40
QWEN.md
40
QWEN.md
@@ -232,42 +232,4 @@ Common issues and solutions:
|
|||||||
6. Verify responsive design on different screen sizes
|
6. Verify responsive design on different screen sizes
|
||||||
|
|
||||||
## Qwen Added Memories
|
## Qwen Added Memories
|
||||||
- **GitHub Workflow Execution**: Project ini memiliki 3 workflow GitHub Action:
|
- **GitHub Workflows**: Project ini memiliki workflow GitHub Action untuk deployment. User akan menangani workflow secara manual di GitHub.
|
||||||
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"`
|
|
||||||
- **Deployment Workflow HARUS Sequential (Berurutan)**:
|
|
||||||
|
|
||||||
Saat deploy ke stg atau prod, workflow TIDAK BOLEH dijalankan bersamaan. Harus menunggu yang pertama SELESAI total baru trigger yang kedua.
|
|
||||||
|
|
||||||
**Urutan yang BENAR:**
|
|
||||||
1. ✅ **publish.yml** - Tunggu sampai SELESAI (status: ✓ success)
|
|
||||||
2. ✅ **Setelah publish selesai**, baru trigger **re-pull.yml**
|
|
||||||
|
|
||||||
**JANGAN trigger keduanya bersamaan!** Ini akan menyebabkan race condition karena re-pull akan menarik image yang belum selesai di-build.
|
|
||||||
|
|
||||||
**Cara cek workflow selesai:**
|
|
||||||
```bash
|
|
||||||
gh run view <run_id> --json status --jq '.status'
|
|
||||||
# Harus return "completed" baru lanjut ke re-pull
|
|
||||||
```
|
|
||||||
|
|
||||||
**Atau polling sampai selesai:**
|
|
||||||
```bash
|
|
||||||
gh run watch <publish_run_id>
|
|
||||||
# Tunggu sampai ada checkmark ✓
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const fileStorageCreate = async (context: Context) => {
|
|||||||
data: {
|
data: {
|
||||||
name: finalName,
|
name: finalName,
|
||||||
realName: file.name,
|
realName: file.name,
|
||||||
path: rootPath,
|
path: pathName, // Store relative path (e.g., "images", "audio", "documents")
|
||||||
mimeType: finalMimeType,
|
mimeType: finalMimeType,
|
||||||
category,
|
category,
|
||||||
link: `/api/fileStorage/findUnique/${finalName}`,
|
link: `/api/fileStorage/findUnique/${finalName}`,
|
||||||
|
|||||||
@@ -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 {
|
try {
|
||||||
// Hapus file dari filesystem
|
// Hapus file dari filesystem
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Context } from "elysia";
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
const UPLOAD_DIR = process.env.WIBU_UPLOAD_DIR;
|
||||||
|
|
||||||
const fileStorageFindUnique = async (context: Context) => {
|
const fileStorageFindUnique = async (context: Context) => {
|
||||||
const { name } = context.params;
|
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);
|
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 = {
|
context.set.headers = {
|
||||||
"Content-Type": data.mimeType,
|
"Content-Type": data.mimeType,
|
||||||
"Content-Length": file.length,
|
"Content-Length": file.length,
|
||||||
|
|||||||
Reference in New Issue
Block a user