page + upload queue + folders
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user