I'm settling on the idea that strings should never be returned from the userfile system. Instead, indicate which collection you want and pass objects across.
350 lines
9.1 KiB
Go
350 lines
9.1 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"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/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 apiAudioPost(w http.ResponseWriter, r *http.Request, 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 NoteAudioPayload
|
|
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, "Audio note POST JSON decode error")
|
|
http.Error(w, "Failed to decode the payload", http.StatusBadRequest)
|
|
return
|
|
}
|
|
setter := models.NoteAudioSetter{
|
|
Created: omit.From(payload.Created),
|
|
CreatorID: omit.From(u.ID),
|
|
Deleted: omitnull.FromPtr(payload.Deleted),
|
|
DeletorID: omitnull.FromPtr(payload.DeletorID),
|
|
Duration: omit.From(payload.Duration),
|
|
Transcription: omitnull.FromPtr(payload.Transcription),
|
|
TranscriptionUserEdited: omit.From(payload.TranscriptionUserEdited),
|
|
Version: omit.From(payload.Version),
|
|
UUID: omit.From(noteUUID),
|
|
}
|
|
if err := db.NoteAudioCreate(context.Background(), u.R.Organization, u.ID, setter); err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusAccepted)
|
|
}
|
|
|
|
func apiAudioContentPost(w http.ResponseWriter, r *http.Request, u *models.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)
|
|
if err != nil {
|
|
log.Printf("Failed to write content file: %v", err)
|
|
http.Error(w, "failed to write content file", http.StatusInternalServerError)
|
|
}
|
|
|
|
background.AudioTranscode(audioUUID)
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func handleClientIos(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
var sinceStr string
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
render.Render(w, r, errRender(fmt.Errorf("Failed to parse GET form: %w", err)))
|
|
return
|
|
} else {
|
|
sinceStr = r.FormValue("since")
|
|
}
|
|
|
|
var since *time.Time
|
|
if sinceStr == "" {
|
|
since = nil
|
|
} else {
|
|
since, err = parseTime(sinceStr)
|
|
if err != nil {
|
|
render.Render(w, r, errRender(fmt.Errorf("Failed to parse 'since' value: %w", err)))
|
|
return
|
|
}
|
|
}
|
|
|
|
csync, err := platform.ContentClientIos(r.Context(), u, since)
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
var since_used time.Time
|
|
if since == nil {
|
|
since_used = time.Unix(0, 0)
|
|
} else {
|
|
since_used = *since
|
|
}
|
|
response := ResponseClientIos{
|
|
Fieldseeker: toResponseFieldseeker(csync.Fieldseeker),
|
|
Since: since_used,
|
|
}
|
|
if err := render.Render(w, r, response); err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
}
|
|
|
|
func apiImagePost(w http.ResponseWriter, r *http.Request, 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 apiImageContentPost(w http.ResponseWriter, r *http.Request, 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, u *models.User) {
|
|
bounds, err := parseBounds(r)
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
query := db.NewGeoQuery()
|
|
query.Bounds = *bounds
|
|
query.Limit = 100
|
|
sources, err := platform.MosquitoSourceQuery()
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
data := []render.Renderer{}
|
|
for _, s := range sources {
|
|
data = append(data, NewResponseMosquitoSource(s))
|
|
}
|
|
if err := render.RenderList(w, r, data); err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
}
|
|
}
|
|
|
|
func apiTrapData(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
bounds, err := parseBounds(r)
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
query := db.NewGeoQuery()
|
|
query.Bounds = *bounds
|
|
query.Limit = 100
|
|
trap_data, err := platform.TrapDataQuery()
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
data := []render.Renderer{}
|
|
for _, td := range trap_data {
|
|
data = append(data, NewResponseTrapDatum(td))
|
|
}
|
|
if err := render.RenderList(w, r, data); err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
}
|
|
}
|
|
|
|
func apiServiceRequest(w http.ResponseWriter, r *http.Request, u *models.User) {
|
|
bounds, err := parseBounds(r)
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
query := db.NewGeoQuery()
|
|
query.Bounds = *bounds
|
|
query.Limit = 100
|
|
requests, err := platform.ServiceRequestQuery()
|
|
if err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
return
|
|
}
|
|
|
|
data := []render.Renderer{}
|
|
for _, sr := range requests {
|
|
data = append(data, NewResponseServiceRequest(sr))
|
|
}
|
|
if err := render.RenderList(w, r, data); err != nil {
|
|
render.Render(w, r, errRender(err))
|
|
}
|
|
}
|
|
|
|
func parseBounds(r *http.Request) (*db.GeoBounds, error) {
|
|
err := r.ParseForm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
east := r.FormValue("east")
|
|
north := r.FormValue("north")
|
|
south := r.FormValue("south")
|
|
west := r.FormValue("west")
|
|
|
|
bounds := db.GeoBounds{}
|
|
|
|
var temp float64
|
|
temp, err = strconv.ParseFloat(east, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bounds.East = temp
|
|
temp, err = strconv.ParseFloat(north, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bounds.North = temp
|
|
temp, err = strconv.ParseFloat(south, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bounds.South = temp
|
|
temp, err = strconv.ParseFloat(west, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bounds.West = temp
|
|
return &bounds, nil
|
|
}
|
|
|
|
func errRender(err error) render.Renderer {
|
|
log.Error().Err(err).Msg("Rendering error")
|
|
return &ResponseErr{
|
|
Error: err,
|
|
HTTPStatusCode: 500,
|
|
StatusText: "Error rendering response",
|
|
ErrorText: err.Error(),
|
|
}
|
|
}
|
|
|
|
func webhookFieldseeker(w http.ResponseWriter, r *http.Request) {
|
|
// Create or open the log file
|
|
file, err := os.OpenFile("webhook/request.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
log.Printf("Error opening log file: %v", err)
|
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
// Write timestamp
|
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
|
fmt.Fprintf(file, "\n=== Request logged at %s ===\n", timestamp)
|
|
|
|
// Write request line
|
|
fmt.Fprintf(file, "%s %s %s\n", r.Method, r.RequestURI, r.Proto)
|
|
|
|
// Write all headers
|
|
fmt.Fprintf(file, "\nHeaders:\n")
|
|
for name, values := range r.Header {
|
|
for _, value := range values {
|
|
fmt.Fprintf(file, "%s: %s\n", name, value)
|
|
}
|
|
}
|
|
|
|
// Write body
|
|
fmt.Fprintf(file, "\nBody:\n")
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
log.Printf("Error reading request body: %v", err)
|
|
fmt.Fprintf(file, "Error reading body: %v\n", err)
|
|
} else {
|
|
file.Write(body)
|
|
if len(body) == 0 {
|
|
fmt.Fprintf(file, "(empty body)")
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(file, "\n=== End of request ===\n\n")
|
|
|
|
// Extract the crc_token value for the signature portion
|
|
|
|
// Respond with 204 No Content
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func parseTime(x string) (*time.Time, error) {
|
|
created_epoch, err := strconv.ParseInt(x, 10, 64)
|
|
if err != nil {
|
|
return &time.Time{}, fmt.Errorf("Failed to parse time '%s': %w", x, err)
|
|
}
|
|
created := time.UnixMilli(created_epoch)
|
|
return &created, nil
|
|
}
|