nidus-sync/api/review.go
Eli Ribble 44c4f17f32
Massive rework of platform layer user/organization
The goal of this rework is to make it so I can pass around platform.User
instead of a pair of models.Organization and models.User. This is useful
for reason I kind of forget now, but it started with working on
notifications and ballooned massively from there into refactoring a
number of things that were bugging me.

This also includes a tiny amount of work on server-side events (SSE).

 * background stuff lives inside the platform now, which I need for
   having it push updates through SSE
 * userfile now lives in the platform, under file, so other platform
   functions can safely use it
 * oauth is broken into pieces and inside platform because other stuff
   was calling it already, but badly.
 * notifications go into the platform as well
2026-03-12 23:49:16 +00:00

148 lines
4.9 KiB
Go

package api
import (
"context"
"net/http"
"time"
"github.com/Gleipnir-Technology/bob"
"github.com/Gleipnir-Technology/bob/dialect/psql"
"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/models"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/rs/zerolog/log"
/*
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
"github.com/Gleipnir-Technology/nidus-sync/platform/geom"
"github.com/aarondl/opt/omit"
"github.com/aarondl/opt/omitnull"
"github.com/stephenafamo/scan"
*/)
type reviewPoolUpdate struct {
Condition *string `json:"condition"`
Latitude *float32 `json:"latitude"`
Longitude *float32 `json:"longitude"`
}
type createReviewPool struct {
Status string `json:"status"`
TaskID int32 `json:"task_id"`
Updates *reviewPoolUpdate `json:"updates"`
}
type createdReviewPool struct{}
func postReviewPool(ctx context.Context, r *http.Request, user platform.User, req createReviewPool) (*createdReviewPool, *nhttp.ErrorWithStatus) {
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
if err != nil {
return nil, nhttp.NewError("start txn: %w", err)
}
defer txn.Rollback(ctx)
review_task, err := models.ReviewTasks.Query(
models.SelectWhere.ReviewTasks.ID.EQ(req.TaskID),
models.SelectWhere.ReviewTasks.OrganizationID.EQ(user.Organization.ID()),
).One(ctx, txn)
if err != nil {
return nil, nhttp.NewErrorStatus(http.StatusNotFound, "review task %d not found", req.TaskID)
}
var resolution enums.Reviewtaskresolutiontype
err = resolution.Scan(req.Status)
if err != nil {
return nil, nhttp.NewErrorStatus(http.StatusNotFound, "status '%s' is not recognized", req.Status)
}
review_task.Update(ctx, txn, &models.ReviewTaskSetter{
Resolution: omitnull.From(resolution),
Reviewed: omitnull.From(time.Now()),
ReviewerID: omitnull.From(int32(user.ID)),
})
review_task_pool, err := models.ReviewTaskPools.Query(
models.SelectWhere.ReviewTaskPools.ReviewTaskID.EQ(review_task.ID),
).One(ctx, txn)
var e *nhttp.ErrorWithStatus
switch req.Status {
case "discarded":
e = discardReviewPool(ctx, txn, user, req, review_task_pool)
case "committed":
e = commitReviewPool(ctx, txn, user, req, review_task_pool)
default:
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "unrecognized status %s", req.Status)
}
if e != nil {
return nil, e
}
txn.Commit(ctx)
log.Info().Int32("id", review_task.ID).Str("status", req.Status).Msg("committed")
return &createdReviewPool{}, e
}
func discardReviewPool(ctx context.Context, txn bob.Tx, user platform.User, req createReviewPool, review_task_pool *models.ReviewTaskPool) *nhttp.ErrorWithStatus {
return nil
}
func commitReviewPool(ctx context.Context, txn bob.Tx, user platform.User, req createReviewPool, review_task_pool *models.ReviewTaskPool) *nhttp.ErrorWithStatus {
if req.Updates == nil {
return nil
}
up := *req.Updates
feature_pool, err := models.FindFeaturePool(ctx, txn, review_task_pool.FeaturePoolID)
if err != nil {
return nhttp.NewError("find feature pool: %w", err)
}
if up.Condition != nil {
var condition enums.Poolconditiontype
err := condition.Scan(*up.Condition)
if err != nil {
return nhttp.NewErrorStatus(http.StatusBadRequest, "unrecognized condition %s", up.Condition)
}
err = review_task_pool.Update(ctx, txn, &models.ReviewTaskPoolSetter{
Condition: omitnull.From(condition),
})
if err != nil {
return nhttp.NewError("update rewiew task: %w", err)
}
err = feature_pool.Update(ctx, txn, &models.FeaturePoolSetter{
Condition: omit.From(condition),
})
if err != nil {
return nhttp.NewError("update feature_pool: %w", err)
}
}
if up.Latitude != nil || up.Longitude != nil {
if up.Latitude == nil || up.Longitude == nil {
return nhttp.NewErrorStatus(http.StatusBadRequest, "you have to specify lat and lng together")
}
_, err = psql.Update(
um.Table("review_task_pool"),
um.SetCol("location").To(
psql.F("ST_SetSRID",
psql.F("ST_MakePoint",
psql.Arg(*up.Longitude),
psql.Arg(*up.Latitude),
), psql.Arg(4326),
),
),
um.Where(psql.Quote("review_task_pool", "review_task_id").EQ(psql.Arg(review_task_pool.ReviewTaskID))),
).Exec(ctx, txn)
if err != nil {
return nhttp.NewError("save task: %w", err)
}
_, err = psql.Update(
um.Table("feature"),
um.SetCol("location").To(
psql.F("ST_SetSRID",
psql.F("ST_MakePoint",
psql.Arg(*up.Longitude),
psql.Arg(*up.Latitude),
), psql.Arg(4326),
),
),
um.Where(psql.Quote("feature", "id").EQ(psql.Arg(review_task_pool.FeaturePoolID))),
).Exec(ctx, txn)
if err != nil {
return nhttp.NewError("save feature: %w", err)
}
}
return nil
}