Return communication database rows from communication API
This is a pretty big refactor of how communication works to start moving us in the direction we want to go long-term. This adds the new communication row and migrates existing reports to add rows for communication. There's also a bunch of automatic fixes from the new linter. I should have added them separately, but whatever.
This commit is contained in:
parent
a6ce0b7e67
commit
a82732a49c
41 changed files with 365 additions and 220 deletions
|
|
@ -67,7 +67,7 @@ func (c *ConnectionSSE) SendHeartbeat(w http.ResponseWriter, t time.Time) error
|
|||
func SetEventChannel(chan_envelopes <-chan platform.Envelope) {
|
||||
go func() {
|
||||
for envelope := range chan_envelopes {
|
||||
for conn, _ := range connectionsSSE {
|
||||
for conn := range connectionsSSE {
|
||||
if conn.organizationID == envelope.OrganizationID || envelope.OrganizationID == 0 {
|
||||
log.Debug().Int("type", int(envelope.Event.Type)).Int32("env-org", envelope.OrganizationID).Msg("pushed event to client")
|
||||
conn.chanEvent <- envelope.Event
|
||||
|
|
|
|||
|
|
@ -345,19 +345,20 @@ func parseRequest[RequestType any](r *http.Request) (*RequestType, *nhttp.ErrorW
|
|||
var err error
|
||||
var req RequestType
|
||||
content_type := r.Header.Get("Content-Type")
|
||||
if content_type == "application/json" {
|
||||
switch content_type {
|
||||
case "application/json":
|
||||
body, e := io.ReadAll(r.Body)
|
||||
if e != nil {
|
||||
return nil, nhttp.NewError("Failed to read body: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(body, &req)
|
||||
} else if content_type == "application/x-www-form-urlencoded" {
|
||||
case "application/x-www-form-urlencoded":
|
||||
e := r.ParseForm()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewBadRequest("parsing form: %w", e)
|
||||
}
|
||||
err = decoder.Decode(&req, r.PostForm)
|
||||
} else {
|
||||
default:
|
||||
return nil, nhttp.NewBadRequest("unrecognized content type '%s'", content_type)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func AddRoutesSync(r *mux.Router) {
|
|||
r.Handle("/avatar", authenticatedHandlerPostMultipart(avatar.Create, file.CollectionAvatar)).Methods("POST")
|
||||
r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET")
|
||||
communication := resource.Communication(router)
|
||||
r.Handle("/communication", authenticatedHandlerJSON(communication.List)).Methods("GET")
|
||||
r.Handle("/communication", authenticatedHandlerJSONSlice(communication.List)).Methods("GET")
|
||||
r.Handle("/compliance-request/mailer", authenticatedHandlerJSONPost(compliance_request.CreateMailer)).Methods("POST")
|
||||
//r.HandleFunc("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool).Methods("GET")
|
||||
r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST")
|
||||
|
|
@ -121,7 +121,7 @@ func AddRoutesSync(r *mux.Router) {
|
|||
r.Handle("/publicreport/invalid", authenticatedHandlerJSONPost(postPublicreportInvalid)).Methods("POST")
|
||||
r.Handle("/publicreport/signal", authenticatedHandlerJSONPost(postPublicreportSignal)).Methods("POST")
|
||||
r.Handle("/publicreport/message", authenticatedHandlerJSONPost(postPublicreportMessage)).Methods("POST")
|
||||
r.Handle("/publicreport/{id}", authenticatedHandlerBasic(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGetPublic")
|
||||
r.Handle("/publicreport/{id}", authenticatedHandlerBasic(publicreport.ByID)).Methods("GET").Name("publicreport.ByIDGet")
|
||||
r.Handle("/publicreport/compliance/{id}", authenticatedHandlerJSON(pr_compliance.ByID)).Methods("GET").Name("publicreport.compliance.ByIDGet")
|
||||
r.Handle("/publicreport/nuisance/{id}", authenticatedHandlerJSON(nuisance.ByID)).Methods("GET").Name("publicreport.nuisance.ByIDGet")
|
||||
r.Handle("/publicreport/water/{id}", authenticatedHandlerJSON(water.ByID)).Methods("GET").Name("publicreport.water.ByIDGet")
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ func (ea *EnsureAuth) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// Don't send authentication headers for browsers because it forces the authentication popup
|
||||
requested_with := r.Header.Get("X-Requested-With")
|
||||
//log.Debug().Str("x-requested-with", requested_with).Send()
|
||||
if !(strings.HasPrefix(requested_with, "nidus-web") || accept == "text/event-stream") {
|
||||
if !strings.HasPrefix(requested_with, "nidus-web") && accept != "text/event-stream" {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="Nidus Sync"`)
|
||||
// Separate return codes for different authentication failures
|
||||
if _, ok := err.(*NoCredentialsError); ok {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func main() {
|
|||
}
|
||||
|
||||
func scanValue(message string, result *string) {
|
||||
fmt.Printf(message)
|
||||
fmt.Print("%s", message)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
if ok := scanner.Scan(); !ok {
|
||||
log.Fatal(errors.New("Failed to scan input"))
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func Parse() (err error) {
|
|||
if Environment == "" {
|
||||
return fmt.Errorf("You must specify a non-empty ENVIRONMENT")
|
||||
}
|
||||
if !(Environment == "PRODUCTION" || Environment == "DEVELOPMENT") {
|
||||
if Environment != "PRODUCTION" && Environment != "DEVELOPMENT" {
|
||||
return fmt.Errorf("ENVIRONMENT should be either DEVELOPMENT or PRODUCTION")
|
||||
}
|
||||
FieldseekerSchemaDirectory = os.Getenv("FIELDSEEKER_SCHEMA_DIRECTORY")
|
||||
|
|
|
|||
|
|
@ -149,10 +149,6 @@ func InitializeDatabase(ctx context.Context, uri string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("Failed to get database current: %w", err)
|
||||
}
|
||||
err = prepareStatements(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to initialize prepared statements: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import (
|
|||
|
||||
var schemas []string = []string{
|
||||
"arcgis",
|
||||
"public",
|
||||
"publicreport",
|
||||
"stadia",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ CREATE TABLE communication (
|
|||
invalidated_by INTEGER REFERENCES user_(id),
|
||||
opened TIMESTAMP WITHOUT TIME ZONE,
|
||||
opened_by INTEGER REFERENCES user_(id),
|
||||
organization_id INTEGER NOT NULL REFERENCES organization(id),
|
||||
response_email_log_id INTEGER REFERENCES comms.email_log(id),
|
||||
response_text_log_id INTEGER REFERENCES comms.text_log(id),
|
||||
set_pending TIMESTAMP WITHOUT TIME ZONE,
|
||||
|
|
|
|||
37
db/migrations/00148_communications_from_reports.sql
Normal file
37
db/migrations/00148_communications_from_reports.sql
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
-- +goose Up
|
||||
INSERT INTO communication (
|
||||
closed,
|
||||
closed_by,
|
||||
created,
|
||||
--id,
|
||||
invalidated,
|
||||
invalidated_by,
|
||||
opened,
|
||||
opened_by,
|
||||
organization_id,
|
||||
response_email_log_id,
|
||||
response_text_log_id,
|
||||
set_pending,
|
||||
set_pending_by,
|
||||
source_email_log_id,
|
||||
source_report_id,
|
||||
source_text_log_id
|
||||
) SELECT
|
||||
NULL,
|
||||
NULL,
|
||||
created,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
organization_id,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
id,
|
||||
NULL
|
||||
FROM publicreport.report;
|
||||
-- +goose Down
|
||||
DELETE FROM communication;
|
||||
22
db/migrations/00149_permissionaccesstype.sql
Normal file
22
db/migrations/00149_permissionaccesstype.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-- +goose Up
|
||||
CREATE TYPE publicreport.PermissionAccessType AS ENUM (
|
||||
'denied',
|
||||
'granted',
|
||||
'unselected',
|
||||
'with-owner'
|
||||
);
|
||||
ALTER TABLE publicreport.compliance
|
||||
ALTER COLUMN permission_type
|
||||
TYPE publicreport.PermissionAccessType USING permission_type::text::publicreport.PermissionAccessType;
|
||||
DROP TYPE PermissionAccessType;
|
||||
-- +goose Down
|
||||
CREATE TYPE PermissionAccessType AS ENUM (
|
||||
'denied',
|
||||
'granted',
|
||||
'unselected',
|
||||
'with-owner'
|
||||
);
|
||||
ALTER TABLE publicreport.compliance
|
||||
ALTER COLUMN permission_type
|
||||
TYPE PermissionAccessType;
|
||||
DROP TYPE publicreport.PermissionAccessType;
|
||||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
|
@ -21,52 +20,6 @@ import (
|
|||
//go:embed prepared_functions/*.sql
|
||||
var sqlFiles embed.FS
|
||||
|
||||
// PrepareStatements reads all embedded SQL files and executes them
|
||||
// against the provided database connection. This is intended for
|
||||
// preparing statements that will be used later.
|
||||
func prepareStatements(ctx context.Context) error {
|
||||
return nil
|
||||
// Get a list of all embedded SQL files
|
||||
entries, err := sqlFiles.ReadDir("prepared_functions")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read SQL directory: %w", err)
|
||||
}
|
||||
log.Info().Int("len", len(entries)).Msg("Reading prepared functions")
|
||||
|
||||
// Process each SQL file
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sql") {
|
||||
log.Info().Str("name", entry.Name()).Msg("Skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the SQL file content
|
||||
content, err := sqlFiles.ReadFile(filepath.Join("prepared_functions", entry.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read SQL file %s: %w", entry.Name(), err)
|
||||
}
|
||||
|
||||
// Get the statement name from the filename (without extension)
|
||||
statementName := strings.TrimSuffix(filepath.Base(entry.Name()), ".sql")
|
||||
|
||||
// Execute the SQL to prepare the statement
|
||||
_, err = PGInstance.BobDB.Exec(string(content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement %s: %w", statementName, err)
|
||||
}
|
||||
/*
|
||||
query := psql.RawQuery(string(content))
|
||||
stmt, err := bob.Prepare(ctx, PGInstance.BobDB, query)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to prepare statement %s: %w", statementName, err)
|
||||
}
|
||||
*/
|
||||
|
||||
log.Info().Str("statement", statementName).Msg("Prepared statement")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func TestPreparedQueryOld(ctx context.Context) error {
|
||||
type Skn struct {
|
||||
Result int
|
||||
|
|
|
|||
26
db/query/public/communication.go
Normal file
26
db/query/public/communication.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/bob"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/table"
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
)
|
||||
|
||||
func CommunicationInsert(ctx context.Context, txn bob.Tx, m *model.Communication) (*model.Communication, error) {
|
||||
m.Created = time.Now()
|
||||
statement := table.Communication.INSERT(table.Communication.MutableColumns).
|
||||
MODEL(m)
|
||||
return db.ExecuteOne[model.Communication](ctx, statement)
|
||||
}
|
||||
func CommunicationsFromOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) {
|
||||
statement := table.Communication.SELECT(
|
||||
table.Communication.AllColumns,
|
||||
).FROM(table.Communication).
|
||||
WHERE(table.Communication.OrganizationID.EQ(postgres.Int(org_id)))
|
||||
return db.ExecuteMany[model.Communication](ctx, statement)
|
||||
}
|
||||
32
db/query/publicreport/report.go
Normal file
32
db/query/publicreport/report.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
//"time"
|
||||
|
||||
//"github.com/Gleipnir-Technology/bob"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/table"
|
||||
"github.com/go-jet/jet/v2/postgres"
|
||||
)
|
||||
|
||||
/*
|
||||
func CommunicationInsert(ctx context.Context, txn bob.Tx, m *model.Communication) (*model.Communication, error) {
|
||||
m.Created = time.Now()
|
||||
statement := table.Communication.INSERT(table.Communication.MutableColumns).
|
||||
MODEL(m)
|
||||
return db.ExecuteOne[model.Communication](ctx, statement)
|
||||
}
|
||||
*/
|
||||
func PublicReportsFromIDs(ctx context.Context, report_ids []int64) ([]*model.Report, error) {
|
||||
sql_ids := make([]postgres.Expression, len(report_ids))
|
||||
for i, report_id := range report_ids {
|
||||
sql_ids[i] = postgres.Int(report_id)
|
||||
}
|
||||
statement := table.Report.SELECT(
|
||||
table.Report.AllColumns,
|
||||
).FROM(table.Report).
|
||||
WHERE(table.Report.ID.IN(sql_ids...))
|
||||
return db.ExecuteMany[model.Report](ctx, statement)
|
||||
}
|
||||
|
|
@ -76,9 +76,7 @@ func (ts templateSystemEmbed) loadTemplateSubdir(subdir string) error {
|
|||
}
|
||||
|
||||
func (ts templateSystemEmbed) addSubdirTemplates(t *template.Template, subdir string) error {
|
||||
var err error
|
||||
//log.Debug().Msg("Adding subdir templates")
|
||||
err = fs.WalkDir(ts.sourceFS, subdir, func(path string, d fs.DirEntry, err error) error {
|
||||
var err error = fs.WalkDir(ts.sourceFS, subdir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil || d.IsDir() {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ type Draft struct {
|
|||
func NewDraft(projectID int) *DraftRequest {
|
||||
return &DraftRequest{
|
||||
DraftID: 0,
|
||||
Project: string(projectID),
|
||||
Project: fmt.Sprint(rune(projectID)),
|
||||
StartedAt: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func (c *openAIClient) continueConversation(ctx context.Context, tools genai.Opt
|
|||
if m.String() == "" {
|
||||
//log.Debug().Msg("Tool called")
|
||||
} else {
|
||||
var toSay string = m.String()
|
||||
var toSay = m.String()
|
||||
toSay = strings.Replace(toSay, "report-mosquitoes-online: ", "", 1)
|
||||
return Message{
|
||||
Content: toSay,
|
||||
|
|
|
|||
|
|
@ -164,9 +164,7 @@ func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
|
|||
}
|
||||
|
||||
func init() {
|
||||
color := true
|
||||
if runtime.GOOS == "windows" {
|
||||
color = false
|
||||
}
|
||||
color := !(runtime.GOOS == "windows")
|
||||
|
||||
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: !color})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ type flushWriter struct {
|
|||
|
||||
func (f *flushWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl := f.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ type hijackWriter struct {
|
|||
}
|
||||
|
||||
func (f *hijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
hj := f.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
|
|
@ -171,12 +171,12 @@ type flushHijackWriter struct {
|
|||
|
||||
func (f *flushHijackWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl := f.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
func (f *flushHijackWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
hj := f.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
|
|
@ -193,12 +193,12 @@ type httpFancyWriter struct {
|
|||
|
||||
func (f *httpFancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl := f.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
hj := f.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
|
|
@ -207,15 +207,15 @@ func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error {
|
|||
}
|
||||
|
||||
func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||
if f.basicWriter.tee != nil {
|
||||
if f.tee != nil {
|
||||
n, err := io.Copy(&f.basicWriter, r)
|
||||
f.basicWriter.bytes += int(n)
|
||||
f.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
|
||||
f.basicWriter.maybeWriteHeader()
|
||||
rf := f.ResponseWriter.(io.ReaderFrom)
|
||||
f.maybeWriteHeader()
|
||||
n, err := rf.ReadFrom(r)
|
||||
f.basicWriter.bytes += int(n)
|
||||
f.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +234,7 @@ type http2FancyWriter struct {
|
|||
|
||||
func (f *http2FancyWriter) Flush() {
|
||||
f.wroteHeader = true
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl := f.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -792,9 +792,7 @@ func rowmapViaQuery(ctx context.Context, table string, sorted_columns []string,
|
|||
|
||||
// +2 for geometry x and geometry x
|
||||
columnNames := make([]string, len(sorted_columns)+2)
|
||||
for i, c := range sorted_columns {
|
||||
columnNames[i] = c
|
||||
}
|
||||
copy(columnNames, sorted_columns)
|
||||
columnNames[len(sorted_columns)] = "geometry_x"
|
||||
columnNames[len(sorted_columns)+1] = "geometry_y"
|
||||
|
||||
|
|
@ -1031,7 +1029,7 @@ func selectAllFromQueryResult(table string, sorted_columns []string) string {
|
|||
return sb.String()
|
||||
}
|
||||
func toHistoryTable(table string) string {
|
||||
return "History_" + table[3:len(table)]
|
||||
return "History_" + table[3:]
|
||||
}
|
||||
|
||||
func updateRowFromFeatureFS(ctx context.Context, transaction bob.Tx, table string, sorted_columns []string, feature *response.Feature) error {
|
||||
|
|
@ -1618,7 +1616,7 @@ func aggregateAtResolution(ctx context.Context, resolution int, org_id int32, ty
|
|||
if err != nil {
|
||||
return fmt.Errorf("Failed to clear previous aggregation: %w", err)
|
||||
}
|
||||
var to_insert []bob.Mod[*dialect.InsertQuery] = make([]bob.Mod[*dialect.InsertQuery], 0)
|
||||
var to_insert = make([]bob.Mod[*dialect.InsertQuery], 0)
|
||||
to_insert = append(to_insert, im.Into("h3_aggregation", "cell", "resolution", "count_", "type_", "organization_id", "geometry"))
|
||||
for cell, count := range cellToCount {
|
||||
polygon, err := h3utils.CellToPostgisGeometry(cell)
|
||||
|
|
|
|||
12
platform/communication.go
Normal file
12
platform/communication.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model"
|
||||
querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public"
|
||||
)
|
||||
|
||||
func CommunicationsForOrganization(ctx context.Context, org_id int64) ([]*model.Communication, error) {
|
||||
return querypublic.CommunicationsFromOrganization(ctx, org_id)
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ func geocodePool(ctx context.Context, txn bob.Tx, client *stadia.StadiaMaps, job
|
|||
return nil
|
||||
}
|
||||
if geo.Address.Location == nil {
|
||||
addError(ctx, txn, job.csv, pool.LineNumber, 0, fmt.Sprintf("nil location from geocoding"))
|
||||
addError(ctx, txn, job.csv, pool.LineNumber, 0, "nil location from geocoding")
|
||||
return nil
|
||||
}
|
||||
geom_query := geom.PostgisPointQuery(*geo.Address.Location)
|
||||
|
|
@ -329,7 +329,7 @@ func parseHeaders(row []string) ([]headerPoolEnum, []string) {
|
|||
ht := strings.TrimSpace(h)
|
||||
hl := strings.ToLower(ht)
|
||||
log.Debug().Str("header", hl).Msg("Saw CSV header")
|
||||
var type_ headerPoolEnum = headerPoolTag
|
||||
var type_ = headerPoolTag
|
||||
switch hl {
|
||||
case "city":
|
||||
type_ = headerPoolAddressLocality
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func createLabelStudioClient() (*labelstudio.Client, error) {
|
|||
// Get and store the access token
|
||||
err := labelStudioClient.GetAccessToken()
|
||||
if err != nil {
|
||||
return nil, errors.New(fmt.Sprintf("Failed to get access token: %v", err))
|
||||
return nil, fmt.Errorf("Failed to get access token: %v", err)
|
||||
}
|
||||
log.Println("Got label studio client access token")
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ func createTask(client *labelstudio.Client, project *labelstudio.Project, minioC
|
|||
return fmt.Errorf("Failed to upload audio: %v", err)
|
||||
}
|
||||
}
|
||||
var transcription string = ""
|
||||
var transcription = ""
|
||||
//if note.Transcription.IsValue() {
|
||||
//transcription = note.Transcription.MustGet()
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ import (
|
|||
"github.com/Gleipnir-Technology/bob/dialect/psql/um"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/public/model"
|
||||
modelpublicreport "github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
querypublic "github.com/Gleipnir-Technology/nidus-sync/db/query/public"
|
||||
querypublicreport "github.com/Gleipnir-Technology/nidus-sync/db/query/publicreport"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/platform/background"
|
||||
|
|
@ -214,6 +218,9 @@ func PublicReportReporterUpdated(ctx context.Context, org_id int32, report_id st
|
|||
func PublicReportsForOrganization(ctx context.Context, org_id int32, is_public bool) ([]*types.PublicReport, error) {
|
||||
return publicreport.ReportsForOrganization(ctx, org_id, is_public)
|
||||
}
|
||||
func PublicReportsFromIDs(ctx context.Context, report_ids []int64) ([]*modelpublicreport.Report, error) {
|
||||
return querypublicreport.PublicReportsFromIDs(ctx, report_ids)
|
||||
}
|
||||
func PublicReportComplianceCreate(ctx context.Context, setter_report models.PublicreportReportSetter, setter_compliance models.PublicreportComplianceSetter, org_id int32) (*models.PublicreportReport, error) {
|
||||
return publicReportCreate(ctx, setter_report, nil, nil, nil, org_id, func(ctx context.Context, txn bob.Executor, report_id int32) error {
|
||||
setter_compliance.ReportID = omit.From(report_id)
|
||||
|
|
@ -382,6 +389,15 @@ func publicReportCreate(ctx context.Context, setter_report models.PublicreportRe
|
|||
UserID: omitnull.FromPtr[int32](nil),
|
||||
}).One(ctx, txn)
|
||||
|
||||
comm := &model.Communication{
|
||||
SourceReportID: &result.ID,
|
||||
}
|
||||
comm, err = querypublic.CommunicationInsert(ctx, txn, comm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("insert communication: %w", err)
|
||||
}
|
||||
log.Debug().Int32("id", comm.ID).Msg("inserted new communication")
|
||||
|
||||
txn.Commit(ctx)
|
||||
|
||||
event.Created(
|
||||
|
|
|
|||
|
|
@ -59,9 +59,7 @@ func logEntriesByReportID(ctx context.Context, report_ids []int32, is_public boo
|
|||
if !ok {
|
||||
return results, fmt.Errorf("no text logs for %d", report_id)
|
||||
}
|
||||
for _, l := range logs {
|
||||
cur_logs = append(cur_logs, l)
|
||||
}
|
||||
cur_logs = append(cur_logs, logs...)
|
||||
results[report_id] = cur_logs
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,8 @@ func HandleTextMessage(ctx context.Context, source string, destination string, c
|
|||
if err != nil {
|
||||
return fmt.Errorf("Failed to get phone status")
|
||||
}
|
||||
is_visible_to_llm := true
|
||||
if status == enums.CommsPhonestatustypeUnconfirmed {
|
||||
is_visible_to_llm = false
|
||||
}
|
||||
is_visible_to_llm := !(status == enums.CommsPhonestatustypeUnconfirmed)
|
||||
|
||||
l, err := models.CommsTextLogs.Insert(&models.CommsTextLogSetter{
|
||||
//ID:
|
||||
Content: omit.From(content),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
|
@ -257,7 +256,7 @@ func toTemplateTrapsNearby(locations []sql.TrapLocationBySourceIDRow, trap_data
|
|||
for _, td := range trap_data {
|
||||
c, ok := count_by_trap_data_id[td.Globalid]
|
||||
if !ok {
|
||||
return results, errors.New(fmt.Sprintf("Failed to find trap count for %s", td.Globalid))
|
||||
return results, fmt.Errorf("Failed to find trap count for %s", td.Globalid)
|
||||
}
|
||||
loc_id := td.LocID
|
||||
count := &TrapCount{
|
||||
|
|
@ -278,7 +277,7 @@ func toTemplateTrapsNearby(locations []sql.TrapLocationBySourceIDRow, trap_data
|
|||
for _, location := range locations {
|
||||
counts, ok := counts_by_location_id[location.TrapLocationGlobalid]
|
||||
if !ok {
|
||||
return results, errors.New(fmt.Sprintf("Failed to find counts for %s", location.TrapLocationGlobalid))
|
||||
return results, fmt.Errorf("Failed to find counts for %s", location.TrapLocationGlobalid)
|
||||
}
|
||||
trap := TrapNearby{
|
||||
Counts: counts,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -25,14 +26,10 @@ func Communication(r *router) *communicationR {
|
|||
}
|
||||
|
||||
type communication struct {
|
||||
Created time.Time `json:"created"`
|
||||
ID string `json:"id"`
|
||||
PublicReport string `json:"public_report"`
|
||||
Source string `json:"source"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type communicationList struct {
|
||||
Communications []communication `json:"communications"`
|
||||
Created time.Time `json:"created"`
|
||||
ID int32 `json:"id"`
|
||||
Source string `json:"source"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func toImageURLs(m map[string][]uuid.UUID, id string) []string {
|
||||
|
|
@ -46,24 +43,61 @@ func toImageURLs(m map[string][]uuid.UUID, id string) []string {
|
|||
}
|
||||
return urls
|
||||
}
|
||||
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*communicationList, *nhttp.ErrorWithStatus) {
|
||||
reports, err := platform.PublicReportsForOrganization(ctx, user.Organization.ID, false)
|
||||
func (res *communicationR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]*communication, *nhttp.ErrorWithStatus) {
|
||||
comms, err := platform.CommunicationsForOrganization(ctx, int64(user.Organization.ID))
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("nuisance report query: %w", err)
|
||||
}
|
||||
comms := make([]communication, len(reports))
|
||||
for i, report := range reports {
|
||||
populateDistrictURI(report, res.router)
|
||||
populateReportURI(report, res.router, false)
|
||||
comms[i] = communication{
|
||||
Created: report.Created,
|
||||
ID: report.PublicID,
|
||||
PublicReport: report.URI,
|
||||
Type: "publicreport." + string(report.Type),
|
||||
report_ids := make([]int64, 0)
|
||||
for _, comm := range comms {
|
||||
if comm.SourceReportID != nil {
|
||||
report_ids = append(report_ids, int64(*comm.SourceReportID))
|
||||
}
|
||||
}
|
||||
_by_created := func(a, b communication) int {
|
||||
if a.Created == b.Created {
|
||||
public_reports, err := platform.PublicReportsFromIDs(ctx, report_ids)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("public reports from IDs: %w", err)
|
||||
}
|
||||
public_report_id_to_report := make(map[int32]*model.Report, 0)
|
||||
for _, pr := range public_reports {
|
||||
public_report_id_to_report[pr.ID] = pr
|
||||
}
|
||||
result := make([]*communication, len(comms))
|
||||
for i, comm := range comms {
|
||||
source_uri := "unknown"
|
||||
type_ := "unknown"
|
||||
if comm.SourceReportID != nil {
|
||||
public_report, ok := public_report_id_to_report[*comm.SourceReportID]
|
||||
if !ok {
|
||||
return nil, nhttp.NewError("lookup report id %d failed", comm.SourceReportID)
|
||||
}
|
||||
source_uri, err = reportURI(res.router, "", public_report.PublicID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("gen report URI: %w", err)
|
||||
}
|
||||
type_ = "publicreport." + public_report.ReportType.String()
|
||||
} else if comm.SourceEmailLogID != nil {
|
||||
source_uri, err = emailURI(res.router, *comm.SourceEmailLogID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("gen email URI: %w", err)
|
||||
}
|
||||
type_ = "email"
|
||||
} else if comm.SourceTextLogID != nil {
|
||||
source_uri, err = textURI(res.router, *comm.SourceTextLogID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("gen email URI: %w", err)
|
||||
}
|
||||
source_uri = "text"
|
||||
}
|
||||
result[i] = &communication{
|
||||
Created: comm.Created,
|
||||
ID: comm.ID,
|
||||
Source: source_uri,
|
||||
Type: type_,
|
||||
}
|
||||
}
|
||||
_by_created := func(a, b *communication) int {
|
||||
if a.Created.Equal(b.Created) {
|
||||
return 0
|
||||
} else if a.Created.Before(b.Created) {
|
||||
return 1
|
||||
|
|
@ -71,8 +105,13 @@ func (res *communicationR) List(ctx context.Context, r *http.Request, user platf
|
|||
return -1
|
||||
}
|
||||
}
|
||||
slices.SortFunc(comms, _by_created)
|
||||
return &communicationList{
|
||||
Communications: comms,
|
||||
}, nil
|
||||
slices.SortFunc(result, _by_created)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func reportURI(r *router, report_type string, public_id string) (string, error)
|
|||
case "water":
|
||||
route_name = "publicreport.water.ByIDGet"
|
||||
default:
|
||||
return "", fmt.Errorf("Unrecognized report type '%s'", report_type)
|
||||
route_name = "publicreport.ByIDGet"
|
||||
}
|
||||
uri, err := r.IDStrToURI(route_name, public_id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,8 @@ func (qp QueryParams) SortOrDefault(default_name string, ascending bool) (string
|
|||
if s == "" {
|
||||
return default_name, ascending
|
||||
}
|
||||
a := true
|
||||
if s[0] == '-' {
|
||||
a = false
|
||||
}
|
||||
a := !(s[0] == '-')
|
||||
|
||||
if s[0] == '+' || s[0] == '-' {
|
||||
s = s[1:]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ func (res *uploadR) Discard(ctx context.Context, r *http.Request, u platform.Use
|
|||
|
||||
func (res *uploadR) PoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) {
|
||||
// If the organization we're uploading to doesn't have a service area, we can't process the upload correctly
|
||||
if !(u.Organization.HasServiceArea() || u.Organization.IsCatchall()) {
|
||||
if !u.Organization.HasServiceArea() && !u.Organization.IsCatchall() {
|
||||
return "", nhttp.NewErrorStatus(http.StatusConflict, "Your organization does not yet have a service area")
|
||||
}
|
||||
if len(uploads) == 0 {
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.Us
|
|||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "user id conversion: %w", err)
|
||||
}
|
||||
user_changes := &models.UserSetter{}
|
||||
if !(user.HasRoot() || user.IsAccountOwner() || user.ID == user_id) {
|
||||
if !user.HasRoot() && !user.IsAccountOwner() && user.ID != user_id {
|
||||
return "", nhttp.NewForbidden("Only account owners can change other users")
|
||||
}
|
||||
if updates.Avatar.IsValue() {
|
||||
|
|
|
|||
|
|
@ -51,9 +51,7 @@ func main() {
|
|||
log.Printf("%d: %d meters, %d seconds, %s traffic delay", i, s.LengthInMeters, s.TravelTimeInSeconds, s.TrafficDelayInSeconds)
|
||||
for _, leg := range route.Legs {
|
||||
all_stops = append(all_stops, leg.Points[0])
|
||||
for _, p := range leg.Points {
|
||||
all_points = append(all_points, p)
|
||||
}
|
||||
all_points = append(all_points, leg.Points...)
|
||||
}
|
||||
}
|
||||
lines := tomtom.PolylineToGeoJSON(all_points)
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ type PointShort struct {
|
|||
}
|
||||
|
||||
func (ps PointShort) AsPoint() Point {
|
||||
return Point{
|
||||
Latitude: ps.Latitude,
|
||||
Longitude: ps.Longitude,
|
||||
}
|
||||
return Point(ps)
|
||||
}
|
||||
|
||||
type GeocodeResult struct {
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
}"
|
||||
@click="handleClick(comm.id)"
|
||||
>
|
||||
<ListCardPublicReport :comm="comm" />
|
||||
<ListCardCommunication :comm="comm" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import ListCardPublicReport from "@/components/ListCardPublicReport.vue";
|
||||
import ListCardCommunication from "@/components/ListCardCommunication.vue";
|
||||
import { Communication, LogEntry, PublicReport } from "@/type/api";
|
||||
|
||||
interface Props {
|
||||
|
|
|
|||
92
ts/components/ListCardCommunication.vue
Normal file
92
ts/components/ListCardCommunication.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<!-- First row: icon, type badge, and time -->
|
||||
<div class="justify-content-between align-items-center">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="col">
|
||||
<Tooltip placement="top" :title="tooltipTitleForCommunicationType()">
|
||||
<i class="bi fs-4 me-2" :class="iconForReportType()"></i>
|
||||
</Tooltip>
|
||||
<Tooltip placement="top" :title="tooltipTitleForReportType()">
|
||||
<i class="bi fs-4 me-2" :class="iconForCommunicationType()"></i>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<small>
|
||||
<Tooltip placement="top" :title="tooltipTitleForCreated()">
|
||||
<TimeRelative :time="comm.created" />
|
||||
</Tooltip>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TimeRelative from "@/components/TimeRelative.vue";
|
||||
import Tooltip from "@/components/Tooltip.vue";
|
||||
import { formatAddress, formatDate } from "@/format";
|
||||
import { Communication } from "@/type/api";
|
||||
interface Props {
|
||||
comm: Communication;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
function iconForCommunicationType(): string {
|
||||
switch (props.comm.type) {
|
||||
case "publicreport.compliance":
|
||||
return "bi-card-checklist";
|
||||
case "publicreport.nuisance":
|
||||
return "bi-mosquito";
|
||||
case "publicreport.water":
|
||||
return "bi-droplet-fill";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function iconForReportType(): string {
|
||||
switch (props.comm.type) {
|
||||
case "publicreport.compliance":
|
||||
case "publicreport.nuisance":
|
||||
case "publicreport.water":
|
||||
return "bi-postcard";
|
||||
case "email":
|
||||
return "bi-envelope";
|
||||
case "text":
|
||||
return "bi-chat-dots";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function tooltipTitleForCommunicationType(): string {
|
||||
switch (props.comm.type) {
|
||||
case "publicreport.compliance":
|
||||
case "publicreport.nuisance":
|
||||
case "publicreport.water":
|
||||
return "A report made from a member of the public to report.mosquitoes.online";
|
||||
case "email":
|
||||
return "An email received from a member of the public";
|
||||
case "text":
|
||||
return "An SMS/MMS text message received from a member of the public";
|
||||
default:
|
||||
return "I'm actually not sure what this is. How are you even seeing this?";
|
||||
}
|
||||
}
|
||||
function tooltipTitleForReportType(): string {
|
||||
switch (props.comm.type) {
|
||||
case "publicreport.compliance":
|
||||
case "publicreport.nuisance":
|
||||
case "publicreport.water":
|
||||
return "A compliance report either made by scanning a door hanger or by receiving a personal letter through the mail";
|
||||
case "publicreport.nuisance":
|
||||
return "A report of a mosquito nuisance";
|
||||
case "publicreport.water":
|
||||
return "A report of standing water";
|
||||
default:
|
||||
return "I'm actually not sure what this is. This shouldn't be possible.";
|
||||
}
|
||||
}
|
||||
function tooltipTitleForCreated(): string {
|
||||
return `or at exactly ${formatDate(props.comm.created)}`;
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<!-- First row: icon, type badge, and time -->
|
||||
<div class="justify-content-between align-items-center">
|
||||
<div class="row">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="col">
|
||||
<i class="bi fs-4 me-2" :class="iconForType()"></i>
|
||||
</div>
|
||||
<div class="col-6 text-end">
|
||||
<span class="badge" :class="colorForType()">
|
||||
{{ titleForType() }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<small>
|
||||
<TimeRelative :time="comm.created" />
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TimeRelative from "@/components/TimeRelative.vue";
|
||||
import { formatAddress } from "@/format";
|
||||
import { Communication } from "@/type/api";
|
||||
interface Props {
|
||||
comm: Communication;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
function colorForType(): string {
|
||||
if (props.comm.type == "publicreport.compliance") {
|
||||
return "bg-secondary";
|
||||
} else if (props.comm.type == "publicreport.nuisance") {
|
||||
return "bg-danger";
|
||||
} else if (props.comm.type == "publicreport.water") {
|
||||
return "bg-info";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function iconForType(): string {
|
||||
if (props.comm.type == "publicreport.compliance") {
|
||||
return "bi-postcard";
|
||||
} else if (props.comm.type == "publicreport.nuisance") {
|
||||
return "bi-mosquito";
|
||||
} else if (props.comm.type == "publicreport.water") {
|
||||
return "bi-droplet-fill";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function titleForType(): string {
|
||||
if (props.comm.type == "publicreport.compliance") {
|
||||
return "Compliance";
|
||||
} else if (props.comm.type == "publicreport.nuisance") {
|
||||
return "Nuisance";
|
||||
} else if (props.comm.type == "publicreport.water") {
|
||||
return "Standing Water";
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -29,12 +29,14 @@ export function formatBigNumber(n: number): string {
|
|||
return result;
|
||||
}
|
||||
export function formatDate(date: Date): string {
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
timeZoneName: "short",
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,12 +33,10 @@ export const useCommunicationStore = defineStore("communication", () => {
|
|||
//if (typeFilter.value) params.append("type", typeFilter.value);
|
||||
|
||||
const url = `${session.urls.api.communication}?${params}`;
|
||||
const data = await apiClient.JSONGet(url);
|
||||
const data = (await apiClient.JSONGet(url)) as CommunicationDTO[];
|
||||
|
||||
all.value = data.communications.map((c: CommunicationDTO) =>
|
||||
Communication.fromJSON(c),
|
||||
);
|
||||
return data.communications;
|
||||
all.value = data.map((c: CommunicationDTO) => Communication.fromJSON(c));
|
||||
return all.value;
|
||||
} catch (err) {
|
||||
console.error("Error loading communications:", err);
|
||||
throw err;
|
||||
|
|
|
|||
|
|
@ -523,22 +523,22 @@ export class PublicReportWater extends PublicReport {
|
|||
export interface CommunicationDTO {
|
||||
created: string;
|
||||
id: string;
|
||||
public_report?: string;
|
||||
source: string;
|
||||
type: string;
|
||||
}
|
||||
export class Communication {
|
||||
constructor(
|
||||
public created: Date,
|
||||
public id: string,
|
||||
public source: string,
|
||||
public type: string,
|
||||
public public_report?: string,
|
||||
) {}
|
||||
static fromJSON(json: CommunicationDTO): Communication {
|
||||
return new Communication(
|
||||
new Date(json.created),
|
||||
json.id,
|
||||
json.source,
|
||||
json.type,
|
||||
json.public_report,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ const currentImage = computed(() => {
|
|||
});
|
||||
const currentImages = computed(() => {
|
||||
const comm = selectedCommunication.value;
|
||||
if (comm == null || comm.public_report == null) {
|
||||
if (comm == null) {
|
||||
return [];
|
||||
}
|
||||
return selectedReport.value?.images ?? [];
|
||||
|
|
@ -181,13 +181,12 @@ const selectedReport = computedAsync(
|
|||
async (): Promise<PublicReport | undefined> => {
|
||||
if (
|
||||
!(
|
||||
selectedCommunication.value && selectedCommunication.value.public_report
|
||||
selectedCommunication.value &&
|
||||
selectedCommunication.value.type != "publicreport"
|
||||
)
|
||||
)
|
||||
return;
|
||||
return await storePublicReport.fetchByURI(
|
||||
selectedCommunication.value.public_report,
|
||||
);
|
||||
return await storePublicReport.byURI(selectedCommunication.value.source);
|
||||
},
|
||||
);
|
||||
const handleDeselect = (id: string) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue