From 747544bb58cb2e73f40196ed9f288621114e8b30 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 27 Mar 2026 08:39:38 -0700 Subject: [PATCH] Get file upload working Even though the UI doesn't do anything with it yet. --- api/handler.go | 14 +- api/routes.go | 9 +- api/upload.go | 14 +- platform/file/upload.go | 17 +- platform/upload.go | 2 +- ts/components/CSVUpload.vue | 304 ++++++++++++++++++++ ts/view/configuration/UploadPool.vue | 12 +- ts/view/configuration/UploadPoolFlyover.vue | 59 ++-- 8 files changed, 354 insertions(+), 77 deletions(-) create mode 100644 ts/components/CSVUpload.vue diff --git a/api/handler.go b/api/handler.go index abc3d172..339196d5 100644 --- a/api/handler.go +++ b/api/handler.go @@ -11,6 +11,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/html" nhttp "github.com/Gleipnir-Technology/nidus-sync/http" "github.com/Gleipnir-Technology/nidus-sync/platform" + "github.com/Gleipnir-Technology/nidus-sync/platform/file" "github.com/gorilla/schema" "github.com/rs/zerolog/log" ) @@ -124,23 +125,28 @@ func handlerJSONPost[ReqType any](f handlerFunctionPost[ReqType]) http.HandlerFu } } -func authenticatedHandlerPostMultipart[RequestType any](f handlerFunctionPostAuthenticated[RequestType]) http.Handler { +func authenticatedHandlerPostMultipart(f handlerFunctionPostAuthenticated[[]file.Upload], collection file.Collection) http.Handler { return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) { err := r.ParseMultipartForm(32 << 10) // 32 MB buffer if err != nil { respondError(w, http.StatusBadRequest, "Failed to parse form: %w ", err) return } + uploads, err := file.SaveFileUploads(r, collection) + if err != nil { + respondError(w, http.StatusInternalServerError, "failed to save uploads: %w", err) + return + } - var content RequestType - + /* err = decoder.Decode(&content, r.PostForm) if err != nil { respondError(w, http.StatusBadRequest, "Failed to decode form: %w", err) return } + */ ctx := r.Context() - path, e := f(ctx, r, u, content) + path, e := f(ctx, r, u, uploads) if e != nil { http.Error(w, e.Error(), e.Status) return diff --git a/api/routes.go b/api/routes.go index c1a79812..b1c4a720 100644 --- a/api/routes.go +++ b/api/routes.go @@ -5,6 +5,7 @@ import ( "github.com/go-chi/render" "github.com/Gleipnir-Technology/nidus-sync/auth" + "github.com/Gleipnir-Technology/nidus-sync/platform/file" ) func AddRoutes(r chi.Router) { @@ -18,10 +19,6 @@ func AddRoutes(r chi.Router) { r.Method("GET", "/client/ios", auth.NewEnsureAuth(handleClientIos)) r.Method("GET", "/communication", authenticatedHandlerJSON(listCommunication)) r.Method("POST", "/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)) - r.Method("POST", "/configuration/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate)) - r.Method("POST", "/configuration/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate)) - r.Method("POST", "/configuration/upload/{id}/commit", authenticatedHandlerJSONPost(postUploadCommit)) - r.Method("POST", "/configuration/upload/{id}/discard", authenticatedHandlerJSONPost(postUploadDiscard)) r.Method("GET", "/events", auth.NewEnsureAuth(streamEvents)) r.Method("POST", "/image/{uuid}", auth.NewEnsureAuth(apiImagePost)) r.Method("GET", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentGet)) @@ -41,7 +38,11 @@ func AddRoutes(r chi.Router) { r.Method("POST", "/sudo/sse", authenticatedHandlerJSONPost(postSudoSSE)) r.Method("GET", "/trap-data", auth.NewEnsureAuth(apiTrapData)) r.Method("GET", "/tile/{z}/{y}/{x}", auth.NewEnsureAuth(getTile)) + r.Method("POST", "/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate, file.CollectionCSV)) + r.Method("POST", "/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate, file.CollectionCSV)) r.Method("GET", "/upload/{id}", authenticatedHandlerJSON(getUploadByID)) + r.Method("POST", "/upload/{id}/commit", authenticatedHandlerJSONPost(postUploadCommit)) + r.Method("POST", "/upload/{id}/discard", authenticatedHandlerJSONPost(postUploadDiscard)) r.Method("GET", "/user/self", authenticatedHandlerJSON(getUserSelf)) r.Method("GET", "/user/suggestion", authenticatedHandlerJSON(listUserSuggestion)) r.Method("GET", "/user", authenticatedHandlerJSON(listUser)) diff --git a/api/upload.go b/api/upload.go index e6f88dce..9a207e81 100644 --- a/api/upload.go +++ b/api/upload.go @@ -99,17 +99,11 @@ func postUploadDiscard(ctx context.Context, r *http.Request, u platform.User, f return "/configuration/upload", nil } -type FormUploadPool struct{} - -func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) { +func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) { // If the organization we're uploading to doesn't have a service area, we can't process the upload correctly if !(u.Organization.HasServiceArea() || u.Organization.IsCatchall()) { return "", nhttp.NewErrorStatus(http.StatusConflict, "Your organization does not yet have a service area") } - uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV) - if err != nil { - return "", nhttp.NewError("Failed to extract image uploads: %s", err) - } if len(uploads) == 0 { return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found") } @@ -123,11 +117,7 @@ func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platfor } return fmt.Sprintf("/configuration/upload/%d", saved_upload.ID), nil } -func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) { - uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV) - if err != nil { - return "", nhttp.NewError("Failed to extract image uploads: %s", err) - } +func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) { if len(uploads) == 0 { return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found") } diff --git a/platform/file/upload.go b/platform/file/upload.go index 1925e9d9..14e2228d 100644 --- a/platform/file/upload.go +++ b/platform/file/upload.go @@ -11,20 +11,17 @@ import ( "github.com/rs/zerolog/log" ) -type FileUpload struct { +type Upload struct { ContentType string Name string SizeBytes int UUID uuid.UUID } -func SaveFileUpload(r *http.Request, name string, collection Collection) ([]FileUpload, error) { - results := make([]FileUpload, 0) +func SaveFileUploads(r *http.Request, collection Collection) ([]Upload, error) { + results := make([]Upload, 0) for n, fheaders := range r.MultipartForm.File { log.Debug().Str("n", n).Msg("looking at header") - if n != name { - continue - } for _, headers := range fheaders { f, err := saveFileUpload(headers, collection) if err != nil { @@ -35,8 +32,8 @@ func SaveFileUpload(r *http.Request, name string, collection Collection) ([]File } return results, nil } -func saveFileUploads(r *http.Request, collection Collection) ([]FileUpload, error) { - results := make([]FileUpload, 0) +func saveFileUploads(r *http.Request, collection Collection) ([]Upload, error) { + results := make([]Upload, 0) for name, fheaders := range r.MultipartForm.File { for _, headers := range fheaders { upload, err := saveFileUpload(headers, collection) @@ -48,7 +45,7 @@ func saveFileUploads(r *http.Request, collection Collection) ([]FileUpload, erro } return results, nil } -func saveFileUpload(headers *multipart.FileHeader, collection Collection) (upload FileUpload, err error) { +func saveFileUpload(headers *multipart.FileHeader, collection Collection) (upload Upload, err error) { file, err := headers.Open() if err != nil { return upload, fmt.Errorf("Failed to open header: %w", err) @@ -67,7 +64,7 @@ func saveFileUpload(headers *multipart.FileHeader, collection Collection) (uploa return upload, fmt.Errorf("Failed to write file to disk: %w", err) } log.Info().Int("size", len(file_bytes)).Str("uploaded_filename", headers.Filename).Str("content-type", content_type).Str("uuid", u.String()).Msg("Saved an uploaded file to disk") - return FileUpload{ + return Upload{ ContentType: content_type, Name: headers.Filename, SizeBytes: len(file_bytes), diff --git a/platform/upload.go b/platform/upload.go index b4a01471..a75e560e 100644 --- a/platform/upload.go +++ b/platform/upload.go @@ -81,7 +81,7 @@ func GetUploadDetail(ctx context.Context, organization_id int32, file_id int32) return nil, errors.New("No idea what to do with upload type") } -func NewUpload(ctx context.Context, u User, upload file.FileUpload, t enums.FileuploadCsvtype) (*Upload, error) { +func NewUpload(ctx context.Context, u User, upload file.Upload, t enums.FileuploadCsvtype) (*Upload, error) { txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil) if err != nil { return nil, fmt.Errorf("Failed to begin transaction: %w", err) diff --git a/ts/components/CSVUpload.vue b/ts/components/CSVUpload.vue new file mode 100644 index 00000000..d7c8762d --- /dev/null +++ b/ts/components/CSVUpload.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/ts/view/configuration/UploadPool.vue b/ts/view/configuration/UploadPool.vue index 4529c1dd..47859ff3 100644 --- a/ts/view/configuration/UploadPool.vue +++ b/ts/view/configuration/UploadPool.vue @@ -85,12 +85,14 @@ CSV Format - - Pick me - + + diff --git a/ts/view/configuration/UploadPoolFlyover.vue b/ts/view/configuration/UploadPoolFlyover.vue index fca76a3d..7df6b70d 100644 --- a/ts/view/configuration/UploadPoolFlyover.vue +++ b/ts/view/configuration/UploadPoolFlyover.vue @@ -81,47 +81,11 @@
Upload Data
-
-
- - - - -
Select your CSV file
-

- Drag and drop a file here or click to browse -

- -
- -
- -
-
+
@@ -133,3 +97,16 @@ +