Move QueryParams to resource module

This commit is contained in:
Eli Ribble 2026-04-01 18:12:46 +00:00
parent ab519020fc
commit a656d45a6d
No known key found for this signature in database
16 changed files with 641 additions and 583 deletions

View file

@ -1,67 +1 @@
package api
import (
"context"
"net/http"
"slices"
"time"
"github.com/Gleipnir-Technology/nidus-sync/config"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/publicreport"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
"github.com/google/uuid"
//"github.com/rs/zerolog/log"
)
type communication struct {
Created time.Time `json:"created"`
ID string `json:"id"`
PublicReport types.PublicReport `json:"public_report"`
Type string `json:"type"`
}
type contentListCommunication struct {
Communications []communication `json:"communications"`
}
func listCommunication(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListCommunication, *nhttp.ErrorWithStatus) {
reports, err := publicreport.ReportsForOrganization(ctx, 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 {
comms[i] = communication{
Created: report.Created,
ID: report.PublicID,
PublicReport: report,
Type: "publicreport." + string(report.Type),
}
}
_by_created := func(a, b communication) int {
if a.Created == b.Created {
return 0
} else if a.Created.Before(b.Created) {
return 1
} else {
return -1
}
}
slices.SortFunc(comms, _by_created)
return &contentListCommunication{
Communications: comms,
}, nil
}
func toImageURLs(m map[string][]uuid.UUID, id string) []string {
uuids, ok := m[id]
if !ok {
return []string{}
}
urls := make([]string, len(uuids))
for i, u := range uuids {
urls[i] = config.MakeURLNidus("/api/image/%s/content", u.String())
}
return urls
}

View file

@ -12,13 +12,14 @@ import (
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/Gleipnir-Technology/nidus-sync/resource"
"github.com/gorilla/schema"
"github.com/rs/zerolog/log"
)
var decoder = schema.NewDecoder()
type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, queryParams) (*T, *nhttp.ErrorWithStatus)
type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, resource.QueryParams) (*T, *nhttp.ErrorWithStatus)
type wrappedHandler func(http.ResponseWriter, *http.Request)
type contentAuthenticated[T any] struct {
C T
@ -34,7 +35,7 @@ func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
ctx := r.Context()
var body []byte
var params queryParams
var params resource.QueryParams
err := decoder.Decode(&params, r.URL.Query())
if err != nil {
log.Error().Err(err).Msg("decode query failure")

View file

@ -1,51 +1 @@
package api
import (
"context"
"fmt"
"net/http"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
)
type createLead struct {
PoolLocations map[int]platform.Location `json:"pool_locations"`
SignalIDs []int `json:"signal_ids"`
}
type contentListLead struct {
Leads []lead `json:"leads"`
}
type lead struct {
ID int32 `json:"id"`
}
func listLead(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListLead, *nhttp.ErrorWithStatus) {
return &contentListLead{
Leads: make([]lead, 0),
}, nil
}
func postLeads(ctx context.Context, r *http.Request, user platform.User, req createLead) (string, *nhttp.ErrorWithStatus) {
if len(req.SignalIDs) == 0 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with no signals")
}
if len(req.SignalIDs) > 1 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with multiple signals yet")
}
signal_id := req.SignalIDs[0]
var pool_location *platform.Location
l, ok := req.PoolLocations[signal_id]
if ok {
pool_location = &l
}
site_id, err := platform.SiteFromSignal(ctx, user, int32(signal_id))
if err != nil || site_id == nil {
return "", nhttp.NewError("site from signal: %w", err)
}
lead_id, err := platform.LeadCreate(ctx, user, int32(signal_id), *site_id, pool_location)
if err != nil || lead_id == nil {
return "", nhttp.NewError("lead create: %w", err)
}
return fmt.Sprintf("/lead/%d", *lead_id), nil
}

View file

@ -1,26 +0,0 @@
package api
type queryParams struct {
Limit *int `schema:"limit"`
Query *string `schema:"query"`
Sort *string `schema:"sort"`
Type *string `schema:"type"`
}
func (qp queryParams) SortOrDefault(default_name string, ascending bool) (string, bool) {
if qp.Sort == nil {
return default_name, ascending
}
s := *qp.Sort
if s == "" {
return default_name, ascending
}
a := true
if s[0] == '-' {
a = false
}
if s[0] == '+' || s[0] == '-' {
s = s[1:]
}
return s, a
}

View file

@ -1,15 +1 @@
package api
import (
//"encoding/json"
)
type resource[T any] struct {
inner *T
}
func newResource[T any](inner *T) resource[T] {
return resource[T]{
inner: inner,
}
}

View file

@ -1,153 +1 @@
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/sm"
"github.com/Gleipnir-Technology/nidus-sync/db"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
//"github.com/aarondl/opt/null"
"github.com/stephenafamo/scan"
)
type reviewTask struct {
Address types.Address `json:"address"`
Created time.Time `json:"created"`
Creator platform.User `json:"creator"`
ID int32 `json:"id"`
Location types.Location `json:"location"`
Pool reviewTaskPool `json:"pool"`
Reviewed *time.Time `json:"addressed"`
Reviewer *platform.User `json:"addressor"`
}
type reviewTaskPool struct {
Condition string `json:"condition"`
Site types.Site `json:"site"`
}
type contentListReviewTask struct {
Tasks []reviewTask `json:"tasks"`
Total int32 `json:"total"`
}
func listReviewTask(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListReviewTask, *nhttp.ErrorWithStatus) {
limit := 20
if query.Limit != nil {
limit = *query.Limit
}
type _RowTotal struct {
Total int32 `db:"total"`
}
row_total, err := bob.One(ctx, db.PGInstance.BobDB, psql.Select(
sm.Columns(
"COUNT(*) AS total",
),
sm.From("review_task"),
sm.Where(psql.Quote("review_task", "organization_id").EQ(psql.Arg(user.Organization.ID))),
sm.Where(psql.Quote("review_task", "reviewed").IsNull()),
), scan.StructMapper[_RowTotal]())
if err != nil {
return nil, nhttp.NewError("failed to total count: %w", err)
}
type _Row struct {
Address types.Address `db:"address"`
Condition string `db:"condition"`
Created time.Time `db:"created"`
CreatorID int32 `db:"creator_id"`
ID int32 `db:"id"`
Latitude float64 `db:"latitude"`
Longitude float64 `db:"longitude"`
Reviewed *time.Time `db:"reviewed"`
ReviewerID *int32 `db:"reviewer_id"`
Species *string `db:"species"`
Title string `db:"title"`
Type string `db:"type"`
}
rows, err := bob.All(ctx, db.PGInstance.BobDB, psql.Select(
sm.Columns(
"feature_pool.condition AS condition",
"review_task.created AS created",
"review_task.creator_id AS creator_id",
"review_task.id AS id",
"review_task.reviewed AS reviewed",
"review_task.reviewer_id AS reviewer_id",
"address.country AS \"address.country\"",
"address.locality AS \"address.locality\"",
"address.number_ AS \"address.number\"",
"address.postal_code AS \"address.postal_code\"",
"address.region AS \"address.region\"",
"address.street AS \"address.street\"",
"address.unit AS \"address.unit\"",
"ST_Y(address.location) AS latitude",
"ST_X(address.location) AS longitude",
),
sm.From("review_task_pool"),
sm.InnerJoin("feature_pool").OnEQ(
psql.Quote("review_task_pool", "feature_pool_id"),
psql.Quote("feature_pool", "feature_id"),
),
sm.InnerJoin("review_task").OnEQ(
psql.Quote("review_task_pool", "review_task_id"),
psql.Quote("review_task", "id"),
),
sm.InnerJoin("feature").OnEQ(
psql.Quote("feature_pool", "feature_id"),
psql.Quote("feature", "id"),
),
sm.InnerJoin("site").On(
psql.Quote("feature", "site_id").EQ(psql.Quote("site", "id")),
),
sm.InnerJoin("address").OnEQ(
psql.Quote("site", "address_id"),
psql.Quote("address", "id"),
),
sm.Where(psql.Quote("review_task", "organization_id").EQ(psql.Arg(user.Organization.ID))),
sm.Where(psql.Quote("review_task", "reviewed").IsNull()),
sm.Limit(limit),
), scan.StructMapper[_Row]())
if err != nil {
return nil, nhttp.NewError("failed to get review tasks: %w", err)
}
users_by_id, err := platform.UsersByOrg(ctx, user.Organization)
if err != nil {
return nil, nhttp.NewError("users by id: %w", err)
}
tasks := make([]reviewTask, len(rows))
for i, row := range rows {
tasks[i] = reviewTask{
Address: row.Address,
Created: row.Created,
Creator: *users_by_id[row.CreatorID],
ID: row.ID,
Location: types.Location{
Latitude: row.Latitude,
Longitude: row.Longitude,
},
Pool: reviewTaskPool{
Condition: row.Condition,
},
Reviewed: row.Reviewed,
Reviewer: userOrNil(users_by_id, row.ReviewerID),
}
}
return &contentListReviewTask{
Tasks: tasks,
Total: row_total.Total,
}, nil
}
func userOrNil(usersByID map[int32]*platform.User, id *int32) *platform.User {
if id == nil {
return nil
}
u, ok := usersByID[*id]
if !ok {
return nil
}
return u
}

