srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/server/migrations
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2025-12-14 09:24:00 +0200
committersrdusr <[email protected]>2025-12-14 09:24:00 +0200
commit3dbebfbc9345d2603908f32c0dabebc0ff21feb3 (patch)
tree61a4f60187046a087424845eb8e22239ccd7d6ee /crates/server/migrations
parent8fc4440150ce84c9cc3f5ec13d50beb58a5b65bc (diff)
downloadtyperpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.tar.gz
typerpunk-3dbebfbc9345d2603908f32c0dabebc0ff21feb3.zip
Add community text submissions, and make custom text usable for study
Submissions - POST /api/texts proposes a passage; nothing reaches players until a moderator approves it. GET /api/texts serves the approved set, which the client merges on top of its bundled packs at startup. - Validation the server enforces rather than trusts: category from a fixed list, 40 to 600 characters, no control characters (a newline makes a passage untypeable in a single-line input), attribution length, and a unique index on md5(lower(btrim(content))) so the same passage cannot be submitted twice under different whitespace or casing. - Moderation is a flag on users. The queue and the review endpoint both refuse a non-moderator, and reviewing an already-reviewed submission is a 404 rather than a silent second write. - Submissions are rate limited per user: enough for a real contributor, not enough to fill the queue from a script. - A Contribute screen carries the form, your own submissions with their status, and - for moderators only - the review queue. This is the half of TypeRacer's model the packs could not reach by authoring: their corpus is roughly twelve thousand passages, grown by submission. Custom text as a study tool - Imported documents are kept between visits, with how far through each one you are. Custom text lived only in memory, so importing a set of notes and reloading the page lost them - fine for pasting a paragraph to race, useless for working through a file over several sittings. - Position is recorded when a segment is finished, not when the next is started, so closing the tab after a segment does not lose it. - Markdown is chunked as markdown: fenced code blocks are kept whole and typed line by line, and the decoration - hashes, asterisks, backticks, link brackets, table pipes - is stripped so what you retype is the material rather than the punctuation around it. - Everything stays on the device. Notes are not uploaded anywhere. Fixed while doing it: a chunk could contain a newline, which cannot be typed in a single-line input at all. Any paragraph with a line break inside it -- ordinary in notes and in wrapped prose - produced an unfinishable segment. Whitespace inside a chunk is now flattened.
Diffstat (limited to 'crates/server/migrations')
-rw-r--r--crates/server/migrations/0010_text_submissions.sql28
1 files changed, 28 insertions, 0 deletions
diff --git a/crates/server/migrations/0010_text_submissions.sql b/crates/server/migrations/0010_text_submissions.sql
new file mode 100644
index 0000000..b735a8f
--- /dev/null
+++ b/crates/server/migrations/0010_text_submissions.sql
@@ -0,0 +1,28 @@
+- Community-submitted passages, the way a text corpus actually grows past
+- what fits in a repository. Nothing here reaches players until a moderator
+- approves it.
+ALTER TABLE users ADD COLUMN is_moderator BOOLEAN NOT NULL DEFAULT FALSE;
+
+CREATE TABLE text_submissions (
+ id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ category TEXT NOT NULL,
+ content TEXT NOT NULL,
+ attribution TEXT,
+ - Syntax language for code passages; NULL for prose.
+ language TEXT,
+ status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
+ reject_reason TEXT,
+ reviewed_by TEXT REFERENCES users(id) ON DELETE SET NULL,
+ reviewed_at TEXT,
+ created_at TEXT NOT NULL
+);
+
+CREATE INDEX idx_text_submissions_status ON text_submissions(status);
+CREATE INDEX idx_text_submissions_user ON text_submissions(user_id);
+
+- One submission per passage, regardless of whitespace or case. Built on
+- md5() rather than a stored hash column so the constraint cannot drift out
+- of step with the content it is derived from.
+CREATE UNIQUE INDEX idx_text_submissions_dedupe
+ ON text_submissions (md5(lower(btrim(content))));