From 7f6e7f7a10ea30c96e09e4bda820e6de1345cc03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sat, 10 Jan 2026 02:06:03 +0100 Subject: [PATCH] Add GetFileByID method and enhance viewer handlers for file metadata retrieval --- go_cloud/internal/database/db.go | 28 ++++++++++ go_cloud/internal/http/routes.go | 89 ++++++++++++++++++++++++++------ 2 files changed, 101 insertions(+), 16 deletions(-) diff --git a/go_cloud/internal/database/db.go b/go_cloud/internal/database/db.go index f8ce5c6..b7ee547 100644 --- a/go_cloud/internal/database/db.go +++ b/go_cloud/internal/database/db.go @@ -383,6 +383,34 @@ func (db *DB) CreateFile(ctx context.Context, orgID *uuid.UUID, userID *uuid.UUI return &f, nil } +// GetFileByID retrieves a file by its ID +func (db *DB) GetFileByID(ctx context.Context, fileID uuid.UUID) (*File, error) { + var f File + var orgNull sql.NullString + var userNull sql.NullString + + err := db.QueryRowContext(ctx, ` + SELECT id, org_id::text, user_id::text, name, path, type, size, last_modified, created_at + FROM files + WHERE id = $1 + `, fileID).Scan(&f.ID, &orgNull, &userNull, &f.Name, &f.Path, &f.Type, &f.Size, &f.LastModified, &f.CreatedAt) + + if err != nil { + return nil, err + } + + if orgNull.Valid { + oid, _ := uuid.Parse(orgNull.String) + f.OrgID = &oid + } + if userNull.Valid { + uid, _ := uuid.Parse(userNull.String) + f.UserID = &uid + } + + return &f, nil +} + // DeleteFileByPath removes a file or folder matching path for a given org or user func (db *DB) DeleteFileByPath(ctx context.Context, orgID *uuid.UUID, userID *uuid.UUID, path string) error { var res sql.Result diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index b3f0d84..e64f415 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -361,9 +361,29 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi orgID := r.Context().Value("org").(uuid.UUID) fileId := chi.URLParam(r, "fileId") + // Get file metadata to determine path and type + fileUUID, err := uuid.Parse(fileId) + if err != nil { + errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest) + return + } + + file, err := db.GetFileByID(r.Context(), fileUUID) + if err != nil { + errors.LogError(r, err, "Failed to get file metadata") + errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) + return + } + // Log activity db.LogActivity(r.Context(), userID, orgID, &fileId, "view_file", map[string]interface{}{}) + // Build download URL - using full path for frontend to fetch with auth headers + viewUrl := fmt.Sprintf("https://b0esche.cloud/api/orgs/%s/files/download?path=%s", orgID.String(), file.Path) + + // Determine if it's a PDF based on file extension + isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") + session := struct { ViewUrl string `json:"viewUrl"` Capabilities struct { @@ -373,13 +393,13 @@ func viewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, audi } `json:"capabilities"` ExpiresAt string `json:"expiresAt"` }{ - ViewUrl: "https://view.example.com/" + fileId, + ViewUrl: viewUrl, Capabilities: struct { CanEdit bool `json:"canEdit"` CanAnnotate bool `json:"canAnnotate"` IsPdf bool `json:"isPdf"` - }{CanEdit: true, CanAnnotate: true, IsPdf: true}, - ExpiresAt: "2023-01-01T01:00:00Z", + }{CanEdit: false, CanAnnotate: isPdf, IsPdf: isPdf}, + ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339), } w.Header().Set("Content-Type", "application/json") @@ -392,7 +412,29 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, userID, _ := uuid.Parse(userIDStr) fileId := chi.URLParam(r, "fileId") - // For now, return a synthetic viewer session similar to org viewer + // Get file metadata to determine path and type + fileUUID, err := uuid.Parse(fileId) + if err != nil { + errors.WriteError(w, errors.CodeInvalidArgument, "Invalid file ID", http.StatusBadRequest) + return + } + + file, err := db.GetFileByID(r.Context(), fileUUID) + if err != nil { + errors.LogError(r, err, "Failed to get file metadata") + errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) + return + } + + // Optionally log activity without org id + db.LogActivity(r.Context(), userID, uuid.Nil, &fileId, "view_user_file", map[string]interface{}{}) + + // Build download URL for user files - using full path for frontend to fetch with auth headers + viewUrl := fmt.Sprintf("https://b0esche.cloud/api/user/files/download?path=%s", file.Path) + + // Determine if it's a PDF based on file extension + isPdf := strings.HasSuffix(strings.ToLower(file.Name), ".pdf") + session := struct { ViewUrl string `json:"viewUrl"` Capabilities struct { @@ -402,22 +444,19 @@ func userViewerHandler(w http.ResponseWriter, r *http.Request, db *database.DB, } `json:"capabilities"` ExpiresAt string `json:"expiresAt"` }{ - ViewUrl: "https://view.example.com/" + fileId, + ViewUrl: viewUrl, Capabilities: struct { CanEdit bool `json:"canEdit"` CanAnnotate bool `json:"canAnnotate"` IsPdf bool `json:"isPdf"` }{ CanEdit: false, - CanAnnotate: true, - IsPdf: strings.HasSuffix(strings.ToLower(fileId), ".pdf"), + CanAnnotate: isPdf, + IsPdf: isPdf, }, ExpiresAt: time.Now().Add(15 * time.Minute).UTC().Format(time.RFC3339), } - // Optionally log activity without org id - db.LogActivity(r.Context(), userID, uuid.Nil, &fileId, "view_user_file", map[string]interface{}{}) - w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(session) } @@ -1319,10 +1358,19 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database if err == nil { defer reader.Close() - // Set appropriate headers + // Set appropriate headers for inline viewing fileName := path.Base(filePath) - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", "application/octet-stream") + // Determine content type based on file extension + contentType := "application/octet-stream" + if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { + contentType = "application/pdf" + } else if strings.HasSuffix(strings.ToLower(fileName), ".png") { + contentType = "image/png" + } else if strings.HasSuffix(strings.ToLower(fileName), ".jpg") || strings.HasSuffix(strings.ToLower(fileName), ".jpeg") { + contentType = "image/jpeg" + } + w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) + w.Header().Set("Content-Type", contentType) if size > 0 { w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } @@ -1364,10 +1412,19 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas if err == nil { defer reader.Close() - // Set appropriate headers + // Set appropriate headers for inline viewing fileName := path.Base(filePath) - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", "application/octet-stream") + // Determine content type based on file extension + contentType := "application/octet-stream" + if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { + contentType = "application/pdf" + } else if strings.HasSuffix(strings.ToLower(fileName), ".png") { + contentType = "image/png" + } else if strings.HasSuffix(strings.ToLower(fileName), ".jpg") || strings.HasSuffix(strings.ToLower(fileName), ".jpeg") { + contentType = "image/jpeg" + } + w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) + w.Header().Set("Content-Type", contentType) if size > 0 { w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) }