View file

@ -3,6 +3,7 @@ package api
import (
"github.com/Gleipnir-Technology/nidus-sync/auth"
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/Gleipnir-Technology/nidus-sync/resource"
"github.com/gorilla/mux"
)
@ -16,37 +17,45 @@ func AddRoutes(r *mux.Router) {
r.Handle("/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost)).Methods("POST")
r.Handle("/avatar", authenticatedHandlerPostMultipart(avatarPost, file.CollectionAvatar)).Methods("POST")
r.Handle("/client/ios", auth.NewEnsureAuth(handleClientIos)).Methods("GET")
r.Handle("/communication", authenticatedHandlerJSON(listCommunication)).Methods("GET")
communication := resource.Communication(r)
r.Handle("/communication", authenticatedHandlerJSON(communication.List)).Methods("GET")
r.Handle("/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis)).Methods("POST")
r.Handle("/events", auth.NewEnsureAuth(streamEvents)).Methods("GET")
r.Handle("/image/{uuid}", auth.NewEnsureAuth(apiImagePost)).Methods("POST")
r.Handle("/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentGet)).Methods("GET")
r.Handle("/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentPost)).Methods("POST")
r.Handle("/leads", authenticatedHandlerJSON(listLead)).Methods("GET")
r.Handle("/leads", authenticatedHandlerJSONPost(postLeads)).Methods("POST")
lead := resource.Lead(r)
r.Handle("/leads", authenticatedHandlerJSON(lead.List)).Methods("GET")
r.Handle("/leads", authenticatedHandlerJSONPost(lead.Create)).Methods("POST")
r.Handle("/mosquito-source", auth.NewEnsureAuth(apiMosquitoSource)).Methods("GET")
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("/review/pool", authenticatedHandlerJSONPost(postReviewPool)).Methods("POST")
r.Handle("/review-task", authenticatedHandlerJSON(listReviewTask)).Methods("GET")
review_task := resource.ReviewTask(r)
r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET")
r.Handle("/service-request", auth.NewEnsureAuth(apiServiceRequest)).Methods("GET")
r.Handle("/signal", authenticatedHandlerJSON(listSignal)).Methods("GET")
signal := resource.Signal(r)
r.Handle("/signal", authenticatedHandlerJSON(signal.List)).Methods("GET")
r.Handle("/sudo/email", authenticatedHandlerJSONPost(postSudoEmail)).Methods("POST")
r.Handle("/sudo/sms", authenticatedHandlerJSONPost(postSudoSMS)).Methods("POST")
r.Handle("/sudo/sse", authenticatedHandlerJSONPost(postSudoSSE)).Methods("POST")
r.Handle("/trap-data", auth.NewEnsureAuth(apiTrapData)).Methods("GET")
r.Handle("/tile/{z}/{y}/{x}", auth.NewEnsureAuth(getTile)).Methods("GET")
r.Handle("/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate, file.CollectionCSV)).Methods("POST")
r.Handle("/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate, file.CollectionCSV)).Methods("POST")
r.Handle("/upload", authenticatedHandlerJSON(getUploadList)).Methods("GET")
r.Handle("/upload/{id}", authenticatedHandlerJSON(getUploadByID)).Methods("GET")
r.Handle("/upload/{id}/commit", authenticatedHandlerJSONPost(postUploadCommit)).Methods("POST")
r.Handle("/upload/{id}/discard", authenticatedHandlerJSONPost(postUploadDiscard)).Methods("POST")
r.Handle("/user/self", authenticatedHandlerJSON(getUserSelf)).Methods("GET")
r.Handle("/user/suggestion", authenticatedHandlerJSON(listUserSuggestion)).Methods("GET")
r.Handle("/user", authenticatedHandlerJSON(listUser)).Methods("GET")
r.Handle("/user/{id}", authenticatedHandlerJSONPut(userPut)).Methods("PUT")
upload := resource.Upload(r)
r.Handle("/upload/pool/flyover", authenticatedHandlerPostMultipart(upload.PoolFlyoverCreate, file.CollectionCSV)).Methods("POST")
r.Handle("/upload/pool/custom", authenticatedHandlerPostMultipart(upload.PoolCustomCreate, file.CollectionCSV)).Methods("POST")
r.Handle("/upload", authenticatedHandlerJSON(upload.List)).Methods("GET")
r.Handle("/upload/{id}", authenticatedHandlerJSON(upload.ByIDGet)).Methods("GET")
r.Handle("/upload/{id}/commit", authenticatedHandlerJSONPost(upload.Commit)).Methods("POST")
r.Handle("/upload/{id}/discard", authenticatedHandlerJSONPost(upload.Discard)).Methods("POST")
user := resource.NewUser(r)
r.Handle("/user/self", authenticatedHandlerJSON(user.SelfGet)).Methods("GET")
r.Handle("/user/suggestion", authenticatedHandlerJSON(user.SuggestionGet)).Methods("GET")
r.Handle("/user", authenticatedHandlerJSON(user.List)).Methods("GET")
r.Handle("/user/{id}", authenticatedHandlerJSON(user.ByIDGet)).Methods("GET")
r.Handle("/user/{id}", authenticatedHandlerJSONPut(user.ByIDPut)).Methods("PUT")
// Unauthenticated endpoints
r.HandleFunc("/district", apiGetDistrict).Methods("GET")

View file

@ -1,28 +1 @@
package api
import (
"context"
"net/http"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
//"github.com/aarondl/opt/null"
)
type contentListSignal struct {
Signals []*platform.Signal `json:"signals"`
}
func listSignal(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListSignal, *nhttp.ErrorWithStatus) {
limit := 20
if query.Limit != nil {
limit = *query.Limit
}
signals, err := platform.SignalList(ctx, user, limit)
if err != nil {
return nil, nhttp.NewError("list signals: %w", err)
}
return &contentListSignal{
Signals: signals,
}, nil
}

View file

@ -1,141 +1 @@
package api
import (
"context"
"fmt"
"net/http"
"strconv"
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
)
func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query queryParams) (*platform.Upload, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r)
file_id_str := vars["id"]
file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil {
return nil, nhttp.NewError("Failed to parse file_id: %w", err)
}
file_id := int32(file_id_)
detail, err := platform.GetUploadDetail(ctx, u.Organization.ID, file_id)
if err != nil {
return nil, nhttp.NewError("Failed to get pool: %w", err)
}
return detail, nil
}
type contentUploadList struct {
RecentUploads []platform.Upload
}
type contentUploadPlaceholder struct{}
func getUploadList(ctx context.Context, r *http.Request, user platform.User, req queryParams) (*contentUploadPoolList, *nhttp.ErrorWithStatus) {
rows, err := platform.UploadList(ctx, user.Organization)
if err != nil {
return nil, nhttp.NewError("Get upload list: %w", err)
}
return &contentUploadPoolList{
Uploads: rows,
}, nil
}
type contentUploadDetail struct {
CSVFileID int32
Organization platform.Organization
Upload platform.Upload
}
type contentUploadPoolList struct {
Uploads []platform.Upload `json:"uploads"`
}
type contentUploadPool struct{}
func getUploadPool(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPool], *nhttp.ErrorWithStatus) {
data := contentUploadPool{}
return html.NewResponse("sync/upload-csv-pool.html", data), nil
}
type contentUploadPoolFlyoverCreate struct{}
func getUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPoolFlyoverCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolFlyoverCreate{}
return html.NewResponse("sync/upload-csv-pool-flyover.html", data), nil
}
type contentUploadPoolCustomCreate struct{}
func getUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentUploadPoolCustomCreate], *nhttp.ErrorWithStatus) {
data := contentUploadPoolCustomCreate{}
return html.NewResponse("sync/upload-csv-pool-custom.html", data), nil
}
type FormUploadCommit struct{}
func postUploadCommit(ctx context.Context, r *http.Request, u platform.User, f FormUploadCommit) (string, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r)
file_id_str := vars["id"]
file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil {
return "", nhttp.NewError("Failed to parse file_id: %w", err)
}
err = platform.UploadCommit(ctx, u.Organization, int32(file_id_), u)
if err != nil {
return "", nhttp.NewError("Failed to mark committed: %w", err)
}
log.Debug().Int64("file_id", file_id_).Int("user_id", u.ID).Msg("Committed file")
return "/configuration/upload", nil
}
type FormUploadDiscard struct{}
func postUploadDiscard(ctx context.Context, r *http.Request, u platform.User, f FormUploadDiscard) (string, *nhttp.ErrorWithStatus) {
vars := mux.Vars(r)
file_id_str := vars["id"]
file_id_, err := strconv.ParseInt(file_id_str, 10, 32)
if err != nil {
return "", nhttp.NewError("Failed to parse file_id: %w", err)
}
err = platform.UploadDiscard(ctx, u.Organization, int32(file_id_))
if err != nil {
return "", nhttp.NewError("Failed to mark discarded: %w", err)
}
return "/configuration/upload", nil
}
func postUploadPoolFlyoverCreate(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()) {
return "", nhttp.NewErrorStatus(http.StatusConflict, "Your organization does not yet have a service area")
}
if len(uploads) == 0 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
}
if len(uploads) != 1 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
}
upload := uploads[0]
saved_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypeFlyover)
if err != nil {
return "", nhttp.NewError("Failed to create new pool: %w", err)
}
return fmt.Sprintf("/configuration/upload/%d", *saved_upload), nil
}
func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, uploads []file.Upload) (string, *nhttp.ErrorWithStatus) {
if len(uploads) == 0 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "No upload found")
}
if len(uploads) != 1 {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must only submit one file at a time")
}
upload := uploads[0]
pool_upload, err := platform.NewUpload(r.Context(), u, upload, enums.FileuploadCsvtypePoollist)
if err != nil {
return "", nhttp.NewError("Failed to create new pool: %w", err)
}
return fmt.Sprintf("/configuration/upload/%d", *pool_upload), nil
}

