Massive rework of platform layer user/organization
The goal of this rework is to make it so I can pass around platform.User instead of a pair of models.Organization and models.User. This is useful for reason I kind of forget now, but it started with working on notifications and ballooned massively from there into refactoring a number of things that were bugging me. This also includes a tiny amount of work on server-side events (SSE). * background stuff lives inside the platform now, which I need for having it push updates through SSE * userfile now lives in the platform, under file, so other platform functions can safely use it * oauth is broken into pieces and inside platform because other stuff was calling it already, but badly. * notifications go into the platform as well
This commit is contained in:
parent
32dcc50c94
commit
44c4f17f32
85 changed files with 1492 additions and 1384 deletions
86
api/api.go
86
api/api.go
|
|
@ -1,7 +1,6 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -11,11 +10,11 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/background"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
|
@ -24,7 +23,7 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func apiAudioPost(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func apiAudioPost(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
id := chi.URLParam(r, "uuid")
|
||||
noteUUID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
|
|
@ -43,9 +42,10 @@ func apiAudioPost(w http.ResponseWriter, r *http.Request, org *models.Organizati
|
|||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
setter := models.NoteAudioSetter{
|
||||
Created: omit.From(payload.Created),
|
||||
CreatorID: omit.From(u.ID),
|
||||
CreatorID: omit.From(int32(u.ID)),
|
||||
Deleted: omitnull.FromPtr(payload.Deleted),
|
||||
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
||||
Duration: omit.From(payload.Duration),
|
||||
|
|
@ -54,21 +54,21 @@ func apiAudioPost(w http.ResponseWriter, r *http.Request, org *models.Organizati
|
|||
Version: omit.From(payload.Version),
|
||||
UUID: omit.From(noteUUID),
|
||||
}
|
||||
if err := db.NoteAudioCreate(context.Background(), u.R.Organization, u.ID, setter); err != nil {
|
||||
if err := platform.NoteAudioCreate(ctx, u, setter); err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func apiAudioContentPost(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
u_str := chi.URLParam(r, "uuid")
|
||||
audioUUID, err := uuid.Parse(u_str)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to parse image UUID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = userfile.FileContentWrite(r.Body, userfile.CollectionAudioRaw, audioUUID)
|
||||
err = file.FileContentWrite(r.Body, file.CollectionAudioRaw, audioUUID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to write content file: %v", err)
|
||||
http.Error(w, "failed to write content file", http.StatusInternalServerError)
|
||||
|
|
@ -78,7 +78,7 @@ func apiAudioContentPost(w http.ResponseWriter, r *http.Request, org *models.Org
|
|||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func handleClientIos(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func handleClientIos(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
var sinceStr string
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
|
|
@ -121,69 +121,7 @@ func handleClientIos(w http.ResponseWriter, r *http.Request, org *models.Organiz
|
|||
}
|
||||
}
|
||||
|
||||
func apiImagePost(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
id := chi.URLParam(r, "uuid")
|
||||
noteUUID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to decode the uuid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var payload NoteImagePayload
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read the payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
//debugSaveRequest(body, err, "Image note POST JSON decode error")
|
||||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
setter := models.NoteImageSetter{
|
||||
Created: omit.From(payload.Created),
|
||||
CreatorID: omit.From(u.ID),
|
||||
Deleted: omitnull.FromPtr(payload.Deleted),
|
||||
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
||||
Version: omit.From(payload.Version),
|
||||
UUID: omit.From(noteUUID),
|
||||
}
|
||||
err = db.NoteImageCreate(context.Background(), u.R.Organization, u.ID, setter)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func apiImageContentGet(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
u_str := chi.URLParam(r, "uuid")
|
||||
imageUUID, err := uuid.Parse(u_str)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse image UUID")
|
||||
http.Error(w, "Failed to parse image UUID", http.StatusBadRequest)
|
||||
}
|
||||
userfile.PublicImageFileToResponse(w, imageUUID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
func apiImageContentPost(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
u_str := chi.URLParam(r, "uuid")
|
||||
imageUUID, err := uuid.Parse(u_str)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse image UUID")
|
||||
http.Error(w, "Failed to parse image UUID", http.StatusBadRequest)
|
||||
}
|
||||
err = userfile.ImageFileContentWrite(imageUUID, r.Body)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
log.Printf("Saved image file %s\n", imageUUID)
|
||||
fmt.Fprintf(w, "PNG uploaded successfully")
|
||||
}
|
||||
|
||||
func apiMosquitoSource(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func apiMosquitoSource(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
bounds, err := parseBounds(r)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
|
|
@ -208,7 +146,7 @@ func apiMosquitoSource(w http.ResponseWriter, r *http.Request, org *models.Organ
|
|||
}
|
||||
}
|
||||
|
||||
func apiTrapData(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func apiTrapData(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
bounds, err := parseBounds(r)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
|
|
@ -233,7 +171,7 @@ func apiTrapData(w http.ResponseWriter, r *http.Request, org *models.Organizatio
|
|||
}
|
||||
}
|
||||
|
||||
func apiServiceRequest(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
func apiServiceRequest(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
bounds, err := parseBounds(r)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"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/Gleipnir-Technology/nidus-sync/platform/publicreport"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -35,12 +35,12 @@ type contentListCommunication struct {
|
|||
Communications []communication `json:"communications"`
|
||||
}
|
||||
|
||||
func listCommunication(ctx context.Context, r *http.Request, org *models.Organization, user *models.User, query queryParams) (*contentListCommunication, *nhttp.ErrorWithStatus) {
|
||||
nreports, err := publicreport.NuisanceReportForOrganization(ctx, org.ID)
|
||||
func listCommunication(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListCommunication, *nhttp.ErrorWithStatus) {
|
||||
nreports, err := publicreport.NuisanceReportForOrganization(ctx, user.Organization.ID())
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("nuisance report query: %w", err)
|
||||
}
|
||||
wreports, err := publicreport.WaterReportForOrganization(ctx, org.ID)
|
||||
wreports, err := publicreport.WaterReportForOrganization(ctx, user.Organization.ID())
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("water report query: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import (
|
|||
"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/imagetile"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/paulmach/orb/geojson"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -61,10 +59,7 @@ func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) {
|
|||
psql.Quote("organization.id"),
|
||||
),
|
||||
sm.InnerJoin("site").On(
|
||||
psql.And(
|
||||
psql.Quote("lead.site_id").EQ(psql.Quote("site.id")),
|
||||
psql.Quote("lead.site_version").EQ(psql.Quote("site.version")),
|
||||
),
|
||||
psql.Quote("lead.site_id").EQ(psql.Quote("site.id")),
|
||||
),
|
||||
sm.InnerJoin("parcel").OnEQ(
|
||||
psql.Quote("site.parcel_id"),
|
||||
|
|
@ -72,9 +67,13 @@ func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) {
|
|||
),
|
||||
sm.Where(psql.Quote("compliance_report_request").EQ(psql.Arg(code))),
|
||||
), scan.StructMapper[_Row]())
|
||||
org, err := models.FindOrganization(ctx, db.PGInstance.BobDB, row.OrganizationID)
|
||||
org, err := platform.OrganizationByID(ctx, int(row.OrganizationID))
|
||||
if err != nil {
|
||||
http.Error(w, "no org", http.StatusInternalServerError)
|
||||
http.Error(w, "org err", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if org == nil {
|
||||
http.Error(w, "no org", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var polygon geojson.Polygon
|
||||
|
|
@ -86,15 +85,15 @@ func getComplianceRequestImagePool(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
ring := polygon[0]
|
||||
p := ring[0]
|
||||
err = writeImage(ctx, w, org, 19, p[1], p[0])
|
||||
err = writeImage(ctx, w, *org, 19, p[1], p[0])
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("write image")
|
||||
http.Error(w, "failed to write image", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
func writeImage(ctx context.Context, w http.ResponseWriter, org *models.Organization, level uint, lat, lng float64) error {
|
||||
img, err := imagetile.ImageAtPoint(ctx, org, level, lat, lng)
|
||||
func writeImage(ctx context.Context, w http.ResponseWriter, org platform.Organization, level uint, lat, lng float64) error {
|
||||
img, err := platform.ImageAtPoint(ctx, org, level, lat, lng)
|
||||
if err != nil {
|
||||
return fmt.Errorf("image at point: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/userfile"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
|
@ -73,7 +73,7 @@ func apiGetDistrictLogo(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "Logo not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
userfile.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet())
|
||||
file.ImageFileContentWriteLogo(w, org.LogoUUID.MustGet())
|
||||
return
|
||||
default:
|
||||
http.Error(w, "Too many organizations, this is a programmer error", http.StatusInternalServerError)
|
||||
|
|
|
|||
103
api/event.go
Normal file
103
api/event.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func streamEvents(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
}
|
||||
|
||||
type MessageHeartbeat struct {
|
||||
Time time.Time `json:"time"`
|
||||
}
|
||||
type MessageSSE struct {
|
||||
Content any `json:"content"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
type ConnectionSSE struct {
|
||||
chanState chan MessageSSE
|
||||
id string
|
||||
}
|
||||
|
||||
func (c *ConnectionSSE) SendMessage(w http.ResponseWriter, m MessageSSE) error {
|
||||
return send(w, MessageSSE{
|
||||
Type: "heartbeat",
|
||||
})
|
||||
}
|
||||
func (c *ConnectionSSE) SendHeartbeat(w http.ResponseWriter, t time.Time) error {
|
||||
return send(w, MessageSSE{
|
||||
Content: MessageHeartbeat{
|
||||
Time: t,
|
||||
},
|
||||
Type: "heartbeat",
|
||||
})
|
||||
}
|
||||
func send[T any](w http.ResponseWriter, msg T) error {
|
||||
jsonData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling json: %w", err)
|
||||
}
|
||||
// Write in SSE format: "data: <json>\n\n"
|
||||
_, err = fmt.Fprintf(w, "data: %s\n\n", jsonData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing SSE message: %w", err)
|
||||
}
|
||||
|
||||
w.(http.Flusher).Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
type Webserver struct {
|
||||
connections map[*ConnectionSSE]bool
|
||||
}
|
||||
|
||||
// sseHandler handles the Server-Sent Events connection
|
||||
func (web *Webserver) sseHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Set headers for SSE
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
||||
connection := ConnectionSSE{
|
||||
chanState: make(chan MessageSSE),
|
||||
id: fmt.Sprintf("%d", time.Now().UnixNano()),
|
||||
}
|
||||
web.connections[&connection] = true
|
||||
// Send an initial connected event
|
||||
fmt.Fprintf(w, "event: connected\ndata: {\"status\": \"connected\", \"time\": \"%s\"}\n\n", time.Now().Format(time.RFC3339))
|
||||
w.(http.Flusher).Flush()
|
||||
|
||||
// Keep the connection open with a ticker sending periodic events
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// Use a channel to detect when the client disconnects
|
||||
done := r.Context().Done()
|
||||
|
||||
// Keep connection open until client disconnects
|
||||
var err error
|
||||
for {
|
||||
err = nil
|
||||
select {
|
||||
case <-done:
|
||||
log.Info().Msg("Client closed connection")
|
||||
return
|
||||
case t := <-ticker.C:
|
||||
// Send a heartbeat message
|
||||
err = connection.SendHeartbeat(w, t)
|
||||
//case state := <-connection.chanState:
|
||||
//log.Debug().Msg("Sending new state to connection")
|
||||
//err = connection.SendState(w, state)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to send state from webserver")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
|
|
@ -19,7 +17,7 @@ import (
|
|||
|
||||
var decoder = schema.NewDecoder()
|
||||
|
||||
type handlerFunctionGet[T any] func(context.Context, *http.Request, *models.Organization, *models.User, queryParams) (*T, *nhttp.ErrorWithStatus)
|
||||
type handlerFunctionGet[T any] func(context.Context, *http.Request, platform.User, queryParams) (*T, *nhttp.ErrorWithStatus)
|
||||
type wrappedHandler func(http.ResponseWriter, *http.Request)
|
||||
type contentAuthenticated[T any] struct {
|
||||
C T
|
||||
|
|
@ -32,26 +30,17 @@ type ErrorAPI struct {
|
|||
}
|
||||
|
||||
func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
ctx := r.Context()
|
||||
org, err := u.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if org == nil {
|
||||
http.Error(w, "nil org", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var body []byte
|
||||
var params queryParams
|
||||
err = decoder.Decode(¶ms, r.URL.Query())
|
||||
err := decoder.Decode(¶ms, r.URL.Query())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("decode query failure")
|
||||
http.Error(w, "failed to decode query", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
resp, e := f(ctx, r, org, u, params)
|
||||
resp, e := f(ctx, r, u, params)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
//log.Info().Str("template", template).Err(e).Msg("handler done")
|
||||
if e != nil {
|
||||
|
|
@ -74,10 +63,10 @@ func authenticatedHandlerJSON[T any](f handlerFunctionGet[T]) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
type handlerFunctionPost[ReqType any, ResponseType any] func(context.Context, *http.Request, *models.Organization, *models.User, ReqType) (ResponseType, *nhttp.ErrorWithStatus)
|
||||
type handlerFunctionPost[ReqType any, ResponseType any] func(context.Context, *http.Request, platform.User, ReqType) (ResponseType, *nhttp.ErrorWithStatus)
|
||||
|
||||
func authenticatedHandlerJSONPost[ReqType any, ResponseType any](f handlerFunctionPost[ReqType, ResponseType]) http.Handler {
|
||||
return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, org *models.Organization, u *models.User) {
|
||||
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)
|
||||
|
|
@ -91,7 +80,7 @@ func authenticatedHandlerJSONPost[ReqType any, ResponseType any](f handlerFuncti
|
|||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
response, e := f(ctx, r, org, u, req)
|
||||
response, 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()})
|
||||
|
|
|
|||
81
api/image.go
Normal file
81
api/image.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/file"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func apiImagePost(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
id := chi.URLParam(r, "uuid")
|
||||
noteUUID, err := uuid.Parse(id)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to decode the uuid", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var payload NoteImagePayload
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read the payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
//debugSaveRequest(body, err, "Image note POST JSON decode error")
|
||||
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
setter := models.NoteImageSetter{
|
||||
Created: omit.From(payload.Created),
|
||||
CreatorID: omit.From(int32(u.ID)),
|
||||
Deleted: omitnull.FromPtr(payload.Deleted),
|
||||
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
||||
Version: omit.From(payload.Version),
|
||||
UUID: omit.From(noteUUID),
|
||||
}
|
||||
err = platform.NoteImageCreate(ctx, u, setter)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
func apiImageContentGet(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
u_str := chi.URLParam(r, "uuid")
|
||||
imageUUID, err := uuid.Parse(u_str)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse image UUID")
|
||||
http.Error(w, "Failed to parse image UUID", http.StatusBadRequest)
|
||||
}
|
||||
file.PublicImageFileToResponse(w, imageUUID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
func apiImageContentPost(w http.ResponseWriter, r *http.Request, u platform.User) {
|
||||
u_str := chi.URLParam(r, "uuid")
|
||||
imageUUID, err := uuid.Parse(u_str)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to parse image UUID")
|
||||
http.Error(w, "Failed to parse image UUID", http.StatusBadRequest)
|
||||
}
|
||||
err = file.ImageFileContentWrite(imageUUID, r.Body)
|
||||
if err != nil {
|
||||
render.Render(w, r, errRender(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
log.Printf("Saved image file %s\n", imageUUID)
|
||||
fmt.Fprintf(w, "PNG uploaded successfully")
|
||||
}
|
||||
21
api/lead.go
21
api/lead.go
|
|
@ -13,6 +13,7 @@ import (
|
|||
"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/Gleipnir-Technology/nidus-sync/platform/geom"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
|
|
@ -34,12 +35,12 @@ type lead struct {
|
|||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func listLead(ctx context.Context, r *http.Request, org *models.Organization, user *models.User, query queryParams) (*contentListLead, *nhttp.ErrorWithStatus) {
|
||||
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, org *models.Organization, user *models.User, req createLead) (*createdLead, *nhttp.ErrorWithStatus) {
|
||||
func postLeads(ctx context.Context, r *http.Request, user platform.User, req createLead) (*createdLead, *nhttp.ErrorWithStatus) {
|
||||
if len(req.SignalIDs) == 0 {
|
||||
return nil, nhttp.NewErrorStatus(http.StatusBadRequest, "can't make a lead with no signals")
|
||||
}
|
||||
|
|
@ -54,13 +55,11 @@ func postLeads(ctx context.Context, r *http.Request, org *models.Organization, u
|
|||
return nil, nhttp.NewError("start transaction: %w", err)
|
||||
}
|
||||
type _Row struct {
|
||||
ID int32 `db:"site_id"`
|
||||
Version int32 `db:"site_version"`
|
||||
ID int32 `db:"site_id"`
|
||||
}
|
||||
site, err := bob.One(ctx, db.PGInstance.BobDB, psql.Select(
|
||||
sm.Columns(
|
||||
"pool.site_id AS site_id",
|
||||
"pool.site_version AS site_version",
|
||||
),
|
||||
sm.From("signal_pool"),
|
||||
sm.InnerJoin("pool").OnEQ(
|
||||
|
|
@ -68,13 +67,10 @@ func postLeads(ctx context.Context, r *http.Request, org *models.Organization, u
|
|||
psql.Quote("pool", "id"),
|
||||
),
|
||||
sm.InnerJoin("site").On(
|
||||
psql.And(
|
||||
psql.Quote("pool", "site_id").EQ(psql.Quote("site", "id")),
|
||||
psql.Quote("pool", "site_version").EQ(psql.Quote("site", "version")),
|
||||
),
|
||||
psql.Quote("pool", "site_id").EQ(psql.Quote("site", "id")),
|
||||
),
|
||||
sm.Where(psql.Quote("signal_pool", "signal_id").EQ(psql.Arg(signal_id))),
|
||||
sm.Where(psql.Quote("site", "organization_id").EQ(psql.Arg(org.ID))),
|
||||
sm.Where(psql.Quote("site", "organization_id").EQ(psql.Arg(user.Organization.ID()))),
|
||||
), scan.StructMapper[_Row]())
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
|
|
@ -85,11 +81,10 @@ func postLeads(ctx context.Context, r *http.Request, org *models.Organization, u
|
|||
|
||||
lead, err := models.Leads.Insert(&models.LeadSetter{
|
||||
Created: omit.From(time.Now()),
|
||||
Creator: omit.From(user.ID),
|
||||
Creator: omit.From(int32(user.ID)),
|
||||
// ID
|
||||
OrganizationID: omit.From(org.ID),
|
||||
OrganizationID: omit.From(int32(user.Organization.ID())),
|
||||
SiteID: omitnull.From(site.ID),
|
||||
SiteVersion: omitnull.From(site.Version),
|
||||
Type: omit.From(enums.LeadtypeGreenPool),
|
||||
}).One(ctx, txn)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"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"
|
||||
|
|
@ -35,7 +36,7 @@ type createReviewPool struct {
|
|||
}
|
||||
type createdReviewPool struct{}
|
||||
|
||||
func postReviewPool(ctx context.Context, r *http.Request, org *models.Organization, user *models.User, req createReviewPool) (*createdReviewPool, *nhttp.ErrorWithStatus) {
|
||||
func postReviewPool(ctx context.Context, r *http.Request, user platform.User, req createReviewPool) (*createdReviewPool, *nhttp.ErrorWithStatus) {
|
||||
txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("start txn: %w", err)
|
||||
|
|
@ -43,7 +44,7 @@ func postReviewPool(ctx context.Context, r *http.Request, org *models.Organizati
|
|||
defer txn.Rollback(ctx)
|
||||
review_task, err := models.ReviewTasks.Query(
|
||||
models.SelectWhere.ReviewTasks.ID.EQ(req.TaskID),
|
||||
models.SelectWhere.ReviewTasks.OrganizationID.EQ(org.ID),
|
||||
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)
|
||||
|
|
@ -56,7 +57,7 @@ func postReviewPool(ctx context.Context, r *http.Request, org *models.Organizati
|
|||
review_task.Update(ctx, txn, &models.ReviewTaskSetter{
|
||||
Resolution: omitnull.From(resolution),
|
||||
Reviewed: omitnull.From(time.Now()),
|
||||
ReviewerID: omitnull.From(user.ID),
|
||||
ReviewerID: omitnull.From(int32(user.ID)),
|
||||
})
|
||||
review_task_pool, err := models.ReviewTaskPools.Query(
|
||||
models.SelectWhere.ReviewTaskPools.ReviewTaskID.EQ(review_task.ID),
|
||||
|
|
@ -77,10 +78,10 @@ func postReviewPool(ctx context.Context, r *http.Request, org *models.Organizati
|
|||
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 *models.User, req createReviewPool, review_task_pool *models.ReviewTaskPool) *nhttp.ErrorWithStatus {
|
||||
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 *models.User, req createReviewPool, review_task_pool *models.ReviewTaskPool) *nhttp.ErrorWithStatus {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"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/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
|
|
@ -32,7 +31,7 @@ type contentListReviewTaskPool struct {
|
|||
Total int32 `json:"total"`
|
||||
}
|
||||
|
||||
func listReviewTaskPool(ctx context.Context, r *http.Request, org *models.Organization, user *models.User, query queryParams) (*contentListReviewTaskPool, *nhttp.ErrorWithStatus) {
|
||||
func listReviewTaskPool(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListReviewTaskPool, *nhttp.ErrorWithStatus) {
|
||||
limit := 20
|
||||
if query.Limit != nil {
|
||||
limit = *query.Limit
|
||||
|
|
@ -45,7 +44,7 @@ func listReviewTaskPool(ctx context.Context, r *http.Request, org *models.Organi
|
|||
"COUNT(*) AS total",
|
||||
),
|
||||
sm.From("review_task"),
|
||||
sm.Where(psql.Quote("review_task", "organization_id").EQ(psql.Arg(org.ID))),
|
||||
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 {
|
||||
|
|
@ -98,23 +97,20 @@ func listReviewTaskPool(ctx context.Context, r *http.Request, org *models.Organi
|
|||
psql.Quote("feature", "id"),
|
||||
),
|
||||
sm.InnerJoin("site").On(
|
||||
psql.And(
|
||||
psql.Quote("feature", "site_id").EQ(psql.Quote("site", "id")),
|
||||
psql.Quote("feature", "site_version").EQ(psql.Quote("site", "version")),
|
||||
),
|
||||
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(org.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.UsersByID(ctx, org)
|
||||
users_by_id, err := platform.UsersByOrg(ctx, user.Organization)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("users by id: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ func AddRoutes(r chi.Router) {
|
|||
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("GET", "/events", auth.NewEnsureAuth(streamEvents))
|
||||
r.Method("POST", "/image/{uuid}", auth.NewEnsureAuth(apiImagePost))
|
||||
r.Method("GET", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentGet))
|
||||
r.Method("POST", "/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentPost))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"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/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
|
|
@ -33,7 +32,7 @@ type contentListSignal struct {
|
|||
Signals []signal `json:"signals"`
|
||||
}
|
||||
|
||||
func listSignal(ctx context.Context, r *http.Request, org *models.Organization, user *models.User, query queryParams) (*contentListSignal, *nhttp.ErrorWithStatus) {
|
||||
func listSignal(ctx context.Context, r *http.Request, user platform.User, query queryParams) (*contentListSignal, *nhttp.ErrorWithStatus) {
|
||||
type _Row struct {
|
||||
Address types.Address `db:"address"`
|
||||
Addressed *time.Time `db:"addressed"`
|
||||
|
|
@ -82,16 +81,13 @@ func listSignal(ctx context.Context, r *http.Request, org *models.Organization,
|
|||
psql.Quote("pool", "id"),
|
||||
),
|
||||
sm.InnerJoin("site").On(
|
||||
psql.And(
|
||||
psql.Quote("pool", "site_id").EQ(psql.Quote("site", "id")),
|
||||
psql.Quote("pool", "site_version").EQ(psql.Quote("site", "version")),
|
||||
),
|
||||
psql.Quote("pool", "site_id").EQ(psql.Quote("site", "id")),
|
||||
),
|
||||
sm.InnerJoin("address").OnEQ(
|
||||
psql.Quote("site", "address_id"),
|
||||
psql.Quote("address", "id"),
|
||||
),
|
||||
sm.Where(psql.Quote("signal", "organization_id").EQ(psql.Arg(org.ID))),
|
||||
sm.Where(psql.Quote("signal", "organization_id").EQ(psql.Arg(user.Organization.ID()))),
|
||||
sm.Where(psql.Quote("signal", "addressed").IsNull()),
|
||||
sm.Limit(limit),
|
||||
), scan.StructMapper[_Row]())
|
||||
|
|
@ -105,7 +101,7 @@ func listSignal(ctx context.Context, r *http.Request, org *models.Organization,
|
|||
if err != nil {
|
||||
return nil, nhttp.NewError("failed to get signals: %w", err)
|
||||
}
|
||||
users_by_id, err := platform.UsersByID(ctx, org)
|
||||
users_by_id, err := platform.UsersByOrg(ctx, user.Organization)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("users by id: %w", err)
|
||||
}
|
||||
|
|
|
|||
109
api/tile.go
109
api/tile.go
|
|
@ -1,27 +1,15 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/aarondl/opt/omit"
|
||||
//"github.com/Gleipnir-Technology/bob"
|
||||
//"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/imagetile"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func getTile(w http.ResponseWriter, r *http.Request, org *models.Organization, user *models.User) {
|
||||
func getTile(w http.ResponseWriter, r *http.Request, user platform.User) {
|
||||
x_str := chi.URLParam(r, "x")
|
||||
y_str := chi.URLParam(r, "y")
|
||||
z_str := chi.URLParam(r, "z")
|
||||
|
|
@ -41,101 +29,10 @@ func getTile(w http.ResponseWriter, r *http.Request, org *models.Organization, u
|
|||
http.Error(w, "can't parse x as an integer", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
err = handleTile(r.Context(), w, org, uint(z), uint(y), uint(x))
|
||||
err = platform.GetTile(r.Context(), w, user.Organization, uint(z), uint(y), uint(x))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to do tile")
|
||||
http.Error(w, "failed to do tile", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
func handleTile(ctx context.Context, w http.ResponseWriter, org *models.Organization, z, y, x uint) error {
|
||||
if org.ArcgisMapServiceID.IsNull() {
|
||||
return fmt.Errorf("no map service ID set")
|
||||
}
|
||||
map_service_id := org.ArcgisMapServiceID.MustGet()
|
||||
tile_path := tilePath(map_service_id, z, y, x)
|
||||
tile_row, err := models.TileCachedImages.Query(
|
||||
models.SelectWhere.TileCachedImages.ArcgisID.EQ(map_service_id),
|
||||
models.SelectWhere.TileCachedImages.X.EQ(int32(x)),
|
||||
models.SelectWhere.TileCachedImages.Y.EQ(int32(y)),
|
||||
models.SelectWhere.TileCachedImages.Z.EQ(int32(z)),
|
||||
).One(ctx, db.PGInstance.BobDB)
|
||||
if err == nil {
|
||||
var tile *imagetile.TileRaster
|
||||
if tile_row.IsEmpty {
|
||||
tile = imagetile.TileRasterPlaceholder()
|
||||
} else {
|
||||
tile, err = loadTileFromDisk(tile_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load tile from disk: %w", err)
|
||||
}
|
||||
}
|
||||
log.Debug().Uint("z", z).Uint("y", y).Uint("x", x).Bool("is empty", tile_row.IsEmpty).Msg("tile from cache")
|
||||
return writeTile(w, tile)
|
||||
}
|
||||
if err.Error() != "sql: no rows in result set" {
|
||||
return fmt.Errorf("query db: %w", err)
|
||||
}
|
||||
image, err := imagetile.ImageAtTile(ctx, org, uint(z), uint(y), uint(x))
|
||||
if err != nil {
|
||||
return fmt.Errorf("image at tile: %w", err)
|
||||
}
|
||||
if !image.IsPlaceholder {
|
||||
err = saveTileToDisk(image, tile_path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save tile: %w", err)
|
||||
}
|
||||
}
|
||||
_, err = models.TileCachedImages.Insert(&models.TileCachedImageSetter{
|
||||
ArcgisID: omit.From(map_service_id),
|
||||
X: omit.From(int32(x)),
|
||||
Y: omit.From(int32(y)),
|
||||
Z: omit.From(int32(z)),
|
||||
IsEmpty: omit.From(image.IsPlaceholder),
|
||||
}).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return fmt.Errorf("save to db: %w", err)
|
||||
}
|
||||
log.Debug().Uint("z", z).Uint("y", y).Uint("x", x).Bool("placeholder", image.IsPlaceholder).Msg("caching tile")
|
||||
return writeTile(w, image)
|
||||
}
|
||||
func loadTileFromDisk(tile_path string) (*imagetile.TileRaster, error) {
|
||||
file, err := os.Open(tile_path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
img, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("readall from %s: %w", tile_path, err)
|
||||
}
|
||||
return &imagetile.TileRaster{
|
||||
Content: img,
|
||||
IsPlaceholder: false,
|
||||
}, nil
|
||||
}
|
||||
func saveTileToDisk(image *imagetile.TileRaster, tile_path string) error {
|
||||
parent := filepath.Dir(tile_path)
|
||||
err := os.MkdirAll(parent, 0750)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mkdirall: %w", err)
|
||||
}
|
||||
err = os.WriteFile(tile_path, image.Content, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write image file: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func tilePath(map_service_id string, z, y, x uint) string {
|
||||
return fmt.Sprintf("%s/tile-cache/%s/%d/%d/%d.raw", config.FilesDirectory, map_service_id, z, y, x)
|
||||
}
|
||||
|
||||
func writeTile(w http.ResponseWriter, image *imagetile.TileRaster) error {
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(image.Content)))
|
||||
_, err := io.Copy(w, bytes.NewBuffer(image.Content))
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.copy: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue