diff options
| author | srdusr <[email protected]> | 2025-12-14 09:24:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2025-12-14 09:24:00 +0200 |
| commit | 3dbebfbc9345d2603908f32c0dabebc0ff21feb3 (patch) | |
| tree | 61a4f60187046a087424845eb8e22239ccd7d6ee /crates/server/migrations | |
| parent | 8fc4440150ce84c9cc3f5ec13d50beb58a5b65bc (diff) | |
| download | typerpunk-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.sql | 28 |
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)))); |