Move all POST endpoints to the API
This commit is contained in:
parent
3ff7ff05ab
commit
d7c07fc65f
19 changed files with 466 additions and 641 deletions
|
|
@ -1,4 +1,4 @@
|
|||
package sync
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -64,44 +64,90 @@ func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
type handlerFunctionPost[ReqType any, ResponseType any] func(context.Context, *http.Request, platform.User, ReqType) (ResponseType, *nhttp.ErrorWithStatus)
|
||||
type handlerFunctionPost[ReqType any] func(context.Context, *http.Request, ReqType) (string, *nhttp.ErrorWithStatus)
|
||||
type handlerFunctionPostAuthenticated[ReqType any] func(context.Context, *http.Request, platform.User, ReqType) (string, *nhttp.ErrorWithStatus)
|
||||
|
||||
func authenticatedHandlerJSONPost[ReqType any, ResponseType any](f handlerFunctionPost[ReqType, ResponseType]) http.Handler {
|
||||
func authenticatedHandlerJSONPost[ReqType any](f handlerFunctionPostAuthenticated[ReqType]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var req ReqType
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to read body: %w", err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to decode request: %w", err)
|
||||
req, e := parseRequest[ReqType](r)
|
||||
if e != nil {
|
||||
serializeError(w, e)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
response, e := f(ctx, r, u, req)
|
||||
path, e := f(ctx, r, u, *req)
|
||||
if e != nil {
|
||||
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
|
||||
body, err = json.Marshal(ErrorAPI{Message: e.Error()})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to marshal error")
|
||||
http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Error(w, string(body), e.Status)
|
||||
serializeError(w, e)
|
||||
return
|
||||
}
|
||||
resp_body, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusInternalServerError, "Failed to marshal json response: %w", err)
|
||||
return
|
||||
}
|
||||
w.Write(resp_body)
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
})
|
||||
}
|
||||
func parseRequest[ReqType any](r *http.Request) (*ReqType, *nhttp.ErrorWithStatus) {
|
||||
var req ReqType
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to read body: %w", err)
|
||||
}
|
||||
err = json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Failed to decode request: %w", err)
|
||||
}
|
||||
return &req, nil
|
||||
}
|
||||
func serializeError(w http.ResponseWriter, e *nhttp.ErrorWithStatus) {
|
||||
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from api")
|
||||
body, err := json.Marshal(ErrorAPI{Message: e.Error()})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to marshal error")
|
||||
http.Error(w, "{\"message\": \"boom. I can't even tell you what went wrong\"}", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Error(w, string(body), e.Status)
|
||||
return
|
||||
}
|
||||
func handlerJSONPost[ReqType any](f handlerFunctionPost[ReqType]) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
req, e := parseRequest[ReqType](r)
|
||||
if e != nil {
|
||||
serializeError(w, e)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
path, e := f(ctx, r, *req)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func authenticatedHandlerPostMultipart[RequestType any](f handlerFunctionPostAuthenticated[RequestType]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to parse form: %w ", err)
|
||||
return
|
||||
}
|
||||
|
||||
var content RequestType
|
||||
|
||||
err = decoder.Decode(&content, r.PostForm)
|
||||
if err != nil {
|
||||
respondError(w, http.StatusBadRequest, "Failed to decode form: %w", err)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
path, e := f(ctx, r, u, content)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), e.Status)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
})
|
||||
}
|
||||
func respondError(w http.ResponseWriter, status int, format string, args ...any) {
|
||||
outer_err := fmt.Errorf(format, args...)
|
||||
body, err := json.Marshal(ErrorAPI{
|
||||
|
|
|
|||
18
api/lead.go
18
api/lead.go
|
|
@ -2,6 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -12,9 +13,6 @@ type createLead struct {
|
|||
PoolLocations map[int]platform.Location `json:"pool_locations"`
|
||||
SignalIDs []int `json:"signal_ids"`
|
||||
}
|
||||
type createdLead struct {
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
type contentListLead struct {
|
||||
Leads []lead `json:"leads"`
|
||||
}
|
||||
|
|
@ -27,12 +25,12 @@ func listLead(ctx context.Context, r *http.Request, user platform.User, query qu
|
|||
Leads: make([]lead, 0),
|
||||
}, nil
|
||||
}
|
||||
func postLeads(ctx context.Context, r *http.Request, user platform.User, req createLead) (*createdLead, *nhttp.ErrorWithStatus) {
|
||||
func postLeads(ctx context.Context, r *http.Request, user platform.User, req createLead) (string, *nhttp.ErrorWithStatus) {
|
||||
if len(req.SignalIDs) == 0 {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with no signals")
|
||||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with no signals")
|
||||
}
|
||||
if len(req.SignalIDs) > 1 {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with multiple signals yet")
|
||||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with multiple signals yet")
|
||||
}
|
||||
signal_id := req.SignalIDs[0]
|
||||
var pool_location *platform.Location
|
||||
|
|
@ -42,14 +40,12 @@ func postLeads(ctx context.Context, r *http.Request, user platform.User, req cre
|
|||
}
|
||||
site_id, err := platform.SiteFromSignal(ctx, user, int32(signal_id))
|
||||
if err != nil || site_id == nil {
|
||||
return nil, nhttp.NewError("site from signal: %w", err)
|
||||
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 nil, nhttp.NewError("lead create: %w", err)
|
||||
return "", nhttp.NewError("lead create: %w", err)
|
||||
}
|
||||
|
||||
return &createdLead{
|
||||
ID: *lead_id,
|
||||
}, nil
|
||||
return fmt.Sprintf("/lead/%d", *lead_id), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@ package api
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
)
|
||||
|
|
@ -13,54 +12,39 @@ import (
|
|||
type formPublicreportSignal struct {
|
||||
ReportID string `json:"reportID"`
|
||||
}
|
||||
type createdSignal struct {
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func postPublicreportSignal(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (*createdSignal, *nhttp.ErrorWithStatus) {
|
||||
func postPublicreportSignal(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (string, *nhttp.ErrorWithStatus) {
|
||||
signal_id, err := platform.SignalCreateFromPublicreport(ctx, user, req.ReportID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("create signal: %w", err)
|
||||
return "", nhttp.NewError("create signal: %w", err)
|
||||
}
|
||||
return &createdSignal{
|
||||
ID: *signal_id,
|
||||
}, nil
|
||||
return fmt.Sprintf("/signal/%d", *signal_id), nil
|
||||
}
|
||||
|
||||
type formPublicreportInvalid struct {
|
||||
ReportID string `json:"reportID"`
|
||||
}
|
||||
type createdReport struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func postPublicreportInvalid(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (*createdReport, *nhttp.ErrorWithStatus) {
|
||||
func postPublicreportInvalid(ctx context.Context, r *http.Request, user platform.User, req formPublicreportSignal) (string, *nhttp.ErrorWithStatus) {
|
||||
err := platform.PublicreportInvalid(ctx, user, req.ReportID)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("create signal: %w", err)
|
||||
return "", nhttp.NewError("create signal: %w", err)
|
||||
}
|
||||
return &createdReport{
|
||||
URI: config.MakeURLNidus("/publicreport/%s", req.ReportID),
|
||||
}, nil
|
||||
return fmt.Sprintf("/publicreport/%s", req.ReportID), nil
|
||||
}
|
||||
|
||||
type formPublicreportMessage struct {
|
||||
Message string `json:"message"`
|
||||
ReportID string `json:"reportID"`
|
||||
}
|
||||
type createdMessage struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
func postPublicreportMessage(ctx context.Context, r *http.Request, user platform.User, req formPublicreportMessage) (*createdMessage, *nhttp.ErrorWithStatus) {
|
||||
func postPublicreportMessage(ctx context.Context, r *http.Request, user platform.User, req formPublicreportMessage) (string, *nhttp.ErrorWithStatus) {
|
||||
msg_id, err := platform.PublicReportMessageCreate(ctx, user, req.ReportID, req.Message)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("failed to create message: %s", err)
|
||||
return "", nhttp.NewError("failed to create message: %s", err)
|
||||
}
|
||||
if msg_id == nil {
|
||||
return nil, nhttp.NewError("nil message id")
|
||||
return "", nhttp.NewError("nil message id")
|
||||
}
|
||||
return &createdMessage{
|
||||
URI: config.MakeURLNidus("/message/%s", strconv.Itoa(int(*msg_id))),
|
||||
}, nil
|
||||
return fmt.Sprintf("/message/%d", *msg_id), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -14,16 +15,15 @@ type createReviewPool struct {
|
|||
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) {
|
||||
_, err := platform.ReviewPoolCreate(ctx, user, req.TaskID, req.Status, req.Updates)
|
||||
func postReviewPool(ctx context.Context, r *http.Request, user platform.User, req createReviewPool) (string, *nhttp.ErrorWithStatus) {
|
||||
id, err := platform.ReviewPoolCreate(ctx, user, req.TaskID, req.Status, req.Updates)
|
||||
|
||||
if err != nil {
|
||||
if errors.As(err, &platform.ErrorNotFound{}) {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusNotFound, "review task %d not found", req.TaskID)
|
||||
return "", nhttp.NewErrorStatus(http.StatusNotFound, "review task %d not found", req.TaskID)
|
||||
}
|
||||
return nil, nhttp.NewError("failed to set review: %w", err)
|
||||
return "", nhttp.NewError("failed to set review: %w", err)
|
||||
}
|
||||
return &createdReviewPool{}, nil
|
||||
return fmt.Sprintf("/review/%d", id), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,20 @@ import (
|
|||
)
|
||||
|
||||
func AddRoutes(r chi.Router) {
|
||||
// Authenticated endpoints
|
||||
r.Use(render.SetContentType(render.ContentTypeJSON))
|
||||
// Unauthenticated endpoints
|
||||
r.Post("/signin", handlerJSONPost(postSignin))
|
||||
r.Post("/signup", handlerJSONPost(postSignup))
|
||||
// Authenticated endpoints
|
||||
r.Method("POST", "/audio/{uuid}", auth.NewEnsureAuth(apiAudioPost))
|
||||
r.Method("POST", "/audio/{uuid}/content", auth.NewEnsureAuth(apiAudioContentPost))
|
||||
r.Method("GET", "/client/ios", auth.NewEnsureAuth(handleClientIos))
|
||||
r.Method("GET", "/communication", authenticatedHandlerJSON(listCommunication))
|
||||
r.Method("POST", "/configuration/integration/arcgis", authenticatedHandlerJSONPost(postConfigurationIntegrationArcgis))
|
||||
r.Method("POST", "/configuration/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate))
|
||||
r.Method("POST", "/configuration/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate))
|
||||
r.Method("POST", "/configuration/upload/{id}/commit", authenticatedHandlerJSONPost(postUploadCommit))
|
||||
r.Method("POST", "/configuration/upload/{id}/discard", authenticatedHandlerJSONPost(postUploadDiscard))
|
||||
r.Method("GET", "/events", auth.NewEnsureAuth(streamEvents))
|
||||
r.Method("POST", "/image/{uuid}", auth.NewEnsureAuth(apiImagePost))
|
||||
r.Method("GET", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentGet))
|
||||
|
|
@ -28,6 +36,9 @@ func AddRoutes(r chi.Router) {
|
|||
r.Method("GET", "/review-task/pool", authenticatedHandlerJSON(listReviewTaskPool))
|
||||
r.Method("GET", "/service-request", auth.NewEnsureAuth(apiServiceRequest))
|
||||
r.Method("GET", "/signal", authenticatedHandlerJSON(listSignal))
|
||||
r.Method("POST", "/sudo/email", authenticatedHandlerJSONPost(postSudoEmail))
|
||||
r.Method("POST", "/sudo/sms", authenticatedHandlerJSONPost(postSudoSMS))
|
||||
r.Method("POST", "/sudo/sse", authenticatedHandlerJSONPost(postSudoSSE))
|
||||
r.Method("GET", "/trap-data", auth.NewEnsureAuth(apiTrapData))
|
||||
r.Method("GET", "/tile/{z}/{y}/{x}", auth.NewEnsureAuth(getTile))
|
||||
r.Method("GET", "/upload/{id}", authenticatedHandlerJSON(getUploadByID))
|
||||
|
|
@ -39,7 +50,6 @@ func AddRoutes(r chi.Router) {
|
|||
r.Get("/district", apiGetDistrict)
|
||||
r.Get("/district/{slug}/logo", apiGetDistrictLogo)
|
||||
r.Get("/compliance-request/image/pool/{public_id}", getComplianceRequestImagePool)
|
||||
r.Post("/signin", postSignin)
|
||||
r.Post("/twilio/call", twilioCallPost)
|
||||
r.Post("/twilio/call/status", twilioCallStatusPost)
|
||||
r.Post("/twilio/message", twilioMessagePost)
|
||||
|
|
|
|||
|
|
@ -1,46 +1,37 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"github.com/go-chi/render"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func postSignin(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
render.Render(w, r, errRender(fmt.Errorf("Failed to parse POST form: %w", err)))
|
||||
return
|
||||
type reqSignin struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
func postSignin(ctx context.Context, r *http.Request, req reqSignin) (string, *nhttp.ErrorWithStatus) {
|
||||
if req.Password == "" {
|
||||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "Empty password")
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
if password == "" || username == "" {
|
||||
w.Header().Set("WWW-Authenticate-Error", "no-credentials")
|
||||
http.Error(w, "invalid-credentials", http.StatusUnauthorized)
|
||||
return
|
||||
if req.Username == "" {
|
||||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "Empty username")
|
||||
}
|
||||
log.Info().Str("username", username).Msg("API Signin")
|
||||
_, err := auth.SigninUser(r, username, password)
|
||||
log.Info().Str("username", req.Username).Msg("API Signin")
|
||||
_, err := auth.SigninUser(r, req.Username, req.Password)
|
||||
if err != nil {
|
||||
if errors.Is(err, auth.InvalidCredentials{}) {
|
||||
w.Header().Set("WWW-Authenticate-Error", "invalid-credentials")
|
||||
http.Error(w, "invalid-credentials", http.StatusUnauthorized)
|
||||
return
|
||||
return "", nhttp.NewErrorStatus(http.StatusUnauthorized, "invalid credentials")
|
||||
}
|
||||
if errors.Is(err, auth.InvalidUsername{}) {
|
||||
w.Header().Set("WWW-Authenticate-Error", "invalid-credentials")
|
||||
http.Error(w, "invalid-credentials", http.StatusUnauthorized)
|
||||
return
|
||||
return "", nhttp.NewErrorStatus(http.StatusUnauthorized, "invalid credentials")
|
||||
}
|
||||
log.Error().Err(err).Str("username", username).Msg("Login server error")
|
||||
http.Error(w, "signin-server-error", http.StatusInternalServerError)
|
||||
return
|
||||
log.Error().Err(err).Str("username", req.Username).Msg("Login server error")
|
||||
return "", nhttp.NewError("login server error")
|
||||
}
|
||||
|
||||
http.Error(w, "", http.StatusAccepted)
|
||||
return "/", nil
|
||||
}
|
||||
|
|
|
|||
36
api/signup.go
Normal file
36
api/signup.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
type reqSignup struct {
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
Terms bool `json:"terms"`
|
||||
}
|
||||
func postSignup(ctx context.Context, r *http.Request, signup reqSignup) (string, *nhttp.ErrorWithStatus) {
|
||||
|
||||
log.Info().Str("username", signup.Username).Str("name", signup.Name).Str("password", strings.Repeat("*", len(signup.Password))).Msg("Signup")
|
||||
|
||||
if !signup.Terms {
|
||||
log.Warn().Msg("Terms not agreed")
|
||||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "You must agree to the terms to register")
|
||||
}
|
||||
|
||||
user, err := auth.SignupUser(r.Context(), signup.Username, signup.Name, signup.Password)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("Failed to signup user", err)
|
||||
}
|
||||
|
||||
auth.AddUserSession(r, user)
|
||||
|
||||
return "/", nil
|
||||
}
|
||||
|
||||
104
api/sudo.go
Normal file
104
api/sudo.go
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
|
||||
"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/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentSudo struct {
|
||||
ForwardEmailRMOAddress string
|
||||
ForwardEmailNidusAddress string
|
||||
}
|
||||
|
||||
func getSudo(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSudo], *nhttp.ErrorWithStatus) {
|
||||
if !user.HasRoot() {
|
||||
return nil, &nhttp.ErrorWithStatus{
|
||||
Message: "You have to be a root user to access this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
content := contentSudo{
|
||||
ForwardEmailRMOAddress: config.ForwardEmailRMOAddress,
|
||||
ForwardEmailNidusAddress: config.ForwardEmailNidusAddress,
|
||||
}
|
||||
return html.NewResponse("sync/sudo.html", content), nil
|
||||
}
|
||||
|
||||
type FormEmail struct {
|
||||
Body string `schema:"emailBody"`
|
||||
From string `schema:"emailFrom"`
|
||||
Subject string `schema:"emailSubject"`
|
||||
To string `schema:"emailTo"`
|
||||
}
|
||||
|
||||
func postSudoEmail(ctx context.Context, r *http.Request, u platform.User, e FormEmail) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
request := email.Request{
|
||||
From: e.From,
|
||||
HTML: fmt.Sprintf("<html><p>%s</p></html>", e.Body),
|
||||
Sender: e.From,
|
||||
Subject: e.Subject,
|
||||
To: e.To,
|
||||
Text: e.Body,
|
||||
}
|
||||
resp, err := email.Send(ctx, request)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to send email")
|
||||
} else {
|
||||
log.Info().Str("id", resp.ID).Str("to", e.To).Msg("Sent Email")
|
||||
}
|
||||
return "/sudo", nil
|
||||
}
|
||||
|
||||
type FormSMS struct {
|
||||
Message string `schema:"smsMessage"`
|
||||
Phone string `schema:"smsPhone"`
|
||||
}
|
||||
|
||||
func postSudoSMS(ctx context.Context, r *http.Request, u platform.User, sms FormSMS) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
id, err := text.SendText(ctx, config.VoipMSNumber, sms.Phone, sms.Message)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to send SMS")
|
||||
} else {
|
||||
log.Info().Str("id", id).Msg("Sent SMS")
|
||||
}
|
||||
return "/sudo", nil
|
||||
}
|
||||
|
||||
type FormSSE struct {
|
||||
OrganizationID int32 `schema:"organizationID"`
|
||||
Resource string `schema:"resource"`
|
||||
Type string `schema:"type"`
|
||||
URIPath string `schema:"uriPath"`
|
||||
}
|
||||
|
||||
func postSudoSSE(ctx context.Context, r *http.Request, u platform.User, sse FormSSE) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
platform.SudoEvent(sse.OrganizationID, sse.Resource, sse.Type, sse.URIPath)
|
||||
return "/sudo", nil
|
||||
}
|
||||
121
api/upload.go
121
api/upload.go
|
|
@ -1,13 +1,17 @@
|
|||
package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
|
||||
"github.com/go-chi/chi/v5"
|
||||
//"github.com/rs/zerolog/log"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query queryParams) (*platform.UploadPoolDetail, *nhttp.ErrorWithStatus) {
|
||||
file_id_str := chi.URLParam(r, "id")
|
||||
|
|
@ -22,3 +26,118 @@ func getUploadByID(ctx context.Context, r *http.Request, u platform.User, query
|
|||
}
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
type contentUploadList struct {
|
||||
RecentUploads []platform.UploadSummary
|
||||
}
|
||||
type contentUploadPlaceholder struct{}
|
||||
|
||||
func getUploadList(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentUploadList], *nhttp.ErrorWithStatus) {
|
||||
rows, err := platform.UploadSummaryList(ctx, user.Organization)
|
||||
return html.NewResponse("sync/upload-list.html", contentUploadList{
|
||||
RecentUploads: rows,
|
||||
}), nhttp.NewErrorMaybe("get upload list: %w", err)
|
||||
}
|
||||
|
||||
type contentUploadDetail struct {
|
||||
CSVFileID int32
|
||||
Organization platform.Organization
|
||||
Upload platform.UploadPoolDetail
|
||||
}
|
||||
type contentUploadPoolList struct {
|
||||
Uploads []platform.Upload
|
||||
}
|
||||
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) {
|
||||
file_id_str := chi.URLParam(r, "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) {
|
||||
file_id_str := chi.URLParam(r, "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
|
||||
}
|
||||
|
||||
type FormUploadPool struct{}
|
||||
|
||||
func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (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")
|
||||
}
|
||||
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
|
||||
}
|
||||
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.ID), nil
|
||||
}
|
||||
func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
|
||||
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
|
||||
}
|
||||
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.ID), nil
|
||||
}
|
||||
|
|
|
|||
66
platform/dashboard.go
Normal file
66
platform/dashboard.go
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
)
|
||||
type ServiceRequestSummary struct {
|
||||
Date time.Time
|
||||
Location string
|
||||
Status string
|
||||
}
|
||||
type contentDashboard struct {
|
||||
CountTraps int
|
||||
CountMosquitoSources int
|
||||
CountServiceRequests int
|
||||
IsSyncOngoing bool
|
||||
LastSync *time.Time
|
||||
RecentRequests []ServiceRequestSummary
|
||||
}
|
||||
|
||||
func getDashboardData(ctx context.Context, user User) (*contentDashboard, *nhttp.ErrorWithStatus) {
|
||||
var lastSync *time.Time
|
||||
sync, err := user.Organization.FieldseekerSyncLatest(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get syncs: %w", err)
|
||||
} else if sync != nil {
|
||||
lastSync = &sync.Created
|
||||
}
|
||||
is_syncing := user.Organization.IsSyncOngoing()
|
||||
count_trap, err := user.Organization.CountTrap(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get trap count: %w", err)
|
||||
}
|
||||
count_source, err := user.Organization.CountTrap(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get source count: %w", err)
|
||||
}
|
||||
count_service, err := user.Organization.CountServiceRequest(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get service count: %w", err)
|
||||
}
|
||||
service_request_recent, err := user.Organization.ServiceRequestRecent(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get recent service: %w", err)
|
||||
}
|
||||
|
||||
requests := make([]ServiceRequestSummary, 0)
|
||||
for _, r := range service_request_recent {
|
||||
requests = append(requests, ServiceRequestSummary{
|
||||
Date: r.Creationdate.MustGet(),
|
||||
Location: r.Reqaddr1.MustGet(),
|
||||
Status: "Completed",
|
||||
})
|
||||
}
|
||||
content := contentDashboard{
|
||||
CountTraps: int(count_trap),
|
||||
CountMosquitoSources: int(count_source),
|
||||
CountServiceRequests: int(count_service),
|
||||
IsSyncOngoing: is_syncing,
|
||||
LastSync: lastSync,
|
||||
RecentRequests: requests,
|
||||
}
|
||||
return &content, nil
|
||||
}
|
||||
21
sync/cell.go
21
sync/cell.go
|
|
@ -4,7 +4,7 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/h3utils"
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/h3utils"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
|
|
@ -16,7 +16,6 @@ type contentCell struct {
|
|||
BreedingSources []platform.BreedingSourceSummary
|
||||
CellBoundary h3.CellBoundary
|
||||
Inspections []platform.Inspection
|
||||
MapData ComponentMap
|
||||
Traps []platform.TrapSummary
|
||||
Treatments []platform.Treatment
|
||||
}
|
||||
|
|
@ -30,10 +29,6 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
|
|||
if err != nil {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "Cannot convert provided cell to uint64")
|
||||
}
|
||||
center, err := h3.Cell(c).LatLng()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get center: %w", err)
|
||||
}
|
||||
boundary, err := h3.Cell(c).Boundary()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get boundary: %w", err)
|
||||
|
|
@ -42,11 +37,17 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
|
|||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get inspections by cell: %w", err)
|
||||
}
|
||||
/*
|
||||
center, err := h3.Cell(c).LatLng()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get center: %w", err)
|
||||
}
|
||||
geojson, err := h3utils.H3ToGeoJSON([]h3.Cell{h3.Cell(c)})
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get boundaries: %w", err)
|
||||
}
|
||||
resolution := h3.Cell(c).Resolution()
|
||||
*/
|
||||
sources, err := platform.BreedingSourcesByCell(ctx, user.Organization, h3.Cell(c))
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get sources: %w", err)
|
||||
|
|
@ -64,14 +65,6 @@ func getCellDetails(ctx context.Context, r *http.Request, user platform.User) (*
|
|||
BreedingSources: sources,
|
||||
CellBoundary: boundary,
|
||||
Inspections: inspections,
|
||||
MapData: ComponentMap{
|
||||
Center: h3.LatLng{
|
||||
Lat: center.Lat,
|
||||
Lng: center.Lng,
|
||||
},
|
||||
GeoJSON: geojson,
|
||||
Zoom: resolution + 5,
|
||||
},
|
||||
Traps: traps,
|
||||
Treatments: treatments,
|
||||
}), nil
|
||||
|
|
|
|||
93
sync/dash.go
93
sync/dash.go
|
|
@ -2,9 +2,7 @@ package sync
|
|||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -15,7 +13,6 @@ import (
|
|||
|
||||
type contentSource struct {
|
||||
Inspections []platform.Inspection
|
||||
MapData ComponentMap
|
||||
Source *platform.BreedingSourceDetail
|
||||
Traps []platform.TrapNearby
|
||||
Treatments []platform.Treatment
|
||||
|
|
@ -24,21 +21,9 @@ type contentSource struct {
|
|||
User platform.User
|
||||
}
|
||||
type contentTrap struct {
|
||||
MapData ComponentMap
|
||||
Trap platform.Trap
|
||||
User platform.User
|
||||
}
|
||||
type contentDashboard struct {
|
||||
CountTraps int
|
||||
CountMosquitoSources int
|
||||
CountServiceRequests int
|
||||
Geo template.JS
|
||||
IsSyncOngoing bool
|
||||
LastSync *time.Time
|
||||
MapData ComponentMap
|
||||
RecentRequests []ServiceRequestSummary
|
||||
}
|
||||
|
||||
type contentLayoutTest struct {
|
||||
User platform.User
|
||||
}
|
||||
|
|
@ -54,50 +39,8 @@ func getLayoutTest(ctx context.Context, r *http.Request, user platform.User) (*h
|
|||
return html.NewResponse("sync/layout-test.html", contentLayoutTest{}), nil
|
||||
}
|
||||
|
||||
func getRoot(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentDashboard], *nhttp.ErrorWithStatus) {
|
||||
var lastSync *time.Time
|
||||
sync, err := user.Organization.FieldseekerSyncLatest(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get syncs: %w", err)
|
||||
} else if sync != nil {
|
||||
lastSync = &sync.Created
|
||||
}
|
||||
is_syncing := user.Organization.IsSyncOngoing()
|
||||
count_trap, err := user.Organization.CountTrap(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get trap count: %w", err)
|
||||
}
|
||||
count_source, err := user.Organization.CountTrap(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get source count: %w", err)
|
||||
}
|
||||
count_service, err := user.Organization.CountServiceRequest(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get service count: %w", err)
|
||||
}
|
||||
service_request_recent, err := user.Organization.ServiceRequestRecent(ctx)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get recent service: %w", err)
|
||||
}
|
||||
|
||||
requests := make([]ServiceRequestSummary, 0)
|
||||
for _, r := range service_request_recent {
|
||||
requests = append(requests, ServiceRequestSummary{
|
||||
Date: r.Creationdate.MustGet(),
|
||||
Location: r.Reqaddr1.MustGet(),
|
||||
Status: "Completed",
|
||||
})
|
||||
}
|
||||
content := contentDashboard{
|
||||
CountTraps: int(count_trap),
|
||||
CountMosquitoSources: int(count_source),
|
||||
CountServiceRequests: int(count_service),
|
||||
IsSyncOngoing: is_syncing,
|
||||
LastSync: lastSync,
|
||||
MapData: ComponentMap{},
|
||||
RecentRequests: requests,
|
||||
}
|
||||
return html.NewResponse("sync/authenticated.html", content), nil
|
||||
func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||
html.RenderOrError(w, "static/gen/main.html", struct{}{})
|
||||
}
|
||||
|
||||
func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSource], *nhttp.ErrorWithStatus) {
|
||||
|
|
@ -127,22 +70,8 @@ func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.
|
|||
return nil, nhttp.NewError("Failed to get treatments: %w", err)
|
||||
}
|
||||
treatment_models := platform.ModelTreatment(treatments)
|
||||
latlng, err := s.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get latlng: %w", err)
|
||||
}
|
||||
data := contentSource{
|
||||
Inspections: inspections,
|
||||
MapData: ComponentMap{
|
||||
Center: latlng,
|
||||
//GeoJSON:
|
||||
Markers: []MapMarker{
|
||||
MapMarker{
|
||||
LatLng: latlng,
|
||||
},
|
||||
},
|
||||
Zoom: 13,
|
||||
},
|
||||
Source: s,
|
||||
Traps: traps,
|
||||
Treatments: treatments,
|
||||
|
|
@ -153,12 +82,6 @@ func getSource(ctx context.Context, r *http.Request, user platform.User) (*html.
|
|||
return html.NewResponse("sync/source.html", data), nil
|
||||
}
|
||||
|
||||
func getStadia(ctx context.Context, r *http.Request, u platform.User) (*html.Response[contentDashboard], *nhttp.ErrorWithStatus) {
|
||||
data := contentDashboard{
|
||||
MapData: ComponentMap{},
|
||||
}
|
||||
return html.NewResponse("sync/stadia.html", data), nil
|
||||
}
|
||||
func getTemplateTest(w http.ResponseWriter, r *http.Request) {
|
||||
html.RenderOrError(w, "sync/template-test.html", nil)
|
||||
}
|
||||
|
|
@ -175,21 +98,13 @@ func getTrap(ctx context.Context, r *http.Request, user platform.User) (*html.Re
|
|||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get trap: %w", err)
|
||||
}
|
||||
/*
|
||||
latlng, err := t.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("Failed to get latlng: %w", err)
|
||||
}
|
||||
*/
|
||||
data := contentTrap{
|
||||
MapData: ComponentMap{
|
||||
Center: latlng,
|
||||
//GeoJSON:
|
||||
Markers: []MapMarker{
|
||||
MapMarker{
|
||||
LatLng: latlng,
|
||||
},
|
||||
},
|
||||
Zoom: 13,
|
||||
},
|
||||
Trap: *t,
|
||||
User: user,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"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/schema"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var decoder = schema.NewDecoder()
|
||||
|
||||
type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User) (*html.Response[T], *nhttp.ErrorWithStatus)
|
||||
type wrappedHandler func(http.ResponseWriter, *http.Request)
|
||||
type contentAuthenticated[T any] struct {
|
||||
C T
|
||||
Config html.ContentConfig
|
||||
Organization platform.Organization
|
||||
URL html.ContentURL
|
||||
User platform.User
|
||||
}
|
||||
|
||||
// w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
func authenticatedHandler[T any](f handlerFunctionGet[T]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
ctx := r.Context()
|
||||
resp, e := f(ctx, r, u)
|
||||
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
||||
if e != nil {
|
||||
log.Warn().Int("status", e.Status).Err(e).Str("user message", e.Message).Msg("Responding with an error from sync pages")
|
||||
http.Error(w, e.Error(), e.Status)
|
||||
return
|
||||
}
|
||||
html.RenderOrError(w, resp.Template, contentAuthenticated[T]{
|
||||
C: resp.Content,
|
||||
Config: html.NewContentConfig(),
|
||||
Organization: u.Organization,
|
||||
URL: html.NewContentURL(),
|
||||
User: u,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type handlerFunctionPost[T any] func(context.Context, *http.Request, platform.User, T) (string, *nhttp.ErrorWithStatus)
|
||||
|
||||
func authenticatedHandlerPost[T any](f handlerFunctionPost[T]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var content T
|
||||
|
||||
err = decoder.Decode(&content, r.PostForm)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
path, e := f(ctx, r, u, content)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), e.Status)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
})
|
||||
}
|
||||
func authenticatedHandlerPostMultipart[T any](f handlerFunctionPost[T]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
err := r.ParseMultipartForm(32 << 10) // 32 MB buffer
|
||||
if err != nil {
|
||||
respondError(w, "Failed to parse form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var content T
|
||||
|
||||
err = decoder.Decode(&content, r.PostForm)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to decode form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
path, e := f(ctx, r, u, content)
|
||||
if e != nil {
|
||||
http.Error(w, e.Error(), e.Status)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, path, http.StatusFound)
|
||||
})
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package sync
|
|||
|
||||
import (
|
||||
"github.com/Gleipnir-Technology/nidus-sync/api"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/static"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
|
@ -31,66 +30,13 @@ func Router() chi.Router {
|
|||
r.Get("/privacy", getPrivacy)
|
||||
r.Get("/qr-code/report/{code}", getQRCodeReport)
|
||||
r.Get("/qr-code/mailer/{code}", getQRCodeMailer)
|
||||
r.Get("/signin", getSignin)
|
||||
r.Post("/signin", postSignin)
|
||||
r.Get("/signup", getSignup)
|
||||
r.Post("/signup", postSignup)
|
||||
r.Get("/template-test", getTemplateTest)
|
||||
|
||||
// Authenticated endpoints
|
||||
r.Route("/api", api.AddRoutes)
|
||||
|
||||
r.Method("GET", "/", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/communication", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/integration", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/integration/arcgis", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/organization", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/pesticide", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/pesticide/add", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/upload", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/upload/pool", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/upload/pool/custom", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/upload/pool/flyover", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/upload/{id}", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/user", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/configuration/user/add", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/intelligence", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/operations", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/oauth/refresh/arcgis", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/planning", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/review", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/review/pool", authenticatedHandler(getRoot))
|
||||
r.Method("GET", "/review/site", authenticatedHandler(getRoot))
|
||||
|
||||
r.Method("GET", "/admin", authenticatedHandler(getAdminDash))
|
||||
r.Method("GET", "/cell/{cell}", authenticatedHandler(getCellDetails))
|
||||
r.Method("POST", "/configuration/integration/arcgis", authenticatedHandlerPost(postConfigurationIntegrationArcgis))
|
||||
r.Method("POST", "/configuration/upload/pool/flyover", authenticatedHandlerPostMultipart(postUploadPoolFlyoverCreate))
|
||||
r.Method("POST", "/configuration/upload/pool/custom", authenticatedHandlerPostMultipart(postUploadPoolCustomCreate))
|
||||
r.Method("POST", "/configuration/upload/{id}/commit", authenticatedHandlerPost(postUploadCommit))
|
||||
r.Method("POST", "/configuration/upload/{id}/discard", authenticatedHandlerPost(postUploadDiscard))
|
||||
r.Method("GET", "/download", authenticatedHandler(getDownloadList))
|
||||
r.Method("GET", "/layout-test", authenticatedHandler(getLayoutTest))
|
||||
r.Method("GET", "/message", authenticatedHandler(getMessageList))
|
||||
r.Method("GET", "/notification", authenticatedHandler(getNotificationList))
|
||||
r.Method("GET", "/parcel", authenticatedHandler(getParcel))
|
||||
r.Method("GET", "/pool", authenticatedHandler(getPoolList))
|
||||
r.Method("GET", "/pool/create", authenticatedHandler(getPoolCreate))
|
||||
r.Method("GET", "/pool/{id}", authenticatedHandler(getPoolByID))
|
||||
r.Method("GET", "/radar", authenticatedHandler(getRadar))
|
||||
r.Method("GET", "/service-request", authenticatedHandler(getServiceRequestList))
|
||||
r.Method("GET", "/service-request/{id}", authenticatedHandler(getServiceRequestDetail))
|
||||
r.Method("GET", "/signout", auth.NewEnsureAuth(getSignout))
|
||||
r.Method("GET", "/source/{globalid}", authenticatedHandler(getSource))
|
||||
r.Method("GET", "/stadia", authenticatedHandler(getStadia))
|
||||
r.Method("GET", "/sudo", authenticatedHandler(getSudo))
|
||||
r.Method("POST", "/sudo/email", authenticatedHandlerPost(postSudoEmail))
|
||||
r.Method("POST", "/sudo/sms", authenticatedHandlerPost(postSudoSMS))
|
||||
r.Method("POST", "/sudo/sse", authenticatedHandlerPost(postSudoSSE))
|
||||
r.Method("GET", "/trap/{globalid}", authenticatedHandler(getTrap))
|
||||
r.Method("GET", "/text/{destination}", authenticatedHandler(getTextMessages))
|
||||
r.Method("GET", "/tile/gps", auth.NewEnsureAuth(getTileGPS))
|
||||
r.Get("/", getRoot)
|
||||
r.Get("/_/*", getRoot)
|
||||
|
||||
static.AddStaticRoute(r, "/static")
|
||||
return r
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package sync
|
|||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
|
|
@ -16,7 +15,6 @@ type contentSignin struct {
|
|||
InvalidCredentials bool
|
||||
Next string
|
||||
}
|
||||
type contentSignup struct{}
|
||||
|
||||
func getSignin(w http.ResponseWriter, r *http.Request) {
|
||||
errorCode := r.URL.Query().Get("error")
|
||||
|
|
@ -29,15 +27,6 @@ func getSignout(w http.ResponseWriter, r *http.Request, user platform.User) {
|
|||
http.Redirect(w, r, "/signin", http.StatusFound)
|
||||
}
|
||||
|
||||
func getSignup(w http.ResponseWriter, r *http.Request) {
|
||||
data := contentSignup{}
|
||||
html.RenderOrError(w, "sync/signup.html", contentUnauthenticated[contentSignup]{
|
||||
C: data,
|
||||
Config: html.NewContentConfig(),
|
||||
URL: html.NewContentURL(),
|
||||
})
|
||||
}
|
||||
|
||||
func postSignin(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondError(w, "Could not parse form", err, http.StatusBadRequest)
|
||||
|
|
@ -70,36 +59,6 @@ func postSignin(w http.ResponseWriter, r *http.Request) {
|
|||
http.Redirect(w, r, location, http.StatusFound)
|
||||
}
|
||||
|
||||
func postSignup(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
respondError(w, "Could not parse form", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
name := r.FormValue("name")
|
||||
password := r.FormValue("password")
|
||||
terms := r.FormValue("terms")
|
||||
|
||||
log.Info().Str("username", username).Str("name", name).Str("password", strings.Repeat("*", len(password))).Msg("Signup")
|
||||
|
||||
if terms != "on" {
|
||||
log.Warn().Msg("Terms not agreed")
|
||||
http.Error(w, "You must agree to the terms to register", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := auth.SignupUser(r.Context(), username, name, password)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to signup user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auth.AddUserSession(r, user)
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
type contentUnauthenticated[T any] struct {
|
||||
C T
|
||||
Config html.ContentConfig
|
||||
|
|
|
|||
103
sync/sudo.go
103
sync/sudo.go
|
|
@ -1,104 +1 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/comms/text"
|
||||
"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/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentSudo struct {
|
||||
ForwardEmailRMOAddress string
|
||||
ForwardEmailNidusAddress string
|
||||
}
|
||||
|
||||
func getSudo(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentSudo], *nhttp.ErrorWithStatus) {
|
||||
if !user.HasRoot() {
|
||||
return nil, &nhttp.ErrorWithStatus{
|
||||
Message: "You have to be a root user to access this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
content := contentSudo{
|
||||
ForwardEmailRMOAddress: config.ForwardEmailRMOAddress,
|
||||
ForwardEmailNidusAddress: config.ForwardEmailNidusAddress,
|
||||
}
|
||||
return html.NewResponse("sync/sudo.html", content), nil
|
||||
}
|
||||
|
||||
type FormEmail struct {
|
||||
Body string `schema:"emailBody"`
|
||||
From string `schema:"emailFrom"`
|
||||
Subject string `schema:"emailSubject"`
|
||||
To string `schema:"emailTo"`
|
||||
}
|
||||
|
||||
func postSudoEmail(ctx context.Context, r *http.Request, u platform.User, e FormEmail) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
request := email.Request{
|
||||
From: e.From,
|
||||
HTML: fmt.Sprintf("<html><p>%s</p></html>", e.Body),
|
||||
Sender: e.From,
|
||||
Subject: e.Subject,
|
||||
To: e.To,
|
||||
Text: e.Body,
|
||||
}
|
||||
resp, err := email.Send(ctx, request)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to send email")
|
||||
} else {
|
||||
log.Info().Str("id", resp.ID).Str("to", e.To).Msg("Sent Email")
|
||||
}
|
||||
return "/sudo", nil
|
||||
}
|
||||
|
||||
type FormSMS struct {
|
||||
Message string `schema:"smsMessage"`
|
||||
Phone string `schema:"smsPhone"`
|
||||
}
|
||||
|
||||
func postSudoSMS(ctx context.Context, r *http.Request, u platform.User, sms FormSMS) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
id, err := text.SendText(ctx, config.VoipMSNumber, sms.Phone, sms.Message)
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to send SMS")
|
||||
} else {
|
||||
log.Info().Str("id", id).Msg("Sent SMS")
|
||||
}
|
||||
return "/sudo", nil
|
||||
}
|
||||
|
||||
type FormSSE struct {
|
||||
OrganizationID int32 `schema:"organizationID"`
|
||||
Resource string `schema:"resource"`
|
||||
Type string `schema:"type"`
|
||||
URIPath string `schema:"uriPath"`
|
||||
}
|
||||
|
||||
func postSudoSSE(ctx context.Context, r *http.Request, u platform.User, sse FormSSE) (string, *nhttp.ErrorWithStatus) {
|
||||
if !u.HasRoot() {
|
||||
return "", &nhttp.ErrorWithStatus{
|
||||
Message: "You must have sudo powers to do this",
|
||||
Status: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
platform.SudoEvent(sse.OrganizationID, sse.Resource, sse.Type, sse.URIPath)
|
||||
return "/sudo", nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,12 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
||||
type MapMarker struct {
|
||||
LatLng h3.LatLng
|
||||
}
|
||||
type ComponentMap struct {
|
||||
Center h3.LatLng
|
||||
GeoJSON interface{}
|
||||
Markers []MapMarker
|
||||
Zoom int
|
||||
}
|
||||
type ContentMockURLs struct {
|
||||
Dispatch string
|
||||
DispatchResults string
|
||||
|
|
@ -43,8 +35,3 @@ type Link struct {
|
|||
Href string
|
||||
Title string
|
||||
}
|
||||
type ServiceRequestSummary struct {
|
||||
Date time.Time
|
||||
Location string
|
||||
Status string
|
||||
}
|
||||
|
|
|
|||
127
sync/upload.go
127
sync/upload.go
|
|
@ -1,131 +1,4 @@
|
|||
package sync
|
||||
|
||||
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/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type contentUploadList struct {
|
||||
RecentUploads []platform.UploadSummary
|
||||
}
|
||||
type contentUploadPlaceholder struct{}
|
||||
|
||||
func getUploadList(ctx context.Context, r *http.Request, user platform.User) (*html.Response[contentUploadList], *nhttp.ErrorWithStatus) {
|
||||
rows, err := platform.UploadSummaryList(ctx, user.Organization)
|
||||
return html.NewResponse("sync/upload-list.html", contentUploadList{
|
||||
RecentUploads: rows,
|
||||
}), nhttp.NewErrorMaybe("get upload list: %w", err)
|
||||
}
|
||||
|
||||
type contentUploadDetail struct {
|
||||
CSVFileID int32
|
||||
Organization platform.Organization
|
||||
Upload platform.UploadPoolDetail
|
||||
}
|
||||
type contentUploadPoolList struct {
|
||||
Uploads []platform.Upload
|
||||
}
|
||||
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) {
|
||||
file_id_str := chi.URLParam(r, "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) {
|
||||
file_id_str := chi.URLParam(r, "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
|
||||
}
|
||||
|
||||
type FormUploadPool struct{}
|
||||
|
||||
func postUploadPoolFlyoverCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (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")
|
||||
}
|
||||
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
|
||||
}
|
||||
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.ID), nil
|
||||
}
|
||||
func postUploadPoolCustomCreate(ctx context.Context, r *http.Request, u platform.User, f FormUploadPool) (string, *nhttp.ErrorWithStatus) {
|
||||
uploads, err := file.SaveFileUpload(r, "csvfile", file.CollectionCSV)
|
||||
if err != nil {
|
||||
return "", nhttp.NewError("Failed to extract image uploads: %s", err)
|
||||
}
|
||||
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.ID), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue