Move review actions into the platform, emit events on change
Still not seeing updates in the sidebar, however.
This commit is contained in:
parent
954a4330ee
commit
ee61b6d24b
6 changed files with 181 additions and 133 deletions
143
api/review.go
143
api/review.go
|
|
@ -2,147 +2,28 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"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"`
|
||||
Status string `json:"status"`
|
||||
TaskID int32 `json:"task_id"`
|
||||
Updates *platform.PoolUpdate `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)
|
||||
_, err := platform.ReviewPoolCreate(ctx, user, req.TaskID, req.Status, req.Updates)
|
||||
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("start txn: %w", err)
|
||||
if errors.As(err, &platform.ErrorNotFound{}) {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusNotFound, "review task %d not found", req.TaskID)
|
||||
}
|
||||
return nil, nhttp.NewError("failed to set review: %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
|
||||
return &createdReviewPool{}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@
|
|||
class="position-absolute translate-middle badge rounded-pill bg-primary"
|
||||
>
|
||||
<span
|
||||
x-text="notification_counts.reviwe > 99 ? '99+' : notification_counts.review"
|
||||
x-text="notification_counts.review > 99 ? '99+' : notification_counts.review"
|
||||
></span>
|
||||
<span class="visually-hidden">unread notifications</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<script>
|
||||
const USER = {{ .User.AsJSON|json }};
|
||||
SSEManager.subscribe("*", function (e) {
|
||||
if (e.type == "created" && e.resource.startsWith("rmo:")) {
|
||||
if (e.type != "heartbeat") {
|
||||
updateUserState();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
14
platform/error.go
Normal file
14
platform/error.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrorNotFound struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e ErrorNotFound) Error() string { return fmt.Sprintf("not found: %s", e.message) }
|
||||
func newNotFound(format string, m ...any) error {
|
||||
return &ErrorNotFound{message: fmt.Sprintf(format, m...)}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package event
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
|
|
@ -14,6 +15,16 @@ type Event struct {
|
|||
Type EventType `json:"type"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func (e Event) MarshalJSON() ([]byte, error) {
|
||||
to_marshal := make(map[string]any, 0)
|
||||
to_marshal["resource"] = e.Resource
|
||||
to_marshal["time"] = e.Time
|
||||
to_marshal["type"] = e.Type.String()
|
||||
to_marshal["uri"] = e.URI
|
||||
return json.Marshal(to_marshal)
|
||||
}
|
||||
|
||||
type Envelope struct {
|
||||
OrganizationID int32
|
||||
Event Event
|
||||
|
|
@ -73,6 +84,7 @@ type ResourceType int
|
|||
const (
|
||||
TypeUnknown = iota
|
||||
TypeFileCSV
|
||||
TypeReviewTask
|
||||
TypeRMONuisance
|
||||
TypeRMOReport
|
||||
TypeRMOWater
|
||||
|
|
@ -107,6 +119,8 @@ func resourceString(t ResourceType) string {
|
|||
switch t {
|
||||
case TypeFileCSV:
|
||||
return "sync:filecsv"
|
||||
case TypeReviewTask:
|
||||
return "sync:review_task"
|
||||
case TypeRMONuisance:
|
||||
return "rmo:nuisance"
|
||||
case TypeRMOReport:
|
||||
|
|
@ -121,6 +135,8 @@ func makeURI(t ResourceType, id string) string {
|
|||
switch t {
|
||||
case TypeFileCSV:
|
||||
return config.MakeURLNidus("/upload/%s", id)
|
||||
case TypeReviewTask:
|
||||
return config.MakeURLNidus("/review/%s", id)
|
||||
case TypeRMONuisance:
|
||||
return config.MakeURLReport("/report/%s", id)
|
||||
case TypeRMOWater:
|
||||
|
|
|
|||
137
platform/review.go
Normal file
137
platform/review.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"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/event"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type PoolUpdate struct {
|
||||
Condition *string `json:"condition"`
|
||||
Latitude *float32 `json:"latitude"`
|
||||
Longitude *float32 `json:"longitude"`
|
||||
}
|
||||
|
||||
func ReviewPoolCreate(ctx context.Context, user User, task_id int32, status string, update *PoolUpdate) (int32, error) {
|
||||
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return 0, nhttp.NewError("start txn: %w", err)
|
||||
}
|
||||
defer txn.Rollback(ctx)
|
||||
review_task, err := models.ReviewTasks.Query(
|
||||
models.SelectWhere.ReviewTasks.ID.EQ(task_id),
|
||||
models.SelectWhere.ReviewTasks.OrganizationID.EQ(user.Organization.ID()),
|
||||
).One(ctx, txn)
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
return 0, newNotFound("review task %d", task_id)
|
||||
}
|
||||
return 0, fmt.Errorf("find review task: %d", task_id)
|
||||
}
|
||||
var resolution enums.Reviewtaskresolutiontype
|
||||
err = resolution.Scan(status)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("status '%s' is not recognized: %w", status, err)
|
||||
}
|
||||
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)
|
||||
switch status {
|
||||
case "discarded":
|
||||
// Nothing to do, we already discarded it above
|
||||
case "committed":
|
||||
err = commitReviewPool(ctx, txn, user, review_task_pool, update)
|
||||
default:
|
||||
return 0, nhttp.NewErrorStatus(http.StatusBadRequest, "unrecognized status %s", status)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
event.Updated(event.TypeReviewTask, user.Organization.ID(), strconv.Itoa(int(review_task.ID)))
|
||||
txn.Commit(ctx)
|
||||
log.Info().Int32("id", review_task.ID).Str("status", status).Msg("review completed")
|
||||
return review_task.ID, err
|
||||
}
|
||||
func commitReviewPool(ctx context.Context, txn bob.Tx, user User, review_task_pool *models.ReviewTaskPool, update *PoolUpdate) error {
|
||||
if update == nil {
|
||||
return nil
|
||||
}
|
||||
feature_pool, err := models.FindFeaturePool(ctx, txn, review_task_pool.FeaturePoolID)
|
||||
if err != nil {
|
||||
return nhttp.NewError("find feature pool: %w", err)
|
||||
}
|
||||
if update.Condition != nil {
|
||||
var condition enums.Poolconditiontype
|
||||
err := condition.Scan(*update.Condition)
|
||||
if err != nil {
|
||||
return nhttp.NewErrorStatus(http.StatusBadRequest, "unrecognized condition %s", update.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 update.Latitude != nil || update.Longitude != nil {
|
||||
if update.Latitude == nil || update.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(update.Longitude),
|
||||
psql.Arg(update.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(*update.Longitude),
|
||||
psql.Arg(*update.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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue