package server import ( "archive/zip" "fmt" "io" "net/http" "os" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/helpers" "warpbox/lib/models" ) func handleIndex(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{}) } func handleShowBox(ctx *gin.Context) { boxID := ctx.Param("id") if !boxstore.ValidBoxID(boxID) { ctx.String(http.StatusBadRequest, "Invalid box id") return } files, err := boxstore.ListFiles(boxID) if err != nil { ctx.String(http.StatusNotFound, "Box not found") return } ctx.HTML(http.StatusOK, "box.html", gin.H{ "BoxID": boxID, "Files": files, "FileCount": len(files), "DownloadAll": "/box/" + boxID + "/download", "PollMS": helpers.EnvInt("WARPBOX_BOX_POLL_INTERVAL_MS", 5000, 1000), }) } func handleBoxStatus(ctx *gin.Context) { boxID := ctx.Param("id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } files, err := boxstore.ListFiles(boxID) if err != nil { ctx.JSON(http.StatusNotFound, gin.H{"error": "Box not found"}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "files": files}) } func handleDownloadBox(ctx *gin.Context) { boxID := ctx.Param("id") if !boxstore.ValidBoxID(boxID) { ctx.String(http.StatusBadRequest, "Invalid box id") return } files, err := boxstore.ListFiles(boxID) if err != nil { ctx.String(http.StatusNotFound, "Box not found") return } ctx.Header("Content-Type", "application/zip") ctx.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="warpbox-%s.zip"`, boxID)) zipWriter := zip.NewWriter(ctx.Writer) defer zipWriter.Close() for _, file := range files { if !file.IsComplete { continue } if err := boxstore.AddFileToZip(zipWriter, boxID, file.Name); err != nil { ctx.Status(http.StatusInternalServerError) return } } } func handleDownloadFile(ctx *gin.Context) { boxID := ctx.Param("id") filename, ok := helpers.SafeFilename(ctx.Param("filename")) if !boxstore.ValidBoxID(boxID) || !ok { ctx.String(http.StatusBadRequest, "Invalid file") return } path, ok := boxstore.SafeBoxFilePath(boxID, filename) if !ok { ctx.String(http.StatusBadRequest, "Invalid file") return } if _, err := os.Stat(path); err != nil { ctx.String(http.StatusNotFound, "File not found") return } ctx.FileAttachment(path, filename) } func handleCreateBox(ctx *gin.Context) { boxID, err := boxstore.NewBoxID() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"}) return } if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"}) return } var request models.CreateBoxRequest if err := ctx.ShouldBindJSON(&request); err != nil && err != io.EOF { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box payload"}) return } files, err := boxstore.CreateManifest(boxID, request.Files) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": files}) } func handleManifestFileUpload(ctx *gin.Context) { boxID := ctx.Param("id") fileID := ctx.Param("file_id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } file, err := ctx.FormFile("file") if err != nil { boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"}) return } savedFile, err := boxstore.SaveManifestUpload(boxID, fileID, file) if err != nil { boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile}) } func handleFileStatusUpdate(ctx *gin.Context) { boxID := ctx.Param("id") fileID := ctx.Param("file_id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } var request models.UpdateFileStatusRequest if err := ctx.ShouldBindJSON(&request); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid status payload"}) return } file, err := boxstore.MarkFileStatus(boxID, fileID, request.Status) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"file": file}) } func handleDirectBoxUpload(ctx *gin.Context) { boxID := ctx.Param("id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } file, err := ctx.FormFile("file") if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"}) return } savedFile, err := boxstore.SaveUpload(boxID, file) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile}) } func handleLegacyUpload(ctx *gin.Context) { form, err := ctx.MultipartForm() if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } files := form.File["files"] if len(files) == 0 { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } boxID, err := boxstore.NewBoxID() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"}) return } if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"}) return } savedFiles := make([]models.BoxFile, 0, len(files)) for _, file := range files { savedFile, err := boxstore.SaveUpload(boxID, file) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } savedFiles = append(savedFiles, savedFile) } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": savedFiles}) }