Initial work marking communications

And a bunch of lint fixes
This commit is contained in:
Eli Ribble 2026-05-04 19:07:29 +00:00
parent 67c99436d1
commit 3153e8bf13
No known key found for this signature in database
36 changed files with 1958 additions and 487 deletions

View file

@ -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)")
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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,
}
}

View file

@ -62,7 +62,7 @@ var PublicreportCompliances = Table[
},
PermissionType: column{
Name: "permission_type",
DBType: "public.permissionaccesstype",
DBType: "publicreport.permissionaccesstype",
Default: "",
Comment: "",
Nullable: false,

View file

@ -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"

View file

@ -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);

File diff suppressed because it is too large Load diff

View file

@ -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 {

View file

@ -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),

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

25
lint/error.go Normal file
View file

@ -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)
}
}

18
main.go
View file

@ -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)

View file

@ -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)
}

View file

@ -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
}

View file

@ -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{})

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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()),

View file

@ -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) {

View file

@ -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),
}

View file

@ -8,7 +8,7 @@
<div class="card shadow-sm h-100">
<div class="card-header bg-light pane-header">Actions</div>
<div class="card-body scroll-pane">
<div v-if="loading" class="loading">Loading...</div>
<div v-if="isLoading" class="loading">Loading...</div>
<div v-else>
<div
v-if="!selectedCommunication"
@ -25,22 +25,51 @@
class="actions-panel d-flex flex-column"
>
<div class="p-3 flex-grow-1">
<!-- Create Signal -->
<p class="text-muted mt-1">Send to planning</p>
<div class="d-grid mb-3">
<button class="btn btn-success btn-lg" @click="markSignal()">
<i class="bi bi-plus-circle me-2"></i>Mark Signal
</button>
<small class="text-muted mt-1"
>This report is useful signal</small
>
<ButtonLoading
@click="markPossibleIssue()"
:disabled="!selectedReport"
icon="bi-plus-circle"
:loading="isLoading"
text="Possible Issue"
variant="warning"
/>
</div>
<!-- Mark Invalid -->
<div class="d-grid mb-3">
<button class="btn btn-outline-danger" @click="markInvalid()">
<i class="bi bi-x-circle me-2"></i>Mark Invalid
</button>
<small class="text-muted mt-1">This report isn't useful</small>
<ButtonLoading
@click="markPossibleResolved()"
:disabled="!selectedReport"
icon="bi-x-circle"
:loading="isLoading"
text="May Be Resolved"
variant="outline-success"
/>
</div>
<hr />
<div class="d-grid mb-3">
<p class="text-muted mt-1">Resolve immediately</p>
<ButtonLoading
@click="markInvalid()"
:disabled="!selectedReport"
icon="bi-x-circle"
:loading="isLoading"
text="Invalid"
variant="outline-danger"
/>
</div>
<div class="d-grid mb-3">
<ButtonLoading
@click="markPendingResponse()"
:disabled="!selectedReport"
icon="bi-clock"
:loading="isLoading"
text="Pending Response"
variant="secondary"
/>
</div>
<hr />
@ -134,14 +163,17 @@
<script setup lang="ts">
import { ref } from "vue";
import { Communication, PublicReport, User } from "@/type/api";
import ButtonLoading from "@/components/common/ButtonLoading.vue";
import ListCardActivityLog from "@/components/ListCardActivityLog.vue";
interface Emits {
(e: "markSignal"): void;
(e: "markInvalid"): void;
(e: "markPendingResponse"): void;
(e: "markPossibleIssue"): void;
(e: "markPossibleResolved"): void;
(e: "sendMessage", message: string): void;
}
interface Props {
loading: boolean;
isLoading: boolean;
selectedCommunication: Communication | null;
selectedReport: PublicReport | undefined;
}
@ -169,8 +201,14 @@ function handleTemplateChange(event: Event) {
function markInvalid() {
emit("markInvalid");
}
function markSignal() {
emit("markSignal");
function markPendingResponse() {
emit("markPendingResponse");
}
function markPossibleResolved() {
emit("markPossibleResolved");
}
function markPossibleIssue() {
emit("markPossibleIssue");
}
function sendMessage() {
emit("sendMessage", messageText.value);

View file

@ -534,6 +534,7 @@ export interface CommunicationDTO {
id: string;
source: string;
type: string;
uri: string;
}
export class Communication {
constructor(
@ -541,6 +542,7 @@ export class Communication {
public id: string,
public source: string,
public type: string,
public uri: string,
) {}
static fromJSON(json: CommunicationDTO): Communication {
return new Communication(
@ -548,6 +550,7 @@ export class Communication {
json.id,
json.source,
json.type,
json.uri,
);
}
}

View file

@ -33,9 +33,11 @@
</template>
<template #right>
<CommunicationColumnAction
:loading="storePublicReport.loading || storeCommunication.loading"
:isLoading="storePublicReport.loading || storeCommunication.loading"
@markInvalid="markInvalid"
@markSignal="markSignal"
@markPendingResponse="markPendingResponse"
@markPossibleIssue="markPossibleIssue"
@markPossibleResolved="markPossibleResolved"
@sendMessage="sendMessage"
:selectedCommunication="selectedCommunication"
:selectedReport="selectedReport"
@ -62,6 +64,7 @@ import { computed, onMounted, ref, watch } from "vue";
import { computedAsync } from "@vueuse/core";
import maplibregl from "maplibre-gl";
import { apiClient } from "@/client";
import CommunicationColumnAction from "@/components/CommunicationColumnAction.vue";
import CommunicationColumnDetail from "@/components/CommunicationColumnDetail.vue";
import CommunicationColumnList from "@/components/CommunicationColumnList.vue";
@ -212,60 +215,32 @@ function openImageViewer(index: number) {
}
async function markInvalid() {
markReport("Invalid", "invalid");
}
async function markPendingResponse() {
markReport("Pending Response", "pending-response");
}
async function markPossibleIssue() {
markReport("Possible Issue", "possible-issue");
}
async function markPossibleResolved() {
markReport("Possibly Resolved", "possible-resolved");
}
async function markReport(title: string, status: string) {
if (selectedCommunication.value == null) {
return;
}
console.log("Marking report as invalid:", selectedCommunication.value.id);
const payload = {
reportID: selectedCommunication.value.id,
};
const response = await fetch("/api/publicreport/invalid", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const url = `${selectedCommunication.value.uri}/${status}`;
const result = apiClient.JSONPost(url, {});
showNotification(
"Report Marked Invalid",
`Report #${selectedCommunication.value.id} has been marked as invalid`,
`Report Marked ${title}`,
`Report #${selectedCommunication.value.id} has been updated`,
);
removeCurrentFromList();
await storeCommunication.fetchAll();
}
async function markSignal() {
if (selectedCommunication.value == null) {
return;
}
console.log("Marking report as signal:", selectedCommunication.value.id);
try {
const report_id = selectedCommunication.value.id;
const payload = {
reportID: report_id,
};
removeCurrentFromList();
const response = await fetch("api/publicreport/signal", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error("Failed to submit signal");
}
showNotification(
"Report Marked Signal",
`Report #${report_id} has been marked as useful signal`,
);
await storeCommunication.fetchAll();
} catch (err) {
console.error("Error creating lead:", err);
}
}
function removeCurrentFromList() {
if (storeCommunication.all == null) {
return;