View file

@ -1,115 +1 @@
package api
import (
"context"
"net/http"
"strconv"
"github.com/Gleipnir-Technology/nidus-sync/config"
"github.com/Gleipnir-Technology/nidus-sync/html"
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
"github.com/Gleipnir-Technology/nidus-sync/platform"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
)
type contentURLAPI struct {
Avatar string `json:"avatar"`
Communication string `json:"communication"`
PublicreportMessage string `json:"publicreport_message"`
ReviewTask string `json:"review_task"`
Signal string `json:"signal"`
Upload string `json:"upload"`
User string `json:"user"`
}
type contentURLs struct {
API contentURLAPI `json:"api"`
Tegola string `json:"tegola"`
Tile string `json:"tile"`
}
type contentUserSelf struct {
Self platform.User `json:"self"`
URLs contentURLs `json:"urls"`
}
func getUserSelf(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentUserSelf, *nhttp.ErrorWithStatus) {
counts, err := platform.NotificationCountsForUser(ctx, user)
if err != nil {
return nil, nhttp.NewError("get notifications: %w", err)
}
org, err := platform.OrganizationByID(ctx, int(user.Organization.ID))
if err != nil {
return nil, nhttp.NewError("get org: %w", err)
}
user.Organization = *org
user.NotificationCounts = *counts
urls := html.NewContentURL()
return &contentUserSelf{
Self: user,
URLs: contentURLs{
API: contentURLAPI{
Avatar: config.MakeURLNidus("/api/avatar"),
Communication: urls.API.Communication,
PublicreportMessage: urls.API.Publicreport.Message,
ReviewTask: config.MakeURLNidus("/api/review-task"),
Signal: config.MakeURLNidus("/api/signal"),
Upload: config.MakeURLNidus("/api/upload"),
User: config.MakeURLNidus("/api/user"),
},
Tegola: urls.Tegola,
Tile: config.MakeURLNidus("/api/tile/{z}/{y}/{x}"),
},
}, nil
}
type responseListUser struct {
Users []*platform.User `json:"users"`
}
func listUser(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*responseListUser, *nhttp.ErrorWithStatus) {
users, err := platform.UsersByOrg(ctx, user.Organization)
if err != nil {
return nil, nhttp.NewError("list users: %w", err)
}
results := make([]*platform.User, len(users))
i := 0
for _, v := range users {
results[i] = v
i++
}
return &responseListUser{
Users: results,
}, nil
}
type responseListUserSuggestion struct {
Users []*platform.User `json:"users"`
}
func listUserSuggestion(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*responseListUserSuggestion, *nhttp.ErrorWithStatus) {
if query.Query == nil {
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "you need to include a query")
}
users, err := platform.UserSuggestion(ctx, user, *query.Query)
if err != nil {
return nil, nhttp.NewError("query suggestions: %w", err)
}
return &responseListUserSuggestion{
Users: users,
}, nil
}
func userPut(ctx context.Context, r *http.Request, user platform.User, updates platform.UserChangeRequest) (string, *nhttp.ErrorWithStatus) {
log.Info().Str("avatar", updates.Avatar).Msg("doing updates")
vars := mux.Vars(r)
user_id_str := vars["id"]
user_id, err := strconv.Atoi(user_id_str)
if err != nil {
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "user update: %w", err)
}
err = platform.UserUpdate(ctx, user, user_id, updates)
if err != nil {
return "", nhttp.NewError("user update: %w", err)
}
return "", nil
}