- Added support for Google OAuth login, including new API endpoints for OAuth providers and callbacks. - Updated user authentication logic to handle OAuth-only users. - Enhanced README.md and deployment documentation with OAuth setup instructions. - Modified frontend components to include OAuth login options and improved error handling. - Updated configuration files to include new environment variables for OAuth integration.
210 lines
4.5 KiB
Go
210 lines
4.5 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
// App is the main application (map server) state.
|
|
type App struct {
|
|
gridStorage string
|
|
frontendRoot string
|
|
db *bbolt.DB
|
|
|
|
characters map[string]Character
|
|
chmu sync.RWMutex
|
|
|
|
gridUpdates topic
|
|
mergeUpdates mergeTopic
|
|
}
|
|
|
|
// NewApp creates an App with the given storage paths and database.
|
|
// frontendRoot is the directory for the map SPA (e.g. "frontend").
|
|
func NewApp(gridStorage, frontendRoot string, db *bbolt.DB) (*App, error) {
|
|
return &App{
|
|
gridStorage: gridStorage,
|
|
frontendRoot: frontendRoot,
|
|
db: db,
|
|
characters: make(map[string]Character),
|
|
}, nil
|
|
}
|
|
|
|
type Session struct {
|
|
ID string
|
|
Username string
|
|
Auths Auths `json:"-"`
|
|
TempAdmin bool
|
|
}
|
|
|
|
type Character struct {
|
|
Name string `json:"name"`
|
|
ID int `json:"id"`
|
|
Map int `json:"map"`
|
|
Position Position `json:"position"`
|
|
Type string `json:"type"`
|
|
updated time.Time
|
|
}
|
|
|
|
type Marker struct {
|
|
Name string `json:"name"`
|
|
ID int `json:"id"`
|
|
GridID string `json:"gridID"`
|
|
Position Position `json:"position"`
|
|
Image string `json:"image"`
|
|
Hidden bool `json:"hidden"`
|
|
}
|
|
|
|
type FrontendMarker struct {
|
|
Name string `json:"name"`
|
|
ID int `json:"id"`
|
|
Map int `json:"map"`
|
|
Position Position `json:"position"`
|
|
Image string `json:"image"`
|
|
Hidden bool `json:"hidden"`
|
|
}
|
|
|
|
type MapInfo struct {
|
|
ID int
|
|
Name string
|
|
Hidden bool
|
|
Priority bool
|
|
}
|
|
|
|
type GridData struct {
|
|
ID string
|
|
Coord Coord
|
|
NextUpdate time.Time
|
|
Map int
|
|
}
|
|
|
|
type Coord struct {
|
|
X int `json:"x"`
|
|
Y int `json:"y"`
|
|
}
|
|
|
|
type Position struct {
|
|
X int `json:"x"`
|
|
Y int `json:"y"`
|
|
}
|
|
|
|
func (c Coord) Name() string {
|
|
return fmt.Sprintf("%d_%d", c.X, c.Y)
|
|
}
|
|
|
|
func (c Coord) Parent() Coord {
|
|
if c.X < 0 {
|
|
c.X--
|
|
}
|
|
if c.Y < 0 {
|
|
c.Y--
|
|
}
|
|
return Coord{
|
|
X: c.X / 2,
|
|
Y: c.Y / 2,
|
|
}
|
|
}
|
|
|
|
type Auths []string
|
|
|
|
func (a Auths) Has(auth string) bool {
|
|
for _, v := range a {
|
|
if v == auth {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
const (
|
|
AUTH_ADMIN = "admin"
|
|
AUTH_MAP = "map"
|
|
AUTH_MARKERS = "markers"
|
|
AUTH_UPLOAD = "upload"
|
|
)
|
|
|
|
type User struct {
|
|
Pass []byte
|
|
Auths Auths
|
|
Tokens []string
|
|
// OAuth: provider -> subject (unique ID from provider)
|
|
OAuthLinks map[string]string `json:"oauth_links,omitempty"` // e.g. "google" -> "123456789"
|
|
}
|
|
|
|
type Page struct {
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
// serveMapFrontend serves the map SPA: static files from frontend, fallback to index.html for client-side routes.
|
|
func (a *App) serveMapFrontend(rw http.ResponseWriter, req *http.Request) {
|
|
path := req.URL.Path
|
|
if len(path) <= len("/map/") {
|
|
path = ""
|
|
} else {
|
|
path = path[len("/map/"):]
|
|
}
|
|
root := a.frontendRoot
|
|
if path == "" {
|
|
path = "index.html"
|
|
}
|
|
path = filepath.Clean(path)
|
|
if path == "." || path == ".." || (len(path) >= 2 && path[:2] == "..") {
|
|
http.NotFound(rw, req)
|
|
return
|
|
}
|
|
tryPaths := []string{filepath.Join("map", path), path}
|
|
var f http.File
|
|
for _, p := range tryPaths {
|
|
var err error
|
|
f, err = http.Dir(root).Open(p)
|
|
if err == nil {
|
|
path = p
|
|
break
|
|
}
|
|
}
|
|
if f == nil {
|
|
http.ServeFile(rw, req, filepath.Join(root, "index.html"))
|
|
return
|
|
}
|
|
defer f.Close()
|
|
stat, err := f.Stat()
|
|
if err != nil || stat.IsDir() {
|
|
http.ServeFile(rw, req, filepath.Join(root, "index.html"))
|
|
return
|
|
}
|
|
http.ServeContent(rw, req, stat.Name(), stat.ModTime(), f)
|
|
}
|
|
|
|
// CleanChars runs a background loop that removes stale character entries. Call once as a goroutine.
|
|
func (a *App) CleanChars() {
|
|
for range time.Tick(time.Second * 10) {
|
|
a.chmu.Lock()
|
|
for n, c := range a.characters {
|
|
if c.updated.Before(time.Now().Add(-10 * time.Second)) {
|
|
delete(a.characters, n)
|
|
}
|
|
}
|
|
a.chmu.Unlock()
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers all HTTP handlers for the app.
|
|
func (a *App) RegisterRoutes() {
|
|
http.HandleFunc("/client/", a.client)
|
|
|
|
http.HandleFunc("/login", a.redirectLogin)
|
|
http.HandleFunc("/logout", a.redirectLogout)
|
|
http.HandleFunc("/admin", a.redirectAdmin)
|
|
http.HandleFunc("/admin/", a.redirectAdmin)
|
|
http.HandleFunc("/", a.redirectRoot)
|
|
|
|
http.HandleFunc("/map/api/", a.apiRouter)
|
|
http.HandleFunc("/map/updates", a.watchGridUpdates)
|
|
http.HandleFunc("/map/grids/", a.gridTile)
|
|
http.HandleFunc("/map/", a.serveMapFrontend)
|
|
}
|