mirror of
https://github.com/get-drexa/drive.git
synced 2025-12-01 05:51:39 +00:00
feat: impl upload api endpoints
This commit is contained in:
@@ -8,8 +8,11 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/auth"
|
||||
"github.com/get-drexa/drexa/internal/blob"
|
||||
"github.com/get-drexa/drexa/internal/database"
|
||||
"github.com/get-drexa/drexa/internal/upload"
|
||||
"github.com/get-drexa/drexa/internal/user"
|
||||
"github.com/get-drexa/drexa/internal/virtualfs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
@@ -21,21 +24,34 @@ type ServerConfig struct {
|
||||
JWTSecretKey []byte
|
||||
}
|
||||
|
||||
func NewServer(c ServerConfig) *fiber.App {
|
||||
func NewServer(c ServerConfig) (*fiber.App, error) {
|
||||
app := fiber.New()
|
||||
db := database.NewFromPostgres(c.PostgresURL)
|
||||
|
||||
// TODO: load correct blob store and resolver from config
|
||||
blobStore := blob.NewFSStore(blob.FSStoreConfig{
|
||||
Root: os.Getenv("BLOB_ROOT"),
|
||||
UploadURL: os.Getenv("BLOB_UPLOAD_URL"),
|
||||
})
|
||||
keyResolver := virtualfs.NewFlatKeyResolver()
|
||||
vfs, err := virtualfs.NewVirtualFS(db, blobStore, keyResolver)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create virtual file system: %w", err)
|
||||
}
|
||||
|
||||
userService := user.NewService(db)
|
||||
authService := auth.NewService(db, userService, auth.TokenConfig{
|
||||
Issuer: c.JWTIssuer,
|
||||
Audience: c.JWTAudience,
|
||||
SecretKey: c.JWTSecretKey,
|
||||
})
|
||||
uploadService := upload.NewService(vfs, blobStore)
|
||||
|
||||
api := app.Group("/api")
|
||||
auth.RegisterAPIRoutes(api, authService)
|
||||
upload.RegisterAPIRoutes(api, uploadService)
|
||||
|
||||
return app
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// ServerConfigFromEnv creates a ServerConfig from environment variables.
|
||||
|
||||
104
apps/backend/internal/upload/http.go
Normal file
104
apps/backend/internal/upload/http.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/get-drexa/drexa/internal/auth"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const uploadServiceKey = "uploadService"
|
||||
|
||||
type createUploadRequest struct {
|
||||
ParentID string `json:"parentId"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type updateUploadRequest struct {
|
||||
Status Status `json:"status"`
|
||||
}
|
||||
|
||||
func RegisterAPIRoutes(api fiber.Router, s *Service) {
|
||||
upload := api.Group("/uploads", func(c *fiber.Ctx) error {
|
||||
c.Locals(uploadServiceKey, s)
|
||||
return c.Next()
|
||||
})
|
||||
|
||||
upload.Post("/", createUpload)
|
||||
upload.Put("/:uploadID/content", receiveUpload)
|
||||
upload.Patch("/:uploadID", updateUpload)
|
||||
}
|
||||
|
||||
func mustUploadService(c *fiber.Ctx) *Service {
|
||||
return c.Locals(uploadServiceKey).(*Service)
|
||||
}
|
||||
|
||||
func createUpload(c *fiber.Ctx) error {
|
||||
u, err := auth.AuthenticatedUser(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
|
||||
}
|
||||
|
||||
s := mustUploadService(c)
|
||||
|
||||
req := new(createUploadRequest)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
|
||||
}
|
||||
|
||||
upload, err := s.CreateUpload(c.Context(), u.ID, CreateUploadOptions{
|
||||
ParentID: req.ParentID,
|
||||
Name: req.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal server error"})
|
||||
}
|
||||
|
||||
return c.JSON(upload)
|
||||
}
|
||||
|
||||
func receiveUpload(c *fiber.Ctx) error {
|
||||
u, err := auth.AuthenticatedUser(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
|
||||
}
|
||||
|
||||
s := mustUploadService(c)
|
||||
|
||||
uploadID := c.Params("uploadID")
|
||||
|
||||
err = s.ReceiveUpload(c.Context(), u.ID, uploadID, c.Request().BodyStream())
|
||||
defer c.Request().CloseBodyStream()
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal server error"})
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
func updateUpload(c *fiber.Ctx) error {
|
||||
u, err := auth.AuthenticatedUser(c)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Unauthorized"})
|
||||
}
|
||||
|
||||
s := mustUploadService(c)
|
||||
|
||||
req := new(updateUploadRequest)
|
||||
if err := c.BodyParser(req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request"})
|
||||
}
|
||||
|
||||
if req.Status == StatusCompleted {
|
||||
upload, err := s.CompleteUpload(c.Context(), u.ID, c.Params("uploadID"))
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Internal server error"})
|
||||
}
|
||||
return c.JSON(upload)
|
||||
}
|
||||
|
||||
return c.SendStatus(fiber.StatusBadRequest)
|
||||
}
|
||||
@@ -33,17 +33,6 @@ type CreateUploadOptions struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type Upload struct {
|
||||
ID string
|
||||
TargetNode *virtualfs.Node
|
||||
UploadURL string
|
||||
}
|
||||
|
||||
type CreateUploadResult struct {
|
||||
UploadID string
|
||||
UploadURL string
|
||||
}
|
||||
|
||||
func (s *Service) CreateUpload(ctx context.Context, userID uuid.UUID, opts CreateUploadOptions) (*Upload, error) {
|
||||
parentNode, err := s.vfs.FindNodeByPublicID(ctx, userID, opts.ParentID)
|
||||
if err != nil {
|
||||
@@ -77,6 +66,7 @@ func (s *Service) CreateUpload(ctx context.Context, userID uuid.UUID, opts Creat
|
||||
|
||||
upload := &Upload{
|
||||
ID: node.PublicID,
|
||||
Status: StatusPending,
|
||||
TargetNode: node,
|
||||
UploadURL: uploadURL,
|
||||
}
|
||||
@@ -106,36 +96,37 @@ func (s *Service) ReceiveUpload(ctx context.Context, userID uuid.UUID, uploadID
|
||||
return err
|
||||
}
|
||||
|
||||
s.pendingUploads.Delete(uploadID)
|
||||
upload.Status = StatusCompleted
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CompleteUpload(ctx context.Context, userID uuid.UUID, uploadID string) error {
|
||||
func (s *Service) CompleteUpload(ctx context.Context, userID uuid.UUID, uploadID string) (*Upload, error) {
|
||||
n, ok := s.pendingUploads.Load(uploadID)
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
upload, ok := n.(*Upload)
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
if upload.TargetNode.UserID != userID {
|
||||
return ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
if upload.TargetNode.Status == virtualfs.NodeStatusReady {
|
||||
return nil
|
||||
if upload.TargetNode.Status == virtualfs.NodeStatusReady && upload.Status == StatusCompleted {
|
||||
return upload, nil
|
||||
}
|
||||
|
||||
err := s.vfs.WriteFile(ctx, upload.TargetNode, virtualfs.FileContentFromBlobKey(upload.TargetNode.BlobKey))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upload.Status = StatusCompleted
|
||||
s.pendingUploads.Delete(uploadID)
|
||||
|
||||
return nil
|
||||
return upload, nil
|
||||
}
|
||||
|
||||
18
apps/backend/internal/upload/upload.go
Normal file
18
apps/backend/internal/upload/upload.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package upload
|
||||
|
||||
import "github.com/get-drexa/drexa/internal/virtualfs"
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
StatusPending Status = "pending"
|
||||
StatusCompleted Status = "completed"
|
||||
StatusFailed Status = "failed"
|
||||
)
|
||||
|
||||
type Upload struct {
|
||||
ID string `json:"id"`
|
||||
Status Status `json:"status"`
|
||||
TargetNode *virtualfs.Node `json:"-"`
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
}
|
||||
@@ -15,6 +15,10 @@ type BlobKeyResolver interface {
|
||||
|
||||
type FlatKeyResolver struct{}
|
||||
|
||||
func NewFlatKeyResolver() *FlatKeyResolver {
|
||||
return &FlatKeyResolver{}
|
||||
}
|
||||
|
||||
func (r *FlatKeyResolver) KeyMode() blob.KeyMode {
|
||||
return blob.KeyModeStable
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user