diff --git a/backend/internal/handler/files.go b/backend/internal/handler/files.go index 29aa78c..9d8a9e0 100644 --- a/backend/internal/handler/files.go +++ b/backend/internal/handler/files.go @@ -55,6 +55,8 @@ func (h *FileHandler) Upload(c *gin.Context) { } func (h *FileHandler) List(c *gin.Context) { + thumbnailQuality := c.Query("thumbnail") + files, err := h.files.List() if err != nil { log.Printf("ERROR List files: %v", err) @@ -71,6 +73,7 @@ func (h *FileHandler) List(c *gin.Context) { type fileResponse struct { ID string `json:"id"` URL string `json:"url"` + ThumbnailURL string `json:"thumbnailUrl,omitempty"` Name string `json:"name"` Size int64 `json:"size"` Tags []tagResponse `json:"tags"` @@ -84,21 +87,27 @@ func (h *FileHandler) List(c *gin.Context) { resp := make([]fileResponse, len(files)) for i, f := range files { - tags := []tagResponse{} - for _, tag := range f.Tags { - tags = append(tags, tagResponse{ ID: tag.ID, TagName: tag.Name, TagType: tag.TagType, }) - } + + downloadURL := h.urls.GenerateDownloadURL(f.ID) + thumbURL := downloadURL + if thumbnailQuality != "" { + if best := h.files.GetBestThumbnail(f.ID, thumbnailQuality); best != nil { + thumbURL = h.urls.GenerateThumbnailURL(best.ID) + } + } + resp[i] = fileResponse{ ID: f.ID, - URL: h.urls.GenerateDownloadURL(f.ID), + URL: downloadURL, + ThumbnailURL: thumbURL, Name: f.Name, Size: f.Size, Tags: tags, @@ -135,21 +144,75 @@ func (h *FileHandler) Download(c *gin.Context) { func (h *FileHandler) Get(c *gin.Context) { id := c.Param("id") + thumbnailQuality := c.Query("thumbnail") + file, err := h.files.Get(id) if err != nil { api.Error(c, http.StatusNotFound, "FILE_NOT_FOUND", "File not found") return } + type tagResponse struct { + ID string `json:"id"` + TagName string `json:"tag_name"` + TagType string `json:"tag_type"` + } + + type thumbnailResponse struct { + ID string `json:"id"` + PageNumber int `json:"pageNumber"` + ResolutionLabel string `json:"resolutionLabel"` + Width int `json:"width"` + Height int `json:"height"` + URL string `json:"url"` + MimeType string `json:"mimeType"` + } + + tags := []tagResponse{} + for _, tag := range file.Tags { + tags = append(tags, tagResponse{ + ID: tag.ID, + TagName: tag.Name, + TagType: tag.TagType, + }) + } + + downloadURL := h.urls.GenerateDownloadURL(file.ID) + thumbURL := downloadURL + if thumbnailQuality != "" { + if best := h.files.GetBestThumbnail(file.ID, thumbnailQuality); best != nil { + thumbURL = h.urls.GenerateThumbnailURL(best.ID) + } + } + + dbThumbnails, _ := h.files.GetThumbnailsByFileID(file.ID) + thumbnails := make([]thumbnailResponse, len(dbThumbnails)) + for i, t := range dbThumbnails { + thumbnails[i] = thumbnailResponse{ + ID: t.ID, + PageNumber: t.PageNumber, + ResolutionLabel: t.ResolutionLabel, + Width: t.Width, + Height: t.Height, + URL: h.urls.GenerateThumbnailURL(t.ID), + MimeType: t.MimeType, + } + } + api.Success(c, gin.H{ - "id": file.ID, - "name": file.Name, - "url": h.urls.GenerateDownloadURL(file.ID), - "size": file.Size, - "mimeType": file.MimeType, - "createdAt": file.CreatedAt, - "updatedAt": file.UpdatedAt, - "ocrText": file.OcrText, + "id": file.ID, + "name": file.Name, + "url": downloadURL, + "thumbnailUrl": thumbURL, + "size": file.Size, + "mimeType": file.MimeType, + "tags": tags, + "createdAt": file.CreatedAt, + "updatedAt": file.UpdatedAt, + "ocrText": file.OcrText, + "isFolder": file.IsFolder, + "parentFileID": file.ParentFileID, + "thumbnails": thumbnails, }) } @@ -250,12 +313,71 @@ func (h *FileHandler) CreateFolder(c *gin.Context) { func (h *FileHandler) ListFilesByParent(c *gin.Context) { parentID := c.Param("id") + thumbnailQuality := c.Query("thumbnail") + files, err := h.files.ListFilesByParentID(parentID) if err != nil { api.Error(c, http.StatusInternalServerError, "DB_ERROR", "Failed to list files in folder") return } - api.Success(c, files) + + type tagResponse struct { + ID string `json:"id"` + TagName string `json:"tag_name"` + TagType string `json:"tag_type"` + } + + type fileResponse struct { + ID string `json:"id"` + URL string `json:"url"` + ThumbnailURL string `json:"thumbnailUrl,omitempty"` + Name string `json:"name"` + Size int64 `json:"size"` + Tags []tagResponse `json:"tags"` + CreatedAt string `json:"createdAt"` + MimeType string `json:"mimeType"` + OcrText string `json:"ocrText,omitempty"` + ParentFileID string `json:"parentFileID,omitempty"` + IsFolder bool `json:"isFolder"` + UpdatedAt string `json:"updatedAt"` + } + + resp := make([]fileResponse, len(files)) + for i, f := range files { + tags := []tagResponse{} + for _, tag := range f.Tags { + tags = append(tags, tagResponse{ + ID: tag.ID, + TagName: tag.Name, + TagType: tag.TagType, + }) + } + + downloadURL := h.urls.GenerateDownloadURL(f.ID) + thumbURL := downloadURL + if thumbnailQuality != "" { + if best := h.files.GetBestThumbnail(f.ID, thumbnailQuality); best != nil { + thumbURL = h.urls.GenerateThumbnailURL(best.ID) + } + } + + resp[i] = fileResponse{ + ID: f.ID, + URL: downloadURL, + ThumbnailURL: thumbURL, + Name: f.Name, + Size: f.Size, + Tags: tags, + CreatedAt: f.CreatedAt, + ParentFileID: f.ParentFileID, + OcrText: f.OcrText, + IsFolder: f.IsFolder, + UpdatedAt: f.UpdatedAt, + MimeType: f.MimeType, + } + } + + api.Success(c, resp) } func (h *FileHandler) GetThumbnails(c *gin.Context) { diff --git a/backend/internal/handler/router.go b/backend/internal/handler/router.go index b52c229..8025478 100644 --- a/backend/internal/handler/router.go +++ b/backend/internal/handler/router.go @@ -14,6 +14,8 @@ func SetupRoutes(r *gin.Engine, h *Handler, authMiddleware *auth.AuthService) { api.POST("/auth/login", h.Auth.Login) api.POST("/auth/refresh", h.Auth.Refresh) api.POST("/auth/logout", h.Auth.Logout) + api.GET("/files/download/:id", h.File.Download) + api.GET("/thumbnails/:id", h.File.ServeThumbnail) // Protected protected := api.Group("") @@ -25,7 +27,6 @@ func SetupRoutes(r *gin.Engine, h *Handler, authMiddleware *auth.AuthService) { protected.POST("/files/folders", h.File.CreateFolder) protected.GET("/files/folders", h.File.ListFolders) protected.GET("/files/folders/:id/files", h.File.ListFilesByParent) - protected.GET("/files/download/:id", h.File.Download) protected.DELETE("/files/:id", h.File.Delete) protected.GET("/files/:id", h.File.Get) @@ -36,6 +37,4 @@ func SetupRoutes(r *gin.Engine, h *Handler, authMiddleware *auth.AuthService) { protected.GET("/ocr/jobs/:id", h.OCR.GetJobStatus) protected.GET("/files/:id/thumbnails", h.File.GetThumbnails) - - api.GET("/thumbnails/:id", h.File.ServeThumbnail) } diff --git a/backend/internal/service/file.go b/backend/internal/service/file.go index 71075cd..8614826 100644 --- a/backend/internal/service/file.go +++ b/backend/internal/service/file.go @@ -238,6 +238,48 @@ func (s *FileService) GetThumbnailStoragePath(id string) (string, error) { return t.StorageKey, nil } +func (s *FileService) GetBestThumbnail(fileID, preferredLabel string) *model.Thumbnail { + dbThumbnails, err := s.queries.GetThumbnailsByFileID(context.Background(), fileID) + if err != nil || len(dbThumbnails) == 0 { + return nil + } + + var fallback *model.Thumbnail + for _, t := range dbThumbnails { + if t.PageNumber != 1 { + continue + } + if t.ResolutionLabel == preferredLabel { + return &model.Thumbnail{ + ID: t.ID, + FileID: t.FileID, + PageNumber: int(t.PageNumber), + ResolutionLabel: t.ResolutionLabel, + Width: int(t.Width), + Height: int(t.Height), + StorageKey: t.StorageKey, + MimeType: t.MimeType, + CreatedAt: t.CreatedAt.String(), + } + } + if fallback == nil { + fallback = &model.Thumbnail{ + ID: t.ID, + FileID: t.FileID, + PageNumber: int(t.PageNumber), + ResolutionLabel: t.ResolutionLabel, + Width: int(t.Width), + Height: int(t.Height), + StorageKey: t.StorageKey, + MimeType: t.MimeType, + CreatedAt: t.CreatedAt.String(), + } + } + } + + return fallback +} + func dbToModel(f db.File, dbTags []db.Tag) model.File { tags := make([]model.Tag, len(dbTags)) for i, t := range dbTags {