srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/server/migrations
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-09-11 21:53:00 +0200
committersrdusr <[email protected]>2025-09-11 21:53:00 +0200
commita726d9f5fb56e1fd7983c5ac806d408ad78daa86 (patch)
tree4129a058e2a9e8fa4e0a51e3ed943b746750c3cf /crates/server/migrations
parent8c95e90fa54db565919ec323818f77e7256812c8 (diff)
downloadtyperpunk-a726d9f5fb56e1fd7983c5ac806d408ad78daa86.tar.gz
typerpunk-a726d9f5fb56e1fd7983c5ac806d408ad78daa86.zip
Add multiplayer bots, typing languages, and rework the UI layout
Multiplayer - Quick match: POST /api/multiplayer/quickmatch returns whichever room is still filling, or opens one. Players never see a room code; joining by code stays for racing specific people. - Bots fill quick-match rooms after a short wait so a new game is never an empty lobby. They only ever join quick-match rooms, never a room opened by code. One or two per room, drawn from separate ~40 and ~80 WPM tiers so two bots are never near each other's pace, and they stall to correct mistakes rather than typing a clean straight line. - Live player count via GET /api/multiplayer/online, shown on the Multiplayer control and under the main menu's Multiplayer button. - Per-racer colours: you are the theme accent, opponents take distinct hues that stay the same from lobby to race. - The countdown no longer holds the room lock for its full three seconds, which is what reset clients mid-countdown. Typing languages - 16 languages for the generated-word modes, each with its own high-frequency vocabulary rather than a translation of the English list. - Picker in the top-right rail; non-English uses its own list at every difficulty tier instead of falling back to English words. Fix UTF-8 accuracy in the game core - update_game_state mixed byte and character counts: total_characters_typed accumulated byte-length deltas while total_correct_characters compared a char index against that byte count. Equal on ASCII, so it went unnoticed; a correctly typed Spanish passage scored 6%. The old byte slicing would also have panicked if an index landed inside a multi-byte character. Rewritten char-based, with regression tests. Programming mode - Replaced prose about programming with real code: 26 syntax-highlighted snippets across JavaScript, Python, Rust, C/Go/Java and shell. Single-line by necessity, since the typing input is a single-line field. Layout and readability - One icon rail arrangement on every screen: Settings/Store under the wordmark, Language/Theme/Friends/Account top-right, Stats/Leaderboard/ Multiplayer bottom-right. - Main menu: mode picker moved out of the Single Player button, which it was notching a divider through and pushing the label off-centre. - Escape returns to the menu, closing any open popover first, and confirms before abandoning a live race. - Split --text-color and --sub-color per theme; they shared one value that measured 3.65:1 against the background, below the 4.5:1 body-text floor. - Semantic colours used in exactly one place each: gold for a personal best, amber for the race countdown and the mobile-result badge. - Passage now sits in the same place on the typing and end screens, and its column is a whole number of characters wide so wrapping cannot leave a permanent gap on the right. - End screen: keystrokes and a correct/wrong/extra/missed split, attribution carried over from the typing screen, and a graph with a separate error axis, axis titles including seconds, and smoothed lines.
Diffstat (limited to 'crates/server/migrations')
-rw-r--r--crates/server/migrations/0001_users_sessions.sql14
-rw-r--r--crates/server/migrations/0002_test_results.sql13
-rw-r--r--crates/server/migrations/0003_friendships.sql11
-rw-r--r--crates/server/migrations/0004_spotify_tokens.sql6
-rw-r--r--crates/server/migrations/0005_fairness.sql2
-rw-r--r--crates/server/migrations/0006_cosmetics.sql33
-rw-r--r--crates/server/migrations/0007_api_tokens.sql8
7 files changed, 87 insertions, 0 deletions
diff --git a/crates/server/migrations/0001_users_sessions.sql b/crates/server/migrations/0001_users_sessions.sql
new file mode 100644
index 0000000..1fe3cee
--- /dev/null
+++ b/crates/server/migrations/0001_users_sessions.sql
@@ -0,0 +1,14 @@
+CREATE TABLE users (
+ id TEXT PRIMARY KEY,
+ username TEXT NOT NULL UNIQUE,
+ password_hash TEXT NOT NULL,
+ created_at TEXT NOT NULL
+);
+
+CREATE TABLE sessions (
+ id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ expires_at TEXT NOT NULL
+);
+
+CREATE INDEX idx_sessions_user_id ON sessions(user_id);
diff --git a/crates/server/migrations/0002_test_results.sql b/crates/server/migrations/0002_test_results.sql
new file mode 100644
index 0000000..aa876b5
--- /dev/null
+++ b/crates/server/migrations/0002_test_results.sql
@@ -0,0 +1,13 @@
+CREATE TABLE test_results (
+ id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ mode_key TEXT NOT NULL,
+ wpm REAL NOT NULL,
+ raw_wpm REAL NOT NULL,
+ accuracy REAL NOT NULL,
+ time_seconds REAL NOT NULL,
+ created_at TEXT NOT NULL
+);
+
+CREATE INDEX idx_test_results_user_id ON test_results(user_id);
+CREATE INDEX idx_test_results_mode_key ON test_results(mode_key);
diff --git a/crates/server/migrations/0003_friendships.sql b/crates/server/migrations/0003_friendships.sql
new file mode 100644
index 0000000..c44e62a
--- /dev/null
+++ b/crates/server/migrations/0003_friendships.sql
@@ -0,0 +1,11 @@
+CREATE TABLE friendships (
+ id TEXT PRIMARY KEY,
+ requester_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ addressee_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ status TEXT NOT NULL CHECK (status IN ('pending', 'accepted')),
+ created_at TEXT NOT NULL,
+ UNIQUE (requester_id, addressee_id)
+);
+
+CREATE INDEX idx_friendships_requester ON friendships(requester_id);
+CREATE INDEX idx_friendships_addressee ON friendships(addressee_id);
diff --git a/crates/server/migrations/0004_spotify_tokens.sql b/crates/server/migrations/0004_spotify_tokens.sql
new file mode 100644
index 0000000..0129660
--- /dev/null
+++ b/crates/server/migrations/0004_spotify_tokens.sql
@@ -0,0 +1,6 @@
+CREATE TABLE spotify_tokens (
+ user_id TEXT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
+ access_token TEXT NOT NULL,
+ refresh_token TEXT NOT NULL,
+ expires_at TEXT NOT NULL
+);
diff --git a/crates/server/migrations/0005_fairness.sql b/crates/server/migrations/0005_fairness.sql
new file mode 100644
index 0000000..28152d6
--- /dev/null
+++ b/crates/server/migrations/0005_fairness.sql
@@ -0,0 +1,2 @@
+ALTER TABLE test_results ADD COLUMN device_type TEXT NOT NULL DEFAULT 'desktop';
+ALTER TABLE test_results ADD COLUMN flagged INTEGER NOT NULL DEFAULT 0;
diff --git a/crates/server/migrations/0006_cosmetics.sql b/crates/server/migrations/0006_cosmetics.sql
new file mode 100644
index 0000000..a795e0b
--- /dev/null
+++ b/crates/server/migrations/0006_cosmetics.sql
@@ -0,0 +1,33 @@
+- Scoped to 'caret' and 'flair' only, not 'theme' - the app already has 7
+- free built-in themes (see web/src/theme.js), and selling some themes
+- while others stay free in the same picker would be a confusing mix of
+- paid and unpaid options in one list. Caret color and a small username
+- flair badge are both genuinely new, non-overlapping cosmetic slots.
+CREATE TABLE cosmetics (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ category TEXT NOT NULL CHECK (category IN ('caret', 'flair')),
+ price_cents INTEGER NOT NULL,
+ - Caret: a CSS color. Flair: a short glyph/emoji shown next to a
+ - username on the leaderboard and public profile.
+ value TEXT NOT NULL
+);
+
+CREATE TABLE user_cosmetics (
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ cosmetic_id TEXT NOT NULL REFERENCES cosmetics(id) ON DELETE CASCADE,
+ acquired_at TEXT NOT NULL,
+ PRIMARY KEY (user_id, cosmetic_id)
+);
+
+ALTER TABLE users ADD COLUMN equipped_caret TEXT;
+ALTER TABLE users ADD COLUMN equipped_flair TEXT;
+
+- A small seed catalog so the store isn't empty on a fresh install.
+INSERT INTO cosmetics (id, name, category, price_cents, value) VALUES
+ ('caret-magenta', 'Magenta Caret', 'caret', 199, '#ff2fb0'),
+ ('caret-amber', 'Amber Caret', 'caret', 199, '#ffb703'),
+ ('caret-cyan', 'Cyan Caret', 'caret', 199, '#00e5ff'),
+ ('flair-star', 'Star', 'flair', 149, 'star'),
+ ('flair-bolt', 'Bolt', 'flair', 149, 'bolt'),
+ ('flair-skull', 'Skull', 'flair', 149, 'skull');
diff --git a/crates/server/migrations/0007_api_tokens.sql b/crates/server/migrations/0007_api_tokens.sql
new file mode 100644
index 0000000..61b6697
--- /dev/null
+++ b/crates/server/migrations/0007_api_tokens.sql
@@ -0,0 +1,8 @@
+- Long-lived personal-access tokens for CLI-style clients (the TUI) that
+- have no cookie jar. Separate from the browser's session cookie so
+- revoking one doesn't touch the other.
+CREATE TABLE api_tokens (
+ token TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL REFERENCES users(id),
+ created_at TEXT NOT NULL
+);