From 169fd20aaba15c6e9c766a5c13bd0752f743b4cc Mon Sep 17 00:00:00 2001 From: Abhishek Shroff Date: Tue, 20 Jan 2026 11:48:06 +0530 Subject: [PATCH] [server] Allow creating empty resources --- server/internal/api/v1/fs/create_file.go | 93 ++++++++++++++++++++++++ server/internal/api/v1/fs/routes.go | 1 + 2 files changed, 94 insertions(+) create mode 100644 server/internal/api/v1/fs/create_file.go diff --git a/server/internal/api/v1/fs/create_file.go b/server/internal/api/v1/fs/create_file.go new file mode 100644 index 00000000..0b6c5e98 --- /dev/null +++ b/server/internal/api/v1/fs/create_file.go @@ -0,0 +1,93 @@ +package fs + +import ( + "errors" + "io" + "net/http" + + "codeberg.org/shroff/phylum/server/internal/api/authenticator" + "codeberg.org/shroff/phylum/server/internal/api/v1/responses" + "codeberg.org/shroff/phylum/server/internal/core" + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type createFileParams struct { + Path string `json:"path" form:"path" binding:"required"` + ID string `json:"id" form:"id" binding:"omitempty,uuid"` + VersionID string `json:"version_id" form:"version_id" binding:"omitempty,uuid"` + CreateParents bool `json:"create_parents" form:"create_parents"` + Conflict core.ResourceBindConflictResolution `json:"conflict" form:"conflict"` + SHA256 string `json:"sha256" form:"sha256"` +} + +func handleCreateFileRequest(c *gin.Context) { + var params createFileParams + err := c.ShouldBindQuery(¶ms) + if err != nil { + panic(err) + } + var id uuid.UUID + var versionID uuid.UUID + if params.ID != "" { + id, err = uuid.Parse(params.ID) + if err != nil { + panic(err) + } + } + if params.VersionID != "" { + versionID, err = uuid.Parse(params.VersionID) + if err != nil { + panic(err) + } + } + + // TODO: Calculate and verify sha sum + + f := authenticator.GetFileSystem(c) + + file, err := c.FormFile("contents") + if err != nil { + if !errors.Is(err, http.ErrMissingFile) { + panic(err) + } + + r, err := f.CreateResourceByPath(params.Path, id, false, false, params.Conflict) + if err != nil && !errors.Is(err, core.ErrIDConflict) { + panic(err) + } + c.JSON(200, responses.ResourceFromFS(r)) + return + } + + err = func() error { + // TODO: #perf disk I/O in tx + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + out, err := f.CreateFileByPath(params.Path, id, versionID, params.Conflict) + if err != nil { + return err + } + + if _, err := io.Copy(out, src); err != nil { + out.Close() + return err + } else { + return out.Close() + } + }() + if err != nil && !errors.Is(err, core.ErrIDConflict) { + panic(err) + } + + // id may have changed if this is an overwrite + r, err := f.ResourceByPathWithRoot(params.Path) + if err != nil { + panic(err) + } + c.JSON(200, responses.ResourceFromFS(r)) +} diff --git a/server/internal/api/v1/fs/routes.go b/server/internal/api/v1/fs/routes.go index 004171cc..534975ca 100644 --- a/server/internal/api/v1/fs/routes.go +++ b/server/internal/api/v1/fs/routes.go @@ -27,6 +27,7 @@ func SetupRoutes(r *gin.RouterGroup) { group.POST("/delete_version", handleDeleteVersionRequest) group.POST("/mkdir", handleMkdirRequest) + group.POST("/create_file", handleCreateFileRequest) group.PUT("/upload", handleUploadRequest) group.GET("/search", handleSearchRequest)