diff --git a/api/api.go b/api/api.go index c12117dd..ba29d42a 100644 --- a/api/api.go +++ b/api/api.go @@ -230,7 +230,12 @@ func webhookFieldseeker(w http.ResponseWriter, r *http.Request) { // Write timestamp timestamp := time.Now().Format("2006-01-02 15:04:05") - fmt.Fprintf(file, "\n=== Request logged at %s ===\n", timestamp) + _, err = fmt.Fprintf(file, "\n=== Request logged at %s ===\n", timestamp) + if err != nil { + log.Error().Err(err).Msg("writing response") + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } // Write request line fmt.Fprintf(file, "%s %s %s\n", r.Method, r.RequestURI, r.Proto) @@ -250,7 +255,13 @@ func webhookFieldseeker(w http.ResponseWriter, r *http.Request) { log.Printf("Error reading request body: %v", err) fmt.Fprintf(file, "Error reading body: %v\n", err) } else { - file.Write(body) + _, err = file.Write(body) + if err != nil { + log.Error().Err(err).Msg("writing response") + http.Error(w, "Internal server error", http.StatusInternalServerError) + return + } + if len(body) == 0 { fmt.Fprintf(file, "(empty body)") } diff --git a/api/audio.go b/api/audio.go index 8d60423a..2ed7675f 100644 --- a/api/audio.go +++ b/api/audio.go @@ -69,6 +69,7 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, user platform.U if err != nil { log.Printf("Failed to write content file: %v", err) http.Error(w, "failed to write content file", http.StatusInternalServerError) + return } ctx := r.Context() a, err := models.NoteAudios.Query( @@ -78,8 +79,15 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, user platform.U if err != nil { log.Printf("Failed to get note audio %s for org %d: %w", u_str, user.Organization.ID, err) http.Error(w, "failed to update database", http.StatusBadRequest) + return + } + + err = background.NewAudioTranscode(ctx, db.PGInstance.BobDB, a.ID) + if err != nil { + log.Printf("Failed to transcode audio %s for org %d: %w", u_str, user.Organization.ID, err) + http.Error(w, "failed to transcode audio", http.StatusBadRequest) + return } - background.NewAudioTranscode(ctx, db.PGInstance.BobDB, a.ID) w.WriteHeader(http.StatusOK) } diff --git a/api/debug.go b/api/debug.go index 4bc40d78..4c025478 100644 --- a/api/debug.go +++ b/api/debug.go @@ -5,6 +5,7 @@ import ( "net/http" "os" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/rs/zerolog/log" ) @@ -14,7 +15,7 @@ func debugSaveRequest(r *http.Request) { log.Error().Err(err).Msg("failed to create temp file for debugSaveRequest") return } - defer tmpFile.Close() + defer lint.LogOnErr(tmpFile.Close, "close temp file") _, err = io.Copy(tmpFile, r.Body) if err != nil { diff --git a/api/handler.go b/api/handler.go index b44638c5..6c585dc7 100644 --- a/api/handler.go +++ b/api/handler.go @@ -30,7 +30,7 @@ type handlerFunctionGet[T any] func(context.Context, *http.Request, resource.Que type handlerFunctionGetAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus) type handlerFunctionGetImage func(context.Context, *http.Request, platform.User) (file.Collection, uuid.UUID, *nhttp.ErrorWithStatus) type handlerFunctionGetSlice[T any] func(context.Context, *http.Request, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus) -type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]*T, *nhttp.ErrorWithStatus) +type handlerFunctionGetSliceAuthenticated[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) ([]T, *nhttp.ErrorWithStatus) type handlerFunctionPost[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (ResponseType, *nhttp.ErrorWithStatus) type handlerFunctionPostAuthenticated[RequestType any, ResponseType any] func(context.Context, *http.Request, platform.User, RequestType) (ResponseType, *nhttp.ErrorWithStatus) type handlerFunctionPostFormMultipart[RequestType any, ResponseType any] func(context.Context, *http.Request, RequestType) (*ResponseType, *nhttp.ErrorWithStatus) diff --git a/api/routes.go b/api/routes.go index 47222d02..99ea9660 100644 --- a/api/routes.go +++ b/api/routes.go @@ -99,6 +99,11 @@ func AddRoutesSync(r *mux.Router) { r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET") communication := resource.Communication(router) r.Handle("/communication", authenticatedHandlerJSONSlice(communication.List)).Methods("GET") + r.Handle("/communication/{id}", authenticatedHandlerJSON(communication.Get)).Methods("GET").Name("communication.ByIDGet") + r.Handle("/communication/{id}/mark/invalid", authenticatedHandlerJSONPost(communication.MarkInvalid)).Methods("GET").Name("communication.MarkInvalid") + r.Handle("/communication/{id}/mark/pending-response", authenticatedHandlerJSONPost(communication.MarkPendingResponse)).Methods("GET").Name("communication.MarkPendingResponse") + r.Handle("/communication/{id}/mark/possible-issue", authenticatedHandlerJSONPost(communication.MarkPossibleIssue)).Methods("GET").Name("communication.MarkPossibleIssue") + r.Handle("/communication/{id}/mark/possible-resolved", authenticatedHandlerJSONPost(communication.MarkPossibleResolved)).Methods("GET").Name("communication.MarkPossibleResolved") r.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST") //r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET") r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST") diff --git a/auth/auth.go b/auth/auth.go index 7d55ddc9..45f58dd9 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -143,7 +143,10 @@ func (ea *EnsureAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) { } w.WriteHeader(401) - w.Write(msg) + _, err = w.Write(msg) + if err != nil { + log.Error().Err(err).Msg("failed to write response") + } return } ea.handler(w, r, *user) diff --git a/comms/text/voipms.go b/comms/text/voipms.go index fe1816cc..4bad095a 100644 --- a/comms/text/voipms.go +++ b/comms/text/voipms.go @@ -5,12 +5,13 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strconv" "github.com/Gleipnir-Technology/nidus-sync/config" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/rs/zerolog/log" ) @@ -87,10 +88,10 @@ func makeVoipMSRequest(params url.Values) (VoipMSResponse, error) { log.Warn().Err(err).Str("url", full_url).Msg("Failed to make request to Voip.MS") return result, fmt.Errorf("Error making request: %w", err) } - defer resp.Body.Close() + defer lint.LogOnErr(resp.Body.Close, "failed closing response body") // Read the response body - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { log.Warn().Err(err).Str("url", full_url).Msg("Failed to read Voip.MS response body") return result, fmt.Errorf("Failed to read response: %w", err) diff --git a/db/dbinfo/communication.bob.go b/db/dbinfo/communication.bob.go index a0849ad7..7560689d 100644 --- a/db/dbinfo/communication.bob.go +++ b/db/dbinfo/communication.bob.go @@ -87,6 +87,15 @@ var Communications = Table[ Generated: false, AutoIncr: false, }, + OrganizationID: column{ + Name: "organization_id", + DBType: "integer", + Default: "", + Comment: "", + Nullable: false, + Generated: false, + AutoIncr: false, + }, ResponseEmailLogID: column{ Name: "response_email_log_id", DBType: "integer", @@ -150,6 +159,42 @@ var Communications = Table[ Generated: false, AutoIncr: false, }, + SetPossibleIssue: column{ + Name: "set_possible_issue", + DBType: "timestamp without time zone", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + SetPossibleIssueBy: column{ + Name: "set_possible_issue_by", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + SetPossibleResolved: column{ + Name: "set_possible_resolved", + DBType: "timestamp without time zone", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, + SetPossibleResolvedBy: column{ + Name: "set_possible_resolved_by", + DBType: "integer", + Default: "NULL", + Comment: "", + Nullable: true, + Generated: false, + AutoIncr: false, + }, }, Indexes: communicationIndexes{ CommunicationPkey: index{ @@ -203,6 +248,15 @@ var Communications = Table[ ForeignTable: "user_", ForeignColumns: []string{"id"}, }, + CommunicationCommunicationOrganizationIDFkey: foreignKey{ + constraint: constraint{ + Name: "communication.communication_organization_id_fkey", + Columns: []string{"organization_id"}, + Comment: "", + }, + ForeignTable: "organization", + ForeignColumns: []string{"id"}, + }, CommunicationCommunicationResponseEmailLogIDFkey: foreignKey{ constraint: constraint{ Name: "communication.communication_response_email_log_id_fkey", @@ -230,6 +284,24 @@ var Communications = Table[ ForeignTable: "user_", ForeignColumns: []string{"id"}, }, + CommunicationCommunicationSetPossibleIssueByFkey: foreignKey{ + constraint: constraint{ + Name: "communication.communication_set_possible_issue_by_fkey", + Columns: []string{"set_possible_issue_by"}, + Comment: "", + }, + ForeignTable: "user_", + ForeignColumns: []string{"id"}, + }, + CommunicationCommunicationSetPossibleResolvedByFkey: foreignKey{ + constraint: constraint{ + Name: "communication.communication_set_possible_resolved_by_fkey", + Columns: []string{"set_possible_resolved_by"}, + Comment: "", + }, + ForeignTable: "user_", + ForeignColumns: []string{"id"}, + }, CommunicationCommunicationSourceEmailLogIDFkey: foreignKey{ constraint: constraint{ Name: "communication.communication_source_email_log_id_fkey", @@ -263,26 +335,31 @@ var Communications = Table[ } type communicationColumns struct { - Closed column - ClosedBy column - Created column - ID column - Invalidated column - InvalidatedBy column - Opened column - OpenedBy column - ResponseEmailLogID column - ResponseTextLogID column - SetPending column - SetPendingBy column - SourceEmailLogID column - SourceReportID column - SourceTextLogID column + Closed column + ClosedBy column + Created column + ID column + Invalidated column + InvalidatedBy column + Opened column + OpenedBy column + OrganizationID column + ResponseEmailLogID column + ResponseTextLogID column + SetPending column + SetPendingBy column + SourceEmailLogID column + SourceReportID column + SourceTextLogID column + SetPossibleIssue column + SetPossibleIssueBy column + SetPossibleResolved column + SetPossibleResolvedBy column } func (c communicationColumns) AsSlice() []column { return []column{ - c.Closed, c.ClosedBy, c.Created, c.ID, c.Invalidated, c.InvalidatedBy, c.Opened, c.OpenedBy, c.ResponseEmailLogID, c.ResponseTextLogID, c.SetPending, c.SetPendingBy, c.SourceEmailLogID, c.SourceReportID, c.SourceTextLogID, + c.Closed, c.ClosedBy, c.Created, c.ID, c.Invalidated, c.InvalidatedBy, c.Opened, c.OpenedBy, c.OrganizationID, c.ResponseEmailLogID, c.ResponseTextLogID, c.SetPending, c.SetPendingBy, c.SourceEmailLogID, c.SourceReportID, c.SourceTextLogID, c.SetPossibleIssue, c.SetPossibleIssueBy, c.SetPossibleResolved, c.SetPossibleResolvedBy, } } @@ -297,20 +374,23 @@ func (i communicationIndexes) AsSlice() []index { } type communicationForeignKeys struct { - CommunicationCommunicationClosedByFkey foreignKey - CommunicationCommunicationInvalidatedByFkey foreignKey - CommunicationCommunicationOpenedByFkey foreignKey - CommunicationCommunicationResponseEmailLogIDFkey foreignKey - CommunicationCommunicationResponseTextLogIDFkey foreignKey - CommunicationCommunicationSetPendingByFkey foreignKey - CommunicationCommunicationSourceEmailLogIDFkey foreignKey - CommunicationCommunicationSourceReportIDFkey foreignKey - CommunicationCommunicationSourceTextLogIDFkey foreignKey + CommunicationCommunicationClosedByFkey foreignKey + CommunicationCommunicationInvalidatedByFkey foreignKey + CommunicationCommunicationOpenedByFkey foreignKey + CommunicationCommunicationOrganizationIDFkey foreignKey + CommunicationCommunicationResponseEmailLogIDFkey foreignKey + CommunicationCommunicationResponseTextLogIDFkey foreignKey + CommunicationCommunicationSetPendingByFkey foreignKey + CommunicationCommunicationSetPossibleIssueByFkey foreignKey + CommunicationCommunicationSetPossibleResolvedByFkey foreignKey + CommunicationCommunicationSourceEmailLogIDFkey foreignKey + CommunicationCommunicationSourceReportIDFkey foreignKey + CommunicationCommunicationSourceTextLogIDFkey foreignKey } func (f communicationForeignKeys) AsSlice() []foreignKey { return []foreignKey{ - f.CommunicationCommunicationClosedByFkey, f.CommunicationCommunicationInvalidatedByFkey, f.CommunicationCommunicationOpenedByFkey, f.CommunicationCommunicationResponseEmailLogIDFkey, f.CommunicationCommunicationResponseTextLogIDFkey, f.CommunicationCommunicationSetPendingByFkey, f.CommunicationCommunicationSourceEmailLogIDFkey, f.CommunicationCommunicationSourceReportIDFkey, f.CommunicationCommunicationSourceTextLogIDFkey, + f.CommunicationCommunicationClosedByFkey, f.CommunicationCommunicationInvalidatedByFkey, f.CommunicationCommunicationOpenedByFkey, f.CommunicationCommunicationOrganizationIDFkey, f.CommunicationCommunicationResponseEmailLogIDFkey, f.CommunicationCommunicationResponseTextLogIDFkey, f.CommunicationCommunicationSetPendingByFkey, f.CommunicationCommunicationSetPossibleIssueByFkey, f.CommunicationCommunicationSetPossibleResolvedByFkey, f.CommunicationCommunicationSourceEmailLogIDFkey, f.CommunicationCommunicationSourceReportIDFkey, f.CommunicationCommunicationSourceTextLogIDFkey, } } diff --git a/db/dbinfo/publicreport.compliance.bob.go b/db/dbinfo/publicreport.compliance.bob.go index 439afaff..559ce95f 100644 --- a/db/dbinfo/publicreport.compliance.bob.go +++ b/db/dbinfo/publicreport.compliance.bob.go @@ -62,7 +62,7 @@ var PublicreportCompliances = Table[ }, PermissionType: column{ Name: "permission_type", - DBType: "public.permissionaccesstype", + DBType: "publicreport.permissionaccesstype", Default: "", Comment: "", Nullable: false, diff --git a/db/enums/enums.bob.go b/db/enums/enums.bob.go index c25ed42e..f7b57f75 100644 --- a/db/enums/enums.bob.go +++ b/db/enums/enums.bob.go @@ -1283,85 +1283,6 @@ func (e *Notificationtype) Scan(value any) error { return nil } -// Enum values for Permissionaccesstype -const ( - PermissionaccesstypeDenied Permissionaccesstype = "denied" - PermissionaccesstypeGranted Permissionaccesstype = "granted" - PermissionaccesstypeUnselected Permissionaccesstype = "unselected" - PermissionaccesstypeWithOwner Permissionaccesstype = "with-owner" -) - -func AllPermissionaccesstype() []Permissionaccesstype { - return []Permissionaccesstype{ - PermissionaccesstypeDenied, - PermissionaccesstypeGranted, - PermissionaccesstypeUnselected, - PermissionaccesstypeWithOwner, - } -} - -type Permissionaccesstype string - -func (e Permissionaccesstype) String() string { - return string(e) -} - -func (e Permissionaccesstype) Valid() bool { - switch e { - case PermissionaccesstypeDenied, - PermissionaccesstypeGranted, - PermissionaccesstypeUnselected, - PermissionaccesstypeWithOwner: - return true - default: - return false - } -} - -// useful when testing in other packages -func (e Permissionaccesstype) All() []Permissionaccesstype { - return AllPermissionaccesstype() -} - -func (e Permissionaccesstype) MarshalText() ([]byte, error) { - return []byte(e), nil -} - -func (e *Permissionaccesstype) UnmarshalText(text []byte) error { - return e.Scan(text) -} - -func (e Permissionaccesstype) MarshalBinary() ([]byte, error) { - return []byte(e), nil -} - -func (e *Permissionaccesstype) UnmarshalBinary(data []byte) error { - return e.Scan(data) -} - -func (e Permissionaccesstype) Value() (driver.Value, error) { - return string(e), nil -} - -func (e *Permissionaccesstype) Scan(value any) error { - switch x := value.(type) { - case string: - *e = Permissionaccesstype(x) - case []byte: - *e = Permissionaccesstype(x) - case nil: - return fmt.Errorf("cannot nil into Permissionaccesstype") - default: - return fmt.Errorf("cannot scan type %T: %v", value, value) - } - - if !e.Valid() { - return fmt.Errorf("invalid Permissionaccesstype value: %s", *e) - } - - return nil -} - // Enum values for Poolconditiontype const ( PoolconditiontypeBlue Poolconditiontype = "blue" @@ -1629,6 +1550,85 @@ func (e *PublicreportNuisancedurationtype) Scan(value any) error { return nil } +// Enum values for PublicreportPermissionaccesstype +const ( + PublicreportPermissionaccesstypeDenied PublicreportPermissionaccesstype = "denied" + PublicreportPermissionaccesstypeGranted PublicreportPermissionaccesstype = "granted" + PublicreportPermissionaccesstypeUnselected PublicreportPermissionaccesstype = "unselected" + PublicreportPermissionaccesstypeWithOwner PublicreportPermissionaccesstype = "with-owner" +) + +func AllPublicreportPermissionaccesstype() []PublicreportPermissionaccesstype { + return []PublicreportPermissionaccesstype{ + PublicreportPermissionaccesstypeDenied, + PublicreportPermissionaccesstypeGranted, + PublicreportPermissionaccesstypeUnselected, + PublicreportPermissionaccesstypeWithOwner, + } +} + +type PublicreportPermissionaccesstype string + +func (e PublicreportPermissionaccesstype) String() string { + return string(e) +} + +func (e PublicreportPermissionaccesstype) Valid() bool { + switch e { + case PublicreportPermissionaccesstypeDenied, + PublicreportPermissionaccesstypeGranted, + PublicreportPermissionaccesstypeUnselected, + PublicreportPermissionaccesstypeWithOwner: + return true + default: + return false + } +} + +// useful when testing in other packages +func (e PublicreportPermissionaccesstype) All() []PublicreportPermissionaccesstype { + return AllPublicreportPermissionaccesstype() +} + +func (e PublicreportPermissionaccesstype) MarshalText() ([]byte, error) { + return []byte(e), nil +} + +func (e *PublicreportPermissionaccesstype) UnmarshalText(text []byte) error { + return e.Scan(text) +} + +func (e PublicreportPermissionaccesstype) MarshalBinary() ([]byte, error) { + return []byte(e), nil +} + +func (e *PublicreportPermissionaccesstype) UnmarshalBinary(data []byte) error { + return e.Scan(data) +} + +func (e PublicreportPermissionaccesstype) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *PublicreportPermissionaccesstype) Scan(value any) error { + switch x := value.(type) { + case string: + *e = PublicreportPermissionaccesstype(x) + case []byte: + *e = PublicreportPermissionaccesstype(x) + case nil: + return fmt.Errorf("cannot nil into PublicreportPermissionaccesstype") + default: + return fmt.Errorf("cannot scan type %T: %v", value, value) + } + + if !e.Valid() { + return fmt.Errorf("invalid PublicreportPermissionaccesstype value: %s", *e) + } + + return nil +} + // Enum values for PublicreportPoolsourceduration const ( PublicreportPoolsourcedurationNone PublicreportPoolsourceduration = "none" diff --git a/db/migrations/00150_communication_possible_status.sql b/db/migrations/00150_communication_possible_status.sql new file mode 100644 index 00000000..912a2594 --- /dev/null +++ b/db/migrations/00150_communication_possible_status.sql @@ -0,0 +1,6 @@ +-- +goose Up +ALTER TABLE communication + ADD COLUMN set_possible_issue TIMESTAMP WITHOUT TIME ZONE, + ADD COLUMN set_possible_issue_by INTEGER REFERENCES user_(id), + ADD COLUMN set_possible_resolved TIMESTAMP WITHOUT TIME ZONE, + ADD COLUMN set_possible_resolved_by INTEGER REFERENCES user_(id); diff --git a/db/models/communication.bob.go b/db/models/communication.bob.go index 9d7e8ebb..4b95bce5 100644 --- a/db/models/communication.bob.go +++ b/db/models/communication.bob.go @@ -25,21 +25,26 @@ import ( // Communication is an object representing the database table. type Communication struct { - Closed null.Val[time.Time] `db:"closed" ` - ClosedBy null.Val[int32] `db:"closed_by" ` - Created time.Time `db:"created" ` - ID int32 `db:"id,pk" ` - Invalidated null.Val[time.Time] `db:"invalidated" ` - InvalidatedBy null.Val[int32] `db:"invalidated_by" ` - Opened null.Val[time.Time] `db:"opened" ` - OpenedBy null.Val[int32] `db:"opened_by" ` - ResponseEmailLogID null.Val[int32] `db:"response_email_log_id" ` - ResponseTextLogID null.Val[int32] `db:"response_text_log_id" ` - SetPending null.Val[time.Time] `db:"set_pending" ` - SetPendingBy null.Val[int32] `db:"set_pending_by" ` - SourceEmailLogID null.Val[int32] `db:"source_email_log_id" ` - SourceReportID null.Val[int32] `db:"source_report_id" ` - SourceTextLogID null.Val[int32] `db:"source_text_log_id" ` + Closed null.Val[time.Time] `db:"closed" ` + ClosedBy null.Val[int32] `db:"closed_by" ` + Created time.Time `db:"created" ` + ID int32 `db:"id,pk" ` + Invalidated null.Val[time.Time] `db:"invalidated" ` + InvalidatedBy null.Val[int32] `db:"invalidated_by" ` + Opened null.Val[time.Time] `db:"opened" ` + OpenedBy null.Val[int32] `db:"opened_by" ` + OrganizationID int32 `db:"organization_id" ` + ResponseEmailLogID null.Val[int32] `db:"response_email_log_id" ` + ResponseTextLogID null.Val[int32] `db:"response_text_log_id" ` + SetPending null.Val[time.Time] `db:"set_pending" ` + SetPendingBy null.Val[int32] `db:"set_pending_by" ` + SourceEmailLogID null.Val[int32] `db:"source_email_log_id" ` + SourceReportID null.Val[int32] `db:"source_report_id" ` + SourceTextLogID null.Val[int32] `db:"source_text_log_id" ` + SetPossibleIssue null.Val[time.Time] `db:"set_possible_issue" ` + SetPossibleIssueBy null.Val[int32] `db:"set_possible_issue_by" ` + SetPossibleResolved null.Val[time.Time] `db:"set_possible_resolved" ` + SetPossibleResolvedBy null.Val[int32] `db:"set_possible_resolved_by" ` R communicationR `db:"-" ` } @@ -56,59 +61,72 @@ type CommunicationsQuery = *psql.ViewQuery[*Communication, CommunicationSlice] // communicationR is where relationships are stored. type communicationR struct { - ClosedByUser *User // communication.communication_closed_by_fkey - InvalidatedByUser *User // communication.communication_invalidated_by_fkey - OpenedByUser *User // communication.communication_opened_by_fkey - ResponseEmailLogEmailLog *CommsEmailLog // communication.communication_response_email_log_id_fkey - ResponseTextLogTextLog *CommsTextLog // communication.communication_response_text_log_id_fkey - SetPendingByUser *User // communication.communication_set_pending_by_fkey - SourceEmailLogEmailLog *CommsEmailLog // communication.communication_source_email_log_id_fkey - SourceReportReport *PublicreportReport // communication.communication_source_report_id_fkey - SourceTextLogTextLog *CommsTextLog // communication.communication_source_text_log_id_fkey + ClosedByUser *User // communication.communication_closed_by_fkey + InvalidatedByUser *User // communication.communication_invalidated_by_fkey + OpenedByUser *User // communication.communication_opened_by_fkey + Organization *Organization // communication.communication_organization_id_fkey + ResponseEmailLogEmailLog *CommsEmailLog // communication.communication_response_email_log_id_fkey + ResponseTextLogTextLog *CommsTextLog // communication.communication_response_text_log_id_fkey + SetPendingByUser *User // communication.communication_set_pending_by_fkey + SetPossibleIssueByUser *User // communication.communication_set_possible_issue_by_fkey + SetPossibleResolvedByUser *User // communication.communication_set_possible_resolved_by_fkey + SourceEmailLogEmailLog *CommsEmailLog // communication.communication_source_email_log_id_fkey + SourceReportReport *PublicreportReport // communication.communication_source_report_id_fkey + SourceTextLogTextLog *CommsTextLog // communication.communication_source_text_log_id_fkey } func buildCommunicationColumns(alias string) communicationColumns { return communicationColumns{ ColumnsExpr: expr.NewColumnsExpr( - "closed", "closed_by", "created", "id", "invalidated", "invalidated_by", "opened", "opened_by", "response_email_log_id", "response_text_log_id", "set_pending", "set_pending_by", "source_email_log_id", "source_report_id", "source_text_log_id", + "closed", "closed_by", "created", "id", "invalidated", "invalidated_by", "opened", "opened_by", "organization_id", "response_email_log_id", "response_text_log_id", "set_pending", "set_pending_by", "source_email_log_id", "source_report_id", "source_text_log_id", "set_possible_issue", "set_possible_issue_by", "set_possible_resolved", "set_possible_resolved_by", ).WithParent("communication"), - tableAlias: alias, - Closed: psql.Quote(alias, "closed"), - ClosedBy: psql.Quote(alias, "closed_by"), - Created: psql.Quote(alias, "created"), - ID: psql.Quote(alias, "id"), - Invalidated: psql.Quote(alias, "invalidated"), - InvalidatedBy: psql.Quote(alias, "invalidated_by"), - Opened: psql.Quote(alias, "opened"), - OpenedBy: psql.Quote(alias, "opened_by"), - ResponseEmailLogID: psql.Quote(alias, "response_email_log_id"), - ResponseTextLogID: psql.Quote(alias, "response_text_log_id"), - SetPending: psql.Quote(alias, "set_pending"), - SetPendingBy: psql.Quote(alias, "set_pending_by"), - SourceEmailLogID: psql.Quote(alias, "source_email_log_id"), - SourceReportID: psql.Quote(alias, "source_report_id"), - SourceTextLogID: psql.Quote(alias, "source_text_log_id"), + tableAlias: alias, + Closed: psql.Quote(alias, "closed"), + ClosedBy: psql.Quote(alias, "closed_by"), + Created: psql.Quote(alias, "created"), + ID: psql.Quote(alias, "id"), + Invalidated: psql.Quote(alias, "invalidated"), + InvalidatedBy: psql.Quote(alias, "invalidated_by"), + Opened: psql.Quote(alias, "opened"), + OpenedBy: psql.Quote(alias, "opened_by"), + OrganizationID: psql.Quote(alias, "organization_id"), + ResponseEmailLogID: psql.Quote(alias, "response_email_log_id"), + ResponseTextLogID: psql.Quote(alias, "response_text_log_id"), + SetPending: psql.Quote(alias, "set_pending"), + SetPendingBy: psql.Quote(alias, "set_pending_by"), + SourceEmailLogID: psql.Quote(alias, "source_email_log_id"), + SourceReportID: psql.Quote(alias, "source_report_id"), + SourceTextLogID: psql.Quote(alias, "source_text_log_id"), + SetPossibleIssue: psql.Quote(alias, "set_possible_issue"), + SetPossibleIssueBy: psql.Quote(alias, "set_possible_issue_by"), + SetPossibleResolved: psql.Quote(alias, "set_possible_resolved"), + SetPossibleResolvedBy: psql.Quote(alias, "set_possible_resolved_by"), } } type communicationColumns struct { expr.ColumnsExpr - tableAlias string - Closed psql.Expression - ClosedBy psql.Expression - Created psql.Expression - ID psql.Expression - Invalidated psql.Expression - InvalidatedBy psql.Expression - Opened psql.Expression - OpenedBy psql.Expression - ResponseEmailLogID psql.Expression - ResponseTextLogID psql.Expression - SetPending psql.Expression - SetPendingBy psql.Expression - SourceEmailLogID psql.Expression - SourceReportID psql.Expression - SourceTextLogID psql.Expression + tableAlias string + Closed psql.Expression + ClosedBy psql.Expression + Created psql.Expression + ID psql.Expression + Invalidated psql.Expression + InvalidatedBy psql.Expression + Opened psql.Expression + OpenedBy psql.Expression + OrganizationID psql.Expression + ResponseEmailLogID psql.Expression + ResponseTextLogID psql.Expression + SetPending psql.Expression + SetPendingBy psql.Expression + SourceEmailLogID psql.Expression + SourceReportID psql.Expression + SourceTextLogID psql.Expression + SetPossibleIssue psql.Expression + SetPossibleIssueBy psql.Expression + SetPossibleResolved psql.Expression + SetPossibleResolvedBy psql.Expression } func (c communicationColumns) Alias() string { @@ -123,25 +141,30 @@ func (communicationColumns) AliasedAs(alias string) communicationColumns { // All values are optional, and do not have to be set // Generated columns are not included type CommunicationSetter struct { - Closed omitnull.Val[time.Time] `db:"closed" ` - ClosedBy omitnull.Val[int32] `db:"closed_by" ` - Created omit.Val[time.Time] `db:"created" ` - ID omit.Val[int32] `db:"id,pk" ` - Invalidated omitnull.Val[time.Time] `db:"invalidated" ` - InvalidatedBy omitnull.Val[int32] `db:"invalidated_by" ` - Opened omitnull.Val[time.Time] `db:"opened" ` - OpenedBy omitnull.Val[int32] `db:"opened_by" ` - ResponseEmailLogID omitnull.Val[int32] `db:"response_email_log_id" ` - ResponseTextLogID omitnull.Val[int32] `db:"response_text_log_id" ` - SetPending omitnull.Val[time.Time] `db:"set_pending" ` - SetPendingBy omitnull.Val[int32] `db:"set_pending_by" ` - SourceEmailLogID omitnull.Val[int32] `db:"source_email_log_id" ` - SourceReportID omitnull.Val[int32] `db:"source_report_id" ` - SourceTextLogID omitnull.Val[int32] `db:"source_text_log_id" ` + Closed omitnull.Val[time.Time] `db:"closed" ` + ClosedBy omitnull.Val[int32] `db:"closed_by" ` + Created omit.Val[time.Time] `db:"created" ` + ID omit.Val[int32] `db:"id,pk" ` + Invalidated omitnull.Val[time.Time] `db:"invalidated" ` + InvalidatedBy omitnull.Val[int32] `db:"invalidated_by" ` + Opened omitnull.Val[time.Time] `db:"opened" ` + OpenedBy omitnull.Val[int32] `db:"opened_by" ` + OrganizationID omit.Val[int32] `db:"organization_id" ` + ResponseEmailLogID omitnull.Val[int32] `db:"response_email_log_id" ` + ResponseTextLogID omitnull.Val[int32] `db:"response_text_log_id" ` + SetPending omitnull.Val[time.Time] `db:"set_pending" ` + SetPendingBy omitnull.Val[int32] `db:"set_pending_by" ` + SourceEmailLogID omitnull.Val[int32] `db:"source_email_log_id" ` + SourceReportID omitnull.Val[int32] `db:"source_report_id" ` + SourceTextLogID omitnull.Val[int32] `db:"source_text_log_id" ` + SetPossibleIssue omitnull.Val[time.Time] `db:"set_possible_issue" ` + SetPossibleIssueBy omitnull.Val[int32] `db:"set_possible_issue_by" ` + SetPossibleResolved omitnull.Val[time.Time] `db:"set_possible_resolved" ` + SetPossibleResolvedBy omitnull.Val[int32] `db:"set_possible_resolved_by" ` } func (s CommunicationSetter) SetColumns() []string { - vals := make([]string, 0, 15) + vals := make([]string, 0, 20) if !s.Closed.IsUnset() { vals = append(vals, "closed") } @@ -166,6 +189,9 @@ func (s CommunicationSetter) SetColumns() []string { if !s.OpenedBy.IsUnset() { vals = append(vals, "opened_by") } + if s.OrganizationID.IsValue() { + vals = append(vals, "organization_id") + } if !s.ResponseEmailLogID.IsUnset() { vals = append(vals, "response_email_log_id") } @@ -187,6 +213,18 @@ func (s CommunicationSetter) SetColumns() []string { if !s.SourceTextLogID.IsUnset() { vals = append(vals, "source_text_log_id") } + if !s.SetPossibleIssue.IsUnset() { + vals = append(vals, "set_possible_issue") + } + if !s.SetPossibleIssueBy.IsUnset() { + vals = append(vals, "set_possible_issue_by") + } + if !s.SetPossibleResolved.IsUnset() { + vals = append(vals, "set_possible_resolved") + } + if !s.SetPossibleResolvedBy.IsUnset() { + vals = append(vals, "set_possible_resolved_by") + } return vals } @@ -215,6 +253,9 @@ func (s CommunicationSetter) Overwrite(t *Communication) { if !s.OpenedBy.IsUnset() { t.OpenedBy = s.OpenedBy.MustGetNull() } + if s.OrganizationID.IsValue() { + t.OrganizationID = s.OrganizationID.MustGet() + } if !s.ResponseEmailLogID.IsUnset() { t.ResponseEmailLogID = s.ResponseEmailLogID.MustGetNull() } @@ -236,6 +277,18 @@ func (s CommunicationSetter) Overwrite(t *Communication) { if !s.SourceTextLogID.IsUnset() { t.SourceTextLogID = s.SourceTextLogID.MustGetNull() } + if !s.SetPossibleIssue.IsUnset() { + t.SetPossibleIssue = s.SetPossibleIssue.MustGetNull() + } + if !s.SetPossibleIssueBy.IsUnset() { + t.SetPossibleIssueBy = s.SetPossibleIssueBy.MustGetNull() + } + if !s.SetPossibleResolved.IsUnset() { + t.SetPossibleResolved = s.SetPossibleResolved.MustGetNull() + } + if !s.SetPossibleResolvedBy.IsUnset() { + t.SetPossibleResolvedBy = s.SetPossibleResolvedBy.MustGetNull() + } } func (s *CommunicationSetter) Apply(q *dialect.InsertQuery) { @@ -244,7 +297,7 @@ func (s *CommunicationSetter) 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, 15) + vals := make([]bob.Expression, 20) if !s.Closed.IsUnset() { vals[0] = psql.Arg(s.Closed.MustGetNull()) } else { @@ -293,48 +346,78 @@ func (s *CommunicationSetter) Apply(q *dialect.InsertQuery) { vals[7] = psql.Raw("DEFAULT") } - if !s.ResponseEmailLogID.IsUnset() { - vals[8] = psql.Arg(s.ResponseEmailLogID.MustGetNull()) + if s.OrganizationID.IsValue() { + vals[8] = psql.Arg(s.OrganizationID.MustGet()) } else { vals[8] = psql.Raw("DEFAULT") } - if !s.ResponseTextLogID.IsUnset() { - vals[9] = psql.Arg(s.ResponseTextLogID.MustGetNull()) + if !s.ResponseEmailLogID.IsUnset() { + vals[9] = psql.Arg(s.ResponseEmailLogID.MustGetNull()) } else { vals[9] = psql.Raw("DEFAULT") } - if !s.SetPending.IsUnset() { - vals[10] = psql.Arg(s.SetPending.MustGetNull()) + if !s.ResponseTextLogID.IsUnset() { + vals[10] = psql.Arg(s.ResponseTextLogID.MustGetNull()) } else { vals[10] = psql.Raw("DEFAULT") } - if !s.SetPendingBy.IsUnset() { - vals[11] = psql.Arg(s.SetPendingBy.MustGetNull()) + if !s.SetPending.IsUnset() { + vals[11] = psql.Arg(s.SetPending.MustGetNull()) } else { vals[11] = psql.Raw("DEFAULT") } - if !s.SourceEmailLogID.IsUnset() { - vals[12] = psql.Arg(s.SourceEmailLogID.MustGetNull()) + if !s.SetPendingBy.IsUnset() { + vals[12] = psql.Arg(s.SetPendingBy.MustGetNull()) } else { vals[12] = psql.Raw("DEFAULT") } - if !s.SourceReportID.IsUnset() { - vals[13] = psql.Arg(s.SourceReportID.MustGetNull()) + if !s.SourceEmailLogID.IsUnset() { + vals[13] = psql.Arg(s.SourceEmailLogID.MustGetNull()) } else { vals[13] = psql.Raw("DEFAULT") } - if !s.SourceTextLogID.IsUnset() { - vals[14] = psql.Arg(s.SourceTextLogID.MustGetNull()) + if !s.SourceReportID.IsUnset() { + vals[14] = psql.Arg(s.SourceReportID.MustGetNull()) } else { vals[14] = psql.Raw("DEFAULT") } + if !s.SourceTextLogID.IsUnset() { + vals[15] = psql.Arg(s.SourceTextLogID.MustGetNull()) + } else { + vals[15] = psql.Raw("DEFAULT") + } + + if !s.SetPossibleIssue.IsUnset() { + vals[16] = psql.Arg(s.SetPossibleIssue.MustGetNull()) + } else { + vals[16] = psql.Raw("DEFAULT") + } + + if !s.SetPossibleIssueBy.IsUnset() { + vals[17] = psql.Arg(s.SetPossibleIssueBy.MustGetNull()) + } else { + vals[17] = psql.Raw("DEFAULT") + } + + if !s.SetPossibleResolved.IsUnset() { + vals[18] = psql.Arg(s.SetPossibleResolved.MustGetNull()) + } else { + vals[18] = psql.Raw("DEFAULT") + } + + if !s.SetPossibleResolvedBy.IsUnset() { + vals[19] = psql.Arg(s.SetPossibleResolvedBy.MustGetNull()) + } else { + vals[19] = psql.Raw("DEFAULT") + } + return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "") })) } @@ -344,7 +427,7 @@ func (s CommunicationSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] { } func (s CommunicationSetter) Expressions(prefix ...string) []bob.Expression { - exprs := make([]bob.Expression, 0, 15) + exprs := make([]bob.Expression, 0, 20) if !s.Closed.IsUnset() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ @@ -402,6 +485,13 @@ func (s CommunicationSetter) 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.ResponseEmailLogID.IsUnset() { exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ psql.Quote(append(prefix, "response_email_log_id")...), @@ -451,6 +541,34 @@ func (s CommunicationSetter) Expressions(prefix ...string) []bob.Expression { }}) } + if !s.SetPossibleIssue.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "set_possible_issue")...), + psql.Arg(s.SetPossibleIssue), + }}) + } + + if !s.SetPossibleIssueBy.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "set_possible_issue_by")...), + psql.Arg(s.SetPossibleIssueBy), + }}) + } + + if !s.SetPossibleResolved.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "set_possible_resolved")...), + psql.Arg(s.SetPossibleResolved), + }}) + } + + if !s.SetPossibleResolvedBy.IsUnset() { + exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{ + psql.Quote(append(prefix, "set_possible_resolved_by")...), + psql.Arg(s.SetPossibleResolvedBy), + }}) + } + return exprs } @@ -749,6 +867,30 @@ func (os CommunicationSlice) OpenedByUser(mods ...bob.Mod[*dialect.SelectQuery]) )...) } +// Organization starts a query for related objects on organization +func (o *Communication) Organization(mods ...bob.Mod[*dialect.SelectQuery]) OrganizationsQuery { + return Organizations.Query(append(mods, + sm.Where(Organizations.Columns.ID.EQ(psql.Arg(o.OrganizationID))), + )...) +} + +func (os CommunicationSlice) 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)), + )...) +} + // ResponseEmailLogEmailLog starts a query for related objects on comms.email_log func (o *Communication) ResponseEmailLogEmailLog(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { return CommsEmailLogs.Query(append(mods, @@ -821,6 +963,54 @@ func (os CommunicationSlice) SetPendingByUser(mods ...bob.Mod[*dialect.SelectQue )...) } +// SetPossibleIssueByUser starts a query for related objects on user_ +func (o *Communication) SetPossibleIssueByUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(Users.Columns.ID.EQ(psql.Arg(o.SetPossibleIssueBy))), + )...) +} + +func (os CommunicationSlice) SetPossibleIssueByUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + pkSetPossibleIssueBy := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkSetPossibleIssueBy = append(pkSetPossibleIssueBy, o.SetPossibleIssueBy) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkSetPossibleIssueBy), "integer[]")), + )) + + return Users.Query(append(mods, + sm.Where(psql.Group(Users.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + +// SetPossibleResolvedByUser starts a query for related objects on user_ +func (o *Communication) SetPossibleResolvedByUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + return Users.Query(append(mods, + sm.Where(Users.Columns.ID.EQ(psql.Arg(o.SetPossibleResolvedBy))), + )...) +} + +func (os CommunicationSlice) SetPossibleResolvedByUser(mods ...bob.Mod[*dialect.SelectQuery]) UsersQuery { + pkSetPossibleResolvedBy := make(pgtypes.Array[null.Val[int32]], 0, len(os)) + for _, o := range os { + if o == nil { + continue + } + pkSetPossibleResolvedBy = append(pkSetPossibleResolvedBy, o.SetPossibleResolvedBy) + } + PKArgExpr := psql.Select(sm.Columns( + psql.F("unnest", psql.Cast(psql.Arg(pkSetPossibleResolvedBy), "integer[]")), + )) + + return Users.Query(append(mods, + sm.Where(psql.Group(Users.Columns.ID).OP("IN", PKArgExpr)), + )...) +} + // SourceEmailLogEmailLog starts a query for related objects on comms.email_log func (o *Communication) SourceEmailLogEmailLog(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailLogsQuery { return CommsEmailLogs.Query(append(mods, @@ -1037,6 +1227,54 @@ func (communication0 *Communication) AttachOpenedByUser(ctx context.Context, exe return nil } +func attachCommunicationOrganization0(ctx context.Context, exec bob.Executor, count int, communication0 *Communication, organization1 *Organization) (*Communication, error) { + setter := &CommunicationSetter{ + OrganizationID: omit.From(organization1.ID), + } + + err := communication0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommunicationOrganization0: %w", err) + } + + return communication0, nil +} + +func (communication0 *Communication) 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 = attachCommunicationOrganization0(ctx, exec, 1, communication0, organization1) + if err != nil { + return err + } + + communication0.R.Organization = organization1 + + organization1.R.Communications = append(organization1.R.Communications, communication0) + + return nil +} + +func (communication0 *Communication) AttachOrganization(ctx context.Context, exec bob.Executor, organization1 *Organization) error { + var err error + + _, err = attachCommunicationOrganization0(ctx, exec, 1, communication0, organization1) + if err != nil { + return err + } + + communication0.R.Organization = organization1 + + organization1.R.Communications = append(organization1.R.Communications, communication0) + + return nil +} + func attachCommunicationResponseEmailLogEmailLog0(ctx context.Context, exec bob.Executor, count int, communication0 *Communication, commsEmailLog1 *CommsEmailLog) (*Communication, error) { setter := &CommunicationSetter{ ResponseEmailLogID: omitnull.From(commsEmailLog1.ID), @@ -1181,6 +1419,102 @@ func (communication0 *Communication) AttachSetPendingByUser(ctx context.Context, return nil } +func attachCommunicationSetPossibleIssueByUser0(ctx context.Context, exec bob.Executor, count int, communication0 *Communication, user1 *User) (*Communication, error) { + setter := &CommunicationSetter{ + SetPossibleIssueBy: omitnull.From(user1.ID), + } + + err := communication0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommunicationSetPossibleIssueByUser0: %w", err) + } + + return communication0, nil +} + +func (communication0 *Communication) InsertSetPossibleIssueByUser(ctx context.Context, exec bob.Executor, related *UserSetter) error { + var err error + + user1, err := Users.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommunicationSetPossibleIssueByUser0(ctx, exec, 1, communication0, user1) + if err != nil { + return err + } + + communication0.R.SetPossibleIssueByUser = user1 + + user1.R.SetPossibleIssueByCommunications = append(user1.R.SetPossibleIssueByCommunications, communication0) + + return nil +} + +func (communication0 *Communication) AttachSetPossibleIssueByUser(ctx context.Context, exec bob.Executor, user1 *User) error { + var err error + + _, err = attachCommunicationSetPossibleIssueByUser0(ctx, exec, 1, communication0, user1) + if err != nil { + return err + } + + communication0.R.SetPossibleIssueByUser = user1 + + user1.R.SetPossibleIssueByCommunications = append(user1.R.SetPossibleIssueByCommunications, communication0) + + return nil +} + +func attachCommunicationSetPossibleResolvedByUser0(ctx context.Context, exec bob.Executor, count int, communication0 *Communication, user1 *User) (*Communication, error) { + setter := &CommunicationSetter{ + SetPossibleResolvedBy: omitnull.From(user1.ID), + } + + err := communication0.Update(ctx, exec, setter) + if err != nil { + return nil, fmt.Errorf("attachCommunicationSetPossibleResolvedByUser0: %w", err) + } + + return communication0, nil +} + +func (communication0 *Communication) InsertSetPossibleResolvedByUser(ctx context.Context, exec bob.Executor, related *UserSetter) error { + var err error + + user1, err := Users.Insert(related).One(ctx, exec) + if err != nil { + return fmt.Errorf("inserting related objects: %w", err) + } + + _, err = attachCommunicationSetPossibleResolvedByUser0(ctx, exec, 1, communication0, user1) + if err != nil { + return err + } + + communication0.R.SetPossibleResolvedByUser = user1 + + user1.R.SetPossibleResolvedByCommunications = append(user1.R.SetPossibleResolvedByCommunications, communication0) + + return nil +} + +func (communication0 *Communication) AttachSetPossibleResolvedByUser(ctx context.Context, exec bob.Executor, user1 *User) error { + var err error + + _, err = attachCommunicationSetPossibleResolvedByUser0(ctx, exec, 1, communication0, user1) + if err != nil { + return err + } + + communication0.R.SetPossibleResolvedByUser = user1 + + user1.R.SetPossibleResolvedByCommunications = append(user1.R.SetPossibleResolvedByCommunications, communication0) + + return nil +} + func attachCommunicationSourceEmailLogEmailLog0(ctx context.Context, exec bob.Executor, count int, communication0 *Communication, commsEmailLog1 *CommsEmailLog) (*Communication, error) { setter := &CommunicationSetter{ SourceEmailLogID: omitnull.From(commsEmailLog1.ID), @@ -1326,21 +1660,26 @@ func (communication0 *Communication) AttachSourceTextLogTextLog(ctx context.Cont } type communicationWhere[Q psql.Filterable] struct { - Closed psql.WhereNullMod[Q, time.Time] - ClosedBy psql.WhereNullMod[Q, int32] - Created psql.WhereMod[Q, time.Time] - ID psql.WhereMod[Q, int32] - Invalidated psql.WhereNullMod[Q, time.Time] - InvalidatedBy psql.WhereNullMod[Q, int32] - Opened psql.WhereNullMod[Q, time.Time] - OpenedBy psql.WhereNullMod[Q, int32] - ResponseEmailLogID psql.WhereNullMod[Q, int32] - ResponseTextLogID psql.WhereNullMod[Q, int32] - SetPending psql.WhereNullMod[Q, time.Time] - SetPendingBy psql.WhereNullMod[Q, int32] - SourceEmailLogID psql.WhereNullMod[Q, int32] - SourceReportID psql.WhereNullMod[Q, int32] - SourceTextLogID psql.WhereNullMod[Q, int32] + Closed psql.WhereNullMod[Q, time.Time] + ClosedBy psql.WhereNullMod[Q, int32] + Created psql.WhereMod[Q, time.Time] + ID psql.WhereMod[Q, int32] + Invalidated psql.WhereNullMod[Q, time.Time] + InvalidatedBy psql.WhereNullMod[Q, int32] + Opened psql.WhereNullMod[Q, time.Time] + OpenedBy psql.WhereNullMod[Q, int32] + OrganizationID psql.WhereMod[Q, int32] + ResponseEmailLogID psql.WhereNullMod[Q, int32] + ResponseTextLogID psql.WhereNullMod[Q, int32] + SetPending psql.WhereNullMod[Q, time.Time] + SetPendingBy psql.WhereNullMod[Q, int32] + SourceEmailLogID psql.WhereNullMod[Q, int32] + SourceReportID psql.WhereNullMod[Q, int32] + SourceTextLogID psql.WhereNullMod[Q, int32] + SetPossibleIssue psql.WhereNullMod[Q, time.Time] + SetPossibleIssueBy psql.WhereNullMod[Q, int32] + SetPossibleResolved psql.WhereNullMod[Q, time.Time] + SetPossibleResolvedBy psql.WhereNullMod[Q, int32] } func (communicationWhere[Q]) AliasedAs(alias string) communicationWhere[Q] { @@ -1349,21 +1688,26 @@ func (communicationWhere[Q]) AliasedAs(alias string) communicationWhere[Q] { func buildCommunicationWhere[Q psql.Filterable](cols communicationColumns) communicationWhere[Q] { return communicationWhere[Q]{ - Closed: psql.WhereNull[Q, time.Time](cols.Closed), - ClosedBy: psql.WhereNull[Q, int32](cols.ClosedBy), - Created: psql.Where[Q, time.Time](cols.Created), - ID: psql.Where[Q, int32](cols.ID), - Invalidated: psql.WhereNull[Q, time.Time](cols.Invalidated), - InvalidatedBy: psql.WhereNull[Q, int32](cols.InvalidatedBy), - Opened: psql.WhereNull[Q, time.Time](cols.Opened), - OpenedBy: psql.WhereNull[Q, int32](cols.OpenedBy), - ResponseEmailLogID: psql.WhereNull[Q, int32](cols.ResponseEmailLogID), - ResponseTextLogID: psql.WhereNull[Q, int32](cols.ResponseTextLogID), - SetPending: psql.WhereNull[Q, time.Time](cols.SetPending), - SetPendingBy: psql.WhereNull[Q, int32](cols.SetPendingBy), - SourceEmailLogID: psql.WhereNull[Q, int32](cols.SourceEmailLogID), - SourceReportID: psql.WhereNull[Q, int32](cols.SourceReportID), - SourceTextLogID: psql.WhereNull[Q, int32](cols.SourceTextLogID), + Closed: psql.WhereNull[Q, time.Time](cols.Closed), + ClosedBy: psql.WhereNull[Q, int32](cols.ClosedBy), + Created: psql.Where[Q, time.Time](cols.Created), + ID: psql.Where[Q, int32](cols.ID), + Invalidated: psql.WhereNull[Q, time.Time](cols.Invalidated), + InvalidatedBy: psql.WhereNull[Q, int32](cols.InvalidatedBy), + Opened: psql.WhereNull[Q, time.Time](cols.Opened), + OpenedBy: psql.WhereNull[Q, int32](cols.OpenedBy), + OrganizationID: psql.Where[Q, int32](cols.OrganizationID), + ResponseEmailLogID: psql.WhereNull[Q, int32](cols.ResponseEmailLogID), + ResponseTextLogID: psql.WhereNull[Q, int32](cols.ResponseTextLogID), + SetPending: psql.WhereNull[Q, time.Time](cols.SetPending), + SetPendingBy: psql.WhereNull[Q, int32](cols.SetPendingBy), + SourceEmailLogID: psql.WhereNull[Q, int32](cols.SourceEmailLogID), + SourceReportID: psql.WhereNull[Q, int32](cols.SourceReportID), + SourceTextLogID: psql.WhereNull[Q, int32](cols.SourceTextLogID), + SetPossibleIssue: psql.WhereNull[Q, time.Time](cols.SetPossibleIssue), + SetPossibleIssueBy: psql.WhereNull[Q, int32](cols.SetPossibleIssueBy), + SetPossibleResolved: psql.WhereNull[Q, time.Time](cols.SetPossibleResolved), + SetPossibleResolvedBy: psql.WhereNull[Q, int32](cols.SetPossibleResolvedBy), } } @@ -1409,6 +1753,18 @@ func (o *Communication) Preload(name string, retrieved any) error { rel.R.OpenedByCommunications = CommunicationSlice{o} } return nil + case "Organization": + rel, ok := retrieved.(*Organization) + if !ok { + return fmt.Errorf("communication cannot load %T as %q", retrieved, name) + } + + o.R.Organization = rel + + if rel != nil { + rel.R.Communications = CommunicationSlice{o} + } + return nil case "ResponseEmailLogEmailLog": rel, ok := retrieved.(*CommsEmailLog) if !ok { @@ -1445,6 +1801,30 @@ func (o *Communication) Preload(name string, retrieved any) error { rel.R.SetPendingByCommunications = CommunicationSlice{o} } return nil + case "SetPossibleIssueByUser": + rel, ok := retrieved.(*User) + if !ok { + return fmt.Errorf("communication cannot load %T as %q", retrieved, name) + } + + o.R.SetPossibleIssueByUser = rel + + if rel != nil { + rel.R.SetPossibleIssueByCommunications = CommunicationSlice{o} + } + return nil + case "SetPossibleResolvedByUser": + rel, ok := retrieved.(*User) + if !ok { + return fmt.Errorf("communication cannot load %T as %q", retrieved, name) + } + + o.R.SetPossibleResolvedByUser = rel + + if rel != nil { + rel.R.SetPossibleResolvedByCommunications = CommunicationSlice{o} + } + return nil case "SourceEmailLogEmailLog": rel, ok := retrieved.(*CommsEmailLog) if !ok { @@ -1487,15 +1867,18 @@ func (o *Communication) Preload(name string, retrieved any) error { } type communicationPreloader struct { - ClosedByUser func(...psql.PreloadOption) psql.Preloader - InvalidatedByUser func(...psql.PreloadOption) psql.Preloader - OpenedByUser func(...psql.PreloadOption) psql.Preloader - ResponseEmailLogEmailLog func(...psql.PreloadOption) psql.Preloader - ResponseTextLogTextLog func(...psql.PreloadOption) psql.Preloader - SetPendingByUser func(...psql.PreloadOption) psql.Preloader - SourceEmailLogEmailLog func(...psql.PreloadOption) psql.Preloader - SourceReportReport func(...psql.PreloadOption) psql.Preloader - SourceTextLogTextLog func(...psql.PreloadOption) psql.Preloader + ClosedByUser func(...psql.PreloadOption) psql.Preloader + InvalidatedByUser func(...psql.PreloadOption) psql.Preloader + OpenedByUser func(...psql.PreloadOption) psql.Preloader + Organization func(...psql.PreloadOption) psql.Preloader + ResponseEmailLogEmailLog func(...psql.PreloadOption) psql.Preloader + ResponseTextLogTextLog func(...psql.PreloadOption) psql.Preloader + SetPendingByUser func(...psql.PreloadOption) psql.Preloader + SetPossibleIssueByUser func(...psql.PreloadOption) psql.Preloader + SetPossibleResolvedByUser func(...psql.PreloadOption) psql.Preloader + SourceEmailLogEmailLog func(...psql.PreloadOption) psql.Preloader + SourceReportReport func(...psql.PreloadOption) psql.Preloader + SourceTextLogTextLog func(...psql.PreloadOption) psql.Preloader } func buildCommunicationPreloader() communicationPreloader { @@ -1539,6 +1922,19 @@ func buildCommunicationPreloader() communicationPreloader { }, }, Users.Columns.Names(), opts...) }, + Organization: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*Organization, OrganizationSlice](psql.PreloadRel{ + Name: "Organization", + Sides: []psql.PreloadSide{ + { + From: Communications, + To: Organizations, + FromColumns: []string{"organization_id"}, + ToColumns: []string{"id"}, + }, + }, + }, Organizations.Columns.Names(), opts...) + }, ResponseEmailLogEmailLog: func(opts ...psql.PreloadOption) psql.Preloader { return psql.Preload[*CommsEmailLog, CommsEmailLogSlice](psql.PreloadRel{ Name: "ResponseEmailLogEmailLog", @@ -1578,6 +1974,32 @@ func buildCommunicationPreloader() communicationPreloader { }, }, Users.Columns.Names(), opts...) }, + SetPossibleIssueByUser: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*User, UserSlice](psql.PreloadRel{ + Name: "SetPossibleIssueByUser", + Sides: []psql.PreloadSide{ + { + From: Communications, + To: Users, + FromColumns: []string{"set_possible_issue_by"}, + ToColumns: []string{"id"}, + }, + }, + }, Users.Columns.Names(), opts...) + }, + SetPossibleResolvedByUser: func(opts ...psql.PreloadOption) psql.Preloader { + return psql.Preload[*User, UserSlice](psql.PreloadRel{ + Name: "SetPossibleResolvedByUser", + Sides: []psql.PreloadSide{ + { + From: Communications, + To: Users, + FromColumns: []string{"set_possible_resolved_by"}, + ToColumns: []string{"id"}, + }, + }, + }, Users.Columns.Names(), opts...) + }, SourceEmailLogEmailLog: func(opts ...psql.PreloadOption) psql.Preloader { return psql.Preload[*CommsEmailLog, CommsEmailLogSlice](psql.PreloadRel{ Name: "SourceEmailLogEmailLog", @@ -1621,15 +2043,18 @@ func buildCommunicationPreloader() communicationPreloader { } type communicationThenLoader[Q orm.Loadable] struct { - ClosedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - InvalidatedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - OpenedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ResponseEmailLogEmailLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ResponseTextLogTextLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SetPendingByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourceEmailLogEmailLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourceReportReport func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SourceTextLogTextLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ClosedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + InvalidatedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + OpenedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ResponseEmailLogEmailLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ResponseTextLogTextLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPendingByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPossibleIssueByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPossibleResolvedByUser func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceEmailLogEmailLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceReportReport func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SourceTextLogTextLog func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildCommunicationThenLoader[Q orm.Loadable]() communicationThenLoader[Q] { @@ -1642,6 +2067,9 @@ func buildCommunicationThenLoader[Q orm.Loadable]() communicationThenLoader[Q] { type OpenedByUserLoadInterface interface { LoadOpenedByUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type OrganizationLoadInterface interface { + LoadOrganization(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type ResponseEmailLogEmailLogLoadInterface interface { LoadResponseEmailLogEmailLog(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -1651,6 +2079,12 @@ func buildCommunicationThenLoader[Q orm.Loadable]() communicationThenLoader[Q] { type SetPendingByUserLoadInterface interface { LoadSetPendingByUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type SetPossibleIssueByUserLoadInterface interface { + LoadSetPossibleIssueByUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SetPossibleResolvedByUserLoadInterface interface { + LoadSetPossibleResolvedByUser(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type SourceEmailLogEmailLogLoadInterface interface { LoadSourceEmailLogEmailLog(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -1680,6 +2114,12 @@ func buildCommunicationThenLoader[Q orm.Loadable]() communicationThenLoader[Q] { return retrieved.LoadOpenedByUser(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...) + }, + ), ResponseEmailLogEmailLog: thenLoadBuilder[Q]( "ResponseEmailLogEmailLog", func(ctx context.Context, exec bob.Executor, retrieved ResponseEmailLogEmailLogLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1698,6 +2138,18 @@ func buildCommunicationThenLoader[Q orm.Loadable]() communicationThenLoader[Q] { return retrieved.LoadSetPendingByUser(ctx, exec, mods...) }, ), + SetPossibleIssueByUser: thenLoadBuilder[Q]( + "SetPossibleIssueByUser", + func(ctx context.Context, exec bob.Executor, retrieved SetPossibleIssueByUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSetPossibleIssueByUser(ctx, exec, mods...) + }, + ), + SetPossibleResolvedByUser: thenLoadBuilder[Q]( + "SetPossibleResolvedByUser", + func(ctx context.Context, exec bob.Executor, retrieved SetPossibleResolvedByUserLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSetPossibleResolvedByUser(ctx, exec, mods...) + }, + ), SourceEmailLogEmailLog: thenLoadBuilder[Q]( "SourceEmailLogEmailLog", func(ctx context.Context, exec bob.Executor, retrieved SourceEmailLogEmailLogLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -1884,6 +2336,58 @@ func (os CommunicationSlice) LoadOpenedByUser(ctx context.Context, exec bob.Exec return nil } +// LoadOrganization loads the communication's Organization into the .R struct +func (o *Communication) 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.Communications = CommunicationSlice{o} + + o.R.Organization = related + return nil +} + +// LoadOrganization loads the communication's Organization into the .R struct +func (os CommunicationSlice) 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.Communications = append(rel.R.Communications, o) + + o.R.Organization = rel + break + } + } + + return nil +} + // LoadResponseEmailLogEmailLog loads the communication's ResponseEmailLogEmailLog into the .R struct func (o *Communication) LoadResponseEmailLogEmailLog(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { @@ -2049,6 +2553,116 @@ func (os CommunicationSlice) LoadSetPendingByUser(ctx context.Context, exec bob. return nil } +// LoadSetPossibleIssueByUser loads the communication's SetPossibleIssueByUser into the .R struct +func (o *Communication) LoadSetPossibleIssueByUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SetPossibleIssueByUser = nil + + related, err := o.SetPossibleIssueByUser(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.SetPossibleIssueByCommunications = CommunicationSlice{o} + + o.R.SetPossibleIssueByUser = related + return nil +} + +// LoadSetPossibleIssueByUser loads the communication's SetPossibleIssueByUser into the .R struct +func (os CommunicationSlice) LoadSetPossibleIssueByUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.SetPossibleIssueByUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range users { + if !o.SetPossibleIssueBy.IsValue() { + continue + } + + if !(o.SetPossibleIssueBy.IsValue() && o.SetPossibleIssueBy.MustGet() == rel.ID) { + continue + } + + rel.R.SetPossibleIssueByCommunications = append(rel.R.SetPossibleIssueByCommunications, o) + + o.R.SetPossibleIssueByUser = rel + break + } + } + + return nil +} + +// LoadSetPossibleResolvedByUser loads the communication's SetPossibleResolvedByUser into the .R struct +func (o *Communication) LoadSetPossibleResolvedByUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SetPossibleResolvedByUser = nil + + related, err := o.SetPossibleResolvedByUser(mods...).One(ctx, exec) + if err != nil { + return err + } + + related.R.SetPossibleResolvedByCommunications = CommunicationSlice{o} + + o.R.SetPossibleResolvedByUser = related + return nil +} + +// LoadSetPossibleResolvedByUser loads the communication's SetPossibleResolvedByUser into the .R struct +func (os CommunicationSlice) LoadSetPossibleResolvedByUser(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + users, err := os.SetPossibleResolvedByUser(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range users { + if !o.SetPossibleResolvedBy.IsValue() { + continue + } + + if !(o.SetPossibleResolvedBy.IsValue() && o.SetPossibleResolvedBy.MustGet() == rel.ID) { + continue + } + + rel.R.SetPossibleResolvedByCommunications = append(rel.R.SetPossibleResolvedByCommunications, o) + + o.R.SetPossibleResolvedByUser = rel + break + } + } + + return nil +} + // LoadSourceEmailLogEmailLog loads the communication's SourceEmailLogEmailLog into the .R struct func (o *Communication) LoadSourceEmailLogEmailLog(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { diff --git a/db/models/organization.bob.go b/db/models/organization.bob.go index 01ce4fbb..e6d53f92 100644 --- a/db/models/organization.bob.go +++ b/db/models/organization.bob.go @@ -78,6 +78,7 @@ type OrganizationsQuery = *psql.ViewQuery[*Organization, OrganizationSlice] // organizationR is where relationships are stored. type organizationR struct { + Communications CommunicationSlice // communication.communication_organization_id_fkey EmailContacts CommsEmailContactSlice // district_subscription_email.district_subscription_email_email_contact_address_fkeydistrict_subscription_email.district_subscription_email_organization_id_fkey Phones CommsPhoneSlice // district_subscription_phone.district_subscription_phone_organization_id_fkeydistrict_subscription_phone.district_subscription_phone_phone_e164_fkey Features FeatureSlice // feature.feature_organization_id_fkey @@ -972,6 +973,30 @@ func (o OrganizationSlice) ReloadAll(ctx context.Context, exec bob.Executor) err return nil } +// Communications starts a query for related objects on communication +func (o *Organization) Communications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + return Communications.Query(append(mods, + sm.Where(Communications.Columns.OrganizationID.EQ(psql.Arg(o.ID))), + )...) +} + +func (os OrganizationSlice) Communications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + 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 Communications.Query(append(mods, + sm.Where(psql.Group(Communications.Columns.OrganizationID).OP("IN", PKArgExpr)), + )...) +} + // EmailContacts starts a query for related objects on comms.email_contact func (o *Organization) EmailContacts(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { return CommsEmailContacts.Query(append(mods, @@ -1990,6 +2015,74 @@ func (os OrganizationSlice) User(mods ...bob.Mod[*dialect.SelectQuery]) UsersQue )...) } +func insertOrganizationCommunications0(ctx context.Context, exec bob.Executor, communications1 []*CommunicationSetter, organization0 *Organization) (CommunicationSlice, error) { + for i := range communications1 { + communications1[i].OrganizationID = omit.From(organization0.ID) + } + + ret, err := Communications.Insert(bob.ToMods(communications1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertOrganizationCommunications0: %w", err) + } + + return ret, nil +} + +func attachOrganizationCommunications0(ctx context.Context, exec bob.Executor, count int, communications1 CommunicationSlice, organization0 *Organization) (CommunicationSlice, error) { + setter := &CommunicationSetter{ + OrganizationID: omit.From(organization0.ID), + } + + err := communications1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachOrganizationCommunications0: %w", err) + } + + return communications1, nil +} + +func (organization0 *Organization) InsertCommunications(ctx context.Context, exec bob.Executor, related ...*CommunicationSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + communications1, err := insertOrganizationCommunications0(ctx, exec, related, organization0) + if err != nil { + return err + } + + organization0.R.Communications = append(organization0.R.Communications, communications1...) + + for _, rel := range communications1 { + rel.R.Organization = organization0 + } + return nil +} + +func (organization0 *Organization) AttachCommunications(ctx context.Context, exec bob.Executor, related ...*Communication) error { + if len(related) == 0 { + return nil + } + + var err error + communications1 := CommunicationSlice(related) + + _, err = attachOrganizationCommunications0(ctx, exec, len(related), communications1, organization0) + if err != nil { + return err + } + + organization0.R.Communications = append(organization0.R.Communications, communications1...) + + for _, rel := range related { + rel.R.Organization = organization0 + } + + return nil +} + func attachOrganizationEmailContacts0(ctx context.Context, exec bob.Executor, count int, organization0 *Organization, commsEmailContacts2 CommsEmailContactSlice) (DistrictSubscriptionEmailSlice, error) { setters := make([]*DistrictSubscriptionEmailSetter, count) for i := range count { @@ -4928,6 +5021,20 @@ func (o *Organization) Preload(name string, retrieved any) error { } switch name { + case "Communications": + rels, ok := retrieved.(CommunicationSlice) + if !ok { + return fmt.Errorf("organization cannot load %T as %q", retrieved, name) + } + + o.R.Communications = rels + + for _, rel := range rels { + if rel != nil { + rel.R.Organization = o + } + } + return nil case "EmailContacts": rels, ok := retrieved.(CommsEmailContactSlice) if !ok { @@ -5528,6 +5635,7 @@ func buildOrganizationPreloader() organizationPreloader { } type organizationThenLoader[Q orm.Loadable] struct { + Communications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] EmailContacts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Phones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] Features func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] @@ -5573,6 +5681,9 @@ type organizationThenLoader[Q orm.Loadable] struct { } func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { + type CommunicationsLoadInterface interface { + LoadCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type EmailContactsLoadInterface interface { LoadEmailContacts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -5701,6 +5812,12 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { } return organizationThenLoader[Q]{ + Communications: thenLoadBuilder[Q]( + "Communications", + func(ctx context.Context, exec bob.Executor, retrieved CommunicationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadCommunications(ctx, exec, mods...) + }, + ), EmailContacts: thenLoadBuilder[Q]( "EmailContacts", func(ctx context.Context, exec bob.Executor, retrieved EmailContactsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -5956,6 +6073,67 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] { } } +// LoadCommunications loads the organization's Communications into the .R struct +func (o *Organization) LoadCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.Communications = nil + + related, err := o.Communications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.Organization = o + } + + o.R.Communications = related + return nil +} + +// LoadCommunications loads the organization's Communications into the .R struct +func (os OrganizationSlice) LoadCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + communications, err := os.Communications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.Communications = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range communications { + + if !(o.ID == rel.OrganizationID) { + continue + } + + rel.R.Organization = o + + o.R.Communications = append(o.R.Communications, rel) + } + } + + return nil +} + // LoadEmailContacts loads the organization's EmailContacts into the .R struct func (o *Organization) LoadEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { diff --git a/db/models/publicreport.compliance.bob.go b/db/models/publicreport.compliance.bob.go index 3fe11332..b9a38e18 100644 --- a/db/models/publicreport.compliance.bob.go +++ b/db/models/publicreport.compliance.bob.go @@ -26,16 +26,16 @@ import ( // PublicreportCompliance is an object representing the database table. type PublicreportCompliance struct { - AccessInstructions string `db:"access_instructions" ` - AvailabilityNotes string `db:"availability_notes" ` - Comments string `db:"comments" ` - GateCode string `db:"gate_code" ` - HasDog null.Val[bool] `db:"has_dog" ` - PermissionType enums.Permissionaccesstype `db:"permission_type" ` - ReportID int32 `db:"report_id,pk" ` - ReportPhoneCanText null.Val[bool] `db:"report_phone_can_text" ` - WantsScheduled null.Val[bool] `db:"wants_scheduled" ` - Submitted null.Val[time.Time] `db:"submitted" ` + AccessInstructions string `db:"access_instructions" ` + AvailabilityNotes string `db:"availability_notes" ` + Comments string `db:"comments" ` + GateCode string `db:"gate_code" ` + HasDog null.Val[bool] `db:"has_dog" ` + PermissionType enums.PublicreportPermissionaccesstype `db:"permission_type" ` + ReportID int32 `db:"report_id,pk" ` + ReportPhoneCanText null.Val[bool] `db:"report_phone_can_text" ` + WantsScheduled null.Val[bool] `db:"wants_scheduled" ` + Submitted null.Val[time.Time] `db:"submitted" ` R publicreportComplianceR `db:"-" ` } @@ -101,16 +101,16 @@ func (publicreportComplianceColumns) AliasedAs(alias string) publicreportComplia // All values are optional, and do not have to be set // Generated columns are not included type PublicreportComplianceSetter struct { - AccessInstructions omit.Val[string] `db:"access_instructions" ` - AvailabilityNotes omit.Val[string] `db:"availability_notes" ` - Comments omit.Val[string] `db:"comments" ` - GateCode omit.Val[string] `db:"gate_code" ` - HasDog omitnull.Val[bool] `db:"has_dog" ` - PermissionType omit.Val[enums.Permissionaccesstype] `db:"permission_type" ` - ReportID omit.Val[int32] `db:"report_id,pk" ` - ReportPhoneCanText omitnull.Val[bool] `db:"report_phone_can_text" ` - WantsScheduled omitnull.Val[bool] `db:"wants_scheduled" ` - Submitted omitnull.Val[time.Time] `db:"submitted" ` + AccessInstructions omit.Val[string] `db:"access_instructions" ` + AvailabilityNotes omit.Val[string] `db:"availability_notes" ` + Comments omit.Val[string] `db:"comments" ` + GateCode omit.Val[string] `db:"gate_code" ` + HasDog omitnull.Val[bool] `db:"has_dog" ` + PermissionType omit.Val[enums.PublicreportPermissionaccesstype] `db:"permission_type" ` + ReportID omit.Val[int32] `db:"report_id,pk" ` + ReportPhoneCanText omitnull.Val[bool] `db:"report_phone_can_text" ` + WantsScheduled omitnull.Val[bool] `db:"wants_scheduled" ` + Submitted omitnull.Val[time.Time] `db:"submitted" ` } func (s PublicreportComplianceSetter) SetColumns() []string { @@ -633,7 +633,7 @@ type publicreportComplianceWhere[Q psql.Filterable] struct { Comments psql.WhereMod[Q, string] GateCode psql.WhereMod[Q, string] HasDog psql.WhereNullMod[Q, bool] - PermissionType psql.WhereMod[Q, enums.Permissionaccesstype] + PermissionType psql.WhereMod[Q, enums.PublicreportPermissionaccesstype] ReportID psql.WhereMod[Q, int32] ReportPhoneCanText psql.WhereNullMod[Q, bool] WantsScheduled psql.WhereNullMod[Q, bool] @@ -651,7 +651,7 @@ func buildPublicreportComplianceWhere[Q psql.Filterable](cols publicreportCompli Comments: psql.Where[Q, string](cols.Comments), GateCode: psql.Where[Q, string](cols.GateCode), HasDog: psql.WhereNull[Q, bool](cols.HasDog), - PermissionType: psql.Where[Q, enums.Permissionaccesstype](cols.PermissionType), + PermissionType: psql.Where[Q, enums.PublicreportPermissionaccesstype](cols.PermissionType), ReportID: psql.Where[Q, int32](cols.ReportID), ReportPhoneCanText: psql.WhereNull[Q, bool](cols.ReportPhoneCanText), WantsScheduled: psql.WhereNull[Q, bool](cols.WantsScheduled), diff --git a/db/models/user_.bob.go b/db/models/user_.bob.go index 44c82b3f..9edca83d 100644 --- a/db/models/user_.bob.go +++ b/db/models/user_.bob.go @@ -60,36 +60,38 @@ type UsersQuery = *psql.ViewQuery[*User, UserSlice] // userR is where relationships are stored. type userR struct { - CreatorTextJobs CommsTextJobSlice // comms.text_job.text_job_creator_id_fkey - ClosedByCommunications CommunicationSlice // communication.communication_closed_by_fkey - InvalidatedByCommunications CommunicationSlice // communication.communication_invalidated_by_fkey - OpenedByCommunications CommunicationSlice // communication.communication_opened_by_fkey - SetPendingByCommunications CommunicationSlice // communication.communication_set_pending_by_fkey - CreatorComplianceReportRequests ComplianceReportRequestSlice // compliance_report_request.compliance_report_request_creator_fkey - CreatorFeatures FeatureSlice // feature.feature_creator_id_fkey - CommitterFiles FileuploadFileSlice // fileupload.file.file_committer_fkey - CreatorFiles FileuploadFileSlice // fileupload.file.file_creator_id_fkey - FileuploadPool FileuploadPoolSlice // fileupload.pool.pool_creator_id_fkey - CreatorLeads LeadSlice // lead.lead_creator_fkey - ImpersonatorLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_impersonator_id_fkey - TargetLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_target_id_fkey - CreatorNoteAudios NoteAudioSlice // note_audio.note_audio_creator_id_fkey - DeletorNoteAudios NoteAudioSlice // note_audio.note_audio_deletor_id_fkey - CreatorNoteImages NoteImageSlice // note_image.note_image_creator_id_fkey - DeletorNoteImages NoteImageSlice // note_image.note_image_deletor_id_fkey - UserNotifications NotificationSlice // notification.notification_user_id_fkey - ReviewerNuisanceOlds PublicreportNuisanceOldSlice // publicreport.nuisance_old.nuisance_reviewer_id_fkey - ReviewerReports PublicreportReportSlice // publicreport.report.report_reviewer_id_fkey - UserReportLogs PublicreportReportLogSlice // publicreport.report_log.report_log_user_id_fkey - ReviewerWaterOlds PublicreportWaterOldSlice // publicreport.water_old.water_reviewer_id_fkey - CreatorReportTexts ReportTextSlice // report_text.report_text_creator_id_fkey - CreatorResidents ResidentSlice // resident.resident_creator_fkey - CreatorReviewTasks ReviewTaskSlice // review_task.review_task_creator_id_fkey - ReviewerReviewTasks ReviewTaskSlice // review_task.review_task_reviewer_id_fkey - AddressorSignals SignalSlice // signal.signal_addressor_fkey - CreatorSignals SignalSlice // signal.signal_creator_fkey - CreatorSites SiteSlice // site.site_creator_id_fkey - Organization *Organization // user_.user__organization_id_fkey + CreatorTextJobs CommsTextJobSlice // comms.text_job.text_job_creator_id_fkey + ClosedByCommunications CommunicationSlice // communication.communication_closed_by_fkey + InvalidatedByCommunications CommunicationSlice // communication.communication_invalidated_by_fkey + OpenedByCommunications CommunicationSlice // communication.communication_opened_by_fkey + SetPendingByCommunications CommunicationSlice // communication.communication_set_pending_by_fkey + SetPossibleIssueByCommunications CommunicationSlice // communication.communication_set_possible_issue_by_fkey + SetPossibleResolvedByCommunications CommunicationSlice // communication.communication_set_possible_resolved_by_fkey + CreatorComplianceReportRequests ComplianceReportRequestSlice // compliance_report_request.compliance_report_request_creator_fkey + CreatorFeatures FeatureSlice // feature.feature_creator_id_fkey + CommitterFiles FileuploadFileSlice // fileupload.file.file_committer_fkey + CreatorFiles FileuploadFileSlice // fileupload.file.file_creator_id_fkey + FileuploadPool FileuploadPoolSlice // fileupload.pool.pool_creator_id_fkey + CreatorLeads LeadSlice // lead.lead_creator_fkey + ImpersonatorLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_impersonator_id_fkey + TargetLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_target_id_fkey + CreatorNoteAudios NoteAudioSlice // note_audio.note_audio_creator_id_fkey + DeletorNoteAudios NoteAudioSlice // note_audio.note_audio_deletor_id_fkey + CreatorNoteImages NoteImageSlice // note_image.note_image_creator_id_fkey + DeletorNoteImages NoteImageSlice // note_image.note_image_deletor_id_fkey + UserNotifications NotificationSlice // notification.notification_user_id_fkey + ReviewerNuisanceOlds PublicreportNuisanceOldSlice // publicreport.nuisance_old.nuisance_reviewer_id_fkey + ReviewerReports PublicreportReportSlice // publicreport.report.report_reviewer_id_fkey + UserReportLogs PublicreportReportLogSlice // publicreport.report_log.report_log_user_id_fkey + ReviewerWaterOlds PublicreportWaterOldSlice // publicreport.water_old.water_reviewer_id_fkey + CreatorReportTexts ReportTextSlice // report_text.report_text_creator_id_fkey + CreatorResidents ResidentSlice // resident.resident_creator_fkey + CreatorReviewTasks ReviewTaskSlice // review_task.review_task_creator_id_fkey + ReviewerReviewTasks ReviewTaskSlice // review_task.review_task_reviewer_id_fkey + AddressorSignals SignalSlice // signal.signal_addressor_fkey + CreatorSignals SignalSlice // signal.signal_creator_fkey + CreatorSites SiteSlice // site.site_creator_id_fkey + Organization *Organization // user_.user__organization_id_fkey } func buildUserColumns(alias string) userColumns { @@ -866,6 +868,54 @@ func (os UserSlice) SetPendingByCommunications(mods ...bob.Mod[*dialect.SelectQu )...) } +// SetPossibleIssueByCommunications starts a query for related objects on communication +func (o *User) SetPossibleIssueByCommunications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + return Communications.Query(append(mods, + sm.Where(Communications.Columns.SetPossibleIssueBy.EQ(psql.Arg(o.ID))), + )...) +} + +func (os UserSlice) SetPossibleIssueByCommunications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + 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 Communications.Query(append(mods, + sm.Where(psql.Group(Communications.Columns.SetPossibleIssueBy).OP("IN", PKArgExpr)), + )...) +} + +// SetPossibleResolvedByCommunications starts a query for related objects on communication +func (o *User) SetPossibleResolvedByCommunications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + return Communications.Query(append(mods, + sm.Where(Communications.Columns.SetPossibleResolvedBy.EQ(psql.Arg(o.ID))), + )...) +} + +func (os UserSlice) SetPossibleResolvedByCommunications(mods ...bob.Mod[*dialect.SelectQuery]) CommunicationsQuery { + 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 Communications.Query(append(mods, + sm.Where(psql.Group(Communications.Columns.SetPossibleResolvedBy).OP("IN", PKArgExpr)), + )...) +} + // CreatorComplianceReportRequests starts a query for related objects on compliance_report_request func (o *User) CreatorComplianceReportRequests(mods ...bob.Mod[*dialect.SelectQuery]) ComplianceReportRequestsQuery { return ComplianceReportRequests.Query(append(mods, @@ -1806,6 +1856,142 @@ func (user0 *User) AttachSetPendingByCommunications(ctx context.Context, exec bo return nil } +func insertUserSetPossibleIssueByCommunications0(ctx context.Context, exec bob.Executor, communications1 []*CommunicationSetter, user0 *User) (CommunicationSlice, error) { + for i := range communications1 { + communications1[i].SetPossibleIssueBy = omitnull.From(user0.ID) + } + + ret, err := Communications.Insert(bob.ToMods(communications1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertUserSetPossibleIssueByCommunications0: %w", err) + } + + return ret, nil +} + +func attachUserSetPossibleIssueByCommunications0(ctx context.Context, exec bob.Executor, count int, communications1 CommunicationSlice, user0 *User) (CommunicationSlice, error) { + setter := &CommunicationSetter{ + SetPossibleIssueBy: omitnull.From(user0.ID), + } + + err := communications1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachUserSetPossibleIssueByCommunications0: %w", err) + } + + return communications1, nil +} + +func (user0 *User) InsertSetPossibleIssueByCommunications(ctx context.Context, exec bob.Executor, related ...*CommunicationSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + communications1, err := insertUserSetPossibleIssueByCommunications0(ctx, exec, related, user0) + if err != nil { + return err + } + + user0.R.SetPossibleIssueByCommunications = append(user0.R.SetPossibleIssueByCommunications, communications1...) + + for _, rel := range communications1 { + rel.R.SetPossibleIssueByUser = user0 + } + return nil +} + +func (user0 *User) AttachSetPossibleIssueByCommunications(ctx context.Context, exec bob.Executor, related ...*Communication) error { + if len(related) == 0 { + return nil + } + + var err error + communications1 := CommunicationSlice(related) + + _, err = attachUserSetPossibleIssueByCommunications0(ctx, exec, len(related), communications1, user0) + if err != nil { + return err + } + + user0.R.SetPossibleIssueByCommunications = append(user0.R.SetPossibleIssueByCommunications, communications1...) + + for _, rel := range related { + rel.R.SetPossibleIssueByUser = user0 + } + + return nil +} + +func insertUserSetPossibleResolvedByCommunications0(ctx context.Context, exec bob.Executor, communications1 []*CommunicationSetter, user0 *User) (CommunicationSlice, error) { + for i := range communications1 { + communications1[i].SetPossibleResolvedBy = omitnull.From(user0.ID) + } + + ret, err := Communications.Insert(bob.ToMods(communications1...)).All(ctx, exec) + if err != nil { + return ret, fmt.Errorf("insertUserSetPossibleResolvedByCommunications0: %w", err) + } + + return ret, nil +} + +func attachUserSetPossibleResolvedByCommunications0(ctx context.Context, exec bob.Executor, count int, communications1 CommunicationSlice, user0 *User) (CommunicationSlice, error) { + setter := &CommunicationSetter{ + SetPossibleResolvedBy: omitnull.From(user0.ID), + } + + err := communications1.UpdateAll(ctx, exec, *setter) + if err != nil { + return nil, fmt.Errorf("attachUserSetPossibleResolvedByCommunications0: %w", err) + } + + return communications1, nil +} + +func (user0 *User) InsertSetPossibleResolvedByCommunications(ctx context.Context, exec bob.Executor, related ...*CommunicationSetter) error { + if len(related) == 0 { + return nil + } + + var err error + + communications1, err := insertUserSetPossibleResolvedByCommunications0(ctx, exec, related, user0) + if err != nil { + return err + } + + user0.R.SetPossibleResolvedByCommunications = append(user0.R.SetPossibleResolvedByCommunications, communications1...) + + for _, rel := range communications1 { + rel.R.SetPossibleResolvedByUser = user0 + } + return nil +} + +func (user0 *User) AttachSetPossibleResolvedByCommunications(ctx context.Context, exec bob.Executor, related ...*Communication) error { + if len(related) == 0 { + return nil + } + + var err error + communications1 := CommunicationSlice(related) + + _, err = attachUserSetPossibleResolvedByCommunications0(ctx, exec, len(related), communications1, user0) + if err != nil { + return err + } + + user0.R.SetPossibleResolvedByCommunications = append(user0.R.SetPossibleResolvedByCommunications, communications1...) + + for _, rel := range related { + rel.R.SetPossibleResolvedByUser = user0 + } + + return nil +} + func insertUserCreatorComplianceReportRequests0(ctx context.Context, exec bob.Executor, complianceReportRequests1 []*ComplianceReportRequestSetter, user0 *User) (ComplianceReportRequestSlice, error) { for i := range complianceReportRequests1 { complianceReportRequests1[i].Creator = omit.From(user0.ID) @@ -3608,6 +3794,34 @@ func (o *User) Preload(name string, retrieved any) error { } } return nil + case "SetPossibleIssueByCommunications": + rels, ok := retrieved.(CommunicationSlice) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.SetPossibleIssueByCommunications = rels + + for _, rel := range rels { + if rel != nil { + rel.R.SetPossibleIssueByUser = o + } + } + return nil + case "SetPossibleResolvedByCommunications": + rels, ok := retrieved.(CommunicationSlice) + if !ok { + return fmt.Errorf("user cannot load %T as %q", retrieved, name) + } + + o.R.SetPossibleResolvedByCommunications = rels + + for _, rel := range rels { + if rel != nil { + rel.R.SetPossibleResolvedByUser = o + } + } + return nil case "CreatorComplianceReportRequests": rels, ok := retrieved.(ComplianceReportRequestSlice) if !ok { @@ -3984,36 +4198,38 @@ func buildUserPreloader() userPreloader { } type userThenLoader[Q orm.Loadable] struct { - CreatorTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ClosedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - InvalidatedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - OpenedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - SetPendingByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorComplianceReportRequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorFeatures func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CommitterFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - FileuploadPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorLeads func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ImpersonatorLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - TargetLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - UserNotifications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ReviewerNuisanceOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ReviewerReports func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - UserReportLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ReviewerWaterOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorReportTexts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorResidents func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - ReviewerReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - AddressorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - CreatorSites func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] - Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorTextJobs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ClosedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + InvalidatedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + OpenedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPendingByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPossibleIssueByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + SetPossibleResolvedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorComplianceReportRequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorFeatures func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CommitterFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + FileuploadPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorLeads func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ImpersonatorLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + TargetLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + UserNotifications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ReviewerNuisanceOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ReviewerReports func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + UserReportLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ReviewerWaterOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorReportTexts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorResidents func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + ReviewerReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + AddressorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + CreatorSites func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] + Organization func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] } func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { @@ -4032,6 +4248,12 @@ func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { type SetPendingByCommunicationsLoadInterface interface { LoadSetPendingByCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } + type SetPossibleIssueByCommunicationsLoadInterface interface { + LoadSetPossibleIssueByCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } + type SetPossibleResolvedByCommunicationsLoadInterface interface { + LoadSetPossibleResolvedByCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error + } type CreatorComplianceReportRequestsLoadInterface interface { LoadCreatorComplianceReportRequests(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error } @@ -4139,6 +4361,18 @@ func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] { return retrieved.LoadSetPendingByCommunications(ctx, exec, mods...) }, ), + SetPossibleIssueByCommunications: thenLoadBuilder[Q]( + "SetPossibleIssueByCommunications", + func(ctx context.Context, exec bob.Executor, retrieved SetPossibleIssueByCommunicationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSetPossibleIssueByCommunications(ctx, exec, mods...) + }, + ), + SetPossibleResolvedByCommunications: thenLoadBuilder[Q]( + "SetPossibleResolvedByCommunications", + func(ctx context.Context, exec bob.Executor, retrieved SetPossibleResolvedByCommunicationsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { + return retrieved.LoadSetPossibleResolvedByCommunications(ctx, exec, mods...) + }, + ), CreatorComplianceReportRequests: thenLoadBuilder[Q]( "CreatorComplianceReportRequests", func(ctx context.Context, exec bob.Executor, retrieved CreatorComplianceReportRequestsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { @@ -4612,6 +4846,134 @@ func (os UserSlice) LoadSetPendingByCommunications(ctx context.Context, exec bob return nil } +// LoadSetPossibleIssueByCommunications loads the user's SetPossibleIssueByCommunications into the .R struct +func (o *User) LoadSetPossibleIssueByCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SetPossibleIssueByCommunications = nil + + related, err := o.SetPossibleIssueByCommunications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.SetPossibleIssueByUser = o + } + + o.R.SetPossibleIssueByCommunications = related + return nil +} + +// LoadSetPossibleIssueByCommunications loads the user's SetPossibleIssueByCommunications into the .R struct +func (os UserSlice) LoadSetPossibleIssueByCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + communications, err := os.SetPossibleIssueByCommunications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.SetPossibleIssueByCommunications = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range communications { + + if !rel.SetPossibleIssueBy.IsValue() { + continue + } + if !(rel.SetPossibleIssueBy.IsValue() && o.ID == rel.SetPossibleIssueBy.MustGet()) { + continue + } + + rel.R.SetPossibleIssueByUser = o + + o.R.SetPossibleIssueByCommunications = append(o.R.SetPossibleIssueByCommunications, rel) + } + } + + return nil +} + +// LoadSetPossibleResolvedByCommunications loads the user's SetPossibleResolvedByCommunications into the .R struct +func (o *User) LoadSetPossibleResolvedByCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if o == nil { + return nil + } + + // Reset the relationship + o.R.SetPossibleResolvedByCommunications = nil + + related, err := o.SetPossibleResolvedByCommunications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, rel := range related { + rel.R.SetPossibleResolvedByUser = o + } + + o.R.SetPossibleResolvedByCommunications = related + return nil +} + +// LoadSetPossibleResolvedByCommunications loads the user's SetPossibleResolvedByCommunications into the .R struct +func (os UserSlice) LoadSetPossibleResolvedByCommunications(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { + if len(os) == 0 { + return nil + } + + communications, err := os.SetPossibleResolvedByCommunications(mods...).All(ctx, exec) + if err != nil { + return err + } + + for _, o := range os { + if o == nil { + continue + } + + o.R.SetPossibleResolvedByCommunications = nil + } + + for _, o := range os { + if o == nil { + continue + } + + for _, rel := range communications { + + if !rel.SetPossibleResolvedBy.IsValue() { + continue + } + if !(rel.SetPossibleResolvedBy.IsValue() && o.ID == rel.SetPossibleResolvedBy.MustGet()) { + continue + } + + rel.R.SetPossibleResolvedByUser = o + + o.R.SetPossibleResolvedByCommunications = append(o.R.SetPossibleResolvedByCommunications, rel) + } + } + + return nil +} + // LoadCreatorComplianceReportRequests loads the user's CreatorComplianceReportRequests into the .R struct func (o *User) LoadCreatorComplianceReportRequests(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error { if o == nil { diff --git a/db/query/public/communication.go b/db/query/public/communication.go index 5ed64ce9..eba16e2e 100644 --- a/db/query/public/communication.go +++ b/db/query/public/communication.go @@ -18,6 +18,13 @@ func CommunicationInsert(ctx context.Context, txn bob.Tx, m *model.Communication RETURNING(table.Communication.AllColumns) return db.ExecuteOne[model.Communication](ctx, statement) } +func CommunicationFromID(ctx context.Context, comm_id int64) (*model.Communication, error) { + statement := table.Communication.SELECT( + table.Communication.AllColumns, + ).FROM(table.Communication). + WHERE(table.Communication.ID.EQ(postgres.Int(comm_id))) + return db.ExecuteOne[model.Communication](ctx, statement) +} func CommunicationsFromOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) { statement := table.Communication.SELECT( table.Communication.AllColumns, @@ -25,3 +32,35 @@ func CommunicationsFromOrganization(ctx context.Context, org_id int64) ([]*model WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id))) return db.ExecuteMany[model.Communication](ctx, statement) } +func CommunicationMarkInvalid(ctx context.Context, org_id int64, user_id int64, comm_id int64) error { + statement := table.Communication.UPDATE(). + SET(table.Communication.Invalidated.SET(postgres.LOCALTIMESTAMP())). + SET(table.Communication.InvalidatedBy.SET(postgres.Int(user_id))). + WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)).AND( + table.Communication.ID.EQ(postgres.Int(comm_id)))) + return db.ExecuteNone(ctx, statement) +} +func CommunicationMarkPendingResponse(ctx context.Context, org_id int64, user_id int64, comm_id int64) error { + statement := table.Communication.UPDATE(). + SET(table.Communication.SetPending.SET(postgres.LOCALTIMESTAMP())). + SET(table.Communication.SetPendingBy.SET(postgres.Int(user_id))). + WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)).AND( + table.Communication.ID.EQ(postgres.Int(comm_id)))) + return db.ExecuteNone(ctx, statement) +} +func CommunicationMarkPossibleIssue(ctx context.Context, org_id int64, user_id int64, comm_id int64) error { + statement := table.Communication.UPDATE(). + SET(table.Communication.SetPossibleIssue.SET(postgres.LOCALTIMESTAMP())). + SET(table.Communication.SetPossibleIssueBy.SET(postgres.Int(user_id))). + WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)).AND( + table.Communication.ID.EQ(postgres.Int(comm_id)))) + return db.ExecuteNone(ctx, statement) +} +func CommunicationMarkPossibleResolved(ctx context.Context, org_id int64, user_id int64, comm_id int64) error { + statement := table.Communication.UPDATE(). + SET(table.Communication.SetPossibleResolved.SET(postgres.LOCALTIMESTAMP())). + SET(table.Communication.SetPossibleResolvedBy.SET(postgres.Int(user_id))). + WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)).AND( + table.Communication.ID.EQ(postgres.Int(comm_id)))) + return db.ExecuteNone(ctx, statement) +} diff --git a/html/image-upload.go b/html/image-upload.go index c145381f..df61b78c 100644 --- a/html/image-upload.go +++ b/html/image-upload.go @@ -3,6 +3,7 @@ package html import ( "bytes" "fmt" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform/file" "github.com/google/uuid" @@ -21,7 +22,7 @@ func ExtractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpl if err != nil { return upload, fmt.Errorf("Failed to open header: %w", err) } - defer f.Close() + defer lint.LogOnErr(f.Close, "close headers") file_bytes, err := io.ReadAll(f) content_type := http.DetectContentType(file_bytes) diff --git a/label-studio/list_tasks.go b/label-studio/list_tasks.go index bd0e66e0..8b9c64d9 100644 --- a/label-studio/list_tasks.go +++ b/label-studio/list_tasks.go @@ -5,6 +5,8 @@ import ( "fmt" "net/url" "time" + + "github.com/Gleipnir-Technology/nidus-sync/lint" ) // TasksListResponse represents the response from the /api/tasks endpoint @@ -131,7 +133,7 @@ func (c *Client) ListTasks(options *TasksListOptions) (*TasksListResponse, error if err != nil { return nil, fmt.Errorf("Failed to request %s: %v", path, err) } - defer resp.Body.Close() + defer lint.LogOnErr(resp.Body.Close, "close response body") // Parse response var tasksResponse TasksListResponse diff --git a/label-studio/projects.go b/label-studio/projects.go index 3fa2c4b6..882eea9b 100644 --- a/label-studio/projects.go +++ b/label-studio/projects.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "time" + + "github.com/Gleipnir-Technology/nidus-sync/lint" ) // ProjectsResponse represents the response from the /api/projects endpoint @@ -114,7 +116,7 @@ func (c *Client) Projects() (*ProjectsResponse, error) { if err != nil { return nil, fmt.Errorf("Failed to GET /api/projects: %w", err) } - defer resp.Body.Close() + defer lint.LogOnErr(resp.Body.Close, "resp.Body.Close") // Parse response var projects ProjectsResponse diff --git a/label-studio/tasks_annotation.go b/label-studio/tasks_annotation.go index aa26a5cd..d267da9b 100644 --- a/label-studio/tasks_annotation.go +++ b/label-studio/tasks_annotation.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "time" + + "github.com/Gleipnir-Technology/nidus-sync/lint" ) // AnnotationRequest represents the request body for creating a draft @@ -63,7 +65,7 @@ func (c *Client) CreateAnnotation(taskID int, annotation *AnnotationRequest) (*A if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } - defer resp.Body.Close() + defer lint.LogOnErr(resp.Body.Close, "close resp body") // Parse response var createdAnnotation Annotation diff --git a/lint/error.go b/lint/error.go new file mode 100644 index 00000000..f9433d2d --- /dev/null +++ b/lint/error.go @@ -0,0 +1,25 @@ +package lint + +import ( + "context" + + "github.com/rs/zerolog/log" +) + +type Errorable = func() error + +func LogOnErr(f Errorable, msg string) { + e := f() + if e != nil { + log.Error().Err(e).Msg(msg) + } +} + +type ErrorableCtx = func(context.Context) error + +func LogOnErrCtx(f ErrorableCtx, ctx context.Context, msg string) { + e := f(ctx) + if e != nil { + log.Error().Err(e).Msg(msg) + } +} diff --git a/main.go b/main.go index 897abb00..d73581b1 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/html" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/Gleipnir-Technology/nidus-sync/llm" "github.com/Gleipnir-Technology/nidus-sync/middleware" "github.com/Gleipnir-Technology/nidus-sync/platform" @@ -71,7 +72,7 @@ func main() { log.Fatal().Err(err).Msg("Failed to create sentry writer") os.Exit(2) } - defer sentryWriter.Close() + defer lint.LogOnErr(sentryWriter.Close, "close sentry writer") zerolog.TimeFieldFormat = zerolog.TimeFormatUnix log.Logger = log.Output(zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr}, sentryWriter)) @@ -84,8 +85,14 @@ func main() { // Defer cleanup in reverse order - these will execute LAST (LIFO) defer func() { log.Info().Msg("Final cleanup") - os.Stderr.Sync() - sentryWriter.Close() + err = os.Stderr.Sync() + if err != nil { + log.Error().Err(err).Msg("sync stderr") + } + err = sentryWriter.Close() + if err != nil { + log.Error().Err(err).Msg("close sentrywriter") + } sentry.Flush(2 * time.Second) }() @@ -183,7 +190,10 @@ func main() { <-signalCh log.Info().Msg("Received shutdown signal, shutting down...") // Ensure logs are flushed - os.Stderr.Sync() + err = os.Stderr.Sync() + if err != nil { + log.Error().Err(err).Msg("stderr sync") + } platform.EventShutdown() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) diff --git a/middleware/request_id.go b/middleware/request_id.go index c4f8e033..d4ddf144 100644 --- a/middleware/request_id.go +++ b/middleware/request_id.go @@ -51,7 +51,10 @@ func init() { var buf [12]byte var b64 string for len(b64) < 10 { - rand.Read(buf[:]) + _, err = rand.Read(buf[:]) + if err != nil { + panic("failed to rand.Read") + } b64 = base64.StdEncoding.EncodeToString(buf[:]) b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) } diff --git a/middleware/terminal.go b/middleware/terminal.go index 5ead7b92..4686e4b7 100644 --- a/middleware/terminal.go +++ b/middleware/terminal.go @@ -52,12 +52,19 @@ func init() { } // colorWrite -func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) { +func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) error { if IsTTY && useColor { - w.Write(color) + _, err := w.Write(color) + if err != nil { + return fmt.Errorf("write color: %w", err) + } } fmt.Fprintf(w, s, args...) if IsTTY && useColor { - w.Write(reset) + _, err := w.Write(reset) + if err != nil { + return fmt.Errorf("write color: %w", err) + } } + return nil } diff --git a/minio/client.go b/minio/client.go index cbdbdf44..0b0f9917 100644 --- a/minio/client.go +++ b/minio/client.go @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) @@ -66,7 +67,7 @@ func (minioClient *Client) UploadFile(bucketName string, filePath string, upload if err != nil { return fmt.Errorf("Failed to open file %s to upload: %v", filePath, err) } - defer file.Close() + defer lint.LogOnErr(file.Close, "close file") // Upload the file _, err = minioClient.client.FPutObject(context.Background(), bucketName, uploadPath, filePath, minio.PutObjectOptions{}) diff --git a/platform/arcgis.go b/platform/arcgis.go index 9676aebc..bc3e903d 100644 --- a/platform/arcgis.go +++ b/platform/arcgis.go @@ -37,6 +37,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db/types" "github.com/Gleipnir-Technology/nidus-sync/debug" "github.com/Gleipnir-Technology/nidus-sync/h3utils" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/Gleipnir-Technology/nidus-sync/platform/oauth" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" @@ -170,7 +171,7 @@ func downloadFieldseekerSchema(ctx context.Context, fieldseekerClient *fieldseek log.Error().Err(err).Msg("Failed to open output") return } - defer output.Close() + defer lint.LogOnErr(output.Close, "close schema output file") schema, err := fieldseekerClient.SchemaRaw(ctx, uint(i)) if err != nil { log.Error().Err(err).Msg("Failed to get schema") @@ -212,7 +213,10 @@ func generateCodeChallenge(codeVerifier string) string { // Generate a random code verifier for PKCE func generateCodeVerifier() string { bytes := make([]byte, 64) // 64 bytes = 512 bits - rand.Read(bytes) + _, err := rand.Read(bytes) + if err != nil { + return "" + } return base64.RawURLEncoding.EncodeToString(bytes) } diff --git a/platform/communication.go b/platform/communication.go index e8fa7902..ed295598 100644 --- a/platform/communication.go +++ b/platform/communication.go @@ -10,3 +10,25 @@ import ( func CommunicationsForOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) { return querypublic.CommunicationsFromOrganization(ctx, org_id) } +func CommunicationFromID(ctx context.Context, user User, comm_id int64) (*model.Communication, error) { + comm, err := querypublic.CommunicationFromID(ctx, comm_id) + if err != nil { + return nil, err + } + if comm.OrganizationID != user.Organization.ID { + return nil, nil + } + return comm, nil +} +func CommunicationMarkInvalid(ctx context.Context, user User, comm_id int64) error { + return querypublic.CommunicationMarkInvalid(ctx, int64(user.Organization.ID), int64(user.ID), comm_id) +} +func CommunicationMarkPendingResponse(ctx context.Context, user User, comm_id int64) error { + return querypublic.CommunicationMarkPendingResponse(ctx, int64(user.Organization.ID), int64(user.ID), comm_id) +} +func CommunicationMarkPossibleIssue(ctx context.Context, user User, comm_id int64) error { + return querypublic.CommunicationMarkPossibleIssue(ctx, int64(user.Organization.ID), int64(user.ID), comm_id) +} +func CommunicationMarkPossibleResolved(ctx context.Context, user User, comm_id int64) error { + return querypublic.CommunicationMarkPossibleResolved(ctx, int64(user.Organization.ID), int64(user.ID), comm_id) +} diff --git a/platform/csv/csv.go b/platform/csv/csv.go index 5e846467..cae32708 100644 --- a/platform/csv/csv.go +++ b/platform/csv/csv.go @@ -256,9 +256,12 @@ func importCSV[T any](ctx context.Context, file_id int32, parser csvParserFunc[T return fmt.Errorf("process parsed file: %w", err) } - file.Update(ctx, txn, &models.FileuploadFileSetter{ + err = file.Update(ctx, txn, &models.FileuploadFileSetter{ Status: omit.From(enums.FileuploadFilestatustypeParsed), }) + if err != nil { + return fmt.Errorf("update: %w", err) + } log.Info().Int32("file.ID", file.ID).Msg("Set file to parsed") txn.Commit(ctx) return nil diff --git a/platform/csv/pool.go b/platform/csv/pool.go index b5cc3368..d246de30 100644 --- a/platform/csv/pool.go +++ b/platform/csv/pool.go @@ -187,9 +187,12 @@ func parseCSVPoollist(ctx context.Context, txn bob.Tx, f *models.FileuploadFile, missing_headers := missingRequiredHeaders(header_types) for _, mh := range missing_headers { errorMissingHeader(ctx, txn, c, mh) - f.Update(ctx, txn, &models.FileuploadFileSetter{ + err = f.Update(ctx, txn, &models.FileuploadFileSetter{ Status: omit.From(enums.FileuploadFilestatustypeError), }) + if err != nil { + return pools, fmt.Errorf("update: %w", err) + } return pools, nil } for i, header_name := range header_names { @@ -279,7 +282,11 @@ func parseCSVPoollist(ctx context.Context, txn bob.Tx, f *models.FileuploadFile, addError(ctx, txn, c, int32(line_number), int32(i), fmt.Sprintf("'%s' is not a phone number that we recognize. Ideally it should be of the form '+12223334444'", col)) continue } - text.EnsureInDB(ctx, txn, *phone) + err = text.EnsureInDB(ctx, txn, *phone) + if err != nil { + log.Error().Err(err).Str("phone", col).Msg("ensure in DB failure") + continue + } setter.PropertyOwnerPhoneE164 = omitnull.From(phone.PhoneString()) case headerPoolResidentOwned: boolValue, err := parseBool(col) @@ -294,7 +301,11 @@ func parseCSVPoollist(ctx context.Context, txn bob.Tx, f *models.FileuploadFile, addError(ctx, txn, c, int32(line_number), int32(i), fmt.Sprintf("'%s' is not a phone number that we recognize. Ideally it should be of the form '+12223334444'", col)) continue } - text.EnsureInDB(ctx, txn, *phone) + err = text.EnsureInDB(ctx, txn, *phone) + if err != nil { + log.Error().Err(err).Str("phone", col).Msg("ensure in DB failure") + continue + } setter.ResidentPhoneE164 = omitnull.From(phone.PhoneString()) case headerPoolTag: tags[header_names[i]] = col diff --git a/platform/email/template.go b/platform/email/template.go index 34e403ed..c5738c48 100644 --- a/platform/email/template.go +++ b/platform/email/template.go @@ -23,6 +23,7 @@ import ( "github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/models" + "github.com/Gleipnir-Technology/nidus-sync/lint" "github.com/aarondl/opt/omit" "github.com/aarondl/opt/omitnull" "github.com/rs/zerolog/log" @@ -67,7 +68,7 @@ func LoadTemplates() error { if err != nil { return fmt.Errorf("Failed to start transaction: %w", err) } - defer tx.Rollback(ctx) + defer lint.LogOnErrCtx(tx.Rollback, ctx, "rollback") templateByID = make(map[int32]*builtTemplate, 0) for name, p := range all_templates { template_id, err := templateDBID(tx, name, p) diff --git a/platform/mailer/mailer.go b/platform/mailer/mailer.go index 4f943b0c..f7c4ea8d 100644 --- a/platform/mailer/mailer.go +++ b/platform/mailer/mailer.go @@ -91,7 +91,7 @@ func ComplianceSend(ctx context.Context, row_id int32) error { if err != nil { return fmt.Errorf("start txn: %w", err) } - defer txn.Rollback(nil) + defer txn.Rollback(ctx) mailer, err := models.CommsMailers.Insert(&models.CommsMailerSetter{ AddressID: omit.From(address.ID), Created: omit.From(time.Now()), diff --git a/resource/communication.go b/resource/communication.go index d0cfbf5c..664631c6 100644 --- a/resource/communication.go +++ b/resource/communication.go @@ -4,6 +4,7 @@ import ( "context" "net/http" "slices" + "strconv" "time" "github.com/Gleipnir-Technology/nidus-sync/config" @@ -12,7 +13,7 @@ import ( nhttp "github.com/Gleipnir-Technology/nidus-sync/http" "github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/google/uuid" - //"github.com/gorilla/mux" + "github.com/gorilla/mux" //"github.com/rs/zerolog/log" ) @@ -36,6 +37,7 @@ type communication struct { Response string `json:"response"` Source string `json:"source"` Type string `json:"type"` + URI string `json:"uri"` } func toImageURLs(m map[string][]uuid.UUID, id string) []string { @@ -49,7 +51,10 @@ func toImageURLs(m map[string][]uuid.UUID, id string) []string { } return urls } -func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]*communication, *nhttp.ErrorWithStatus) { +func (res *communicationR) Get(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communication, *nhttp.ErrorWithStatus) { + return nil, nil +} +func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]communication, *nhttp.ErrorWithStatus) { comms, err := platform.CommunicationsForOrganization(ctx, int64(user.Organization.ID)) if err != nil { return nil, nhttp.NewError("nuisance report query: %w", err) @@ -68,58 +73,19 @@ func (res *communicationR) List(ctx context.Context, r *http.Request, user platf for _, pr := range public_reports { public_report_id_to_report[pr.ID] = pr } - result := make([]*communication, len(comms)) + result := make([]communication, len(comms)) for i, comm := range comms { - source_uri := "unknown" - type_ := "unknown" - if comm.SourceReportID != nil { - public_report, ok := public_report_id_to_report[*comm.SourceReportID] - if !ok { - return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID) - } - source_uri, err = reportURI(res.router, "", public_report.PublicID) - if err != nil { - return nil, nhttp.NewError("gen report URI: %w", err) - } - type_ = "publicreport." + public_report.ReportType.String() - } else if comm.SourceEmailLogID != nil { - source_uri, err = emailURI(res.router, *comm.SourceEmailLogID) - if err != nil { - return nil, nhttp.NewError("gen email URI: %w", err) - } - type_ = "email" - } else if comm.SourceTextLogID != nil { - source_uri, err = textURI(res.router, *comm.SourceTextLogID) - if err != nil { - return nil, nhttp.NewError("gen email URI: %w", err) - } - source_uri = "text" + public_report, ok := public_report_id_to_report[*comm.SourceReportID] + if !ok { + return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID) } - closed_by, err := userURI(res.router, comm.ClosedBy) + c, err := res.hydrateCommunication(*comm, public_report) if err != nil { - return nil, nhttp.NewError("gen closed_by URI: %w", err) - } - opened_by, err := userURI(res.router, comm.OpenedBy) - if err != nil { - return nil, nhttp.NewError("gen opened_by URI: %w", err) - } - response, err := responseURI(res.router, comm) - if err != nil { - return nil, nhttp.NewError("gen response URI: %w", err) - } - result[i] = &communication{ - Closed: comm.Closed, - ClosedBy: closed_by, - Created: comm.Created, - ID: comm.ID, - Opened: comm.Opened, - OpenedBy: opened_by, - Response: response, - Source: source_uri, - Type: type_, + return nil, err } + result[i] = c } - _by_created := func(a, b *communication) int { + _by_created := func(a, b communication) int { if a.Created.Equal(b.Created) { return 0 } else if a.Created.Before(b.Created) { @@ -132,10 +98,93 @@ func (res *communicationR) List(ctx context.Context, r *http.Request, user platf return result, nil } -func emailURI(r *router, id int32) (string, error) { - return "fake email uri", nil +type communicationMarkRequest struct{} + +func (res *communicationR) MarkInvalid(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) { + return res.markReport(ctx, r, user, platform.CommunicationMarkInvalid) } -func responseURI(r *router, comm *modelpublic.Communication) (string, error) { +func (res *communicationR) MarkPendingResponse(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) { + return res.markReport(ctx, r, user, platform.CommunicationMarkPendingResponse) +} +func (res *communicationR) MarkPossibleIssue(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) { + return res.markReport(ctx, r, user, platform.CommunicationMarkPossibleIssue) +} +func (res *communicationR) MarkPossibleResolved(ctx context.Context, r *http.Request, user platform.User, cmr communicationMarkRequest) (communication, *nhttp.ErrorWithStatus) { + return res.markReport(ctx, r, user, platform.CommunicationMarkPossibleResolved) +} +func (res *communicationR) hydrateCommunication(comm modelpublic.Communication, public_report *modelpublicreport.Report) (communication, *nhttp.ErrorWithStatus) { + var err error + source_uri := "unknown" + type_ := "unknown" + if comm.SourceReportID != nil { + source_uri, err = reportURI(res.router, "", public_report.PublicID) + if err != nil { + return communication{}, nhttp.NewError("gen report URI: %w", err) + } + type_ = "publicreport." + public_report.ReportType.String() + } else if comm.SourceEmailLogID != nil { + source_uri, err = emailURI(*res.router, *comm.SourceEmailLogID) + if err != nil { + return communication{}, nhttp.NewError("gen email URI: %w", err) + } + type_ = "email" + } else if comm.SourceTextLogID != nil { + source_uri, err = textURI(*res.router, *comm.SourceTextLogID) + if err != nil { + return communication{}, nhttp.NewError("gen email URI: %w", err) + } + source_uri = "text" + } + closed_by, err := userURI(res.router, comm.ClosedBy) + if err != nil { + return communication{}, nhttp.NewError("gen closed_by URI: %w", err) + } + opened_by, err := userURI(res.router, comm.OpenedBy) + if err != nil { + return communication{}, nhttp.NewError("gen opened_by URI: %w", err) + } + response, err := responseURI(*res.router, comm) + if err != nil { + return communication{}, nhttp.NewError("gen response URI: %w", err) + } + uri, err := res.router.IDToURI("communication.ByIDGet", int(comm.ID)) + if err != nil { + return communication{}, nhttp.NewError("gen comm uri: %w", err) + } + return communication{ + Closed: comm.Closed, + ClosedBy: closed_by, + Created: comm.Created, + ID: comm.ID, + Opened: comm.Opened, + OpenedBy: opened_by, + Response: response, + Source: source_uri, + Type: type_, + URI: uri, + }, nil +} + +type markFunc = func(context.Context, platform.User, int64) error + +func (res *communicationR) markReport(ctx context.Context, r *http.Request, user platform.User, m markFunc) (communication, *nhttp.ErrorWithStatus) { + vars := mux.Vars(r) + report_id_str := vars["id"] + if report_id_str == "" { + return communication{}, nhttp.NewBadRequest("no id provided") + } + report_id, err := strconv.Atoi(report_id_str) + if err != nil { + return communication{}, nhttp.NewBadRequest("can't turn report ID into an int: %w", err) + } + m(ctx, user, int64(report_id)) + result, err := platform.CommunicationFromID(ctx, user, int64(report_id)) + if result == nil { + return communication{}, nhttp.NewUnauthorized("you are not authorized to modify communication %d", report_id) + } + return res.hydrateCommunication(*result, nil) +} +func responseURI(r router, comm modelpublic.Communication) (string, error) { if comm.ResponseEmailLogID != nil { return emailURI(r, *comm.ResponseEmailLogID) } else if comm.ResponseTextLogID != nil { @@ -144,7 +193,11 @@ func responseURI(r *router, comm *modelpublic.Communication) (string, error) { return "", nil } } -func textURI(r *router, id int32) (string, error) { +func emailURI(r router, id int32) (string, error) { + return "fake email uri", nil +} + +func textURI(r router, id int32) (string, error) { return "fake text uri", nil } func userURI(r *router, id *int32) (string, error) { diff --git a/resource/publicreport_compliance.go b/resource/publicreport_compliance.go index 4c287e40..90f5b22b 100644 --- a/resource/publicreport_compliance.go +++ b/resource/publicreport_compliance.go @@ -29,21 +29,21 @@ type complianceR struct { } type publicReportComplianceForm struct { - AccessInstructions omit.Val[string] `schema:"access_instructions" json:"access_instructions"` - Address omit.Val[types.Address] `schema:"address" json:"address"` - AvailabilityNotes omit.Val[string] `schema:"availability_notes" json:"availability_notes"` - ClientID uuid.UUID `schema:"client_id" json:"client_id"` - Comments omit.Val[string] `schema:"comments" json:"comments"` - District omit.Val[string] `schema:"district" json:"district"` - GateCode omit.Val[string] `schema:"gate_code" json:"gate_code"` - HasDog omitnull.Val[bool] `schema:"has_dog" json:"has_dog"` - Location omit.Val[types.Location] `schema:"location" json:"location"` - MailerID omit.Val[string] `schema:"mailer_id" json:"mailer_id"` - PermissionType omit.Val[enums.Permissionaccesstype] `schema:"permission_type" json:"permission_type"` - Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"` - ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"` - Submitted omitnull.Val[time.Time] `schema:"submitted" json:"submitted"` - WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"` + AccessInstructions omit.Val[string] `schema:"access_instructions" json:"access_instructions"` + Address omit.Val[types.Address] `schema:"address" json:"address"` + AvailabilityNotes omit.Val[string] `schema:"availability_notes" json:"availability_notes"` + ClientID uuid.UUID `schema:"client_id" json:"client_id"` + Comments omit.Val[string] `schema:"comments" json:"comments"` + District omit.Val[string] `schema:"district" json:"district"` + GateCode omit.Val[string] `schema:"gate_code" json:"gate_code"` + HasDog omitnull.Val[bool] `schema:"has_dog" json:"has_dog"` + Location omit.Val[types.Location] `schema:"location" json:"location"` + MailerID omit.Val[string] `schema:"mailer_id" json:"mailer_id"` + PermissionType omit.Val[enums.PublicreportPermissionaccesstype] `schema:"permission_type" json:"permission_type"` + Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"` + ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"` + Submitted omitnull.Val[time.Time] `schema:"submitted" json:"submitted"` + WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"` } func (res *complianceR) ByID(ctx context.Context, r *http.Request, u platform.User, query QueryParams) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) { @@ -88,7 +88,7 @@ func (res *complianceR) Create(ctx context.Context, r *http.Request, n publicRep Comments: omit.From(""), GateCode: omit.From(""), HasDog: omitnull.FromPtr[bool](nil), - PermissionType: omit.From(enums.PermissionaccesstypeUnselected), + PermissionType: omit.From(enums.PublicreportPermissionaccesstypeUnselected), //ReportID omit.Val[int32] WantsScheduled: omitnull.FromPtr[bool](nil), } diff --git a/ts/components/CommunicationColumnAction.vue b/ts/components/CommunicationColumnAction.vue index 455fd38f..38be11bf 100644 --- a/ts/components/CommunicationColumnAction.vue +++ b/ts/components/CommunicationColumnAction.vue @@ -8,7 +8,7 @@
Actions
-
Loading...
+
Loading...
- +

Send to planning

- - This report is useful signal +
-
- - This report isn't useful + +
+ +
+
+

Resolve immediately

+ +
+ +
+

@@ -134,14 +163,17 @@