mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 13:21:17 +00:00
docs: add OpenAPI documentation with Scalar UI
- Add swaggo annotations to all HTTP handlers - Add Swagger/OpenAPI spec generation with swag - Create separate docs server binary (drexa-docs) - Add Makefile with build, run, and docs targets - Configure Scalar as the API documentation UI Run 'make docs' to regenerate, 'make run-docs' to serve.
This commit is contained in:
@@ -17,23 +17,39 @@ const (
|
||||
DirItemKindFile = "file"
|
||||
)
|
||||
|
||||
// DirectoryInfo represents directory metadata
|
||||
// @Description Directory information including path and timestamps
|
||||
type DirectoryInfo struct {
|
||||
Kind string `json:"kind"`
|
||||
ID string `json:"id"`
|
||||
Path virtualfs.Path `json:"path,omitempty"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
// Item type, always "directory"
|
||||
Kind string `json:"kind" example:"directory"`
|
||||
// Unique directory identifier
|
||||
ID string `json:"id" example:"kRp2XYTq9A55"`
|
||||
// Full path from root (included when ?include=path)
|
||||
Path virtualfs.Path `json:"path,omitempty"`
|
||||
// Directory name
|
||||
Name string `json:"name" example:"My Documents"`
|
||||
// When the directory was created (ISO 8601)
|
||||
CreatedAt time.Time `json:"createdAt" example:"2024-12-13T15:04:05Z"`
|
||||
// When the directory was last updated (ISO 8601)
|
||||
UpdatedAt time.Time `json:"updatedAt" example:"2024-12-13T16:30:00Z"`
|
||||
// When the directory was trashed, null if not trashed (ISO 8601)
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" example:"2024-12-14T10:00:00Z"`
|
||||
}
|
||||
|
||||
// createDirectoryRequest represents a new directory creation request
|
||||
// @Description Request to create a new directory
|
||||
type createDirectoryRequest struct {
|
||||
ParentID string `json:"parentID"`
|
||||
Name string `json:"name"`
|
||||
// ID of the parent directory
|
||||
ParentID string `json:"parentID" example:"kRp2XYTq9A55"`
|
||||
// Name for the new directory
|
||||
Name string `json:"name" example:"New Folder"`
|
||||
}
|
||||
|
||||
// postDirectoryContentRequest represents a move items request
|
||||
// @Description Request to move items into this directory
|
||||
type postDirectoryContentRequest struct {
|
||||
Items []string `json:"items"`
|
||||
// Array of file/directory IDs to move
|
||||
Items []string `json:"items" example:"mElnUNCm8F22,kRp2XYTq9A55"`
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) currentDirectoryMiddleware(c *fiber.Ctx) error {
|
||||
@@ -64,6 +80,21 @@ func includeParam(c *fiber.Ctx) []string {
|
||||
return strings.Split(c.Query("include"), ",")
|
||||
}
|
||||
|
||||
// createDirectory creates a new directory
|
||||
// @Summary Create directory
|
||||
// @Description Create a new directory within a parent directory
|
||||
// @Tags directories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param request body createDirectoryRequest true "Directory details"
|
||||
// @Param include query string false "Include additional fields" Enums(path)
|
||||
// @Success 200 {object} DirectoryInfo "Created directory"
|
||||
// @Failure 400 {object} map[string]string "Parent not found or not a directory"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 409 {object} map[string]string "Directory already exists"
|
||||
// @Router /accounts/{accountID}/directories [post]
|
||||
func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
account := account.CurrentAccount(c)
|
||||
if account == nil {
|
||||
@@ -127,6 +158,19 @@ func (h *HTTPHandler) createDirectory(c *fiber.Ctx) error {
|
||||
return c.JSON(i)
|
||||
}
|
||||
|
||||
// fetchDirectory returns directory metadata
|
||||
// @Summary Get directory info
|
||||
// @Description Retrieve metadata for a specific directory
|
||||
// @Tags directories
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param directoryID path string true "Directory ID"
|
||||
// @Param include query string false "Include additional fields" Enums(path)
|
||||
// @Success 200 {object} DirectoryInfo "Directory metadata"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "Directory not found"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID} [get]
|
||||
func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
|
||||
node := mustCurrentDirectoryNode(c)
|
||||
|
||||
@@ -151,6 +195,18 @@ func (h *HTTPHandler) fetchDirectory(c *fiber.Ctx) error {
|
||||
return c.JSON(i)
|
||||
}
|
||||
|
||||
// listDirectory returns directory contents
|
||||
// @Summary List directory contents
|
||||
// @Description Get all files and subdirectories within a directory
|
||||
// @Tags directories
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param directoryID path string true "Directory ID"
|
||||
// @Success 200 {array} interface{} "Array of FileInfo and DirectoryInfo objects"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "Directory not found"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID}/content [get]
|
||||
func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
|
||||
node := mustCurrentDirectoryNode(c)
|
||||
children, err := h.vfs.ListChildren(c.Context(), h.db, node)
|
||||
@@ -190,6 +246,21 @@ func (h *HTTPHandler) listDirectory(c *fiber.Ctx) error {
|
||||
return c.JSON(items)
|
||||
}
|
||||
|
||||
// patchDirectory updates directory properties
|
||||
// @Summary Update directory
|
||||
// @Description Update directory properties such as name (rename)
|
||||
// @Tags directories
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param directoryID path string true "Directory ID"
|
||||
// @Param request body patchDirectoryRequest true "Directory update"
|
||||
// @Success 200 {object} DirectoryInfo "Updated directory metadata"
|
||||
// @Failure 400 {object} map[string]string "Invalid request"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "Directory not found"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID} [patch]
|
||||
func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
|
||||
node := mustCurrentDirectoryNode(c)
|
||||
|
||||
@@ -229,6 +300,18 @@ func (h *HTTPHandler) patchDirectory(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// deleteDirectory removes a directory
|
||||
// @Summary Delete directory
|
||||
// @Description Delete a directory permanently or move it to trash. Deleting a directory also affects all its contents.
|
||||
// @Tags directories
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param directoryID path string true "Directory ID"
|
||||
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
|
||||
// @Success 204 {string} string "Directory deleted"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "Directory not found"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID} [delete]
|
||||
func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
|
||||
node := mustCurrentDirectoryNode(c)
|
||||
|
||||
@@ -259,6 +342,21 @@ func (h *HTTPHandler) deleteDirectory(c *fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// moveItemsToDirectory moves files and directories into this directory
|
||||
// @Summary Move items to directory
|
||||
// @Description Move one or more files or directories into this directory. All items must currently be in the same source directory.
|
||||
// @Tags directories
|
||||
// @Accept json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param directoryID path string true "Target directory ID"
|
||||
// @Param request body postDirectoryContentRequest true "Items to move"
|
||||
// @Success 204 {string} string "Items moved successfully"
|
||||
// @Failure 400 {object} map[string]string "Invalid request or items not in same directory"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {object} map[string]string "One or more items not found"
|
||||
// @Failure 409 {object} map[string]string "Name conflict in target directory"
|
||||
// @Router /accounts/{accountID}/directories/{directoryID}/content [post]
|
||||
func (h *HTTPHandler) moveItemsToDirectory(c *fiber.Ctx) error {
|
||||
acc := account.CurrentAccount(c)
|
||||
if acc == nil {
|
||||
|
||||
@@ -11,15 +11,25 @@ import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// FileInfo represents file metadata
|
||||
// @Description File information including name, size, and timestamps
|
||||
type FileInfo struct {
|
||||
Kind string `json:"kind"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
MimeType string `json:"mimeType"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty"`
|
||||
// Item type, always "file"
|
||||
Kind string `json:"kind" example:"file"`
|
||||
// Unique file identifier
|
||||
ID string `json:"id" example:"mElnUNCm8F22"`
|
||||
// File name
|
||||
Name string `json:"name" example:"document.pdf"`
|
||||
// File size in bytes
|
||||
Size int64 `json:"size" example:"1048576"`
|
||||
// MIME type of the file
|
||||
MimeType string `json:"mimeType" example:"application/pdf"`
|
||||
// When the file was created (ISO 8601)
|
||||
CreatedAt time.Time `json:"createdAt" example:"2024-12-13T15:04:05Z"`
|
||||
// When the file was last updated (ISO 8601)
|
||||
UpdatedAt time.Time `json:"updatedAt" example:"2024-12-13T16:30:00Z"`
|
||||
// When the file was trashed, null if not trashed (ISO 8601)
|
||||
DeletedAt *time.Time `json:"deletedAt,omitempty" example:"2024-12-14T10:00:00Z"`
|
||||
}
|
||||
|
||||
func mustCurrentFileNode(c *fiber.Ctx) *virtualfs.Node {
|
||||
@@ -46,6 +56,18 @@ func (h *HTTPHandler) currentFileMiddleware(c *fiber.Ctx) error {
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
// fetchFile returns file metadata
|
||||
// @Summary Get file info
|
||||
// @Description Retrieve metadata for a specific file
|
||||
// @Tags files
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param fileID path string true "File ID"
|
||||
// @Success 200 {object} FileInfo "File metadata"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "File not found"
|
||||
// @Router /accounts/{accountID}/files/{fileID} [get]
|
||||
func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
i := FileInfo{
|
||||
@@ -61,6 +83,19 @@ func (h *HTTPHandler) fetchFile(c *fiber.Ctx) error {
|
||||
return c.JSON(i)
|
||||
}
|
||||
|
||||
// downloadFile streams file content
|
||||
// @Summary Download file
|
||||
// @Description Download the file content. May redirect to a signed URL for external storage.
|
||||
// @Tags files
|
||||
// @Produce application/octet-stream
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param fileID path string true "File ID"
|
||||
// @Success 200 {file} binary "File content stream"
|
||||
// @Success 307 {string} string "Redirect to download URL"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "File not found"
|
||||
// @Router /accounts/{accountID}/files/{fileID}/content [get]
|
||||
func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
|
||||
@@ -89,6 +124,21 @@ func (h *HTTPHandler) downloadFile(c *fiber.Ctx) error {
|
||||
return httperr.Internal(errors.New("vfs returned neither a reader nor a URL"))
|
||||
}
|
||||
|
||||
// patchFile updates file properties
|
||||
// @Summary Update file
|
||||
// @Description Update file properties such as name (rename)
|
||||
// @Tags files
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param fileID path string true "File ID"
|
||||
// @Param request body patchFileRequest true "File update"
|
||||
// @Success 200 {object} FileInfo "Updated file metadata"
|
||||
// @Failure 400 {object} map[string]string "Invalid request"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "File not found"
|
||||
// @Router /accounts/{accountID}/files/{fileID} [patch]
|
||||
func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
|
||||
@@ -131,6 +181,20 @@ func (h *HTTPHandler) patchFile(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// deleteFile removes a file
|
||||
// @Summary Delete file
|
||||
// @Description Delete a file permanently or move it to trash
|
||||
// @Tags files
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param accountID path string true "Account ID" format(uuid)
|
||||
// @Param fileID path string true "File ID"
|
||||
// @Param trash query bool false "Move to trash instead of permanent delete" default(false)
|
||||
// @Success 200 {object} FileInfo "Trashed file info (when trash=true)"
|
||||
// @Success 204 {string} string "Permanently deleted (when trash=false)"
|
||||
// @Failure 401 {string} string "Not authenticated"
|
||||
// @Failure 404 {string} string "File not found"
|
||||
// @Router /accounts/{accountID}/files/{fileID} [delete]
|
||||
func (h *HTTPHandler) deleteFile(c *fiber.Ctx) error {
|
||||
node := mustCurrentFileNode(c)
|
||||
|
||||
|
||||
@@ -11,12 +11,18 @@ type HTTPHandler struct {
|
||||
db *bun.DB
|
||||
}
|
||||
|
||||
// patchFileRequest represents a file update request
|
||||
// @Description Request to update file properties
|
||||
type patchFileRequest struct {
|
||||
Name string `json:"name"`
|
||||
// New name for the file
|
||||
Name string `json:"name" example:"renamed-document.pdf"`
|
||||
}
|
||||
|
||||
// patchDirectoryRequest represents a directory update request
|
||||
// @Description Request to update directory properties
|
||||
type patchDirectoryRequest struct {
|
||||
Name string `json:"name"`
|
||||
// New name for the directory
|
||||
Name string `json:"name" example:"My Documents"`
|
||||
}
|
||||
|
||||
func NewHTTPHandler(vfs *virtualfs.VirtualFS, db *bun.DB) *HTTPHandler {
|
||||
|
||||
Reference in New Issue
Block a user