2026-04-09 23:38:20 +00:00
|
|
|
package resource
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"net/http"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
2026-04-10 16:59:29 +00:00
|
|
|
"github.com/aarondl/opt/omit"
|
|
|
|
|
"github.com/aarondl/opt/omitnull"
|
|
|
|
|
//"github.com/Gleipnir-Technology/nidus-sync/html"
|
2026-04-09 23:38:20 +00:00
|
|
|
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
2026-04-12 18:33:41 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
2026-04-14 14:38:22 +00:00
|
|
|
"github.com/google/uuid"
|
2026-04-12 18:33:41 +00:00
|
|
|
"github.com/gorilla/mux"
|
2026-04-13 20:42:03 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2026-04-09 23:38:20 +00:00
|
|
|
)
|
|
|
|
|
|
2026-04-16 10:15:28 +00:00
|
|
|
func PublicReportCompliance(r *router) *complianceR {
|
2026-04-09 23:38:20 +00:00
|
|
|
return &complianceR{
|
|
|
|
|
router: r,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type complianceR struct {
|
|
|
|
|
router *router
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 16:21:08 +00:00
|
|
|
type publicreportComplianceForm struct {
|
|
|
|
|
AccessInstructions omit.Val[string] `schema:"access_instructions" json:"access_instructions"`
|
|
|
|
|
Address omit.Val[types.Address] `schema:"address" json:"address"`
|
|
|
|
|
AvailabilityNotes omit.Val[string] `schema:"availability_notes" json:"availability_notes"`
|
|
|
|
|
ClientID uuid.UUID `schema:"client_id" json:"client_id"`
|
|
|
|
|
Comments omit.Val[string] `schema:"comments" json:"comments"`
|
|
|
|
|
District omit.Val[string] `schema:"district" json:"district"`
|
|
|
|
|
GateCode omit.Val[string] `schema:"gate_code" json:"gate_code"`
|
|
|
|
|
HasDog omitnull.Val[bool] `schema:"has_dog" json:"has_dog"`
|
|
|
|
|
Location omit.Val[types.Location] `schema:"location" json:"location"`
|
2026-04-21 14:35:13 +00:00
|
|
|
MailerID omit.Val[string] `schema:"mailer_id" json:"mailer_id"`
|
2026-04-20 16:21:08 +00:00
|
|
|
PermissionType omit.Val[enums.Permissionaccesstype] `schema:"permission_type" json:"permission_type"`
|
|
|
|
|
Reporter omit.Val[types.Contact] `schema:"reporter" json:"reporter"`
|
|
|
|
|
ReportPhoneCanSMS omitnull.Val[bool] `schema:"report_phone_can_text" json:"report_phone_can_text"`
|
|
|
|
|
WantsScheduled omitnull.Val[bool] `schema:"wants_scheduled" json:"wants_scheduled"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 18:33:41 +00:00
|
|
|
func (res *complianceR) ByID(ctx context.Context, r *http.Request, query QueryParams) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
public_id := vars["id"]
|
|
|
|
|
if public_id == "" {
|
|
|
|
|
return nil, nhttp.NewBadRequest("You must provid an ID")
|
|
|
|
|
}
|
|
|
|
|
report, err := platform.PublicreportByIDCompliance(ctx, public_id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("get report: %w", err)
|
|
|
|
|
}
|
2026-04-21 21:35:40 +00:00
|
|
|
return res.complianceHydrate(report)
|
2026-04-12 18:33:41 +00:00
|
|
|
}
|
2026-04-21 15:01:01 +00:00
|
|
|
func (res *complianceR) Create(ctx context.Context, r *http.Request, n publicreportComplianceForm) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
|
2026-04-22 19:54:06 +00:00
|
|
|
if n.District.IsUnset() && n.MailerID.IsUnset() {
|
|
|
|
|
return nil, nhttp.NewBadRequest("You must provide a district_id or mailer_id")
|
2026-04-20 16:21:08 +00:00
|
|
|
}
|
2026-04-22 19:54:06 +00:00
|
|
|
var district_id int32
|
|
|
|
|
var err error
|
|
|
|
|
var public_id string
|
|
|
|
|
if n.District.IsValue() {
|
|
|
|
|
district_str := n.District.MustGet()
|
|
|
|
|
var district_id_ptr *int
|
|
|
|
|
district_id_ptr, err = res.router.IDFromURI("district.ByIDGet", district_str)
|
|
|
|
|
if err != nil || district_id_ptr == nil {
|
|
|
|
|
return nil, nhttp.NewBadRequest("parse district ID: %w", err)
|
|
|
|
|
}
|
|
|
|
|
district_id = int32(*district_id_ptr)
|
|
|
|
|
public_id, err = platform.GenerateReportID()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("generate public ID: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if n.MailerID.IsValue() {
|
|
|
|
|
public_id = n.MailerID.MustGet()
|
|
|
|
|
org_id, err := platform.OrganizationIDForComplianceReportRequest(ctx, public_id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewBadRequest("no such mailer")
|
|
|
|
|
}
|
|
|
|
|
district_id = org_id
|
2026-04-20 16:21:08 +00:00
|
|
|
}
|
2026-04-14 14:38:22 +00:00
|
|
|
user_agent := r.Header.Get("User-Agent")
|
2026-04-20 16:21:08 +00:00
|
|
|
err = platform.EnsureClient(ctx, n.ClientID, user_agent)
|
2026-04-14 15:11:07 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("Failed to ensure client: %w", err)
|
|
|
|
|
}
|
2026-04-09 23:38:20 +00:00
|
|
|
setter_report := models.PublicreportReportSetter{
|
|
|
|
|
//AddressID: omitnull.From(latlng.Cell.String()),
|
2026-04-12 18:33:41 +00:00
|
|
|
AddressGid: omit.From(""),
|
|
|
|
|
AddressRaw: omit.From(""),
|
2026-04-14 14:38:22 +00:00
|
|
|
ClientUUID: omitnull.From(n.ClientID),
|
2026-04-12 18:33:41 +00:00
|
|
|
Created: omit.From(time.Now()),
|
2026-04-09 23:38:20 +00:00
|
|
|
//H3cell: omitnull.From(latlng.Cell.String()),
|
|
|
|
|
LatlngAccuracyType: omit.From(enums.PublicreportAccuracytypeBrowser),
|
|
|
|
|
LatlngAccuracyValue: omit.From(float32(0.0)),
|
|
|
|
|
//Location: omitnull.From(fmt.Sprintf("ST_GeometryFromText(Point(%s %s))", longitude, latitude)),
|
2026-04-22 19:54:06 +00:00
|
|
|
Location: omitnull.FromPtr[string](nil),
|
|
|
|
|
MapZoom: omit.From(float32(0.0)),
|
|
|
|
|
OrganizationID: omit.From[int32](district_id),
|
|
|
|
|
PublicID: omit.From(public_id),
|
2026-04-13 22:07:56 +00:00
|
|
|
ReporterEmail: omit.From(""),
|
|
|
|
|
ReporterName: omit.From(""),
|
|
|
|
|
ReporterPhone: omit.From(""),
|
2026-04-14 01:21:50 +00:00
|
|
|
ReporterPhoneCanSMS: omit.From(true),
|
2026-04-13 22:07:56 +00:00
|
|
|
ReportType: omit.From(enums.PublicreportReporttypeCompliance),
|
|
|
|
|
Status: omit.From(enums.PublicreportReportstatustypeReported),
|
2026-04-09 23:38:20 +00:00
|
|
|
}
|
|
|
|
|
setter_compliance := models.PublicreportComplianceSetter{
|
|
|
|
|
AccessInstructions: omit.From(""),
|
|
|
|
|
AvailabilityNotes: omit.From(""),
|
|
|
|
|
Comments: omit.From(""),
|
|
|
|
|
GateCode: omit.From(""),
|
|
|
|
|
HasDog: omitnull.FromPtr[bool](nil),
|
|
|
|
|
PermissionType: omit.From(enums.PermissionaccesstypeUnselected),
|
|
|
|
|
//ReportID omit.Val[int32]
|
2026-04-13 22:07:56 +00:00
|
|
|
WantsScheduled: omitnull.FromPtr[bool](nil),
|
2026-04-09 23:38:20 +00:00
|
|
|
}
|
2026-04-22 19:54:06 +00:00
|
|
|
report, err := platform.PublicReportComplianceCreate(ctx, setter_report, setter_compliance, district_id)
|
2026-04-09 23:38:20 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("create compliance report: %w", err)
|
|
|
|
|
}
|
2026-04-21 15:01:01 +00:00
|
|
|
// Return a fully-fleshed-out report object, even though it's a bit more expensive
|
|
|
|
|
result, err := platform.PublicreportByIDCompliance(ctx, report.PublicID)
|
2026-04-09 23:38:20 +00:00
|
|
|
if err != nil {
|
2026-04-21 15:01:01 +00:00
|
|
|
return nil, nhttp.NewError("get report after creation: %w", err)
|
2026-04-09 23:38:20 +00:00
|
|
|
}
|
2026-04-21 21:35:40 +00:00
|
|
|
return res.complianceHydrate(result)
|
2026-04-09 23:38:20 +00:00
|
|
|
}
|
2026-04-13 19:32:22 +00:00
|
|
|
func (res *complianceR) Update(ctx context.Context, r *http.Request, prf publicreportComplianceForm) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
|
|
|
|
|
vars := mux.Vars(r)
|
|
|
|
|
public_id := vars["id"]
|
|
|
|
|
if public_id == "" {
|
|
|
|
|
return nil, nhttp.NewBadRequest("You must provide an ID")
|
|
|
|
|
}
|
|
|
|
|
report_setter := models.PublicreportReportSetter{}
|
2026-04-13 22:07:56 +00:00
|
|
|
compliance_setter := models.PublicreportComplianceSetter{}
|
2026-04-13 20:42:03 +00:00
|
|
|
var location *types.Location
|
|
|
|
|
if prf.Location.IsValue() {
|
|
|
|
|
l := prf.Location.MustGet()
|
|
|
|
|
location = &l
|
|
|
|
|
if location.Accuracy != nil {
|
|
|
|
|
report_setter.LatlngAccuracyValue = omit.From(*location.Accuracy)
|
2026-04-13 19:32:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-13 20:42:03 +00:00
|
|
|
if prf.Reporter.IsValue() {
|
|
|
|
|
reporter := prf.Reporter.MustGet()
|
|
|
|
|
if reporter.Email != nil {
|
|
|
|
|
report_setter.ReporterEmail = omit.From(*reporter.Email)
|
2026-04-13 19:32:22 +00:00
|
|
|
}
|
2026-04-13 20:42:03 +00:00
|
|
|
if reporter.Name != nil {
|
|
|
|
|
report_setter.ReporterName = omit.From(*reporter.Name)
|
2026-04-13 19:32:22 +00:00
|
|
|
}
|
2026-04-13 20:42:03 +00:00
|
|
|
if reporter.Phone != nil {
|
|
|
|
|
report_setter.ReporterPhone = omit.From(*reporter.Phone)
|
2026-04-13 19:32:22 +00:00
|
|
|
}
|
2026-04-13 22:07:56 +00:00
|
|
|
if reporter.CanSMS != nil {
|
|
|
|
|
report_setter.ReporterPhoneCanSMS = omit.FromPtr(reporter.CanSMS)
|
|
|
|
|
}
|
2026-04-13 19:32:22 +00:00
|
|
|
}
|
2026-04-13 20:42:03 +00:00
|
|
|
var address *types.Address
|
|
|
|
|
if prf.Address.IsValue() {
|
|
|
|
|
a := prf.Address.MustGet()
|
|
|
|
|
address = &a
|
|
|
|
|
}
|
|
|
|
|
if prf.AccessInstructions.IsValue() {
|
|
|
|
|
compliance_setter.AccessInstructions = prf.AccessInstructions
|
|
|
|
|
}
|
|
|
|
|
if prf.AvailabilityNotes.IsValue() {
|
|
|
|
|
compliance_setter.AvailabilityNotes = prf.AvailabilityNotes
|
|
|
|
|
}
|
|
|
|
|
if prf.Comments.IsValue() {
|
|
|
|
|
compliance_setter.Comments = prf.Comments
|
|
|
|
|
}
|
|
|
|
|
if prf.GateCode.IsValue() {
|
|
|
|
|
compliance_setter.GateCode = prf.GateCode
|
|
|
|
|
}
|
|
|
|
|
if prf.HasDog.IsValue() {
|
|
|
|
|
compliance_setter.HasDog = prf.HasDog
|
|
|
|
|
}
|
|
|
|
|
if prf.PermissionType.IsValue() {
|
|
|
|
|
compliance_setter.PermissionType = prf.PermissionType
|
|
|
|
|
}
|
|
|
|
|
if prf.WantsScheduled.IsValue() {
|
|
|
|
|
compliance_setter.WantsScheduled = prf.WantsScheduled
|
|
|
|
|
}
|
|
|
|
|
log.Debug().
|
|
|
|
|
Bool("access_instructions", prf.AccessInstructions.IsValue()).
|
|
|
|
|
Bool("access_instructions", prf.AccessInstructions.IsValue()).
|
|
|
|
|
Bool("access_instructions", prf.AccessInstructions.IsValue()).
|
|
|
|
|
Bool("access_instructions", prf.AccessInstructions.IsValue()).
|
|
|
|
|
Msg("updating compliance")
|
|
|
|
|
report, err := platform.PublicReportUpdateCompliance(ctx, public_id, &report_setter, &compliance_setter, address, location)
|
2026-04-13 19:32:22 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("platform update report compliance: %w", err)
|
|
|
|
|
}
|
2026-04-14 02:38:36 +00:00
|
|
|
// Return a fully-fleshed-out report object, even though it's a bit more expensive
|
|
|
|
|
report, err = platform.PublicreportByIDCompliance(ctx, public_id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nhttp.NewError("get report after update: %w", err)
|
|
|
|
|
}
|
2026-04-21 21:35:40 +00:00
|
|
|
return res.complianceHydrate(report)
|
|
|
|
|
}
|
|
|
|
|
func (res *complianceR) complianceHydrate(report *types.PublicReportCompliance) (*types.PublicReportCompliance, *nhttp.ErrorWithStatus) {
|
|
|
|
|
populateDistrictURI(&report.PublicReport, res.router)
|
|
|
|
|
populateReportURI(&report.PublicReport, res.router)
|
2026-04-22 21:22:03 +00:00
|
|
|
for _, e := range report.Concerns {
|
2026-04-21 21:35:40 +00:00
|
|
|
e.PopulateURL(res.router.router)
|
|
|
|
|
}
|
2026-04-13 19:32:22 +00:00
|
|
|
return report, nil
|
|
|
|
|
}
|