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 --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 ["/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
|
||||
|
||||
## 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"`
|
||||
- **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 ✓
|
||||
```
|
||||
- **GitHub Workflows**: Project ini memiliki workflow GitHub Action untuk deployment. User akan menangani workflow secara manual di GitHub.
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user