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 // Write timestamp
timestamp := time.Now().Format("2006-01-02 15:04:05") 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 // Write request line
fmt.Fprintf(file, "%s %s %s\n", r.Method, r.RequestURI, r.Proto) 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) log.Printf("Error reading request body: %v", err)
fmt.Fprintf(file, "Error reading body: %v\n", err) fmt.Fprintf(file, "Error reading body: %v\n", err)
} else { } 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 { if len(body) == 0 {
fmt.Fprintf(file, "(empty body)") 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 { if err != nil {
log.Printf("Failed to write content file: %v", err) log.Printf("Failed to write content file: %v", err)
http.Error(w, "failed to write content file", http.StatusInternalServerError) http.Error(w, "failed to write content file", http.StatusInternalServerError)
return
} }
ctx := r.Context() ctx := r.Context()
a, err := models.NoteAudios.Query( a, err := models.NoteAudios.Query(
@ -78,8 +79,15 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, user platform.U
if err != nil { if err != nil {
log.Printf("Failed to get note audio %s for org %d: %w", u_str, user.Organization.ID, err) 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) 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) w.WriteHeader(http.StatusOK)
} }

View file

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/rs/zerolog/log" "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") log.Error().Err(err).Msg("failed to create temp file for debugSaveRequest")
return return
} }
defer tmpFile.Close() defer lint.LogOnErr(tmpFile.Close, "close temp file")
_, err = io.Copy(tmpFile, r.Body) _, err = io.Copy(tmpFile, r.Body)
if err != nil { 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 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 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 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 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 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) 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") r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET")
communication := resource.Communication(router) communication := resource.Communication(router)
r.Handle("/communication", authenticatedHandlerJSONSlice(communication.List)).Methods("GET") 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.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST")
//r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET") //r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET")
r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST") 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.WriteHeader(401)
w.Write(msg) _, err = w.Write(msg)
if err != nil {
log.Error().Err(err).Msg("failed to write response")
}
return return
} }
ea.handler(w, r, *user) ea.handler(w, r, *user)

View file

@ -5,12 +5,13 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/rs/zerolog/log" "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") 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) 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 // Read the response body
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Warn().Err(err).Str("url", full_url).Msg("Failed to read Voip.MS response body") 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) return result, fmt.Errorf("Failed to read response: %w", err)

View file

@ -87,6 +87,15 @@ var Communications = Table[
Generated: false, Generated: false,
AutoIncr: false, AutoIncr: false,
}, },
OrganizationID: column{
Name: "organization_id",
DBType: "integer",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
ResponseEmailLogID: column{ ResponseEmailLogID: column{
Name: "response_email_log_id", Name: "response_email_log_id",
DBType: "integer", DBType: "integer",
@ -150,6 +159,42 @@ var Communications = Table[
Generated: false, Generated: false,
AutoIncr: 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{ Indexes: communicationIndexes{
CommunicationPkey: index{ CommunicationPkey: index{
@ -203,6 +248,15 @@ var Communications = Table[
ForeignTable: "user_", ForeignTable: "user_",
ForeignColumns: []string{"id"}, 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{ CommunicationCommunicationResponseEmailLogIDFkey: foreignKey{
constraint: constraint{ constraint: constraint{
Name: "communication.communication_response_email_log_id_fkey", Name: "communication.communication_response_email_log_id_fkey",
@ -230,6 +284,24 @@ var Communications = Table[
ForeignTable: "user_", ForeignTable: "user_",
ForeignColumns: []string{"id"}, 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{ CommunicationCommunicationSourceEmailLogIDFkey: foreignKey{
constraint: constraint{ constraint: constraint{
Name: "communication.communication_source_email_log_id_fkey", Name: "communication.communication_source_email_log_id_fkey",
@ -263,26 +335,31 @@ var Communications = Table[
} }
type communicationColumns struct { type communicationColumns struct {
Closed column Closed column
ClosedBy column ClosedBy column
Created column Created column
ID column ID column
Invalidated column Invalidated column
InvalidatedBy column InvalidatedBy column
Opened column Opened column
OpenedBy column OpenedBy column
ResponseEmailLogID column OrganizationID column
ResponseTextLogID column ResponseEmailLogID column
SetPending column ResponseTextLogID column
SetPendingBy column SetPending column
SourceEmailLogID column SetPendingBy column
SourceReportID column SourceEmailLogID column
SourceTextLogID column SourceReportID column
SourceTextLogID column
SetPossibleIssue column
SetPossibleIssueBy column
SetPossibleResolved column
SetPossibleResolvedBy column
} }
func (c communicationColumns) AsSlice() []column { func (c communicationColumns) AsSlice() []column {
return []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 { type communicationForeignKeys struct {
CommunicationCommunicationClosedByFkey foreignKey CommunicationCommunicationClosedByFkey foreignKey
CommunicationCommunicationInvalidatedByFkey foreignKey CommunicationCommunicationInvalidatedByFkey foreignKey
CommunicationCommunicationOpenedByFkey foreignKey CommunicationCommunicationOpenedByFkey foreignKey
CommunicationCommunicationResponseEmailLogIDFkey foreignKey CommunicationCommunicationOrganizationIDFkey foreignKey
CommunicationCommunicationResponseTextLogIDFkey foreignKey CommunicationCommunicationResponseEmailLogIDFkey foreignKey
CommunicationCommunicationSetPendingByFkey foreignKey CommunicationCommunicationResponseTextLogIDFkey foreignKey
CommunicationCommunicationSourceEmailLogIDFkey foreignKey CommunicationCommunicationSetPendingByFkey foreignKey
CommunicationCommunicationSourceReportIDFkey foreignKey CommunicationCommunicationSetPossibleIssueByFkey foreignKey
CommunicationCommunicationSourceTextLogIDFkey foreignKey CommunicationCommunicationSetPossibleResolvedByFkey foreignKey
CommunicationCommunicationSourceEmailLogIDFkey foreignKey
CommunicationCommunicationSourceReportIDFkey foreignKey
CommunicationCommunicationSourceTextLogIDFkey foreignKey
} }
func (f communicationForeignKeys) AsSlice() []foreignKey { func (f communicationForeignKeys) AsSlice() []foreignKey {
return []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{ PermissionType: column{
Name: "permission_type", Name: "permission_type",
DBType: "public.permissionaccesstype", DBType: "publicreport.permissionaccesstype",
Default: "", Default: "",
Comment: "", Comment: "",
Nullable: false, Nullable: false,

View file

@ -1283,85 +1283,6 @@ func (e *Notificationtype) Scan(value any) error {
return nil 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 // Enum values for Poolconditiontype
const ( const (
PoolconditiontypeBlue Poolconditiontype = "blue" PoolconditiontypeBlue Poolconditiontype = "blue"
@ -1629,6 +1550,85 @@ func (e *PublicreportNuisancedurationtype) Scan(value any) error {
return nil 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 // Enum values for PublicreportPoolsourceduration
const ( const (
PublicreportPoolsourcedurationNone PublicreportPoolsourceduration = "none" 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. // organizationR is where relationships are stored.
type organizationR struct { 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 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 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 Features FeatureSlice // feature.feature_organization_id_fkey
@ -972,6 +973,30 @@ func (o OrganizationSlice) ReloadAll(ctx context.Context, exec bob.Executor) err
return nil 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 // EmailContacts starts a query for related objects on comms.email_contact
func (o *Organization) EmailContacts(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery { func (o *Organization) EmailContacts(mods ...bob.Mod[*dialect.SelectQuery]) CommsEmailContactsQuery {
return CommsEmailContacts.Query(append(mods, 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) { func attachOrganizationEmailContacts0(ctx context.Context, exec bob.Executor, count int, organization0 *Organization, commsEmailContacts2 CommsEmailContactSlice) (DistrictSubscriptionEmailSlice, error) {
setters := make([]*DistrictSubscriptionEmailSetter, count) setters := make([]*DistrictSubscriptionEmailSetter, count)
for i := range count { for i := range count {
@ -4928,6 +5021,20 @@ func (o *Organization) Preload(name string, retrieved any) error {
} }
switch name { 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": case "EmailContacts":
rels, ok := retrieved.(CommsEmailContactSlice) rels, ok := retrieved.(CommsEmailContactSlice)
if !ok { if !ok {
@ -5528,6 +5635,7 @@ func buildOrganizationPreloader() organizationPreloader {
} }
type organizationThenLoader[Q orm.Loadable] struct { type organizationThenLoader[Q orm.Loadable] struct {
Communications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
EmailContacts 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] Phones func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
Features 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] { func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] {
type CommunicationsLoadInterface interface {
LoadCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
}
type EmailContactsLoadInterface interface { type EmailContactsLoadInterface interface {
LoadEmailContacts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error LoadEmailContacts(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error
} }
@ -5701,6 +5812,12 @@ func buildOrganizationThenLoader[Q orm.Loadable]() organizationThenLoader[Q] {
} }
return 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: thenLoadBuilder[Q](
"EmailContacts", "EmailContacts",
func(ctx context.Context, exec bob.Executor, retrieved EmailContactsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { 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 // 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 { func (o *Organization) LoadEmailContacts(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil { if o == nil {

View file

@ -26,16 +26,16 @@ import (
// PublicreportCompliance is an object representing the database table. // PublicreportCompliance is an object representing the database table.
type PublicreportCompliance struct { type PublicreportCompliance struct {
AccessInstructions string `db:"access_instructions" ` AccessInstructions string `db:"access_instructions" `
AvailabilityNotes string `db:"availability_notes" ` AvailabilityNotes string `db:"availability_notes" `
Comments string `db:"comments" ` Comments string `db:"comments" `
GateCode string `db:"gate_code" ` GateCode string `db:"gate_code" `
HasDog null.Val[bool] `db:"has_dog" ` HasDog null.Val[bool] `db:"has_dog" `
PermissionType enums.Permissionaccesstype `db:"permission_type" ` PermissionType enums.PublicreportPermissionaccesstype `db:"permission_type" `
ReportID int32 `db:"report_id,pk" ` ReportID int32 `db:"report_id,pk" `
ReportPhoneCanText null.Val[bool] `db:"report_phone_can_text" ` ReportPhoneCanText null.Val[bool] `db:"report_phone_can_text" `
WantsScheduled null.Val[bool] `db:"wants_scheduled" ` WantsScheduled null.Val[bool] `db:"wants_scheduled" `
Submitted null.Val[time.Time] `db:"submitted" ` Submitted null.Val[time.Time] `db:"submitted" `
R publicreportComplianceR `db:"-" ` R publicreportComplianceR `db:"-" `
} }
@ -101,16 +101,16 @@ func (publicreportComplianceColumns) AliasedAs(alias string) publicreportComplia
// All values are optional, and do not have to be set // All values are optional, and do not have to be set
// Generated columns are not included // Generated columns are not included
type PublicreportComplianceSetter struct { type PublicreportComplianceSetter struct {
AccessInstructions omit.Val[string] `db:"access_instructions" ` AccessInstructions omit.Val[string] `db:"access_instructions" `
AvailabilityNotes omit.Val[string] `db:"availability_notes" ` AvailabilityNotes omit.Val[string] `db:"availability_notes" `
Comments omit.Val[string] `db:"comments" ` Comments omit.Val[string] `db:"comments" `
GateCode omit.Val[string] `db:"gate_code" ` GateCode omit.Val[string] `db:"gate_code" `
HasDog omitnull.Val[bool] `db:"has_dog" ` HasDog omitnull.Val[bool] `db:"has_dog" `
PermissionType omit.Val[enums.Permissionaccesstype] `db:"permission_type" ` PermissionType omit.Val[enums.PublicreportPermissionaccesstype] `db:"permission_type" `
ReportID omit.Val[int32] `db:"report_id,pk" ` ReportID omit.Val[int32] `db:"report_id,pk" `
ReportPhoneCanText omitnull.Val[bool] `db:"report_phone_can_text" ` ReportPhoneCanText omitnull.Val[bool] `db:"report_phone_can_text" `
WantsScheduled omitnull.Val[bool] `db:"wants_scheduled" ` WantsScheduled omitnull.Val[bool] `db:"wants_scheduled" `
Submitted omitnull.Val[time.Time] `db:"submitted" ` Submitted omitnull.Val[time.Time] `db:"submitted" `
} }
func (s PublicreportComplianceSetter) SetColumns() []string { func (s PublicreportComplianceSetter) SetColumns() []string {
@ -633,7 +633,7 @@ type publicreportComplianceWhere[Q psql.Filterable] struct {
Comments psql.WhereMod[Q, string] Comments psql.WhereMod[Q, string]
GateCode psql.WhereMod[Q, string] GateCode psql.WhereMod[Q, string]
HasDog psql.WhereNullMod[Q, bool] HasDog psql.WhereNullMod[Q, bool]
PermissionType psql.WhereMod[Q, enums.Permissionaccesstype] PermissionType psql.WhereMod[Q, enums.PublicreportPermissionaccesstype]
ReportID psql.WhereMod[Q, int32] ReportID psql.WhereMod[Q, int32]
ReportPhoneCanText psql.WhereNullMod[Q, bool] ReportPhoneCanText psql.WhereNullMod[Q, bool]
WantsScheduled 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), Comments: psql.Where[Q, string](cols.Comments),
GateCode: psql.Where[Q, string](cols.GateCode), GateCode: psql.Where[Q, string](cols.GateCode),
HasDog: psql.WhereNull[Q, bool](cols.HasDog), 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), ReportID: psql.Where[Q, int32](cols.ReportID),
ReportPhoneCanText: psql.WhereNull[Q, bool](cols.ReportPhoneCanText), ReportPhoneCanText: psql.WhereNull[Q, bool](cols.ReportPhoneCanText),
WantsScheduled: psql.WhereNull[Q, bool](cols.WantsScheduled), 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. // userR is where relationships are stored.
type userR struct { type userR struct {
CreatorTextJobs CommsTextJobSlice // comms.text_job.text_job_creator_id_fkey CreatorTextJobs CommsTextJobSlice // comms.text_job.text_job_creator_id_fkey
ClosedByCommunications CommunicationSlice // communication.communication_closed_by_fkey ClosedByCommunications CommunicationSlice // communication.communication_closed_by_fkey
InvalidatedByCommunications CommunicationSlice // communication.communication_invalidated_by_fkey InvalidatedByCommunications CommunicationSlice // communication.communication_invalidated_by_fkey
OpenedByCommunications CommunicationSlice // communication.communication_opened_by_fkey OpenedByCommunications CommunicationSlice // communication.communication_opened_by_fkey
SetPendingByCommunications CommunicationSlice // communication.communication_set_pending_by_fkey SetPendingByCommunications CommunicationSlice // communication.communication_set_pending_by_fkey
CreatorComplianceReportRequests ComplianceReportRequestSlice // compliance_report_request.compliance_report_request_creator_fkey SetPossibleIssueByCommunications CommunicationSlice // communication.communication_set_possible_issue_by_fkey
CreatorFeatures FeatureSlice // feature.feature_creator_id_fkey SetPossibleResolvedByCommunications CommunicationSlice // communication.communication_set_possible_resolved_by_fkey
CommitterFiles FileuploadFileSlice // fileupload.file.file_committer_fkey CreatorComplianceReportRequests ComplianceReportRequestSlice // compliance_report_request.compliance_report_request_creator_fkey
CreatorFiles FileuploadFileSlice // fileupload.file.file_creator_id_fkey CreatorFeatures FeatureSlice // feature.feature_creator_id_fkey
FileuploadPool FileuploadPoolSlice // fileupload.pool.pool_creator_id_fkey CommitterFiles FileuploadFileSlice // fileupload.file.file_committer_fkey
CreatorLeads LeadSlice // lead.lead_creator_fkey CreatorFiles FileuploadFileSlice // fileupload.file.file_creator_id_fkey
ImpersonatorLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_impersonator_id_fkey FileuploadPool FileuploadPoolSlice // fileupload.pool.pool_creator_id_fkey
TargetLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_target_id_fkey CreatorLeads LeadSlice // lead.lead_creator_fkey
CreatorNoteAudios NoteAudioSlice // note_audio.note_audio_creator_id_fkey ImpersonatorLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_impersonator_id_fkey
DeletorNoteAudios NoteAudioSlice // note_audio.note_audio_deletor_id_fkey TargetLogImpersonations LogImpersonationSlice // log_impersonation.log_impersonation_target_id_fkey
CreatorNoteImages NoteImageSlice // note_image.note_image_creator_id_fkey CreatorNoteAudios NoteAudioSlice // note_audio.note_audio_creator_id_fkey
DeletorNoteImages NoteImageSlice // note_image.note_image_deletor_id_fkey DeletorNoteAudios NoteAudioSlice // note_audio.note_audio_deletor_id_fkey
UserNotifications NotificationSlice // notification.notification_user_id_fkey CreatorNoteImages NoteImageSlice // note_image.note_image_creator_id_fkey
ReviewerNuisanceOlds PublicreportNuisanceOldSlice // publicreport.nuisance_old.nuisance_reviewer_id_fkey DeletorNoteImages NoteImageSlice // note_image.note_image_deletor_id_fkey
ReviewerReports PublicreportReportSlice // publicreport.report.report_reviewer_id_fkey UserNotifications NotificationSlice // notification.notification_user_id_fkey
UserReportLogs PublicreportReportLogSlice // publicreport.report_log.report_log_user_id_fkey ReviewerNuisanceOlds PublicreportNuisanceOldSlice // publicreport.nuisance_old.nuisance_reviewer_id_fkey
ReviewerWaterOlds PublicreportWaterOldSlice // publicreport.water_old.water_reviewer_id_fkey ReviewerReports PublicreportReportSlice // publicreport.report.report_reviewer_id_fkey
CreatorReportTexts ReportTextSlice // report_text.report_text_creator_id_fkey UserReportLogs PublicreportReportLogSlice // publicreport.report_log.report_log_user_id_fkey
CreatorResidents ResidentSlice // resident.resident_creator_fkey ReviewerWaterOlds PublicreportWaterOldSlice // publicreport.water_old.water_reviewer_id_fkey
CreatorReviewTasks ReviewTaskSlice // review_task.review_task_creator_id_fkey CreatorReportTexts ReportTextSlice // report_text.report_text_creator_id_fkey
ReviewerReviewTasks ReviewTaskSlice // review_task.review_task_reviewer_id_fkey CreatorResidents ResidentSlice // resident.resident_creator_fkey
AddressorSignals SignalSlice // signal.signal_addressor_fkey CreatorReviewTasks ReviewTaskSlice // review_task.review_task_creator_id_fkey
CreatorSignals SignalSlice // signal.signal_creator_fkey ReviewerReviewTasks ReviewTaskSlice // review_task.review_task_reviewer_id_fkey
CreatorSites SiteSlice // site.site_creator_id_fkey AddressorSignals SignalSlice // signal.signal_addressor_fkey
Organization *Organization // user_.user__organization_id_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 { 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 // CreatorComplianceReportRequests starts a query for related objects on compliance_report_request
func (o *User) CreatorComplianceReportRequests(mods ...bob.Mod[*dialect.SelectQuery]) ComplianceReportRequestsQuery { func (o *User) CreatorComplianceReportRequests(mods ...bob.Mod[*dialect.SelectQuery]) ComplianceReportRequestsQuery {
return ComplianceReportRequests.Query(append(mods, return ComplianceReportRequests.Query(append(mods,
@ -1806,6 +1856,142 @@ func (user0 *User) AttachSetPendingByCommunications(ctx context.Context, exec bo
return nil 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) { func insertUserCreatorComplianceReportRequests0(ctx context.Context, exec bob.Executor, complianceReportRequests1 []*ComplianceReportRequestSetter, user0 *User) (ComplianceReportRequestSlice, error) {
for i := range complianceReportRequests1 { for i := range complianceReportRequests1 {
complianceReportRequests1[i].Creator = omit.From(user0.ID) complianceReportRequests1[i].Creator = omit.From(user0.ID)
@ -3608,6 +3794,34 @@ func (o *User) Preload(name string, retrieved any) error {
} }
} }
return nil 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": case "CreatorComplianceReportRequests":
rels, ok := retrieved.(ComplianceReportRequestSlice) rels, ok := retrieved.(ComplianceReportRequestSlice)
if !ok { if !ok {
@ -3984,36 +4198,38 @@ func buildUserPreloader() userPreloader {
} }
type userThenLoader[Q orm.Loadable] struct { type userThenLoader[Q orm.Loadable] struct {
CreatorTextJobs 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] ClosedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
InvalidatedByCommunications 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] OpenedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
SetPendingByCommunications 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] SetPossibleIssueByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorFeatures func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] SetPossibleResolvedByCommunications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CommitterFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorComplianceReportRequests func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorFeatures func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
FileuploadPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CommitterFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorLeads func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorFiles func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
ImpersonatorLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] FileuploadPool func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
TargetLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorLeads func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ImpersonatorLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] TargetLogImpersonations func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DeletorNoteAudios func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
UserNotifications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
ReviewerNuisanceOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] DeletorNoteImages func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
ReviewerReports func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] UserNotifications func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
UserReportLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ReviewerNuisanceOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
ReviewerWaterOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ReviewerReports func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorReportTexts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] UserReportLogs func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorResidents func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ReviewerWaterOlds func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorReportTexts func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
ReviewerReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorResidents func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
AddressorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] CreatorReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] ReviewerReviewTasks func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
CreatorSites func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q] AddressorSignals func(...bob.Mod[*dialect.SelectQuery]) orm.Loader[Q]
Organization 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] { func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] {
@ -4032,6 +4248,12 @@ func buildUserThenLoader[Q orm.Loadable]() userThenLoader[Q] {
type SetPendingByCommunicationsLoadInterface interface { type SetPendingByCommunicationsLoadInterface interface {
LoadSetPendingByCommunications(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error 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 { type CreatorComplianceReportRequestsLoadInterface interface {
LoadCreatorComplianceReportRequests(context.Context, bob.Executor, ...bob.Mod[*dialect.SelectQuery]) error 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...) 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: thenLoadBuilder[Q](
"CreatorComplianceReportRequests", "CreatorComplianceReportRequests",
func(ctx context.Context, exec bob.Executor, retrieved CreatorComplianceReportRequestsLoadInterface, mods ...bob.Mod[*dialect.SelectQuery]) error { 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 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 // 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 { func (o *User) LoadCreatorComplianceReportRequests(ctx context.Context, exec bob.Executor, mods ...bob.Mod[*dialect.SelectQuery]) error {
if o == nil { if o == nil {

View file

@ -18,6 +18,13 @@ func CommunicationInsert(ctx context.Context, txn bob.Tx, m *model.Communication
RETURNING(table.Communication.AllColumns) RETURNING(table.Communication.AllColumns)
return db.ExecuteOne[model.Communication](ctx, statement) 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) { func CommunicationsFromOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) {
statement := table.Communication.SELECT( statement := table.Communication.SELECT(
table.Communication.AllColumns, 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))) WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)))
return db.ExecuteMany[model.Communication](ctx, statement) 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 ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/file" "github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/google/uuid" "github.com/google/uuid"
@ -21,7 +22,7 @@ func ExtractImageUpload(headers *multipart.FileHeader) (upload platform.ImageUpl
if err != nil { if err != nil {
return upload, fmt.Errorf("Failed to open header: %w", err) 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) file_bytes, err := io.ReadAll(f)
content_type := http.DetectContentType(file_bytes) content_type := http.DetectContentType(file_bytes)

View file

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"time" "time"
"github.com/Gleipnir-Technology/nidus-sync/lint"
) )
// TasksListResponse represents the response from the /api/tasks endpoint // TasksListResponse represents the response from the /api/tasks endpoint
@ -131,7 +133,7 @@ func (c *Client) ListTasks(options *TasksListOptions) (*TasksListResponse, error
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to request %s: %v", path, err) 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 // Parse response
var tasksResponse TasksListResponse var tasksResponse TasksListResponse

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time" "time"
"github.com/Gleipnir-Technology/nidus-sync/lint"
) )
// ProjectsResponse represents the response from the /api/projects endpoint // ProjectsResponse represents the response from the /api/projects endpoint
@ -114,7 +116,7 @@ func (c *Client) Projects() (*ProjectsResponse, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to GET /api/projects: %w", err) 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 // Parse response
var projects ProjectsResponse var projects ProjectsResponse

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"time" "time"
"github.com/Gleipnir-Technology/nidus-sync/lint"
) )
// AnnotationRequest represents the request body for creating a draft // 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 { if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err) 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 // Parse response
var createdAnnotation Annotation 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/config"
"github.com/Gleipnir-Technology/nidus-sync/db" "github.com/Gleipnir-Technology/nidus-sync/db"
"github.com/Gleipnir-Technology/nidus-sync/html" "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/llm"
"github.com/Gleipnir-Technology/nidus-sync/middleware" "github.com/Gleipnir-Technology/nidus-sync/middleware"
"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform"
@ -71,7 +72,7 @@ func main() {
log.Fatal().Err(err).Msg("Failed to create sentry writer") log.Fatal().Err(err).Msg("Failed to create sentry writer")
os.Exit(2) os.Exit(2)
} }
defer sentryWriter.Close() defer lint.LogOnErr(sentryWriter.Close, "close sentry writer")
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr}, sentryWriter)) 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 cleanup in reverse order - these will execute LAST (LIFO)
defer func() { defer func() {
log.Info().Msg("Final cleanup") log.Info().Msg("Final cleanup")
os.Stderr.Sync() err = os.Stderr.Sync()
sentryWriter.Close() 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) sentry.Flush(2 * time.Second)
}() }()
@ -183,7 +190,10 @@ func main() {
<-signalCh <-signalCh
log.Info().Msg("Received shutdown signal, shutting down...") log.Info().Msg("Received shutdown signal, shutting down...")
// Ensure logs are flushed // Ensure logs are flushed
os.Stderr.Sync() err = os.Stderr.Sync()
if err != nil {
log.Error().Err(err).Msg("stderr sync")
}
platform.EventShutdown() platform.EventShutdown()
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)

View file

@ -51,7 +51,10 @@ func init() {
var buf [12]byte var buf [12]byte
var b64 string var b64 string
for len(b64) < 10 { 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 = base64.StdEncoding.EncodeToString(buf[:])
b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) b64 = strings.NewReplacer("+", "", "/", "").Replace(b64)
} }

View file

@ -52,12 +52,19 @@ func init() {
} }
// colorWrite // 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 { 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...) fmt.Fprintf(w, s, args...)
if IsTTY && useColor { 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" "os"
"time" "time"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
) )
@ -66,7 +67,7 @@ func (minioClient *Client) UploadFile(bucketName string, filePath string, upload
if err != nil { if err != nil {
return fmt.Errorf("Failed to open file %s to upload: %v", filePath, err) 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 // Upload the file
_, err = minioClient.client.FPutObject(context.Background(), bucketName, uploadPath, filePath, minio.PutObjectOptions{}) _, 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/db/types"
"github.com/Gleipnir-Technology/nidus-sync/debug" "github.com/Gleipnir-Technology/nidus-sync/debug"
"github.com/Gleipnir-Technology/nidus-sync/h3utils" "github.com/Gleipnir-Technology/nidus-sync/h3utils"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/Gleipnir-Technology/nidus-sync/platform/oauth" "github.com/Gleipnir-Technology/nidus-sync/platform/oauth"
"github.com/aarondl/opt/omit" "github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull" "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") log.Error().Err(err).Msg("Failed to open output")
return return
} }
defer output.Close() defer lint.LogOnErr(output.Close, "close schema output file")
schema, err := fieldseekerClient.SchemaRaw(ctx, uint(i)) schema, err := fieldseekerClient.SchemaRaw(ctx, uint(i))
if err != nil { if err != nil {
log.Error().Err(err).Msg("Failed to get schema") 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 // Generate a random code verifier for PKCE
func generateCodeVerifier() string { func generateCodeVerifier() string {
bytes := make([]byte, 64) // 64 bytes = 512 bits bytes := make([]byte, 64) // 64 bytes = 512 bits
rand.Read(bytes) _, err := rand.Read(bytes)
if err != nil {
return ""
}
return base64.RawURLEncoding.EncodeToString(bytes) return base64.RawURLEncoding.EncodeToString(bytes)
} }

View file

@ -10,3 +10,25 @@ import (
func CommunicationsForOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) { func CommunicationsForOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) {
return querypublic.CommunicationsFromOrganization(ctx, org_id) 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) 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), 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") log.Info().Int32("file.ID", file.ID).Msg("Set file to parsed")
txn.Commit(ctx) txn.Commit(ctx)
return nil return nil

View file

@ -187,9 +187,12 @@ func parseCSVPoollist(ctx context.Context, txn bob.Tx, f *models.FileuploadFile,
missing_headers := missingRequiredHeaders(header_types) missing_headers := missingRequiredHeaders(header_types)
for _, mh := range missing_headers { for _, mh := range missing_headers {
errorMissingHeader(ctx, txn, c, mh) errorMissingHeader(ctx, txn, c, mh)
f.Update(ctx, txn, &models.FileuploadFileSetter{ err = f.Update(ctx, txn, &models.FileuploadFileSetter{
Status: omit.From(enums.FileuploadFilestatustypeError), Status: omit.From(enums.FileuploadFilestatustypeError),
}) })
if err != nil {
return pools, fmt.Errorf("update: %w", err)
}
return pools, nil return pools, nil
} }
for i, header_name := range header_names { 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)) 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 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()) setter.PropertyOwnerPhoneE164 = omitnull.From(phone.PhoneString())
case headerPoolResidentOwned: case headerPoolResidentOwned:
boolValue, err := parseBool(col) 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)) 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 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()) setter.ResidentPhoneE164 = omitnull.From(phone.PhoneString())
case headerPoolTag: case headerPoolTag:
tags[header_names[i]] = col 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"
"github.com/Gleipnir-Technology/nidus-sync/db/enums" "github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/db/models" "github.com/Gleipnir-Technology/nidus-sync/db/models"
"github.com/Gleipnir-Technology/nidus-sync/lint"
"github.com/aarondl/opt/omit" "github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull" "github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -67,7 +68,7 @@ func LoadTemplates() error {
if err != nil { if err != nil {
return fmt.Errorf("Failed to start transaction: %w", err) 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) templateByID = make(map[int32]*builtTemplate, 0)
for name, p := range all_templates { for name, p := range all_templates {
template_id, err := templateDBID(tx, name, p) 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 { if err != nil {
return fmt.Errorf("start txn: %w", err) return fmt.Errorf("start txn: %w", err)
} }
defer txn.Rollback(nil) defer txn.Rollback(ctx)
mailer, err := models.CommsMailers.Insert(&models.CommsMailerSetter{ mailer, err := models.CommsMailers.Insert(&models.CommsMailerSetter{
AddressID: omit.From(address.ID), AddressID: omit.From(address.ID),
Created: omit.From(time.Now()), Created: omit.From(time.Now()),

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"net/http" "net/http"
"slices" "slices"
"strconv"
"time" "time"
"github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/config"
@ -12,7 +13,7 @@ import (
nhttp "github.com/Gleipnir-Technology/nidus-sync/http" nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform" "github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/google/uuid" "github.com/google/uuid"
//"github.com/gorilla/mux" "github.com/gorilla/mux"
//"github.com/rs/zerolog/log" //"github.com/rs/zerolog/log"
) )
@ -36,6 +37,7 @@ type communication struct {
Response string `json:"response"` Response string `json:"response"`
Source string `json:"source"` Source string `json:"source"`
Type string `json:"type"` Type string `json:"type"`
URI string `json:"uri"`
} }
func toImageURLs(m map[string][]uuid.UUID, id string) []string { 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 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)) comms, err := platform.CommunicationsForOrganization(ctx, int64(user.Organization.ID))
if err != nil { if err != nil {
return nil, nhttp.NewError("nuisance report query: %w", err) 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 { for _, pr := range public_reports {
public_report_id_to_report[pr.ID] = pr public_report_id_to_report[pr.ID] = pr
} }
result := make([]*communication, len(comms)) result := make([]communication, len(comms))
for i, comm := range comms { for i, comm := range comms {
source_uri := "unknown" public_report, ok := public_report_id_to_report[*comm.SourceReportID]
type_ := "unknown" if !ok {
if comm.SourceReportID != nil { return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID)
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"
} }
closed_by, err := userURI(res.router, comm.ClosedBy) c, err := res.hydrateCommunication(*comm, public_report)
if err != nil { if err != nil {
return nil, nhttp.NewError("gen closed_by URI: %w", err) return nil, 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_,
} }
result[i] = c
} }
_by_created := func(a, b *communication) int { _by_created := func(a, b communication) int {
if a.Created.Equal(b.Created) { if a.Created.Equal(b.Created) {
return 0 return 0
} else if a.Created.Before(b.Created) { } 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 return result, nil
} }
func emailURI(r *router, id int32) (string, error) { type communicationMarkRequest struct{}
return "fake email uri", nil
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 { if comm.ResponseEmailLogID != nil {
return emailURI(r, *comm.ResponseEmailLogID) return emailURI(r, *comm.ResponseEmailLogID)
} else if comm.ResponseTextLogID != nil { } else if comm.ResponseTextLogID != nil {
@ -144,7 +193,11 @@ func responseURI(r *router, comm *modelpublic.Communication) (string, error) {
return "", nil 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 return "fake text uri", nil
} }
func userURI(r *router, id *int32) (string, error) { func userURI(r *router, id *int32) (string, error) {

View file

@ -29,21 +29,21 @@ type complianceR struct {
} }
type publicReportComplianceForm struct { type publicReportComplianceForm struct {
AccessInstructions omit.Val[string] `schema:"access_instructions" json:"access_instructions"` AccessInstructions omit.Val[string] `schema:"access_instructions" json:"access_instructions"`
Address omit.Val[types.Address] `schema:"address" json:"address"` Address omit.Val[types.Address] `schema:"address" json:"address"`
AvailabilityNotes omit.Val[string] `schema:"availability_notes" json:"availability_notes"` AvailabilityNotes omit.Val[string] `schema:"availability_notes" json:"availability_notes"`
ClientID uuid.UUID `schema:"client_id" json:"client_id"` ClientID uuid.UUID `schema:"client_id" json:"client_id"`
Comments omit.Val[string] `schema:"comments" json:"comments"` Comments omit.Val[string] `schema:"comments" json:"comments"`
District omit.Val[string] `schema:"district" json:"district"` District omit.Val[string] `schema:"district" json:"district"`
GateCode omit.Val[string] `schema:"gate_code" json:"gate_code"` GateCode omit.Val[string] `schema:"gate_code" json:"gate_code"`
HasDog omitnull.Val[bool] `schema:"has_dog" json:"has_dog"` HasDog omitnull.Val[bool] `schema:"has_dog" json:"has_dog"`
Location omit.Val[types.Location] `schema:"location" json:"location"` Location omit.Val[types.Location] `schema:"location" json:"location"`
MailerID omit.Val[string] `schema:"mailer_id" json:"mailer_id"` MailerID omit.Val[string] `schema:"mailer_id" json:"mailer_id"`
PermissionType omit.Val[enums.Permissionaccesstype] `schema:"permission_type" json:"permission_type"` PermissionType omit.Val[enums.PublicreportPermissionaccesstype] `schema:"permission_type" json:"permission_type"`
Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"` Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"`
ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"` ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"`
Submitted omitnull.Val[time.Time] `schema:"submitted" json:"submitted"` Submitted omitnull.Val[time.Time] `schema:"submitted" json:"submitted"`
WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"` 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) { 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(""), Comments: omit.From(""),
GateCode: omit.From(""), GateCode: omit.From(""),
HasDog: omitnull.FromPtr[bool](nil), HasDog: omitnull.FromPtr[bool](nil),
PermissionType: omit.From(enums.PermissionaccesstypeUnselected), PermissionType: omit.From(enums.PublicreportPermissionaccesstypeUnselected),
//ReportID omit.Val[int32] //ReportID omit.Val[int32]
WantsScheduled: omitnull.FromPtr[bool](nil), WantsScheduled: omitnull.FromPtr[bool](nil),
} }

View file

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

View file

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

View file

@ -33,9 +33,11 @@
</template> </template>
<template #right> <template #right>
<CommunicationColumnAction <CommunicationColumnAction
:loading="storePublicReport.loading || storeCommunication.loading" :isLoading="storePublicReport.loading || storeCommunication.loading"
@markInvalid="markInvalid" @markInvalid="markInvalid"
@markSignal="markSignal" @markPendingResponse="markPendingResponse"
@markPossibleIssue="markPossibleIssue"
@markPossibleResolved="markPossibleResolved"
@sendMessage="sendMessage" @sendMessage="sendMessage"
:selectedCommunication="selectedCommunication" :selectedCommunication="selectedCommunication"
:selectedReport="selectedReport" :selectedReport="selectedReport"
@ -62,6 +64,7 @@ import { computed, onMounted, ref, watch } from "vue";
import { computedAsync } from "@vueuse/core"; import { computedAsync } from "@vueuse/core";
import maplibregl from "maplibre-gl"; import maplibregl from "maplibre-gl";
import { apiClient } from "@/client";
import CommunicationColumnAction from "@/components/CommunicationColumnAction.vue"; import CommunicationColumnAction from "@/components/CommunicationColumnAction.vue";
import CommunicationColumnDetail from "@/components/CommunicationColumnDetail.vue"; import CommunicationColumnDetail from "@/components/CommunicationColumnDetail.vue";
import CommunicationColumnList from "@/components/CommunicationColumnList.vue"; import CommunicationColumnList from "@/components/CommunicationColumnList.vue";
@ -212,60 +215,32 @@ function openImageViewer(index: number) {
} }
async function markInvalid() { 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) { if (selectedCommunication.value == null) {
return; return;
} }
console.log("Marking report as invalid:", selectedCommunication.value.id); const url = `${selectedCommunication.value.uri}/${status}`;
const payload = { const result = apiClient.JSONPost(url, {});
reportID: selectedCommunication.value.id,
};
const response = await fetch("/api/publicreport/invalid", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
showNotification( showNotification(
"Report Marked Invalid", `Report Marked ${title}`,
`Report #${selectedCommunication.value.id} has been marked as invalid`, `Report #${selectedCommunication.value.id} has been updated`,
); );
removeCurrentFromList(); removeCurrentFromList();
await storeCommunication.fetchAll(); 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() { function removeCurrentFromList() {
if (storeCommunication.all == null) { if (storeCommunication.all == null) {
return; return;