277 lines
8.3 KiB
Go
277 lines
8.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path/filepath"
|
|
|
|
"git.nakama.town/fmartingr/hako/internal/dependencies"
|
|
"git.nakama.town/fmartingr/hako/internal/model"
|
|
"git.nakama.town/fmartingr/hako/internal/server/webcontext"
|
|
)
|
|
|
|
// ArchiveHandler handles archive-related HTTP requests
|
|
type ArchiveHandler struct {
|
|
deps model.Dependencies
|
|
}
|
|
|
|
// NewArchiveHandler creates a new ArchiveHandler
|
|
func NewArchiveHandler(deps model.Dependencies) *ArchiveHandler {
|
|
return &ArchiveHandler{
|
|
deps: deps,
|
|
}
|
|
}
|
|
|
|
// getDeps returns the concrete dependencies implementation
|
|
func (h *ArchiveHandler) getDeps() *dependencies.Dependencies {
|
|
return h.deps.(*dependencies.Dependencies)
|
|
}
|
|
|
|
// ArchiveResponse represents an archive in API responses
|
|
type ArchiveResponse struct {
|
|
ID string `json:"id"`
|
|
LinkID string `json:"link_id"`
|
|
Status string `json:"status"`
|
|
Title string `json:"title,omitempty"`
|
|
ErrorMessage string `json:"error_message,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
CompletedAt *string `json:"completed_at,omitempty"`
|
|
}
|
|
|
|
// HandleGetArchive handles GET /api/v1/archives/{archiveId}
|
|
func (h *ArchiveHandler) HandleGetArchive(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archive ID from path
|
|
archiveID := r.PathValue("archiveId")
|
|
if archiveID == "" {
|
|
http.Error(w, "Archive ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get archive
|
|
archive, err := h.deps.Domains().Archives().GetArchive(c.Context(), archiveID)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to get archive: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Build response
|
|
item := ArchiveResponse{
|
|
ID: archive.ID,
|
|
LinkID: archive.LinkID,
|
|
Status: string(archive.Status),
|
|
Title: archive.Title,
|
|
ErrorMessage: archive.ErrorMessage,
|
|
CreatedAt: archive.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
|
}
|
|
|
|
if archive.CompletedAt != nil {
|
|
completedAt := archive.CompletedAt.Format("2006-01-02T15:04:05Z")
|
|
item.CompletedAt = &completedAt
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(item)
|
|
}
|
|
|
|
// HandleGetArchiveHistory handles GET /api/v1/links/{linkId}/archives
|
|
func (h *ArchiveHandler) HandleGetArchiveHistory(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract link ID from path
|
|
linkID := r.PathValue("linkId")
|
|
if linkID == "" {
|
|
http.Error(w, "Link ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get archive history
|
|
archives, err := h.deps.Domains().Archives().GetArchiveHistory(c.Context(), linkID)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to get archive history: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Build response
|
|
items := make([]ArchiveResponse, 0, len(archives))
|
|
for _, archive := range archives {
|
|
item := ArchiveResponse{
|
|
ID: archive.ID,
|
|
LinkID: archive.LinkID,
|
|
Status: string(archive.Status),
|
|
Title: archive.Title,
|
|
ErrorMessage: archive.ErrorMessage,
|
|
CreatedAt: archive.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
|
}
|
|
|
|
if archive.CompletedAt != nil {
|
|
completedAt := archive.CompletedAt.Format("2006-01-02T15:04:05Z")
|
|
item.CompletedAt = &completedAt
|
|
}
|
|
|
|
items = append(items, item)
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(items)
|
|
}
|
|
|
|
// HandleReArchiveLink handles POST /api/v1/links/{linkId}/archives
|
|
func (h *ArchiveHandler) HandleReArchiveLink(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract link ID from path
|
|
linkID := r.PathValue("linkId")
|
|
if linkID == "" {
|
|
http.Error(w, "Link ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Re-archive the link
|
|
if err := h.deps.Domains().Archives().ReArchiveLink(c.Context(), linkID); err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to re-archive link: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.ResponseWriter().WriteHeader(http.StatusAccepted)
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{
|
|
"message": "Archive job created",
|
|
})
|
|
}
|
|
|
|
// HandleGetArchiveFiles handles GET /api/v1/archives/{archiveId}/files
|
|
func (h *ArchiveHandler) HandleGetArchiveFiles(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archive ID from path
|
|
archiveID := r.PathValue("archiveId")
|
|
if archiveID == "" {
|
|
http.Error(w, "Archive ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get archive files
|
|
files, err := h.deps.Domains().Archives().GetArchiveFiles(c.Context(), archiveID)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to get archive files: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Build response
|
|
items := make([]model.ArchiveFileResponse, 0, len(files))
|
|
for _, file := range files {
|
|
item := model.ArchiveFileResponse{
|
|
ID: file.ID,
|
|
ArchiveID: file.ArchiveID,
|
|
ArchiverKey: file.ArchiverKey,
|
|
Filename: file.Filename,
|
|
MimeType: file.MimeType,
|
|
FileSize: file.FileSize,
|
|
HashSha256: file.HashSha256,
|
|
CreatedAt: file.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
|
DownloadURL: fmt.Sprintf("/api/v1/archives/%s/files/%s/download", archiveID, file.ID),
|
|
Content: file.Content,
|
|
ContentMimeType: file.ContentMimeType,
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
c.ResponseWriter().Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(c.ResponseWriter()).Encode(items)
|
|
}
|
|
|
|
// HandleDownloadFile handles GET /api/v1/archives/{archiveId}/files/{fileId}/download
|
|
// Defaults to inline display. Use ?download=true query parameter to force download.
|
|
func (h *ArchiveHandler) HandleDownloadFile(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract file ID from path
|
|
fileID := r.PathValue("fileId")
|
|
if fileID == "" {
|
|
http.Error(w, "File ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Get archive file
|
|
file, err := h.deps.Domains().Archives().GetArchiveFile(c.Context(), fileID)
|
|
if err != nil {
|
|
http.Error(w, "File not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Check if download is requested (default is inline)
|
|
forceDownload := r.URL.Query().Get("download") == "true"
|
|
|
|
// Get file from storage
|
|
deps := h.getDeps()
|
|
reader, err := deps.Storage.Get(c.Context(), file.StoragePath)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to read file: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer func() { _ = reader.Close() }()
|
|
|
|
// Set headers
|
|
c.ResponseWriter().Header().Set("Content-Type", file.MimeType)
|
|
|
|
// Set Content-Disposition: default to inline, use attachment if download=true
|
|
if forceDownload {
|
|
c.ResponseWriter().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filepath.Base(file.Filename)))
|
|
} else {
|
|
c.ResponseWriter().Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filepath.Base(file.Filename)))
|
|
}
|
|
|
|
// Stream file
|
|
if _, err := io.Copy(c.ResponseWriter(), reader); err != nil {
|
|
// Can't send error response after starting to write body
|
|
return
|
|
}
|
|
}
|
|
|
|
// HandleDeleteArchive handles DELETE /api/v1/archives/{archiveId}
|
|
func (h *ArchiveHandler) HandleDeleteArchive(w http.ResponseWriter, r *http.Request) {
|
|
c := webcontext.NewWebContext(w, r)
|
|
|
|
// Extract archive ID from path
|
|
archiveID := r.PathValue("archiveId")
|
|
if archiveID == "" {
|
|
http.Error(w, "Archive ID is required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Delete the archive
|
|
if err := h.deps.Domains().Archives().DeleteArchive(c.Context(), archiveID); err != nil {
|
|
http.Error(w, fmt.Sprintf("Failed to delete archive: %v", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.ResponseWriter().WriteHeader(http.StatusNoContent)
|
|
}
|