mirror of
https://github.com/parkan/go-hauk.git
synced 2026-05-08 16:47:46 +02:00
add api endpoints
This commit is contained in:
90
api/adopt.go
Normal file
90
api/adopt.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleAdopt(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := r.FormValue("sid")
|
||||||
|
nickname := r.FormValue("nic")
|
||||||
|
aid := r.FormValue("aid")
|
||||||
|
pinStr := r.FormValue("pin")
|
||||||
|
|
||||||
|
if sid == "" || nickname == "" || aid == "" || pinStr == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := model.LoadSession(ctx, s.store, sid, s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shareType, err := model.LoadShareType(ctx, s.store, aid)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Share not found!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if shareType != model.ShareTypeAlone {
|
||||||
|
fmt.Fprintln(w, "Group shares cannot be adopted!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
share, err := model.LoadSoloShare(ctx, s.store, aid, s.cfg.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Share not found!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !share.Adoptable() {
|
||||||
|
fmt.Fprintln(w, "Share adoption not allowed!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hostSession, err := model.LoadSession(ctx, s.store, share.Host(), s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostSession.Encrypted() {
|
||||||
|
fmt.Fprintln(w, "End-to-end encrypted shares cannot be adopted!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pin, _ := strconv.Atoi(pinStr)
|
||||||
|
target, err := model.LoadGroupShareByPin(ctx, s.store, pin, s.cfg.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
target.AddHost(nickname, share.Host())
|
||||||
|
if err := target.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hostSession.AddTarget(target.ID())
|
||||||
|
if err := hostSession.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = session
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
}
|
||||||
211
api/create.go
Normal file
211
api/create.go
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
shareModeCreateAlone = 0
|
||||||
|
shareModeCreateGroup = 1
|
||||||
|
shareModeJoinGroup = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var linkIDRe = regexp.MustCompile(`^[\w-]+$`)
|
||||||
|
|
||||||
|
func (s *Server) handleCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dur := r.FormValue("dur")
|
||||||
|
interval := r.FormValue("int")
|
||||||
|
if dur == "" || interval == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := r.FormValue("usr")
|
||||||
|
pass := r.FormValue("pwd")
|
||||||
|
if err := s.auth.Authenticate(user, pass); err != nil {
|
||||||
|
fmt.Fprintln(w, "Incorrect password!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d, _ := strconv.Atoi(dur)
|
||||||
|
i, _ := strconv.ParseFloat(interval, 64)
|
||||||
|
mod, _ := strconv.Atoi(r.FormValue("mod"))
|
||||||
|
adoptable := r.FormValue("ado") == "1"
|
||||||
|
encrypted := r.FormValue("e2e") == "1"
|
||||||
|
salt := r.FormValue("salt")
|
||||||
|
customLink := r.FormValue("lid")
|
||||||
|
nickname := r.FormValue("nic")
|
||||||
|
pin, _ := strconv.Atoi(r.FormValue("pin"))
|
||||||
|
|
||||||
|
if d > s.cfg.MaxDuration {
|
||||||
|
fmt.Fprintln(w, "Share duration exceeds maximum configured!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i > float64(s.cfg.MaxDuration) {
|
||||||
|
fmt.Fprintln(w, "Interval exceeds maximum configured!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i < s.cfg.MinInterval {
|
||||||
|
fmt.Fprintln(w, "Interval is too short!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod == shareModeCreateGroup || mod == shareModeJoinGroup) && encrypted {
|
||||||
|
fmt.Fprintln(w, "End-to-end encryption is not supported for group shares.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod == shareModeCreateGroup || mod == shareModeJoinGroup) && nickname == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if mod == shareModeJoinGroup && pin == 0 {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if encrypted && salt == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expire := time.Now().Add(time.Duration(d) * time.Second)
|
||||||
|
|
||||||
|
session, err := model.NewSession(s.store, s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
session.SetExpire(expire)
|
||||||
|
session.SetInterval(i)
|
||||||
|
if encrypted {
|
||||||
|
session.SetEncrypted(true, salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
linkGen := func() (string, error) {
|
||||||
|
return s.linkgen.Generate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mod {
|
||||||
|
case shareModeCreateAlone:
|
||||||
|
share, err := model.NewSoloShare(s.store, s.cfg.PublicURL, linkGen)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if customLink != "" && linkIDRe.MatchString(customLink) {
|
||||||
|
if err := s.tryCustomLink(ctx, share, customLink); err == nil {
|
||||||
|
share.SetID(customLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
share.SetAdoptable(adoptable)
|
||||||
|
share.SetHost(session.ID())
|
||||||
|
share.SetExpire(expire)
|
||||||
|
if err := share.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.AddTarget(share.ID())
|
||||||
|
if err := session.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
fmt.Fprintln(w, session.ID())
|
||||||
|
fmt.Fprintln(w, share.ViewLink())
|
||||||
|
fmt.Fprintln(w, share.ID())
|
||||||
|
|
||||||
|
case shareModeCreateGroup:
|
||||||
|
share, err := model.NewGroupShare(s.store, s.cfg.PublicURL, linkGen)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if customLink != "" && linkIDRe.MatchString(customLink) {
|
||||||
|
if err := s.tryCustomLink(ctx, share, customLink); err == nil {
|
||||||
|
share.SetID(customLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
share.AddHost(nickname, session.ID())
|
||||||
|
share.SetExpire(expire)
|
||||||
|
if err := share.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.AddTarget(share.ID())
|
||||||
|
if err := session.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
fmt.Fprintln(w, session.ID())
|
||||||
|
fmt.Fprintln(w, share.ViewLink())
|
||||||
|
fmt.Fprintln(w, share.Pin())
|
||||||
|
fmt.Fprintln(w, share.ID())
|
||||||
|
|
||||||
|
case shareModeJoinGroup:
|
||||||
|
share, err := model.LoadGroupShareByPin(ctx, s.store, pin, s.cfg.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Invalid group PIN!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
share.AddHost(nickname, session.ID())
|
||||||
|
if err := share.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.AddTarget(share.ID())
|
||||||
|
if err := session.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
fmt.Fprintln(w, session.ID())
|
||||||
|
fmt.Fprintln(w, share.ViewLink())
|
||||||
|
fmt.Fprintln(w, share.ID())
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Fprintln(w, "Unsupported share mode!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type customLinkSetter interface {
|
||||||
|
SetID(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) tryCustomLink(ctx context.Context, _ customLinkSetter, link string) error {
|
||||||
|
exists, err := s.store.Exists(ctx, "locdata-"+link)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf("link already exists")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
50
api/dynamic.go
Normal file
50
api/dynamic.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type velocityUnit struct {
|
||||||
|
MpsMultiplier float64 `json:"mpsMultiplier"`
|
||||||
|
Unit string `json:"unit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleDynamic(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/javascript; charset=utf-8")
|
||||||
|
|
||||||
|
var velUnit velocityUnit
|
||||||
|
switch s.cfg.VelocityUnit {
|
||||||
|
case config.MilesPerHour:
|
||||||
|
velUnit = velocityUnit{MpsMultiplier: 3.6 * 0.6213712, Unit: "mph"}
|
||||||
|
case config.MetersPerSecond:
|
||||||
|
velUnit = velocityUnit{MpsMultiplier: 1, Unit: "m/s"}
|
||||||
|
default:
|
||||||
|
velUnit = velocityUnit{MpsMultiplier: 3.6, Unit: "km/h"}
|
||||||
|
}
|
||||||
|
|
||||||
|
tileURI, _ := json.Marshal(s.cfg.MapTileURI)
|
||||||
|
attribution, _ := json.Marshal(s.cfg.MapAttribution)
|
||||||
|
defaultZoom, _ := json.Marshal(s.cfg.DefaultZoom)
|
||||||
|
maxZoom, _ := json.Marshal(s.cfg.MaxZoom)
|
||||||
|
maxPoints, _ := json.Marshal(s.cfg.MaxShownPts)
|
||||||
|
velDelta, _ := json.Marshal(s.cfg.VelocityDataPts)
|
||||||
|
trailColor, _ := json.Marshal(s.cfg.TrailColor)
|
||||||
|
velUnitJSON, _ := json.Marshal(velUnit)
|
||||||
|
offlineTimeout, _ := json.Marshal(s.cfg.OfflineTimeout)
|
||||||
|
requestTimeout, _ := json.Marshal(s.cfg.RequestTimeout)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "var TILE_URI = %s;\n", tileURI)
|
||||||
|
fmt.Fprintf(w, "var ATTRIBUTION = %s;\n", attribution)
|
||||||
|
fmt.Fprintf(w, "var DEFAULT_ZOOM = %s;\n", defaultZoom)
|
||||||
|
fmt.Fprintf(w, "var MAX_ZOOM = %s;\n", maxZoom)
|
||||||
|
fmt.Fprintf(w, "var MAX_POINTS = %s;\n", maxPoints)
|
||||||
|
fmt.Fprintf(w, "var VELOCITY_DELTA_TIME = %s;\n", velDelta)
|
||||||
|
fmt.Fprintf(w, "var TRAIL_COLOR = %s;\n", trailColor)
|
||||||
|
fmt.Fprintf(w, "var VELOCITY_UNIT = %s;\n", velUnitJSON)
|
||||||
|
fmt.Fprintf(w, "var OFFLINE_TIMEOUT = %s;\n", offlineTimeout)
|
||||||
|
fmt.Fprintf(w, "var REQUEST_TIMEOUT = %s;\n", requestTimeout)
|
||||||
|
}
|
||||||
108
api/fetch.go
Normal file
108
api/fetch.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type soloResponse struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Expire int64 `json:"expire"`
|
||||||
|
ServerTime float64 `json:"serverTime"`
|
||||||
|
Interval float64 `json:"interval"`
|
||||||
|
Points [][]any `json:"points"`
|
||||||
|
Encrypted bool `json:"encrypted"`
|
||||||
|
Salt string `json:"salt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type groupResponse struct {
|
||||||
|
Type int `json:"type"`
|
||||||
|
Expire int64 `json:"expire"`
|
||||||
|
ServerTime float64 `json:"serverTime"`
|
||||||
|
Interval float64 `json:"interval"`
|
||||||
|
Points map[string][][]any `json:"points"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleFetch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
id := r.URL.Query().Get("id")
|
||||||
|
if id == "" {
|
||||||
|
fmt.Fprintln(w, "Invalid session!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sinceStr := r.URL.Query().Get("since")
|
||||||
|
since := float64(0)
|
||||||
|
if sinceStr != "" {
|
||||||
|
since, _ = strconv.ParseFloat(sinceStr, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
shareType, err := model.LoadShareType(ctx, s.store, id)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintln(w, "Invalid session!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/json")
|
||||||
|
now := float64(time.Now().UnixNano()) / 1e9
|
||||||
|
|
||||||
|
switch shareType {
|
||||||
|
case model.ShareTypeAlone:
|
||||||
|
share, err := model.LoadSoloShare(ctx, s.store, id, s.cfg.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintln(w, "Invalid session!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := model.LoadSession(ctx, s.store, share.Host(), s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintln(w, "Invalid session!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := soloResponse{
|
||||||
|
Type: share.Type(),
|
||||||
|
Expire: share.Expire().Unix(),
|
||||||
|
ServerTime: now,
|
||||||
|
Interval: session.Interval(),
|
||||||
|
Points: session.GetPoints(since),
|
||||||
|
Encrypted: session.Encrypted(),
|
||||||
|
Salt: session.Salt(),
|
||||||
|
}
|
||||||
|
if resp.Points == nil {
|
||||||
|
resp.Points = [][]any{}
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
|
||||||
|
case model.ShareTypeGroup:
|
||||||
|
share, err := model.LoadGroupShare(ctx, s.store, id, s.cfg.PublicURL)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintln(w, "Invalid session!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
points, _ := share.GetAllPoints(ctx, since, s.cfg.MaxCachedPts)
|
||||||
|
if points == nil {
|
||||||
|
points = make(map[string][][]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := groupResponse{
|
||||||
|
Type: share.Type(),
|
||||||
|
Expire: share.Expire().Unix(),
|
||||||
|
ServerTime: now,
|
||||||
|
Interval: share.GetAutoInterval(ctx, s.cfg.MaxCachedPts),
|
||||||
|
Points: points,
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
60
api/newlink.go
Normal file
60
api/newlink.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleNewLink(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := r.FormValue("sid")
|
||||||
|
adoptable := r.FormValue("ado") == "1"
|
||||||
|
|
||||||
|
if sid == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := model.LoadSession(ctx, s.store, sid, s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkGen := func() (string, error) {
|
||||||
|
return s.linkgen.Generate(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
share, err := model.NewSoloShare(s.store, s.cfg.PublicURL, linkGen)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
share.SetAdoptable(adoptable)
|
||||||
|
share.SetHost(session.ID())
|
||||||
|
share.SetExpire(session.Expire())
|
||||||
|
|
||||||
|
if err := share.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.AddTarget(share.ID())
|
||||||
|
if err := session.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
fmt.Fprintln(w, share.ViewLink())
|
||||||
|
fmt.Fprintln(w, share.ID())
|
||||||
|
}
|
||||||
99
api/post.go
Normal file
99
api/post.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handlePost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := r.FormValue("sid")
|
||||||
|
lat := r.FormValue("lat")
|
||||||
|
lon := r.FormValue("lon")
|
||||||
|
ts := r.FormValue("time")
|
||||||
|
|
||||||
|
if sid == "" || lat == "" || lon == "" || ts == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := model.LoadSession(ctx, s.store, sid, s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var point []any
|
||||||
|
|
||||||
|
if !session.Encrypted() {
|
||||||
|
latF, _ := strconv.ParseFloat(lat, 64)
|
||||||
|
lonF, _ := strconv.ParseFloat(lon, 64)
|
||||||
|
timeF, _ := strconv.ParseFloat(ts, 64)
|
||||||
|
|
||||||
|
if latF < -90 || latF > 90 || lonF < -180 || lonF > 180 {
|
||||||
|
fmt.Fprintln(w, "Invalid location!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var speed, acc *float64
|
||||||
|
if spd := r.FormValue("spd"); spd != "" {
|
||||||
|
v, _ := strconv.ParseFloat(spd, 64)
|
||||||
|
speed = &v
|
||||||
|
}
|
||||||
|
if a := r.FormValue("acc"); a != "" {
|
||||||
|
v, _ := strconv.ParseFloat(a, 64)
|
||||||
|
acc = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
prv := 0
|
||||||
|
if r.FormValue("prv") == "1" {
|
||||||
|
prv = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
point = []any{latF, lonF, timeF, prv, acc, speed}
|
||||||
|
} else {
|
||||||
|
iv := r.FormValue("iv")
|
||||||
|
if iv == "" {
|
||||||
|
fmt.Fprintln(w, "Missing data!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var speed, acc, prv any
|
||||||
|
if spd := r.FormValue("spd"); spd != "" {
|
||||||
|
speed = spd
|
||||||
|
}
|
||||||
|
if a := r.FormValue("acc"); a != "" {
|
||||||
|
acc = a
|
||||||
|
}
|
||||||
|
if p := r.FormValue("prv"); p != "" {
|
||||||
|
prv = p
|
||||||
|
}
|
||||||
|
|
||||||
|
point = []any{iv, lat, lon, ts, prv, acc, speed}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.AddPoint(point)
|
||||||
|
if err := session.Save(ctx); err != nil {
|
||||||
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if session.HasExpired() {
|
||||||
|
fmt.Fprintln(w, "Session expired!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
fmt.Fprintf(w, "%s?%%s\n", s.cfg.PublicURL)
|
||||||
|
fmt.Fprintln(w, strings.Join(session.Targets(), ","))
|
||||||
|
}
|
||||||
61
api/server.go
Normal file
61
api/server.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/auth"
|
||||||
|
"github.com/parkan/go-hauk/config"
|
||||||
|
"github.com/parkan/go-hauk/frontend"
|
||||||
|
"github.com/parkan/go-hauk/linkgen"
|
||||||
|
"github.com/parkan/go-hauk/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
const backendVersion = "1.6.2-go"
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
mux *http.ServeMux
|
||||||
|
cfg *config.Config
|
||||||
|
store store.Store
|
||||||
|
auth auth.Authenticator
|
||||||
|
linkgen *linkgen.Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(cfg *config.Config, s store.Store) *Server {
|
||||||
|
srv := &Server{
|
||||||
|
mux: http.NewServeMux(),
|
||||||
|
cfg: cfg,
|
||||||
|
store: s,
|
||||||
|
linkgen: linkgen.New(s, cfg.LinkStyle),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cfg.AuthMethod {
|
||||||
|
case config.AuthHtpasswd:
|
||||||
|
srv.auth = auth.NewHtpasswdAuth(cfg.HtpasswdPath)
|
||||||
|
case config.AuthLDAP:
|
||||||
|
srv.auth = auth.NewLDAPAuth(
|
||||||
|
cfg.LDAPUri, cfg.LDAPBaseDN, cfg.LDAPBindDN,
|
||||||
|
cfg.LDAPBindPass, cfg.LDAPUserFilter, cfg.LDAPStartTLS,
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
srv.auth = auth.NewPasswordAuth(cfg.PasswordHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv.mux.HandleFunc("POST /api/create.php", srv.handleCreate)
|
||||||
|
srv.mux.HandleFunc("POST /api/post.php", srv.handlePost)
|
||||||
|
srv.mux.HandleFunc("GET /api/fetch.php", srv.handleFetch)
|
||||||
|
srv.mux.HandleFunc("POST /api/stop.php", srv.handleStop)
|
||||||
|
srv.mux.HandleFunc("POST /api/adopt.php", srv.handleAdopt)
|
||||||
|
srv.mux.HandleFunc("POST /api/new-link.php", srv.handleNewLink)
|
||||||
|
srv.mux.HandleFunc("GET /dynamic.js.php", srv.handleDynamic)
|
||||||
|
|
||||||
|
staticFS, _ := fs.Sub(frontend.Files, ".")
|
||||||
|
srv.mux.Handle("/", http.FileServer(http.FS(staticFS)))
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("X-Hauk-Version", backendVersion)
|
||||||
|
s.mux.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
96
api/stop.go
Normal file
96
api/stop.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/parkan/go-hauk/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) handleStop(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
http.Error(w, "bad request", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sid := r.FormValue("sid")
|
||||||
|
if sid == "" {
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lid := r.FormValue("lid")
|
||||||
|
|
||||||
|
session, err := model.LoadSession(ctx, s.store, sid, s.cfg.MaxCachedPts)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if lid != "" {
|
||||||
|
found := false
|
||||||
|
for _, t := range session.Targets() {
|
||||||
|
if t == lid {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shareType, err := model.LoadShareType(ctx, s.store, lid)
|
||||||
|
if err == nil {
|
||||||
|
switch shareType {
|
||||||
|
case model.ShareTypeAlone:
|
||||||
|
share, err := model.LoadSoloShare(ctx, s.store, lid, s.cfg.PublicURL)
|
||||||
|
if err == nil {
|
||||||
|
share.Delete(ctx)
|
||||||
|
}
|
||||||
|
case model.ShareTypeGroup:
|
||||||
|
share, err := model.LoadGroupShare(ctx, s.store, lid, s.cfg.PublicURL)
|
||||||
|
if err == nil {
|
||||||
|
share.RemoveHost(sid)
|
||||||
|
if len(share.Hosts()) == 0 {
|
||||||
|
share.Delete(ctx)
|
||||||
|
} else {
|
||||||
|
share.Save(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.RemoveTarget(lid)
|
||||||
|
session.Save(ctx)
|
||||||
|
} else {
|
||||||
|
for _, t := range session.Targets() {
|
||||||
|
shareType, err := model.LoadShareType(ctx, s.store, t)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch shareType {
|
||||||
|
case model.ShareTypeAlone:
|
||||||
|
share, err := model.LoadSoloShare(ctx, s.store, t, s.cfg.PublicURL)
|
||||||
|
if err == nil {
|
||||||
|
share.Delete(ctx)
|
||||||
|
}
|
||||||
|
case model.ShareTypeGroup:
|
||||||
|
share, err := model.LoadGroupShare(ctx, s.store, t, s.cfg.PublicURL)
|
||||||
|
if err == nil {
|
||||||
|
share.RemoveHost(sid)
|
||||||
|
if len(share.Hosts()) == 0 {
|
||||||
|
share.Delete(ctx)
|
||||||
|
} else {
|
||||||
|
share.Save(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session.Delete(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(w, "OK")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user