mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-13 18:16:06 +00:00
fix(server): name the player on every played word, and bound the process
PlayedWord.player_id was declared and read by the client but never set by the server, so a room of three or four never showed who played each word. The chain byline now comes from the room, with a producer-side test. The process also gains the ceilings it was missing: a cap on live rooms and on open sockets, a per-connection frame-rate limit so a payload-less frame is no longer free, and an opt-in trusted-proxy list so the join limiter can tell players apart behind the documented reverse proxy instead of putting them in one bucket. The typed word is sanitized before the engine stores it, since every seat is shown it; a room exiting on its idle clock releases the sessions still bound to it; the dictionary builder escapes its SQLite path like the store does and renames over the old database instead of deleting it first.
This commit is contained in:
1 parent
a5052fca3f
commit
442ced32cc
14 files changed
+494
-54
No files matched your search
@@ -30,6 +30,7 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/tiennm99dev/noitu/server/internal/dictionary"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
@@ -225,7 +226,7 @@ func parseSenses(cells []string) []sense {
|
||||
// depends on. The in-memory checks above can only prove what the builder
|
||||
// intended; this proves what actually landed on disk.
|
||||
func verify(path string, minWords int, requireCoverage bool) error {
|
||||
db, err := sql.Open("sqlite", "file:"+path+"?mode=ro")
|
||||
db, err := sql.Open("sqlite", dictionary.DSN(path, true))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -376,13 +377,17 @@ func write(path string, words map[string]entry, meanings map[string][]sense, ali
|
||||
return err
|
||||
}
|
||||
|
||||
// os.Rename replaces the destination atomically on POSIX; on Windows it
|
||||
// fails if the target exists, so clear it first.
|
||||
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("remove existing output: %w", err)
|
||||
}
|
||||
// os.Rename replaces the destination atomically on POSIX, so the good
|
||||
// database is never missing from disk. Only where that fails — Windows
|
||||
// refuses to rename over an existing file — is the target cleared first,
|
||||
// accepting the window there rather than opening it everywhere.
|
||||
if err := os.Rename(tmp, path); err != nil {
|
||||
return fmt.Errorf("move temp database into place: %w", err)
|
||||
if rmErr := os.Remove(path); rmErr != nil && !errors.Is(rmErr, os.ErrNotExist) {
|
||||
return fmt.Errorf("remove existing output: %w", rmErr)
|
||||
}
|
||||
if err := os.Rename(tmp, path); err != nil {
|
||||
return fmt.Errorf("move temp database into place: %w", err)
|
||||
}
|
||||
}
|
||||
committed = true
|
||||
|
||||
@@ -390,7 +395,7 @@ func write(path string, words map[string]entry, meanings map[string][]sense, ali
|
||||
}
|
||||
|
||||
func writeTo(path string, words map[string]entry, meanings map[string][]sense, aliases map[string]string, src sourceSpec) error {
|
||||
db, err := sql.Open("sqlite", "file:"+path)
|
||||
db, err := sql.Open("sqlite", dictionary.DSN(path, false))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create output: %w", err)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -36,6 +37,9 @@ type config struct {
|
||||
grace time.Duration
|
||||
allowedOrigins []string
|
||||
webDir string
|
||||
trustedProxies []string
|
||||
maxRooms int
|
||||
maxConnections int
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -74,6 +78,9 @@ func run() error {
|
||||
GraceFor: cfg.grace,
|
||||
AllowedOrigins: cfg.allowedOrigins,
|
||||
WebDir: cfg.webDir,
|
||||
TrustedProxies: cfg.trustedProxies,
|
||||
MaxRooms: cfg.maxRooms,
|
||||
MaxConnections: cfg.maxConnections,
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
@@ -114,9 +121,27 @@ func loadConfig() config {
|
||||
grace: envDuration("NOITU_GRACE", defaultGrace),
|
||||
allowedOrigins: envList("NOITU_ALLOWED_ORIGINS"),
|
||||
webDir: env("NOITU_WEB_DIR", ""),
|
||||
trustedProxies: envList("NOITU_TRUSTED_PROXIES"),
|
||||
maxRooms: envInt("NOITU_MAX_ROOMS", 0),
|
||||
maxConnections: envInt("NOITU_MAX_CONNECTIONS", 0),
|
||||
}
|
||||
}
|
||||
|
||||
// envInt falls back loudly, like envDuration. Zero means "use the built-in
|
||||
// default", so it is what an unset or invalid value becomes.
|
||||
func envInt(key string, fallback int) int {
|
||||
raw := strings.TrimSpace(os.Getenv(key))
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
n, err := strconv.Atoi(raw)
|
||||
if err != nil || n < 0 {
|
||||
slog.Warn("ignoring invalid integer", "key", key, "value", raw, "using", fallback)
|
||||
return fallback
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func env(key, fallback string) string {
|
||||
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
||||
return v
|
||||
|
||||
Reference in new issue
Block a user