From f9c8f37cece00fb28c2416de8270e04b3b59b9aa Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Sun, 8 Feb 2026 05:00:14 +0000 Subject: [PATCH] Add organization to file upload So everyone in the org can see it. --- db/dbinfo/fileupload.file.bob.go | 44 ++- db/factory/bobfactory_context.bob.go | 2 + db/factory/bobfactory_main.bob.go | 7 + db/factory/fileupload.file.bob.go | 132 ++++++++- db/factory/organization.bob.go | 120 ++++++-- db/migrations/00057_csv_pool_upload.sql | 1 + db/models/fileupload.file.bob.go | 357 ++++++++++++++++++------ db/models/organization.bob.go | 254 +++++++++++++++++ platform/csv/pool.go | 5 + platform/pool.go | 17 +- 10 files changed, 812 insertions(+), 127 deletions(-) diff --git a/db/dbinfo/fileupload.file.bob.go b/db/dbinfo/fileupload.file.bob.go index 45ded8f9..fa94a8f6 100644 --- a/db/dbinfo/fileupload.file.bob.go +++ b/db/dbinfo/fileupload.file.bob.go @@ -69,6 +69,15 @@ var FileuploadFiles = Table[ Generated: false, AutoIncr: false, }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, Status: column{ Name: "status", DBType: "fileupload.filestatustype", @@ -131,26 +140,36 @@ var FileuploadFiles = Table[ ForeignTable: "user_", ForeignColumns: []string{"id"}, }, + FileuploadFileFileOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "fileupload.file.file_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, }, Comment: "", } type fileuploadFileColumns struct { - ID column - ContentType column - Created column - CreatorID column - Deleted column - Name column - Status column - SizeBytes column - FileUUID column + ID column + ContentType column + Created column + CreatorID column + Deleted column + Name column + OrganizationID column + Status column + SizeBytes column + FileUUID column } func (c fileuploadFileColumns) AsSlice() []column { return []column{ - c.ID, c.ContentType, c.Created, c.CreatorID, c.Deleted, c.Name, c.Status, c.SizeBytes, c.FileUUID, + c.ID, c.ContentType, c.Created, c.CreatorID, c.Deleted, c.Name, c.OrganizationID, c.Status, c.SizeBytes, c.FileUUID, } } @@ -165,12 +184,13 @@ func (i fileuploadFileIndexes) AsSlice() []index { } type fileuploadFileForeignKeys struct { - FileuploadFileFileCreatorIDFkey foreignKey + FileuploadFileFileCreatorIDFkey foreignKey + FileuploadFileFileOrganizationIDFkey foreignKey } func (f fileuploadFileForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.FileuploadFileFileCreatorIDFkey, + f.FileuploadFileFileCreatorIDFkey, f.FileuploadFileFileOrganizationIDFkey, } } diff --git a/db/factory/bobfactory_context.bob.go b/db/factory/bobfactory_context.bob.go index 5706ee74..87f574c1 100644 --- a/db/factory/bobfactory_context.bob.go +++ b/db/factory/bobfactory_context.bob.go @@ -186,6 +186,7 @@ var ( fileuploadFileRelCSVCtx = newContextual[bool]("fileupload.csv.fileupload.file.fileupload.csv.csv_file_id_fkey") fileuploadFileRelErrorsCtx = newContextual[bool]("fileupload.error.fileupload.file.fileupload.error.error_file_id_fkey") fileuploadFileRelCreatorUserCtx = newContextual[bool]("fileupload.file.user_.fileupload.file.file_creator_id_fkey") + fileuploadFileRelOrganizationCtx = newContextual[bool]("fileupload.file.organization.fileupload.file.file_organization_id_fkey") // Relationship Contexts for geography_columns geographyColumnWithParentsCascadingCtx = newContextual[bool]("geographyColumnWithParentsCascading") @@ -276,6 +277,7 @@ var ( organizationRelZonesCtx = newContextual[bool]("fieldseeker.zones.organization.fieldseeker.zones.zones_organization_id_fkey") organizationRelZones2sCtx = newContextual[bool]("fieldseeker.zones2.organization.fieldseeker.zones2.zones2_organization_id_fkey") organizationRelFieldseekerSyncsCtx = newContextual[bool]("fieldseeker_sync.organization.fieldseeker_sync.fieldseeker_sync_organization_id_fkey") + organizationRelFilesCtx = newContextual[bool]("fileupload.file.organization.fileupload.file.file_organization_id_fkey") organizationRelH3AggregationsCtx = newContextual[bool]("h3_aggregation.organization.h3_aggregation.h3_aggregation_organization_id_fkey") organizationRelNoteAudiosCtx = newContextual[bool]("note_audio.organization.note_audio.note_audio_organization_id_fkey") organizationRelNoteImagesCtx = newContextual[bool]("note_image.organization.note_image.note_image_organization_id_fkey") diff --git a/db/factory/bobfactory_main.bob.go b/db/factory/bobfactory_main.bob.go index cbb6221a..9e813e4a 100644 --- a/db/factory/bobfactory_main.bob.go +++ b/db/factory/bobfactory_main.bob.go @@ -2319,6 +2319,7 @@ func (f *Factory) FromExistingFileuploadFile(m *models.FileuploadFile) *Fileuplo o.CreatorID = func() int32 { return m.CreatorID } o.Deleted = func() null.Val[time.Time] { return m.Deleted } o.Name = func() string { return m.Name } + o.OrganizationID = func() int32 { return m.OrganizationID } o.Status = func() enums.FileuploadFilestatustype { return m.Status } o.SizeBytes = func() int32 { return m.SizeBytes } o.FileUUID = func() uuid.UUID { return m.FileUUID } @@ -2333,6 +2334,9 @@ func (f *Factory) FromExistingFileuploadFile(m *models.FileuploadFile) *Fileuplo if m.R.CreatorUser != nil { FileuploadFileMods.WithExistingCreatorUser(m.R.CreatorUser).Apply(ctx, o) } + if m.R.Organization != nil { + FileuploadFileMods.WithExistingOrganization(m.R.Organization).Apply(ctx, o) + } return o } @@ -2932,6 +2936,9 @@ func (f *Factory) FromExistingOrganization(m *models.Organization) *Organization if len(m.R.FieldseekerSyncs) > 0 { OrganizationMods.AddExistingFieldseekerSyncs(m.R.FieldseekerSyncs...).Apply(ctx, o) } + if len(m.R.Files) > 0 { + OrganizationMods.AddExistingFiles(m.R.Files...).Apply(ctx, o) + } if len(m.R.H3Aggregations) > 0 { OrganizationMods.AddExistingH3Aggregations(m.R.H3Aggregations...).Apply(ctx, o) } diff --git a/db/factory/fileupload.file.bob.go b/db/factory/fileupload.file.bob.go index 7390b2cc..9d8dec5d 100644 --- a/db/factory/fileupload.file.bob.go +++ b/db/factory/fileupload.file.bob.go @@ -39,15 +39,16 @@ func (mods FileuploadFileModSlice) Apply(ctx context.Context, n *FileuploadFileT // FileuploadFileTemplate is an object representing the database table. // all columns are optional and should be set by mods type FileuploadFileTemplate struct { - ID func() int32 - ContentType func() string - Created func() time.Time - CreatorID func() int32 - Deleted func() null.Val[time.Time] - Name func() string - Status func() enums.FileuploadFilestatustype - SizeBytes func() int32 - FileUUID func() uuid.UUID + ID func() int32 + ContentType func() string + Created func() time.Time + CreatorID func() int32 + Deleted func() null.Val[time.Time] + Name func() string + OrganizationID func() int32 + Status func() enums.FileuploadFilestatustype + SizeBytes func() int32 + FileUUID func() uuid.UUID r fileuploadFileR f *Factory @@ -56,9 +57,10 @@ type FileuploadFileTemplate struct { } type fileuploadFileR struct { - CSV *fileuploadFileRCSVR - Errors []*fileuploadFileRErrorsR - CreatorUser *fileuploadFileRCreatorUserR + CSV *fileuploadFileRCSVR + Errors []*fileuploadFileRErrorsR + CreatorUser *fileuploadFileRCreatorUserR + Organization *fileuploadFileROrganizationR } type fileuploadFileRCSVR struct { @@ -71,6 +73,9 @@ type fileuploadFileRErrorsR struct { type fileuploadFileRCreatorUserR struct { o *UserTemplate } +type fileuploadFileROrganizationR struct { + o *OrganizationTemplate +} // Apply mods to the FileuploadFileTemplate func (o *FileuploadFileTemplate) Apply(ctx context.Context, mods ...FileuploadFileMod) { @@ -108,6 +113,13 @@ func (t FileuploadFileTemplate) setModelRels(o *models.FileuploadFile) { o.CreatorID = rel.ID // h2 o.R.CreatorUser = rel } + + if t.r.Organization != nil { + rel := t.r.Organization.o.Build() + rel.R.Files = append(rel.R.Files, o) + o.OrganizationID = rel.ID // h2 + o.R.Organization = rel + } } // BuildSetter returns an *models.FileuploadFileSetter @@ -139,6 +151,10 @@ func (o FileuploadFileTemplate) BuildSetter() *models.FileuploadFileSetter { val := o.Name() m.Name = omit.From(val) } + if o.OrganizationID != nil { + val := o.OrganizationID() + m.OrganizationID = omit.From(val) + } if o.Status != nil { val := o.Status() m.Status = omit.From(val) @@ -191,6 +207,9 @@ func (o FileuploadFileTemplate) Build() *models.FileuploadFile { if o.Name != nil { m.Name = o.Name() } + if o.OrganizationID != nil { + m.OrganizationID = o.OrganizationID() + } if o.Status != nil { m.Status = o.Status() } @@ -236,6 +255,10 @@ func ensureCreatableFileuploadFile(m *models.FileuploadFileSetter) { val := random_string(nil) m.Name = omit.From(val) } + if !(m.OrganizationID.IsValue()) { + val := random_int32(nil) + m.OrganizationID = omit.From(val) + } if !(m.Status.IsValue()) { val := random_enums_FileuploadFilestatustype(nil) m.Status = omit.From(val) @@ -322,12 +345,30 @@ func (o *FileuploadFileTemplate) Create(ctx context.Context, exec bob.Executor) opt.CreatorID = omit.From(rel2.ID) + if o.r.Organization == nil { + FileuploadFileMods.WithNewOrganization().Apply(ctx, o) + } + + var rel3 *models.Organization + + if o.r.Organization.o.alreadyPersisted { + rel3 = o.r.Organization.o.Build() + } else { + rel3, err = o.r.Organization.o.Create(ctx, exec) + if err != nil { + return nil, err + } + } + + opt.OrganizationID = omit.From(rel3.ID) + m, err := models.FileuploadFiles.Insert(opt).One(ctx, exec) if err != nil { return nil, err } m.R.CreatorUser = rel2 + m.R.Organization = rel3 if err := o.insertOptRels(ctx, exec, m); err != nil { return nil, err @@ -412,6 +453,7 @@ func (m fileuploadFileMods) RandomizeAllColumns(f *faker.Faker) FileuploadFileMo FileuploadFileMods.RandomCreatorID(f), FileuploadFileMods.RandomDeleted(f), FileuploadFileMods.RandomName(f), + FileuploadFileMods.RandomOrganizationID(f), FileuploadFileMods.RandomStatus(f), FileuploadFileMods.RandomSizeBytes(f), FileuploadFileMods.RandomFileUUID(f), @@ -626,6 +668,37 @@ func (m fileuploadFileMods) RandomName(f *faker.Faker) FileuploadFileMod { }) } +// Set the model columns to this value +func (m fileuploadFileMods) OrganizationID(val int32) FileuploadFileMod { + return FileuploadFileModFunc(func(_ context.Context, o *FileuploadFileTemplate) { + o.OrganizationID = func() int32 { return val } + }) +} + +// Set the Column from the function +func (m fileuploadFileMods) OrganizationIDFunc(f func() int32) FileuploadFileMod { + return FileuploadFileModFunc(func(_ context.Context, o *FileuploadFileTemplate) { + o.OrganizationID = f + }) +} + +// Clear any values for the column +func (m fileuploadFileMods) UnsetOrganizationID() FileuploadFileMod { + return FileuploadFileModFunc(func(_ context.Context, o *FileuploadFileTemplate) { + o.OrganizationID = nil + }) +} + +// Generates a random value for the column using the given faker +// if faker is nil, a default faker is used +func (m fileuploadFileMods) RandomOrganizationID(f *faker.Faker) FileuploadFileMod { + return FileuploadFileModFunc(func(_ context.Context, o *FileuploadFileTemplate) { + o.OrganizationID = func() int32 { + return random_int32(f) + } + }) +} + // Set the model columns to this value func (m fileuploadFileMods) Status(val enums.FileuploadFilestatustype) FileuploadFileMod { return FileuploadFileModFunc(func(_ context.Context, o *FileuploadFileTemplate) { @@ -735,6 +808,11 @@ func (m fileuploadFileMods) WithParentsCascading() FileuploadFileMod { related := o.f.NewUserWithContext(ctx, UserMods.WithParentsCascading()) m.WithCreatorUser(related).Apply(ctx, o) } + { + + related := o.f.NewOrganizationWithContext(ctx, OrganizationMods.WithParentsCascading()) + m.WithOrganization(related).Apply(ctx, o) + } }) } @@ -798,6 +876,36 @@ func (m fileuploadFileMods) WithoutCreatorUser() FileuploadFileMod { }) } +func (m fileuploadFileMods) WithOrganization(rel *OrganizationTemplate) FileuploadFileMod { + return FileuploadFileModFunc(func(ctx context.Context, o *FileuploadFileTemplate) { + o.r.Organization = &fileuploadFileROrganizationR{ + o: rel, + } + }) +} + +func (m fileuploadFileMods) WithNewOrganization(mods ...OrganizationMod) FileuploadFileMod { + return FileuploadFileModFunc(func(ctx context.Context, o *FileuploadFileTemplate) { + related := o.f.NewOrganizationWithContext(ctx, mods...) + + m.WithOrganization(related).Apply(ctx, o) + }) +} + +func (m fileuploadFileMods) WithExistingOrganization(em *models.Organization) FileuploadFileMod { + return FileuploadFileModFunc(func(ctx context.Context, o *FileuploadFileTemplate) { + o.r.Organization = &fileuploadFileROrganizationR{ + o: o.f.FromExistingOrganization(em), + } + }) +} + +func (m fileuploadFileMods) WithoutOrganization() FileuploadFileMod { + return FileuploadFileModFunc(func(ctx context.Context, o *FileuploadFileTemplate) { + o.r.Organization = nil + }) +} + func (m fileuploadFileMods) WithErrors(number int, related *FileuploadErrorTemplate) FileuploadFileMod { return FileuploadFileModFunc(func(ctx context.Context, o *FileuploadFileTemplate) { o.r.Errors = []*fileuploadFileRErrorsR{{ diff --git a/db/factory/organization.bob.go b/db/factory/organization.bob.go index 6a994e3f..ee9872c5 100644 --- a/db/factory/organization.bob.go +++ b/db/factory/organization.bob.go @@ -84,6 +84,7 @@ type organizationR struct { Zones []*organizationRZonesR Zones2s []*organizationRZones2sR FieldseekerSyncs []*organizationRFieldseekerSyncsR + Files []*organizationRFilesR H3Aggregations []*organizationRH3AggregationsR NoteAudios []*organizationRNoteAudiosR NoteImages []*organizationRNoteImagesR @@ -214,6 +215,10 @@ type organizationRFieldseekerSyncsR struct { number int o *FieldseekerSyncTemplate } +type organizationRFilesR struct { + number int + o *FileuploadFileTemplate +} type organizationRH3AggregationsR struct { number int o *H3AggregationTemplate @@ -644,6 +649,19 @@ func (t OrganizationTemplate) setModelRels(o *models.Organization) { o.R.FieldseekerSyncs = rel } + if t.r.Files != nil { + rel := models.FileuploadFileSlice{} + for _, r := range t.r.Files { + related := r.o.BuildMany(r.number) + for _, rel := range related { + rel.OrganizationID = o.ID // h2 + rel.R.Organization = o + } + rel = append(rel, related...) + } + o.R.Files = rel + } + if t.r.H3Aggregations != nil { rel := models.H3AggregationSlice{} for _, r := range t.r.H3Aggregations { @@ -1465,6 +1483,26 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu } } + isFilesDone, _ := organizationRelFilesCtx.Value(ctx) + if !isFilesDone && o.r.Files != nil { + ctx = organizationRelFilesCtx.WithValue(ctx, true) + for _, r := range o.r.Files { + if r.o.alreadyPersisted { + m.R.Files = append(m.R.Files, r.o.Build()) + } else { + rel30, err := r.o.CreateMany(ctx, exec, r.number) + if err != nil { + return err + } + + err = m.AttachFiles(ctx, exec, rel30...) + if err != nil { + return err + } + } + } + } + isH3AggregationsDone, _ := organizationRelH3AggregationsCtx.Value(ctx) if !isH3AggregationsDone && o.r.H3Aggregations != nil { ctx = organizationRelH3AggregationsCtx.WithValue(ctx, true) @@ -1472,12 +1510,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.H3Aggregations = append(m.R.H3Aggregations, r.o.Build()) } else { - rel30, err := r.o.CreateMany(ctx, exec, r.number) + rel31, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachH3Aggregations(ctx, exec, rel30...) + err = m.AttachH3Aggregations(ctx, exec, rel31...) if err != nil { return err } @@ -1492,12 +1530,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.NoteAudios = append(m.R.NoteAudios, r.o.Build()) } else { - rel31, err := r.o.CreateMany(ctx, exec, r.number) + rel32, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNoteAudios(ctx, exec, rel31...) + err = m.AttachNoteAudios(ctx, exec, rel32...) if err != nil { return err } @@ -1512,12 +1550,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.NoteImages = append(m.R.NoteImages, r.o.Build()) } else { - rel32, err := r.o.CreateMany(ctx, exec, r.number) + rel33, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNoteImages(ctx, exec, rel32...) + err = m.AttachNoteImages(ctx, exec, rel33...) if err != nil { return err } @@ -1531,12 +1569,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if o.r.ImportDistrictGidDistrict.o.alreadyPersisted { m.R.ImportDistrictGidDistrict = o.r.ImportDistrictGidDistrict.o.Build() } else { - var rel33 *models.ImportDistrict - rel33, err = o.r.ImportDistrictGidDistrict.o.Create(ctx, exec) + var rel34 *models.ImportDistrict + rel34, err = o.r.ImportDistrictGidDistrict.o.Create(ctx, exec) if err != nil { return err } - err = m.AttachImportDistrictGidDistrict(ctx, exec, rel33) + err = m.AttachImportDistrictGidDistrict(ctx, exec, rel34) if err != nil { return err } @@ -1551,12 +1589,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Nuisances = append(m.R.Nuisances, r.o.Build()) } else { - rel34, err := r.o.CreateMany(ctx, exec, r.number) + rel35, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachNuisances(ctx, exec, rel34...) + err = m.AttachNuisances(ctx, exec, rel35...) if err != nil { return err } @@ -1571,12 +1609,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.PublicreportPool = append(m.R.PublicreportPool, r.o.Build()) } else { - rel35, err := r.o.CreateMany(ctx, exec, r.number) + rel36, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachPublicreportPool(ctx, exec, rel35...) + err = m.AttachPublicreportPool(ctx, exec, rel36...) if err != nil { return err } @@ -1591,12 +1629,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.Quicks = append(m.R.Quicks, r.o.Build()) } else { - rel36, err := r.o.CreateMany(ctx, exec, r.number) + rel37, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachQuicks(ctx, exec, rel36...) + err = m.AttachQuicks(ctx, exec, rel37...) if err != nil { return err } @@ -1611,12 +1649,12 @@ func (o *OrganizationTemplate) insertOptRels(ctx context.Context, exec bob.Execu if r.o.alreadyPersisted { m.R.User = append(m.R.User, r.o.Build()) } else { - rel37, err := r.o.CreateMany(ctx, exec, r.number) + rel38, err := r.o.CreateMany(ctx, exec, r.number) if err != nil { return err } - err = m.AttachUser(ctx, exec, rel37...) + err = m.AttachUser(ctx, exec, rel38...) if err != nil { return err } @@ -3645,6 +3683,54 @@ func (m organizationMods) WithoutFieldseekerSyncs() OrganizationMod { }) } +func (m organizationMods) WithFiles(number int, related *FileuploadFileTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Files = []*organizationRFilesR{{ + number: number, + o: related, + }} + }) +} + +func (m organizationMods) WithNewFiles(number int, mods ...FileuploadFileMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewFileuploadFileWithContext(ctx, mods...) + m.WithFiles(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddFiles(number int, related *FileuploadFileTemplate) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Files = append(o.r.Files, &organizationRFilesR{ + number: number, + o: related, + }) + }) +} + +func (m organizationMods) AddNewFiles(number int, mods ...FileuploadFileMod) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + related := o.f.NewFileuploadFileWithContext(ctx, mods...) + m.AddFiles(number, related).Apply(ctx, o) + }) +} + +func (m organizationMods) AddExistingFiles(existingModels ...*models.FileuploadFile) OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + for _, em := range existingModels { + o.r.Files = append(o.r.Files, &organizationRFilesR{ + o: o.f.FromExistingFileuploadFile(em), + }) + } + }) +} + +func (m organizationMods) WithoutFiles() OrganizationMod { + return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { + o.r.Files = nil + }) +} + func (m organizationMods) WithH3Aggregations(number int, related *H3AggregationTemplate) OrganizationMod { return OrganizationModFunc(func(ctx context.Context, o *OrganizationTemplate) { o.r.H3Aggregations = []*organizationRH3AggregationsR{{ diff --git a/db/migrations/00057_csv_pool_upload.sql b/db/migrations/00057_csv_pool_upload.sql index f611504a..b0c0386f 100644 --- a/db/migrations/00057_csv_pool_upload.sql +++ b/db/migrations/00057_csv_pool_upload.sql @@ -14,6 +14,7 @@ CREATE TABLE fileupload.file ( creator_id INTEGER REFERENCES user_(id) NOT NULL, deleted TIMESTAMP WITHOUT TIME ZONE, name TEXT NOT NULL, + organization_id INTEGER REFERENCES organization(id) NOT NULL, status fileupload.FileStatusType NOT NULL, size_bytes INTEGER NOT NULL, file_uuid uuid NOT NULL, diff --git a/db/models/fileupload.file.bob.go b/db/models/fileupload.file.bob.go index 86ffc12e..7f2a8663 100644 --- a/db/models/fileupload.file.bob.go +++ b/db/models/fileupload.file.bob.go @@ -28,15 +28,16 @@ import ( // FileuploadFile is an object representing the database table. type FileuploadFile struct { - ID int32 `db:"id,pk" ` - ContentType string `db:"content_type" ` - Created time.Time `db:"created" ` - CreatorID int32 `db:"creator_id" ` - Deleted null.Val[time.Time] `db:"deleted" ` - Name string `db:"name" ` - Status enums.FileuploadFilestatustype `db:"status" ` - SizeBytes int32 `db:"size_bytes" ` - FileUUID uuid.UUID `db:"file_uuid" ` + ID int32 `db:"id,pk" ` + ContentType string `db:"content_type" ` + Created time.Time `db:"created" ` + CreatorID int32 `db:"creator_id" ` + Deleted null.Val[time.Time] `db:"deleted" ` + Name string `db:"name" ` + OrganizationID int32 `db:"organization_id" ` + Status enums.FileuploadFilestatustype `db:"status" ` + SizeBytes int32 `db:"size_bytes" ` + FileUUID uuid.UUID `db:"file_uuid" ` R fileuploadFileR `db:"-" ` @@ -55,41 +56,44 @@ type FileuploadFilesQuery = *psql.ViewQuery[*FileuploadFile, FileuploadFileSlice // fileuploadFileR is where relationships are stored. type fileuploadFileR struct { - CSV *FileuploadCSV // fileupload.csv.csv_file_id_fkey - Errors FileuploadErrorSlice // fileupload.error.error_file_id_fkey - CreatorUser *User // fileupload.file.file_creator_id_fkey + CSV *FileuploadCSV // fileupload.csv.csv_file_id_fkey + Errors FileuploadErrorSlice // fileupload.error.error_file_id_fkey + CreatorUser *User // fileupload.file.file_creator_id_fkey + Organization *Organization // fileupload.file.file_organization_id_fkey } func buildFileuploadFileColumns(alias string) fileuploadFileColumns { return fileuploadFileColumns{ ColumnsExpr: expr.NewColumnsExpr( - "id", "content_type", "created", "creator_id", "deleted", "name", "status", "size_bytes", "file_uuid", + "id", "content_type", "created", "creator_id", "deleted", "name", "organization_id", "status", "size_bytes", "file_uuid", ).WithParent("fileupload.file"), - tableAlias: alias, - ID: psql.Quote(alias, "id"), - ContentType: psql.Quote(alias, "content_type"), - Created: psql.Quote(alias, "created"), - CreatorID: psql.Quote(alias, "creator_id"), - Deleted: psql.Quote(alias, "deleted"), - Name: psql.Quote(alias, "name"), - Status: psql.Quote(alias, "status"), - SizeBytes: psql.Quote(alias, "size_bytes"), - FileUUID: psql.Quote(alias, "file_uuid"), + tableAlias: alias, + ID: psql.Quote(alias, "id"), + ContentType: psql.Quote(alias, "content_type"), + Created: psql.Quote(alias, "created"), + CreatorID: psql.Quote(alias, "creator_id"), + Deleted: psql.Quote(alias, "deleted"), + Name: psql.Quote(alias, "name"), + OrganizationID: psql.Quote(alias, "organization_id"), + Status: psql.Quote(alias, "status"), + SizeBytes: psql.Quote(alias, "size_bytes"), + FileUUID: psql.Quote(alias, "file_uuid"), } } type fileuploadFileColumns struct { expr.ColumnsExpr - tableAlias string - ID psql.Expression - ContentType psql.Expression - Created psql.Expression - CreatorID psql.Expression - Deleted psql.Expression - Name psql.Expression - Status psql.Expression - SizeBytes psql.Expression - FileUUID psql.Expression + tableAlias string + ID psql.Expression + ContentType psql.Expression + Created psql.Expression + CreatorID psql.Expression + Deleted psql.Expression + Name psql.Expression + OrganizationID psql.Expression + Status psql.Expression + SizeBytes psql.Expression + FileUUID psql.Expression } func (c fileuploadFileColumns) Alias() string { @@ -104,19 +108,20 @@ func (fileuploadFileColumns) AliasedAs(alias string) fileuploadFileColumns { // All values are optional, and do not have to be set // Generated columns are not included type FileuploadFileSetter struct { - ID omit.Val[int32] `db:"id,pk" ` - ContentType omit.Val[string] `db:"content_type" ` - Created omit.Val[time.Time] `db:"created" ` - CreatorID omit.Val[int32] `db:"creator_id" ` - Deleted omitnull.Val[time.Time] `db:"deleted" ` - Name omit.Val[string] `db:"name" ` - Status omit.Val[enums.FileuploadFilestatustype] `db:"status" ` - SizeBytes omit.Val[int32] `db:"size_bytes" ` - FileUUID omit.Val[uuid.UUID] `db:"file_uuid" ` + ID omit.Val[int32] `db:"id,pk" ` + ContentType omit.Val[string] `db:"content_type" ` + Created omit.Val[time.Time] `db:"created" ` + CreatorID omit.Val[int32] `db:"creator_id" ` + Deleted omitnull.Val[time.Time] `db:"deleted" ` + Name omit.Val[string] `db:"name" ` + OrganizationID omit.Val[int32] `db:"organization_id" ` + Status omit.Val[enums.FileuploadFilestatustype] `db:"status" ` + SizeBytes omit.Val[int32] `db:"size_bytes" ` + FileUUID omit.Val[uuid.UUID] `db:"file_uuid" ` } func (s FileuploadFileSetter) SetColumns() []string { - vals := make([]string, 0, 9) + vals := make([]string, 0, 10) if s.ID.IsValue() { vals = append(vals, "id") } @@ -135,6 +140,9 @@ func (s FileuploadFileSetter) SetColumns() []string { if s.Name.IsValue() { vals = append(vals, "name") } + if s.OrganizationID.IsValue() { + vals = append(vals, "organization_id") + } if s.Status.IsValue() { vals = append(vals, "status") } @@ -166,6 +174,9 @@ func (s FileuploadFileSetter) Overwrite(t *FileuploadFile) { if s.Name.IsValue() { t.Name = s.Name.MustGet() } + if s.OrganizationID.IsValue() { + t.OrganizationID = s.OrganizationID.MustGet() + } if s.Status.IsValue() { t.Status = s.Status.MustGet() } @@ -183,7 +194,7 @@ func (s *FileuploadFileSetter) Apply(q *dialect.InsertQuery) { }) q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) { - vals := make([]bob.Expression, 9) + vals := make([]bob.Expression, 10) if s.ID.IsValue() { vals[0] = psql.Arg(s.ID.MustGet()) } else { @@ -220,24 +231,30 @@ func (s *FileuploadFileSetter) Apply(q *dialect.InsertQuery) { vals[5] = psql.Raw("DEFAULT") } - if s.Status.IsValue() { - vals[6] = psql.Arg(s.Status.MustGet()) + if s.OrganizationID.IsValue() { + vals[6] = psql.Arg(s.OrganizationID.MustGet()) } else { vals[6] = psql.Raw("DEFAULT") } - if s.SizeBytes.IsValue() { - vals[7] = psql.Arg(s.SizeBytes.MustGet()) + if s.Status.IsValue() { + vals[7] = psql.Arg(s.Status.MustGet()) } else { vals[7] = psql.Raw("DEFAULT") } - if s.FileUUID.IsValue() { - vals[8] = psql.Arg(s.FileUUID.MustGet()) + if s.SizeBytes.IsValue() { + vals[8] = psql.Arg(s.SizeBytes.MustGet()) } else { vals[8] = psql.Raw("DEFAULT") } + if s.FileUUID.IsValue() { + vals[9] = psql.Arg(s.FileUUID.MustGet()) + } else { + vals[9] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -247,7 +264,7 @@ func (s FileuploadFileSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s FileuploadFileSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 9) + exprs := make([]bob.Expression, 0, 10) if s.ID.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -291,6 +308,13 @@ func (s FileuploadFileSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if s.OrganizationID.IsValue() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "organization_id")...), + psql.Arg(s.OrganizationID), + }}) + } + if s.Status.IsValue() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "status")...), @@ -610,6 +634,30 @@ func (os FileuploadFileSlice) CreatorUser(mods ...bob.Mod[*dialect.SelectQuery]) )...) } +// Organization starts a query for related objects on organization +func (o *FileuploadFile) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os FileuploadFileSlice) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + pkOrganizationID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkOrganizationID = append(pkOrganizationID, o.OrganizationID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkOrganizationID), "integer[]")), + )) + + return Organizations.Query(append(mods, + sm.Where(psql.Group(Organizations.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + func insertFileuploadFileCSV0(ctx context.Context, exec bob.Executor, fileuploadCSV1 *FileuploadCSVSetter, fileuploadFile0 *FileuploadFile) (*FileuploadCSV, error) { fileuploadCSV1.FileID = omit.From(fileuploadFile0.ID) @@ -780,16 +828,65 @@ func (fileuploadFile0 *FileuploadFile) AttachCreatorUser(ctx context.Context, ex return nil } +func attachFileuploadFileOrganization0(ctx context.Context, exec bob.Executor, count int, fileuploadFile0 *FileuploadFile, organization1 *Organization) (*FileuploadFile, error) { + setter := &FileuploadFileSetter{ + OrganizationID: omit.From(organization1.ID), + } + + err := fileuploadFile0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachFileuploadFileOrganization0: %w", err) + } + + return fileuploadFile0, nil +} + +func (fileuploadFile0 *FileuploadFile) InsertOrganization(ctx context.Context, exec bob.Executor, related *OrganizationSetter) error { + var err error + + organization1, err := Organizations.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachFileuploadFileOrganization0(ctx, exec, 1, fileuploadFile0, organization1) + if err != nil { + return err + } + + fileuploadFile0.R.Organization = organization1 + + organization1.R.Files = append(organization1.R.Files, fileuploadFile0) + + return nil +} + +func (fileuploadFile0 *FileuploadFile) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachFileuploadFileOrganization0(ctx, exec, 1, fileuploadFile0, organization1) + if err != nil { + return err + } + + fileuploadFile0.R.Organization = organization1 + + organization1.R.Files = append(organization1.R.Files, fileuploadFile0) + + return nil +} + type fileuploadFileWhere[Q psql.Filterable] struct { - ID psql.WhereMod[Q, int32] - ContentType psql.WhereMod[Q, string] - Created psql.WhereMod[Q, time.Time] - CreatorID psql.WhereMod[Q, int32] - Deleted psql.WhereNullMod[Q, time.Time] - Name psql.WhereMod[Q, string] - Status psql.WhereMod[Q, enums.FileuploadFilestatustype] - SizeBytes psql.WhereMod[Q, int32] - FileUUID psql.WhereMod[Q, uuid.UUID] + ID psql.WhereMod[Q, int32] + ContentType psql.WhereMod[Q, string] + Created psql.WhereMod[Q, time.Time] + CreatorID psql.WhereMod[Q, int32] + Deleted psql.WhereNullMod[Q, time.Time] + Name psql.WhereMod[Q, string] + OrganizationID psql.WhereMod[Q, int32] + Status psql.WhereMod[Q, enums.FileuploadFilestatustype] + SizeBytes psql.WhereMod[Q, int32] + FileUUID psql.WhereMod[Q, uuid.UUID] } func (fileuploadFileWhere[Q]) AliasedAs(alias string) fileuploadFileWhere[Q] { @@ -798,15 +895,16 @@ func (fileuploadFileWhere[Q]) AliasedAs(alias string) fileuploadFileWhere[Q] { func buildFileuploadFileWhere[Q psql.Filterable](cols fileuploadFileColumns) fileuploadFileWhere[Q] { return fileuploadFileWhere[Q]{ - ID: psql.Where[Q, int32](cols.ID), - ContentType: psql.Where[Q, string](cols.ContentType), - Created: psql.Where[Q, time.Time](cols.Created), - CreatorID: psql.Where[Q, int32](cols.CreatorID), - Deleted: psql.WhereNull[Q, time.Time](cols.Deleted), - Name: psql.Where[Q, string](cols.Name), - Status: psql.Where[Q, enums.FileuploadFilestatustype](cols.Status), - SizeBytes: psql.Where[Q, int32](cols.SizeBytes), - FileUUID: psql.Where[Q, uuid.UUID](cols.FileUUID), + ID: psql.Where[Q, int32](cols.ID), + ContentType: psql.Where[Q, string](cols.ContentType), + Created: psql.Where[Q, time.Time](cols.Created), + CreatorID: psql.Where[Q, int32](cols.CreatorID), + Deleted: psql.WhereNull[Q, time.Time](cols.Deleted), + Name: psql.Where[Q, string](cols.Name), + OrganizationID: psql.Where[Q, int32](cols.OrganizationID), + Status: psql.Where[Q, enums.FileuploadFilestatustype](cols.Status), + SizeBytes: psql.Where[Q, int32](cols.SizeBytes), + FileUUID: psql.Where[Q, uuid.UUID](cols.FileUUID), } } @@ -854,14 +952,27 @@ func (o *FileuploadFile) Preload(name string, retrieved any) error { rel.R.CreatorFiles = FileuploadFileSlice{o} } return nil + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("fileuploadFile cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.Files = FileuploadFileSlice{o} + } + return nil default: return fmt.Errorf("fileuploadFile has no relationship %q", name) } } type fileuploadFilePreloader struct { - CSV func(...psql.PreloadOption) psql.Preloader - CreatorUser func(...psql.PreloadOption) psql.Preloader + CSV func(...psql.PreloadOption) psql.Preloader + CreatorUser func(...psql.PreloadOption) psql.Preloader + Organization func(...psql.PreloadOption) psql.Preloader } func buildFileuploadFilePreloader() fileuploadFilePreloader { @@ -892,13 +1003,27 @@ func buildFileuploadFilePreloader() fileuploadFilePreloader { }, }, Users.Columns.Names(), opts...) }, + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: FileuploadFiles, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, } } type fileuploadFileThenLoader[Q orm.Loadable] struct { - CSV func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Errors func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CSV func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Errors func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildFileuploadFileThenLoader[Q orm.Loadable]() fileuploadFileThenLoader[Q] { @@ -911,6 +1036,9 @@ func buildFileuploadFileThenLoader[Q orm.Loadable]() fileuploadFileThenLoader[Q] type CreatorUserLoadInterface interface { LoadCreatorUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } return fileuploadFileThenLoader[Q]{ CSV: thenLoadBuilder[Q]( @@ -931,6 +1059,12 @@ func buildFileuploadFileThenLoader[Q orm.Loadable]() fileuploadFileThenLoader[Q] return retrieved.LoadCreatorUser(ctx, exec, mods...) }, ), + Organization: thenLoadBuilder[Q]( + "Organization", + func(ctx context.Context, exec bob.Executor, retrieved OrganizationLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadOrganization(ctx, exec, mods...) + }, + ), } } @@ -1099,6 +1233,58 @@ func (os FileuploadFileSlice) LoadCreatorUser(ctx context.Context, exec bob.Exec return nil } +// LoadOrganization loads the fileuploadFile's Organization into the .R struct +func (o *FileuploadFile) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Organization = nil + + related, err := o.Organization(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.Files = FileuploadFileSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the fileuploadFile's Organization into the .R struct +func (os FileuploadFileSlice) LoadOrganization(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + organizations, err := os.Organization(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range organizations { + + if !(o.OrganizationID == rel.ID) { + continue + } + + rel.R.Files = append(rel.R.Files, o) + + o.R.Organization = rel + break + } + } + + return nil +} + // fileuploadFileC is where relationship counts are stored. type fileuploadFileC struct { Errors *int64 @@ -1193,10 +1379,11 @@ func (os FileuploadFileSlice) LoadCountErrors(ctx context.Context, exec bob.Exec } type fileuploadFileJoins[Q dialect.Joinable] struct { - typ string - CSV modAs[Q, fileuploadCSVColumns] - Errors modAs[Q, fileuploadErrorColumns] - CreatorUser modAs[Q, userColumns] + typ string + CSV modAs[Q, fileuploadCSVColumns] + Errors modAs[Q, fileuploadErrorColumns] + CreatorUser modAs[Q, userColumns] + Organization modAs[Q, organizationColumns] } func (j fileuploadFileJoins[Q]) aliasedAs(alias string) fileuploadFileJoins[Q] { @@ -1245,6 +1432,20 @@ func buildFileuploadFileJoins[Q dialect.Joinable](cols fileuploadFileColumns, ty )) } + return mods + }, + }, + Organization: modAs[Q, organizationColumns]{ + c: Organizations.Columns, + f: func(to organizationColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, Organizations.Name().As(to.Alias())).On( + to.ID.EQ(cols.OrganizationID), + )) + } + return mods }, }, diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 5a2d23de..ac5c5e90 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -85,6 +85,7 @@ type organizationR struct { Zones FieldseekerZoneSlice // fieldseeker.zones.zones_organization_id_fkey Zones2s FieldseekerZones2Slice // fieldseeker.zones2.zones2_organization_id_fkey FieldseekerSyncs FieldseekerSyncSlice // fieldseeker_sync.fieldseeker_sync_organization_id_fkey + Files FileuploadFileSlice // fileupload.file.file_organization_id_fkey H3Aggregations H3AggregationSlice // h3_aggregation.h3_aggregation_organization_id_fkey NoteAudios NoteAudioSlice // note_audio.note_audio_organization_id_fkey NoteImages NoteImageSlice // note_image.note_image_organization_id_fkey @@ -1303,6 +1304,30 @@ func (os OrganizationSlice) FieldseekerSyncs(mods ...bob.Mod[*dialect.SelectQuer )...) } +// Files starts a query for related objects on fileupload.file +func (o *Organization) Files(mods ...bob.Mod[*dialect.SelectQuery]) FileuploadFilesQuery { + return FileuploadFiles.Query(append(mods, + sm.Where(FileuploadFiles.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) Files(mods ...bob.Mod[*dialect.SelectQuery]) FileuploadFilesQuery { + pkID := make(pgtypes.Array[int32], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkID = append(pkID, o.ID) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkID), "integer[]")), + )) + + return FileuploadFiles.Query(append(mods, + sm.Where(psql.Group(FileuploadFiles.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + // H3Aggregations starts a query for related objects on h3_aggregation func (o *Organization) H3Aggregations(mods ...bob.Mod[*dialect.SelectQuery]) H3AggregationsQuery { return H3Aggregations.Query(append(mods, @@ -3529,6 +3554,74 @@ func (organization0 *Organization) AttachFieldseekerSyncs(ctx context.Context, e return nil } +func insertOrganizationFiles0(ctx context.Context, exec bob.Executor, fileuploadFiles1 []*FileuploadFileSetter, organization0 *Organization) (FileuploadFileSlice, error) { + for i := range fileuploadFiles1 { + fileuploadFiles1[i].OrganizationID = omit.From(organization0.ID) + } + + ret, err := FileuploadFiles.Insert(bob.ToMods(fileuploadFiles1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationFiles0: %w", err) + } + + return ret, nil +} + +func attachOrganizationFiles0(ctx context.Context, exec bob.Executor, count int, fileuploadFiles1 FileuploadFileSlice, organization0 *Organization) (FileuploadFileSlice, error) { + setter := &FileuploadFileSetter{ + OrganizationID: omit.From(organization0.ID), + } + + err := fileuploadFiles1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationFiles0: %w", err) + } + + return fileuploadFiles1, nil +} + +func (organization0 *Organization) InsertFiles(ctx context.Context, exec bob.Executor, related ...*FileuploadFileSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + fileuploadFiles1, err := insertOrganizationFiles0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.Files = append(organization0.R.Files, fileuploadFiles1...) + + for _, rel := range fileuploadFiles1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachFiles(ctx context.Context, exec bob.Executor, related ...*FileuploadFile) error { + if len(related) == 0 { + return nil + } + + var err error + fileuploadFiles1 := FileuploadFileSlice(related) + + _, err = attachOrganizationFiles0(ctx, exec, len(related), fileuploadFiles1, organization0) + if err != nil { + return err + } + + organization0.R.Files = append(organization0.R.Files, fileuploadFiles1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + func insertOrganizationH3Aggregations0(ctx context.Context, exec bob.Executor, h3Aggregations1 []*H3AggregationSetter, organization0 *Organization) (H3AggregationSlice, error) { for i := range h3Aggregations1 { h3Aggregations1[i].OrganizationID = omit.From(organization0.ID) @@ -4503,6 +4596,20 @@ func (o *Organization) Preload(name string, retrieved any) error { o.R.FieldseekerSyncs = rels + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil + case "Files": + rels, ok := retrieved.(FileuploadFileSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.Files = rels + for _, rel := range rels { if rel != nil { rel.R.Organization = o @@ -4677,6 +4784,7 @@ type organizationThenLoader[Q orm.Loadable] struct { Zones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Zones2s func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Files func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -4778,6 +4886,9 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { type FieldseekerSyncsLoadInterface interface { LoadFieldseekerSyncs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type FilesLoadInterface interface { + LoadFiles(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type H3AggregationsLoadInterface interface { LoadH3Aggregations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -4984,6 +5095,12 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { return retrieved.LoadFieldseekerSyncs(ctx, exec, mods...) }, ), + Files: thenLoadBuilder[Q]( + "Files", + func(ctx context.Context, exec bob.Executor, retrieved FilesLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadFiles(ctx, exec, mods...) + }, + ), H3Aggregations: thenLoadBuilder[Q]( "H3Aggregations", func(ctx context.Context, exec bob.Executor, retrieved H3AggregationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -6905,6 +7022,67 @@ func (os OrganizationSlice) LoadFieldseekerSyncs(ctx context.Context, exec bob.E return nil } +// LoadFiles loads the organization's Files into the .R struct +func (o *Organization) LoadFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Files = nil + + related, err := o.Files(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.Files = related + return nil +} + +// LoadFiles loads the organization's Files into the .R struct +func (os OrganizationSlice) LoadFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + fileuploadFiles, err := os.Files(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.Files = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range fileuploadFiles { + + if !(o.ID == rel.OrganizationID) { + continue + } + + rel.R.Organization = o + + o.R.Files = append(o.R.Files, rel) + } + } + + return nil +} + // LoadH3Aggregations loads the organization's H3Aggregations into the .R struct func (o *Organization) LoadH3Aggregations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -7428,6 +7606,7 @@ type organizationC struct { Zones *int64 Zones2s *int64 FieldseekerSyncs *int64 + Files *int64 H3Aggregations *int64 NoteAudios *int64 NoteImages *int64 @@ -7504,6 +7683,8 @@ func (o *Organization) PreloadCount(name string, count int64) error { o.C.Zones2s = &count case "FieldseekerSyncs": o.C.FieldseekerSyncs = &count + case "Files": + o.C.Files = &count case "H3Aggregations": o.C.H3Aggregations = &count case "NoteAudios": @@ -7553,6 +7734,7 @@ type organizationCountPreloader struct { Zones func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader Zones2s func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader + Files func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader NoteAudios func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader NoteImages func(...bob.Mod[*dialect.SelectQuery]) psql.Preloader @@ -8080,6 +8262,23 @@ func buildOrganizationCountPreloader() organizationCountPreloader { return psql.Group(psql.Select(subqueryMods...).Expression) }) }, + Files: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { + return countPreloader[*Organization]("Files", func(parent string) bob.Expression { + // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) + if parent == "" { + parent = Organizations.Alias() + } + + subqueryMods := []bob.Mod[*dialect.SelectQuery]{ + sm.Columns(psql.Raw("count(*)")), + + sm.From(FileuploadFiles.Name()), + sm.Where(psql.Quote(FileuploadFiles.Alias(), "organization_id").EQ(psql.Quote(parent, "id"))), + } + subqueryMods = append(subqueryMods, mods...) + return psql.Group(psql.Select(subqueryMods...).Expression) + }) + }, H3Aggregations: func(mods ...bob.Mod[*dialect.SelectQuery]) psql.Preloader { return countPreloader[*Organization]("H3Aggregations", func(parent string) bob.Expression { // Build a correlated subquery: (SELECT COUNT(*) FROM related WHERE fk = parent.pk) @@ -8233,6 +8432,7 @@ type organizationCountThenLoader[Q orm.Loadable] struct { Zones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Zones2s func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] FieldseekerSyncs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Files func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] H3Aggregations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] NoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -8333,6 +8533,9 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa type FieldseekerSyncsCountInterface interface { LoadCountFieldseekerSyncs(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type FilesCountInterface interface { + LoadCountFiles(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type H3AggregationsCountInterface interface { LoadCountH3Aggregations(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -8536,6 +8739,12 @@ func buildOrganizationCountThenLoader[Q orm.Loadable]() organizationCountThenLoa return retrieved.LoadCountFieldseekerSyncs(ctx, exec, mods...) }, ), + Files: countThenLoadBuilder[Q]( + "Files", + func(ctx context.Context, exec bob.Executor, retrieved FilesCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCountFiles(ctx, exec, mods...) + }, + ), H3Aggregations: countThenLoadBuilder[Q]( "H3Aggregations", func(ctx context.Context, exec bob.Executor, retrieved H3AggregationsCountInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -9481,6 +9690,36 @@ func (os OrganizationSlice) LoadCountFieldseekerSyncs(ctx context.Context, exec return nil } +// LoadCountFiles loads the count of Files into the C struct +func (o *Organization) LoadCountFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + count, err := o.Files(mods...).Count(ctx, exec) + if err != nil { + return err + } + + o.C.Files = &count + return nil +} + +// LoadCountFiles loads the count of Files for a slice +func (os OrganizationSlice) LoadCountFiles(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + for _, o := range os { + if err := o.LoadCountFiles(ctx, exec, mods...); err != nil { + return err + } + } + + return nil +} + // LoadCountH3Aggregations loads the count of H3Aggregations into the C struct func (o *Organization) LoadCountH3Aggregations(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -9723,6 +9962,7 @@ type organizationJoins[Q dialect.Joinable] struct { Zones modAs[Q, fieldseekerZoneColumns] Zones2s modAs[Q, fieldseekerZones2Columns] FieldseekerSyncs modAs[Q, fieldseekerSyncColumns] + Files modAs[Q, fileuploadFileColumns] H3Aggregations modAs[Q, h3AggregationColumns] NoteAudios modAs[Q, noteAudioColumns] NoteImages modAs[Q, noteImageColumns] @@ -10176,6 +10416,20 @@ func buildOrganizationJoins[Q dialect.Joinable](cols organizationColumns, typ st return mods }, }, + Files: modAs[Q, fileuploadFileColumns]{ + c: FileuploadFiles.Columns, + f: func(to fileuploadFileColumns) bob.Mod[Q] { + mods := make(mods.QueryMods[Q], 0, 1) + + { + mods = append(mods, dialect.Join[Q](typ, FileuploadFiles.Name().As(to.Alias())).On( + to.OrganizationID.EQ(cols.ID), + )) + } + + return mods + }, + }, H3Aggregations: modAs[Q, h3AggregationColumns]{ c: H3Aggregations.Columns, f: func(to h3AggregationColumns) bob.Mod[Q] { diff --git a/platform/csv/pool.go b/platform/csv/pool.go index 3c5cee2e..7ad854c2 100644 --- a/platform/csv/pool.go +++ b/platform/csv/pool.go @@ -4,6 +4,8 @@ import ( "context" "encoding/csv" "fmt" + "io" + "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/userfile" @@ -31,6 +33,9 @@ func ProcessJob(ctx context.Context, file_id int32) error { for { row, err := reader.Read() if err != nil { + if err == io.EOF { + return nil + } return fmt.Errorf("Failed to read all CSV records for file %d: %w", file_id, err) } log.Debug().Strs("row", row).Msg("Line") diff --git a/platform/pool.go b/platform/pool.go index 61adbc1f..1c4052d5 100644 --- a/platform/pool.go +++ b/platform/pool.go @@ -24,14 +24,15 @@ func NewPoolUpload(ctx context.Context, u *models.User, upload userfile.FileUplo } file, err := models.FileuploadFiles.Insert(&models.FileuploadFileSetter{ - ContentType: omit.From(upload.ContentType), - Created: omit.From(time.Now()), - CreatorID: omit.From(u.ID), - Deleted: omitnull.FromPtr[time.Time](nil), - Name: omit.From(upload.Name), - Status: omit.From(enums.FileuploadFilestatustypeUploaded), - SizeBytes: omit.From(int32(upload.SizeBytes)), - FileUUID: omit.From(upload.UUID), + ContentType: omit.From(upload.ContentType), + Created: omit.From(time.Now()), + CreatorID: omit.From(u.ID), + Deleted: omitnull.FromPtr[time.Time](nil), + Name: omit.From(upload.Name), + OrganizationID: omit.From(u.OrganizationID), + Status: omit.From(enums.FileuploadFilestatustypeUploaded), + SizeBytes: omit.From(int32(upload.SizeBytes)), + FileUUID: omit.From(upload.UUID), }).One(ctx, txn) if err != nil { return PoolUpload{}, fmt.Errorf("Failed to create file upload: %w", err)