page + upload queue + folders

This commit is contained in:
m
2026-07-29 22:16:47 +02:00
parent 470474011d
commit 881767cfec
11 changed files with 248 additions and 41 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ func main() {
conversionSvc.Start(cfg.ConversionWorkers)
defer conversionSvc.Stop()
h := handler.New(resourceSvc, ocrSvc, urlSvc, auth.NewAuthHandler(authSvc), conversionSvc, rebacSvc, placementSvc, syncSvc, eventBroker)
h := handler.New(database, resourceSvc, ocrSvc, urlSvc, auth.NewAuthHandler(authSvc), conversionSvc, rebacSvc, placementSvc, syncSvc, eventBroker)
r := gin.Default()
handler.SetupRoutes(r, h, authSvc)
+12 -2
View File
@@ -56,12 +56,22 @@ SELECT * FROM resources
WHERE checksum = $1 AND is_folder = false AND owner_id = $2
LIMIT 1;
-- name: CountResourcesByOwner :one
SELECT COUNT(*) FROM resources
WHERE owner_id = $1 AND parent_resource_id IS NULL;
-- name: ListResourcesByOwner :many
SELECT * FROM resources
WHERE owner_id = $1 AND parent_resource_id IS NULL
ORDER BY created_at DESC;
ORDER BY created_at DESC
LIMIT $2 OFFSET $3;
-- name: CountResourcesByParentAndOwner :one
SELECT COUNT(*) FROM resources
WHERE parent_resource_id = $1 AND owner_id = $2;
-- name: ListResourcesByParentAndOwner :many
SELECT * FROM resources
WHERE parent_resource_id = $1 AND owner_id = $2
ORDER BY is_folder DESC, created_at DESC;
ORDER BY is_folder DESC, created_at DESC
LIMIT $3 OFFSET $4;
+47 -3
View File
@@ -13,6 +13,35 @@ import (
"github.com/lib/pq"
)
const countResourcesByOwner = `-- name: CountResourcesByOwner :one
SELECT COUNT(*) FROM resources
WHERE owner_id = $1 AND parent_resource_id IS NULL
`
func (q *Queries) CountResourcesByOwner(ctx context.Context, ownerID uuid.UUID) (int64, error) {
row := q.db.QueryRowContext(ctx, countResourcesByOwner, ownerID)
var count int64
err := row.Scan(&count)
return count, err
}
const countResourcesByParentAndOwner = `-- name: CountResourcesByParentAndOwner :one
SELECT COUNT(*) FROM resources
WHERE parent_resource_id = $1 AND owner_id = $2
`
type CountResourcesByParentAndOwnerParams struct {
ParentResourceID uuid.NullUUID `json:"parent_resource_id"`
OwnerID uuid.UUID `json:"owner_id"`
}
func (q *Queries) CountResourcesByParentAndOwner(ctx context.Context, arg CountResourcesByParentAndOwnerParams) (int64, error) {
row := q.db.QueryRowContext(ctx, countResourcesByParentAndOwner, arg.ParentResourceID, arg.OwnerID)
var count int64
err := row.Scan(&count)
return count, err
}
const createFolder = `-- name: CreateFolder :one
INSERT INTO resources (name, is_folder, owner_id, created_at, updated_at)
VALUES ($1, true, $2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
@@ -323,10 +352,17 @@ const listResourcesByOwner = `-- name: ListResourcesByOwner :many
SELECT id, name, mime_type, size, checksum, ocr_text, is_folder, parent_resource_id, owner_id, created_at, updated_at FROM resources
WHERE owner_id = $1 AND parent_resource_id IS NULL
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
func (q *Queries) ListResourcesByOwner(ctx context.Context, ownerID uuid.UUID) ([]Resource, error) {
rows, err := q.db.QueryContext(ctx, listResourcesByOwner, ownerID)
type ListResourcesByOwnerParams struct {
OwnerID uuid.UUID `json:"owner_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListResourcesByOwner(ctx context.Context, arg ListResourcesByOwnerParams) ([]Resource, error) {
rows, err := q.db.QueryContext(ctx, listResourcesByOwner, arg.OwnerID, arg.Limit, arg.Offset)
if err != nil {
return nil, err
}
@@ -364,15 +400,23 @@ const listResourcesByParentAndOwner = `-- name: ListResourcesByParentAndOwner :m
SELECT id, name, mime_type, size, checksum, ocr_text, is_folder, parent_resource_id, owner_id, created_at, updated_at FROM resources
WHERE parent_resource_id = $1 AND owner_id = $2
ORDER BY is_folder DESC, created_at DESC
LIMIT $3 OFFSET $4
`
type ListResourcesByParentAndOwnerParams struct {
ParentResourceID uuid.NullUUID `json:"parent_resource_id"`
OwnerID uuid.UUID `json:"owner_id"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
func (q *Queries) ListResourcesByParentAndOwner(ctx context.Context, arg ListResourcesByParentAndOwnerParams) ([]Resource, error) {
rows, err := q.db.QueryContext(ctx, listResourcesByParentAndOwner, arg.ParentResourceID, arg.OwnerID)
rows, err := q.db.QueryContext(ctx, listResourcesByParentAndOwner,
arg.ParentResourceID,
arg.OwnerID,
arg.Limit,
arg.Offset,
)
if err != nil {
return nil, err
}
+4 -1
View File
@@ -1,6 +1,8 @@
package handler
import (
"database/sql"
"github.com/vaultdrop/backend/internal/auth"
"github.com/vaultdrop/backend/internal/service"
)
@@ -17,6 +19,7 @@ type Handler struct {
}
func New(
database *sql.DB,
resourceSvc *service.ResourceService,
ocrSvc *service.OCRService,
urlSvc *service.URLService,
@@ -36,7 +39,7 @@ func New(
broker: eventBroker,
},
OCR: &OCRHandler{ocr: ocrSvc, resources: resourceSvc},
Health: &HealthHandler{ocr: ocrSvc},
Health: &HealthHandler{db: database, ocr: ocrSvc},
Auth: authHandler,
Share: &ShareHandler{rebac: rebacSvc},
Device: &DeviceHandler{placement: placementSvc},
+31 -1
View File
@@ -1,14 +1,44 @@
package handler
import (
"database/sql"
"github.com/gin-gonic/gin"
"github.com/vaultdrop/backend/pkg/api"
)
type HealthHandler struct {
db *sql.DB
ocr interface{ HealthCheck() error }
}
func (h *HealthHandler) Check(c *gin.Context) {
api.Success(c, gin.H{"status": "healthy"})
checks := gin.H{}
dbErr := h.db.Ping()
if dbErr != nil {
checks["database"] = "error: " + dbErr.Error()
} else {
checks["database"] = "ok"
}
ocrErr := h.ocr.HealthCheck()
if ocrErr != nil {
checks["ocr"] = "error: " + ocrErr.Error()
} else {
checks["ocr"] = "ok"
}
status := "healthy"
for _, v := range checks {
if v != "ok" {
status = "degraded"
break
}
}
api.Success(c, gin.H{
"status": status,
"checks": checks,
})
}
+25 -4
View File
@@ -65,11 +65,31 @@ func (h *ResourceHandler) Upload(c *gin.Context) {
api.Success(c, results)
}
func parsePagination(c *gin.Context) (page, limit int) {
page = 1
limit = 20
if p := c.Query("page"); p != "" {
if n, err := strconv.Atoi(p); err == nil && n > 0 {
page = n
}
}
if l := c.Query("limit"); l != "" {
if n, err := strconv.Atoi(l); err == nil && n > 0 {
if n > 100 {
n = 100
}
limit = n
}
}
return
}
func (h *ResourceHandler) List(c *gin.Context) {
userID := c.GetString(auth.UserIDKey)
thumbnailQuality := c.Query("thumbnail")
page, limit := parsePagination(c)
resources, err := h.resources.List(userID)
resources, total, err := h.resources.List(userID, page, limit)
if err != nil {
log.Printf("ERROR List resources: %v", err)
api.Error(c, http.StatusInternalServerError, "DB_ERROR", "Failed to list resources")
@@ -132,7 +152,7 @@ func (h *ResourceHandler) List(c *gin.Context) {
}
}
api.Paginated(c, resp, 1, len(resp))
api.Paginated(c, resp, page, total)
}
func (h *ResourceHandler) Download(c *gin.Context) {
@@ -335,8 +355,9 @@ func (h *ResourceHandler) ListByParent(c *gin.Context) {
userID := c.GetString(auth.UserIDKey)
parentID := c.Param("id")
thumbnailQuality := c.Query("thumbnail")
page, limit := parsePagination(c)
resources, err := h.resources.ListResourcesByParentID(parentID, userID)
resources, total, err := h.resources.ListResourcesByParentID(parentID, userID, page, limit)
if err != nil {
api.Error(c, http.StatusInternalServerError, "DB_ERROR", "Failed to list resources in folder")
return
@@ -396,7 +417,7 @@ func (h *ResourceHandler) ListByParent(c *gin.Context) {
}
}
api.Success(c, resp)
api.Paginated(c, resp, page, total)
}
func (h *ResourceHandler) GetVariants(c *gin.Context) {
+33 -10
View File
@@ -147,22 +147,33 @@ func (s *ResourceService) ensureServerPlacementQtx(q *db.Queries, resourceID, ow
return placement, nil
}
func (s *ResourceService) List(ownerID string) ([]model.Resource, error) {
func (s *ResourceService) List(ownerID string, page, limit int) ([]model.Resource, int, error) {
ownerUUID, _ := uuid.Parse(ownerID)
dbResources, err := s.queries.ListResourcesByOwner(context.Background(), ownerUUID)
total, err := s.queries.CountResourcesByOwner(context.Background(), ownerUUID)
if err != nil {
return nil, fmt.Errorf("list resources: %w", err)
return nil, 0, fmt.Errorf("count resources: %w", err)
}
offset := (page - 1) * limit
dbResources, err := s.queries.ListResourcesByOwner(context.Background(), db.ListResourcesByOwnerParams{
OwnerID: ownerUUID,
Limit: int32(limit),
Offset: int32(offset),
})
if err != nil {
return nil, 0, fmt.Errorf("list resources: %w", err)
}
resources := make([]model.Resource, len(dbResources))
for i, r := range dbResources {
tags, err := s.queries.GetTagsByResourceID(context.Background(), r.ID)
if err != nil {
return nil, fmt.Errorf("get tags for resource %s: %w", r.ID, err)
return nil, 0, fmt.Errorf("get tags for resource %s: %w", r.ID, err)
}
resources[i] = dbResourceToModel(r, tags)
}
return resources, nil
return resources, int(total), nil
}
func (s *ResourceService) Get(id string) (*model.Resource, error) {
@@ -309,25 +320,37 @@ func (s *ResourceService) ListFolders(ownerID string) ([]model.Resource, error)
return folders, nil
}
func (s *ResourceService) ListResourcesByParentID(parentID, ownerID string) ([]model.Resource, error) {
func (s *ResourceService) ListResourcesByParentID(parentID, ownerID string, page, limit int) ([]model.Resource, int, error) {
parentUUID, _ := uuid.Parse(parentID)
ownerUUID, _ := uuid.Parse(ownerID)
dbResources, err := s.queries.ListResourcesByParentAndOwner(context.Background(), db.ListResourcesByParentAndOwnerParams{
total, err := s.queries.CountResourcesByParentAndOwner(context.Background(), db.CountResourcesByParentAndOwnerParams{
ParentResourceID: uuid.NullUUID{UUID: parentUUID, Valid: true},
OwnerID: ownerUUID,
})
if err != nil {
return nil, fmt.Errorf("list resources by parent: %w", err)
return nil, 0, fmt.Errorf("count resources by parent: %w", err)
}
offset := (page - 1) * limit
dbResources, err := s.queries.ListResourcesByParentAndOwner(context.Background(), db.ListResourcesByParentAndOwnerParams{
ParentResourceID: uuid.NullUUID{UUID: parentUUID, Valid: true},
OwnerID: ownerUUID,
Limit: int32(limit),
Offset: int32(offset),
})
if err != nil {
return nil, 0, fmt.Errorf("list resources by parent: %w", err)
}
resources := make([]model.Resource, len(dbResources))
for i, r := range dbResources {
tags, err := s.queries.GetTagsByResourceID(context.Background(), r.ID)
if err != nil {
return nil, fmt.Errorf("get tags for resource %s: %w", r.ID, err)
return nil, 0, fmt.Errorf("get tags for resource %s: %w", r.ID, err)
}
resources[i] = dbResourceToModel(r, tags)
}
return resources, nil
return resources, int(total), nil
}
func (s *ResourceService) GetVariantsByResourceID(resourceID string) ([]model.Variant, error) {