feat: add configurable data directory and file-based logging

Introduce the `WARPBOX_DATA_DIR` environment variable to define where runtime data is stored. This directory will house uploaded files, the bbolt metadata database, and application logs.

Changes include:
- Added `WARPBOX_DATA_DIR` to configuration, defaulting to `./data`.
- Implemented a custom logging package that writes JSONL logs to the data directory.
- Updated `.gitignore` and `.env.example` to support the new data directory.
- Documented the runtime data structure in `README.md`.
- Updated the frontend upload script to handle form submission and display results.
This commit is contained in:
2026-05-25 16:26:47 +03:00
parent 9b8ef16474
commit e12878887c
21 changed files with 1240 additions and 153 deletions

View File

@@ -1,19 +1,49 @@
package handlers
import (
"errors"
"net/http"
"strconv"
"warpbox.dev/backend/libs/helpers"
"warpbox.dev/backend/libs/services"
)
type uploadPlaceholderResponse struct {
Message string `json:"message"`
MaxUploadSize string `json:"maxUploadSize"`
func (a *App) Upload(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, a.uploadService.MaxUploadSize()*8)
if err := r.ParseMultipartForm(a.uploadService.MaxUploadSize() * 8); err != nil {
helpers.WriteJSONError(w, http.StatusBadRequest, "upload form could not be read")
return
}
files := r.MultipartForm.File["file"]
result, err := a.uploadService.CreateBox(files, services.UploadOptions{
MaxDays: parseInt(r.FormValue("max_days")),
MaxDownloads: parseInt(r.FormValue("max_downloads")),
})
if err != nil {
a.logger.Warn("upload failed", "source", "user-upload", "code", 4001, "error", err.Error())
helpers.WriteJSONError(w, http.StatusBadRequest, err.Error())
return
}
helpers.WriteJSON(w, http.StatusCreated, result)
}
func (a *App) UploadPlaceholder(w http.ResponseWriter, r *http.Request) {
helpers.WriteJSON(w, http.StatusNotImplemented, uploadPlaceholderResponse{
Message: "upload storage is not implemented yet; backend base is ready for the upload pipeline",
MaxUploadSize: a.uploadService.MaxUploadSizeLabel(),
})
func parseInt(value string) int {
if value == "" {
return 0
}
parsed, err := strconv.Atoi(value)
if err != nil {
return 0
}
return parsed
}
func statusForDownloadError(err error) int {
if errors.Is(err, http.ErrMissingFile) {
return http.StatusNotFound
}
return http.StatusForbidden
}