From f60bde7fd9de1d61516d623b9ddacf4b35613708 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Fri, 27 Mar 2026 14:04:33 -0700 Subject: [PATCH] Get rows to show on individual upload page. --- api/routes.go | 1 + api/upload.go | 29 +- platform/pool.go | 9 +- platform/upload.go | 87 ++-- ts/store/upload.ts | 35 +- ts/view/configuration/UploadDetail.vue | 597 ++++++++++++------------- 6 files changed, 389 insertions(+), 369 deletions(-) diff --git a/api/routes.go b/api/routes.go index b1c4a720..2daf69b8 100644 --- a/api/routes.go +++ b/api/routes.go @@ -40,6 +40,7 @@ func AddRoutes(r chi.Router) { 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", authenticatedHandlerJSON(getUploadList)) r.Method("GET", "/upload/{id}", authenticatedHandlerJSON(getUploadByID)) r.Method("POST", "/upload/{id}/commit", authenticatedHandlerJSONPost(postUploadCommit)) r.Method("POST", "/upload/{id}/discard", authenticatedHandlerJSONPost(postUploadDiscard)) diff --git a/api/upload.go b/api/upload.go index 9a207e81..36749983 100644 --- a/api/upload.go +++ b/api/upload.go @@ -1,19 +1,21 @@ package api + import ( "context" "fmt" "net/http" "strconv" - nhttp "github.com/Gleipnir-Technology/nidus-sync/http" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "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/go-chi/chi/v5" "github.com/rs/zerolog/log" ) -func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query queryParams) (*platform.UploadPoolDetail, *nhttp.ErrorWithStatus) { + +func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query queryParams) (*platform.Upload, *nhttp.ErrorWithStatus) { file_id_str := chi.URLParam(r, "id") file_id_, err := strconv.ParseInt(file_id_str, 10, 32) if err != nil { @@ -28,24 +30,27 @@ func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query } type contentUploadList struct { - RecentUploads []platform.UploadSummary + RecentUploads []platform.Upload } type contentUploadPlaceholder struct{} -func getUploadList(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentUploadList], *nhttp.ErrorWithStatus) { - rows, err := platform.UploadSummaryList(ctx, user.Organization) - return html.NewResponse("sync/upload-list.html", contentUploadList{ - RecentUploads: rows, - }), nhttp.NewErrorMaybe("get upload list: %w", err) +func getUploadList(ctx context.Context, r *http.Request, user platform.User, req queryParams) (*contentUploadPoolList, *nhttp.ErrorWithStatus) { + rows, err := platform.UploadList(ctx, user.Organization) + if err != nil { + return nil, nhttp.NewError("Get upload list: %w", err) + } + return &contentUploadPoolList{ + Uploads: rows, + }, nil } type contentUploadDetail struct { CSVFileID int32 Organization platform.Organization - Upload platform.UploadPoolDetail + Upload platform.Upload } type contentUploadPoolList struct { - Uploads []platform.Upload + Uploads []platform.Upload `json:"uploads"` } type contentUploadPool struct{} @@ -115,7 +120,7 @@ func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platfor if err != nil { return "", nhttp.NewError("Failed to create new pool: %w", err) } - return fmt.Sprintf("/configuration/upload/%d", saved_upload.ID), nil + return fmt.Sprintf("/configuration/upload/%d", *saved_upload), nil } func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) { if len(uploads) == 0 { @@ -129,5 +134,5 @@ func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform if err != nil { return "", nhttp.NewError("Failed to create new pool: %w", err) } - return fmt.Sprintf("/configuration/upload/%d", pool_upload.ID), nil + return fmt.Sprintf("/configuration/upload/%d", *pool_upload), nil } diff --git a/platform/pool.go b/platform/pool.go index 8e098dde..7b022b6c 100644 --- a/platform/pool.go +++ b/platform/pool.go @@ -9,7 +9,6 @@ import ( "github.com/Gleipnir-Technology/bob/dialect/psql/sm" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" - "github.com/Gleipnir-Technology/nidus-sync/platform/types" //"github.com/rs/zerolog/log" "github.com/stephenafamo/scan" ) @@ -23,13 +22,7 @@ type UploadPoolError struct { Line uint Message string } -type UploadPoolRow struct { - Address types.Address - Condition string - Errors []UploadPoolError - Status string - Tags map[string]string -} + func errorsByLine(ctx context.Context, file *models.FileuploadFile) ([]UploadPoolError, map[int32][]UploadPoolError, error) { file_errors := make([]UploadPoolError, 0) errors_by_line := make(map[int32][]UploadPoolError, 0) diff --git a/platform/upload.go b/platform/upload.go index a75e560e..69629b37 100644 --- a/platform/upload.go +++ b/platform/upload.go @@ -35,35 +35,34 @@ const ( ) type Upload struct { - Created time.Time `db:"created"` - ID int32 `db:"id"` - Status string `db:"status"` + Created time.Time `db:"created" json:"created"` + Filename string `db:"filename" json:"filename"` + ID int32 `db:"id" json:"id"` + RecordCount int `db:"recordcount" json:"recordcount"` + Status string `db:"status" json:"status"` + Type string `db:"type" json:"type"` + CSVPool *CSVPoolDetail `json:"csv_pool"` } -type UploadSummary struct { - Created time.Time `db:"created"` - Filename string `db:"filename"` - ID int32 `db:"id"` - RecordCount int `db:"recordcount"` - Status string `db:"status"` - Type string `db:"type"` -} -type UploadPoolDetailCount struct { +type CSVPoolDetailCount struct { Existing int `json:"existing"` New int `json:"new"` Outside int `json:"outside"` } -type UploadPoolDetail struct { - Count UploadPoolDetailCount `json:"count"` - Created time.Time `json:"created"` - Errors []UploadPoolError `json:"errors"` - ID int32 `json:"id"` - Name string `json:"name"` - Pools []UploadPoolRow `json:"pools"` - Status string `json:"status"` +type CSVPoolDetail struct { + Count CSVPoolDetailCount `json:"count"` + Errors []UploadPoolError `json:"errors"` + Pools []UploadPoolRow `json:"pools"` +} +type UploadPoolRow struct { + Address types.Address `json:"address"` + Condition string `json:"condition"` + Errors []UploadPoolError `json:"errors"` + Status string `json:"status"` + Tags map[string]string `json:"tags"` } -func GetUploadDetail(ctx context.Context, organization_id int32, file_id int32) (*UploadPoolDetail, error) { +func GetUploadDetail(ctx context.Context, organization_id int32, file_id int32) (*Upload, error) { file, err := models.FindFileuploadFile(ctx, db.PGInstance.BobDB, file_id) if err != nil { return nil, fmt.Errorf("Failed to lookup file %d: %w", file_id, err) @@ -74,14 +73,14 @@ func GetUploadDetail(ctx context.Context, organization_id int32, file_id int32) } switch csv.Type { case enums.FileuploadCsvtypeFlyover: - return getUploadPoollistDetail(ctx, file) + return getUploadDetailPool(ctx, file) case enums.FileuploadCsvtypePoollist: - return getUploadPoollistDetail(ctx, file) + return getUploadDetailPool(ctx, file) } return nil, errors.New("No idea what to do with upload type") } -func NewUpload(ctx context.Context, u User, upload file.Upload, t enums.FileuploadCsvtype) (*Upload, error) { +func NewUpload(ctx context.Context, u User, upload file.Upload, t enums.FileuploadCsvtype) (*int32, error) { txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil) if err != nil { return nil, fmt.Errorf("Failed to begin transaction: %w", err) @@ -117,9 +116,7 @@ func NewUpload(ctx context.Context, u User, upload file.Upload, t enums.Fileuplo return nil, fmt.Errorf("background job create: %w", err) } txn.Commit(ctx) - return &Upload{ - ID: file.ID, - }, nil + return &file.ID, nil } func UploadCommit(ctx context.Context, org Organization, file_id int32, committer User) error { txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil) @@ -155,12 +152,12 @@ func UploadDiscard(ctx context.Context, org Organization, file_id int32) error { ).Exec(ctx, db.PGInstance.BobDB) return err } -func UploadSummaryList(ctx context.Context, org Organization) ([]UploadSummary, error) { - results := make([]UploadSummary, 0) +func UploadList(ctx context.Context, org Organization) ([]Upload, error) { + results := make([]Upload, 0) rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select( sm.Columns( // fileupload.csv columns - //"csv.file_id", + //"csv.file_id AS file_id", //"csv.committed", "csv.rowcount AS recordcount", "csv.type_ AS type", @@ -176,19 +173,19 @@ func UploadSummaryList(ctx context.Context, org Organization) ([]UploadSummary, "file.status AS status", //"file.size_bytes", //"file.file_uuid", - + // Aggregate data ), sm.From("fileupload.csv").As("csv"), sm.InnerJoin("fileupload.file").As("file").OnEQ(psql.Raw("csv.file_id"), psql.Raw("file.id")), sm.Where(psql.Quote("file", "organization_id").EQ(psql.Arg(org.ID))), sm.OrderBy("created").Desc(), - ), scan.StructMapper[UploadSummary]()) + ), scan.StructMapper[Upload]()) if err != nil { return results, fmt.Errorf("Failed to query pool upload rows: %w", err) } return rows, nil } -func getUploadPoollistDetail(ctx context.Context, file *models.FileuploadFile) (*UploadPoolDetail, error) { +func getUploadDetailPool(ctx context.Context, file *models.FileuploadFile) (*Upload, error) { file_errors, errors_by_line, err := errorsByLine(ctx, file) if err != nil { return nil, fmt.Errorf("get errors by line: %w", err) @@ -239,16 +236,20 @@ func getUploadPoollistDetail(ctx context.Context, file *models.FileuploadFile) ( }) } log.Debug().Str("status", file.Status.String()).Int32("id", file.ID).Msg("returning") - return &UploadPoolDetail{ - Count: UploadPoolDetailCount{ - Existing: count_existing, - Outside: count_outside, - New: count_new, + return &Upload{ + Created: file.Created, + Filename: file.Name, + ID: file.ID, + RecordCount: len(pool_rows), + CSVPool: &CSVPoolDetail{ + Count: CSVPoolDetailCount{ + Existing: count_existing, + Outside: count_outside, + New: count_new, + }, + Errors: file_errors, + Pools: pools, }, - Errors: file_errors, - ID: file.ID, - Name: file.Name, - Pools: pools, - Status: file.Status.String(), + Status: file.Status.String(), }, nil } diff --git a/ts/store/upload.ts b/ts/store/upload.ts index cf5a9312..f132577d 100644 --- a/ts/store/upload.ts +++ b/ts/store/upload.ts @@ -1,4 +1,3 @@ - import { defineStore } from "pinia"; import { ref } from "vue"; import { Upload } from "../types"; @@ -7,6 +6,7 @@ import { useUserStore } from "./user"; export const useUploadStore = defineStore("upload", () => { // State + const _byID = ref>(new Map()); const all = ref(null); const loading = ref(false); const error = ref(null); @@ -18,11 +18,8 @@ export const useUploadStore = defineStore("upload", () => { } }); // Actions - function byID(id: int): Upload? { - if (all.value == null) { - return null - } - return all.value.find((upload) => upload.id == id); + function byID(id: int) { + return _byID.value.get(id); } async function fetchAll() { const userStore = useUserStore(); @@ -44,6 +41,31 @@ export const useUploadStore = defineStore("upload", () => { } const data = await response.json(); all.value = data.uploads; + for (const u of data.uploads) { + _byID.value.set(u.id, u); + } + } catch (err) { + console.error("Error loading uploads:", err); + throw err; + } + } + async function fetchOne(id: int) { + const userStore = useUserStore(); + if (userStore.urls == null) { + throw new Error("can't fetch without user URL data"); + } + + loading.value = true; + error.value = null; + try { + const response = await fetch(`${userStore.urls.api.upload}/${id}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + _byID.value.set(data.id, data); + return data; } catch (err) { console.error("Error loading uploads:", err); throw err; @@ -56,5 +78,6 @@ export const useUploadStore = defineStore("upload", () => { // Actions byID, fetchAll, + fetchOne, }; }); diff --git a/ts/view/configuration/UploadDetail.vue b/ts/view/configuration/UploadDetail.vue index 223552d6..52aa36b1 100644 --- a/ts/view/configuration/UploadDetail.vue +++ b/ts/view/configuration/UploadDetail.vue @@ -1,86 +1,86 @@