Files
warpbox-dev/backend/libs/handlers/download.go
Daniel Legt e12878887c 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.
2026-05-25 16:26:47 +03:00

141 lines
3.7 KiB
Go

package handlers
import (
"errors"
"fmt"
"net/http"
"os"
"time"
"warpbox.dev/backend/libs/helpers"
"warpbox.dev/backend/libs/web"
)
type downloadPageData struct {
Box boxView
Files []fileView
ZipURL string
DownloadCount int
MaxDownloads int
ExpiresLabel string
}
type boxView struct {
ID string
}
type fileView struct {
ID string
Name string
Size string
ContentType string
URL string
}
func (a *App) DownloadPage(w http.ResponseWriter, r *http.Request) {
box, err := a.uploadService.GetBox(r.PathValue("boxID"))
if err != nil {
http.NotFound(w, r)
return
}
if err := a.uploadService.CanDownload(box); err != nil {
a.renderer.Render(w, http.StatusForbidden, "download.gohtml", web.PageData{
Title: "Download unavailable",
Description: "This Warpbox link is no longer available.",
Data: downloadPageData{
Box: boxView{ID: box.ID},
ExpiresLabel: err.Error(),
},
})
return
}
files := make([]fileView, 0, len(box.Files))
for _, file := range box.Files {
files = append(files, fileView{
ID: file.ID,
Name: file.Name,
Size: helpers.FormatBytes(file.Size),
ContentType: file.ContentType,
URL: fmt.Sprintf("/d/%s/f/%s", box.ID, file.ID),
})
}
a.renderer.Render(w, http.StatusOK, "download.gohtml", web.PageData{
Title: "Download files",
Description: "Download files shared through Warpbox.",
Data: downloadPageData{
Box: boxView{ID: box.ID},
Files: files,
ZipURL: fmt.Sprintf("/d/%s/zip", box.ID),
DownloadCount: box.DownloadCount,
MaxDownloads: box.MaxDownloads,
ExpiresLabel: box.ExpiresAt.Format("Jan 2, 2006 15:04 MST"),
},
})
}
func (a *App) DownloadFile(w http.ResponseWriter, r *http.Request) {
box, err := a.uploadService.GetBox(r.PathValue("boxID"))
if err != nil {
http.NotFound(w, r)
return
}
if err := a.uploadService.CanDownload(box); err != nil {
http.Error(w, err.Error(), statusForDownloadError(err))
return
}
file, err := a.uploadService.FindFile(box, r.PathValue("fileID"))
if err != nil {
http.NotFound(w, r)
return
}
path := a.uploadService.FilePath(box, file)
source, err := os.Open(path)
if err != nil {
http.NotFound(w, r)
return
}
defer source.Close()
stat, err := source.Stat()
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", file.ContentType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", file.Name))
http.ServeContent(w, r, file.Name, stat.ModTime(), source)
if err := a.uploadService.RecordDownload(box.ID); err != nil && !errors.Is(err, os.ErrNotExist) {
a.logger.Warn("failed to record file download", "source", "download", "code", 4002, "box_id", box.ID, "error", err.Error())
}
}
func (a *App) DownloadZip(w http.ResponseWriter, r *http.Request) {
box, err := a.uploadService.GetBox(r.PathValue("boxID"))
if err != nil {
http.NotFound(w, r)
return
}
if err := a.uploadService.CanDownload(box); err != nil {
http.Error(w, err.Error(), statusForDownloadError(err))
return
}
w.Header().Set("Content-Type", "application/zip")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", "warpbox-"+box.ID+".zip"))
w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
if err := a.uploadService.WriteZip(w, box); err != nil {
a.logger.Error("zip download failed", "source", "download", "code", 5002, "box_id", box.ID, "error", err.Error())
return
}
if err := a.uploadService.RecordDownload(box.ID); err != nil && !errors.Is(err, os.ErrNotExist) {
a.logger.Warn("failed to record zip download", "source", "download", "code", 4003, "box_id", box.ID, "error", err.Error())
}
}