halkyon/apps/learn/halkyon_learn_client/lib/migrations/migration_registry.dart

69 lines
3.3 KiB
Dart
Raw Normal View History

/// AUTOGENERATED BY SERVERPOD create-migration - DO NOT EDIT BY HAND
/// Merging: resolve registry conflicts the same as migration_registry.txt.
library;
import 'package:serverpod_database/serverpod_database.dart';
part '20260930094729362-studysets/migration.dart';
part '20260930122235111-cards/migration.dart';
part '20260930145136285-reviews/migration.dart';
part '20261001064926911-tree-tags-assets/migration.dart';
feat: notes in studysets, synced and merged as cards are, with tags Server: three synced tables, only additions (migration 20261002191122122-notes): `note` (its studyset, a fractional position), `note_revision` (append-only, as a card's: parents, author, report, the payload {schemaVersion, title, body}) and `note_tag` (one row per assignment, never updated, as `card_tag`). Each indexes `spaceId`, and everything goes with its space, studyset, note or tag. A migration of synced tables locks app builds from before it out of syncing (ADR 0002, rule 3): cheap now, before the first release. App, `feature/note`: - `NoteContent` is a title and rich text, merged apart (ADR 0003): a title changed on one device and the body on another both survive, a body changed on two is a conflict. - `NoteRepository` creates a note at the end of its studyset, saves an edit on the revisions it started from (`saveEdit`, as the card composer now does), says when a note was last edited (its newest head's UUID v7), counts notes per studyset, and deletes a note's revisions blanked first (ADR 0002, rule 2). - Notes share the person's tags: `setTagsOfNote`, and folding tags of one name or undoing a tag's deletion covers notes too. - The delayed deletion with its Undo moves to `core/async` (`DelayedDeletions`): `StudysetDeletions` builds on it and now takes a studyset's notes too; `NoteDeletions` deletes a single note. Tests: content, merge, payload, repository, deletions, tags, the device migration guard, and sync tests against the server (four). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 21:53:56 +02:00
part '20261002191122122-notes/migration.dart';
feat: learning paths in studysets, synced, with progress and tags Server: five synced tables, only additions (migration 20261002211346701-learning-paths): - `learning_path`: its studyset, a fractional position, and its own content in the payload {title, secondaryTitle, description}, the last writer winning, as a studyset's; - `learning_path_step`: its path, a position, and its kind ('info', 'singleChoice', 'multipleChoice', 'dragAndDrop', 'fillInTheBlank'), fixed once created; - `learning_path_step_revision`: a step's content as append-only revisions, as a card's: {title, body, exercise, rightFeedback, wrongFeedback, sources}; - `learning_path_answer`: an append-only log (right, wrong, continued, reset), as reviews are, so answers from two devices never conflict; - `learning_path_tag`: one row per assignment, as `card_tag`. App, `feature/learning_path`: the five exercises and their JSON, grading that marks only the learner's own wrong picks, progress worked out from the log (a step is done once answered right since its last fresh start), `LearningPathRepository` (steps saved on what they started from, merged per field) and `PathProgressRepository` (answers, resets with Undo, the times steps were done). Paths share the person's tags; deleting a path or a step can be undone for a moment, and a studyset's deletion takes its paths. Tests: models, codecs, repositories, tags, the device migration guard, and sync tests against the server (four). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 23:31:20 +02:00
part '20261002211346701-learning-paths/migration.dart';
feat: PDF sources: read, take notes and cards from pages, deadlines PDFs live in a studyset's new Files tab: uploaded from Add material (a file the studyset holds already asks first), each row with its pages, cards, notes and size, how far it is read and its reading deadline; renamed, given a deadline (a page or a chapter by a day), marked as read, started afresh, deleted with a moment to undo. The reader (/pdfs/:id) opens where reading left off: on wide windows a rail of tools (search, fit, zoom, fullscreen), a page counter with the chapter, and a sidebar; on phones a top bar, a page scrubber and a sheet. The sidebar holds the PDF's flashcards, by page or chapter, made from selected text or written there, each tied to its page and tagged with its chapter; and the page's note, an ordinary note tied to the page. Screenshots of a region become images that know their page. Cards and screenshots lead back to their page; reading on past a chapter with cards suggests studying it. Home shows the deadlines running and to come, and a timeline of them. Server: pdf_source rows (content in the payload, last writer wins) and an append-only pdf_reading log, the furthest page derived from it so devices never conflict. Files up to 50 MB for PDFs (20 MB for others; a larger file that isn't a PDF is deleted once stored). A deleted PDF takes its file's asset row along unless another PDF has the same file. Card payload 4 and note payload 2 keep the page. The reader draws with pdfrx (PDFium) behind PdfEngine in Dependencies; widget tests use a fake, and integration_test/pdf_engine_test.dart checks the real engine on a device or the desktop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 14:40:08 +02:00
part '20261003110855698-pdf-sources/migration.dart';
part '20261007205440868-card-buried-at/migration.dart';
part '20261008165051407-pdf-position/migration.dart';
feat(feedback): reports and suggestions on published studysets, gathered by the instance Stage 4 of the public tier (ADR 0005, rule 6), its server and the app's data. A learner writes a report or a suggestion on a published studyset, a card, note or step of it, in their own synced space, so it works offline; replies and the author's decisions (closed, hidden, accepted, declined) likewise, in the writer's or the author's space. The instance gathers them: public to everyone signed in, but for what the author hides; for the author's inbox and the writer's. What counts is decided as rows are read, since a modified app could write anything: threads from people with an identity and a public profile the author hasn't blocked, replies from the writer and the author alone, decisions from the author alone, no suggestion under "no derivatives". The author gets the files a suggestion names from its writer's, and the next release credits the writers of the suggestions accepted since the last (Co-authored-by). Deleting a person deletes their blocks and what they read; what they wrote goes with their space. In the app, the repository writes reports and suggestions (a card, note or step as the learner has it, without what is theirs alone, with the files it names) and takes an accepted suggestion into the author's own card, note or step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 01:04:17 +02:00
part '20261009224606274-feedback/migration.dart';
part '20261012162411835-studyset-deleted-at/migration.dart';
part '20261019121406578-halkyon-base/migration.dart';
part '20261019201023295-profiles-in-halkyon-base/migration.dart';
part '20261019204112282-deletion-notices/migration.dart';
part '20261020111943300-passwords/migration.dart';
part '20261020125117584-sessions-ended/migration.dart';
part '20261020185750538-recovery-proof-key/migration.dart';
fix(auth): codes wait after ten wrong in a row, and the owner is told Whoever knew the password could guess codes at 10 an hour forever, unnoticed, each guess checked against every authenticator (the second reading's M4). Every code an account's second factor refuses after a right password (sign-in, a phrase's reset, a change) or from a signed-in device (removing an authenticator, new recovery codes, the code from one confirmed before) now counts, per account, from every network; after every tenth in a row no code is taken (codesWait, saying until when), an hour, then twice as long each time, a day at the most (longestCodeWait, second_factor_waits.dart): at a day, ten guesses a day still have about a 1 % chance a year against one authenticator, so the first wait mails the owner that someone knows their password. The instance keeps no address, so sign-in and the phrase's reset now send the address the device typed (and its language), used only for that mail after its hash is checked against the account's. A code accepted clears it all. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "second_factor_failures" ("id" bigserial PRIMARY KEY, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "waits" bigint NOT NULL, "waitUntil" timestamp, "updatedAt" timestamp NOT NULL) with a unique index on "authUserId": a new table, nothing touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 21:47:38 +02:00
part '20261020194228286-second-factor-waits/migration.dart';
fix(auth): a session's wrong codes end the session, not the account's codes Wrong codes for removing an authenticator, new recovery codes or confirming one counted toward the account's waits (the re-review's third finding): a stolen session alone, without the password, could keep the owner's codes waiting for up to a day at a time, renewed without end, holding them out of signing in and of changing the password, the way to end that session, and nobody was mailed. Those codes now count per session, where the session is (the register's own, or a member's, which forwards the code): each try counted before its code is checked (one statement, so tries sent at once count each), a code accepted clearing the count, and the tenth wrong in a row ending the session (sessionEnded; the device says it was signed out). A try that checked no code (a refusal of something else) is taken back. The account's waits now count only codes after a right password, and the per-account ten tries an hour, which a session could fill as well, go. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "session_code_failures" ("id" bigserial PRIMARY KEY, "sessionId" uuid NOT NULL, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "updatedAt" timestamp NOT NULL) with a unique index on "sessionId": a new table, nothing touched. Its rows go with a code accepted, with the person, and hourly once their session is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 22:53:20 +02:00
part '20261020204619104-session-code-tries/migration.dart';
part '20261020211224692-email-code-purpose/migration.dart';
part '20261021100422048-session-ended-warning/migration.dart';
part '20261021110159867-second-factor-paths/migration.dart';
part '20261021134803749-reset-hold-outs/migration.dart';
part '20261021184601197-private-studysets/migration.dart';
part '20261023181810112-oidc-module/migration.dart';
feat(base): SimpleLogin accounts' keys without SRP, signing up in one call (ADR 0020, step 3) An account that signs in with SimpleLogin keeps its keys under a password that signs nobody in (D19): `account_password` keeps the password's own proof key (Ed25519, `passwordProofKey`) in place of SRP's verifier, which becomes nullable. The migration adds the column and drops the NOT NULL, nothing else. - `simpleLogin.signUp(attempt, handle, setup)`: someone new whose identity waits under the attempt gets, in one transaction, an account (identified), the link, a profile with the handle and the keys; then a session (D18). Refused, the identity still waiting, for a setup out of range and a handle refused as saving a profile refuses it. - `simpleLogin.checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network. - `simpleLogin.complete` says, for an account, whether it has its keys and its handle, and keeps the sign-in ten minutes under its attempt, the account as its caller: D20's confirmation for the changes to come. - `keys.begin`, `keys.unlock`: the keys for a signed-in device once the password is proven with its proof key, for a nonce, bound to the base URL the device addressed; wrong proofs counted per session as ADR 0017's. - An authenticator is enrolled only for a password that signs in. - Members ask the register for all of it; `RemoteRegister` and the instance API carry the four new operations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 21:49:44 +02:00
part '20261023192913178-simplelogin-keys/migration.dart';
/// Migration registry for the client-side database.
class MigrationRegistry {
/// Ordered list of all client-side database migrations.
/// New migrations are always appended at the end.
static final List<MigrationVersionSql> migrations = [
_Migration20260930094729362_studysets(),
_Migration20260930122235111_cards(),
_Migration20260930145136285_reviews(),
_Migration20261001064926911_tree_tags_assets(),
feat: notes in studysets, synced and merged as cards are, with tags Server: three synced tables, only additions (migration 20261002191122122-notes): `note` (its studyset, a fractional position), `note_revision` (append-only, as a card's: parents, author, report, the payload {schemaVersion, title, body}) and `note_tag` (one row per assignment, never updated, as `card_tag`). Each indexes `spaceId`, and everything goes with its space, studyset, note or tag. A migration of synced tables locks app builds from before it out of syncing (ADR 0002, rule 3): cheap now, before the first release. App, `feature/note`: - `NoteContent` is a title and rich text, merged apart (ADR 0003): a title changed on one device and the body on another both survive, a body changed on two is a conflict. - `NoteRepository` creates a note at the end of its studyset, saves an edit on the revisions it started from (`saveEdit`, as the card composer now does), says when a note was last edited (its newest head's UUID v7), counts notes per studyset, and deletes a note's revisions blanked first (ADR 0002, rule 2). - Notes share the person's tags: `setTagsOfNote`, and folding tags of one name or undoing a tag's deletion covers notes too. - The delayed deletion with its Undo moves to `core/async` (`DelayedDeletions`): `StudysetDeletions` builds on it and now takes a studyset's notes too; `NoteDeletions` deletes a single note. Tests: content, merge, payload, repository, deletions, tags, the device migration guard, and sync tests against the server (four). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 21:53:56 +02:00
_Migration20261002191122122_notes(),
feat: learning paths in studysets, synced, with progress and tags Server: five synced tables, only additions (migration 20261002211346701-learning-paths): - `learning_path`: its studyset, a fractional position, and its own content in the payload {title, secondaryTitle, description}, the last writer winning, as a studyset's; - `learning_path_step`: its path, a position, and its kind ('info', 'singleChoice', 'multipleChoice', 'dragAndDrop', 'fillInTheBlank'), fixed once created; - `learning_path_step_revision`: a step's content as append-only revisions, as a card's: {title, body, exercise, rightFeedback, wrongFeedback, sources}; - `learning_path_answer`: an append-only log (right, wrong, continued, reset), as reviews are, so answers from two devices never conflict; - `learning_path_tag`: one row per assignment, as `card_tag`. App, `feature/learning_path`: the five exercises and their JSON, grading that marks only the learner's own wrong picks, progress worked out from the log (a step is done once answered right since its last fresh start), `LearningPathRepository` (steps saved on what they started from, merged per field) and `PathProgressRepository` (answers, resets with Undo, the times steps were done). Paths share the person's tags; deleting a path or a step can be undone for a moment, and a studyset's deletion takes its paths. Tests: models, codecs, repositories, tags, the device migration guard, and sync tests against the server (four). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-02 23:31:20 +02:00
_Migration20261002211346701_learning_paths(),
feat: PDF sources: read, take notes and cards from pages, deadlines PDFs live in a studyset's new Files tab: uploaded from Add material (a file the studyset holds already asks first), each row with its pages, cards, notes and size, how far it is read and its reading deadline; renamed, given a deadline (a page or a chapter by a day), marked as read, started afresh, deleted with a moment to undo. The reader (/pdfs/:id) opens where reading left off: on wide windows a rail of tools (search, fit, zoom, fullscreen), a page counter with the chapter, and a sidebar; on phones a top bar, a page scrubber and a sheet. The sidebar holds the PDF's flashcards, by page or chapter, made from selected text or written there, each tied to its page and tagged with its chapter; and the page's note, an ordinary note tied to the page. Screenshots of a region become images that know their page. Cards and screenshots lead back to their page; reading on past a chapter with cards suggests studying it. Home shows the deadlines running and to come, and a timeline of them. Server: pdf_source rows (content in the payload, last writer wins) and an append-only pdf_reading log, the furthest page derived from it so devices never conflict. Files up to 50 MB for PDFs (20 MB for others; a larger file that isn't a PDF is deleted once stored). A deleted PDF takes its file's asset row along unless another PDF has the same file. Card payload 4 and note payload 2 keep the page. The reader draws with pdfrx (PDFium) behind PdfEngine in Dependencies; widget tests use a fake, and integration_test/pdf_engine_test.dart checks the real engine on a device or the desktop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-03 14:40:08 +02:00
_Migration20261003110855698_pdf_sources(),
_Migration20261007205440868_card_buried_at(),
_Migration20261008165051407_pdf_position(),
feat(feedback): reports and suggestions on published studysets, gathered by the instance Stage 4 of the public tier (ADR 0005, rule 6), its server and the app's data. A learner writes a report or a suggestion on a published studyset, a card, note or step of it, in their own synced space, so it works offline; replies and the author's decisions (closed, hidden, accepted, declined) likewise, in the writer's or the author's space. The instance gathers them: public to everyone signed in, but for what the author hides; for the author's inbox and the writer's. What counts is decided as rows are read, since a modified app could write anything: threads from people with an identity and a public profile the author hasn't blocked, replies from the writer and the author alone, decisions from the author alone, no suggestion under "no derivatives". The author gets the files a suggestion names from its writer's, and the next release credits the writers of the suggestions accepted since the last (Co-authored-by). Deleting a person deletes their blocks and what they read; what they wrote goes with their space. In the app, the repository writes reports and suggestions (a card, note or step as the learner has it, without what is theirs alone, with the files it names) and takes an accepted suggestion into the author's own card, note or step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MMm7usAo6q9ZZRoPkWjoxi Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-10 01:04:17 +02:00
_Migration20261009224606274_feedback(),
_Migration20261012162411835_studyset_deleted_at(),
_Migration20261019121406578_halkyon_base(),
_Migration20261019201023295_profiles_in_halkyon_base(),
_Migration20261019204112282_deletion_notices(),
_Migration20261020111943300_passwords(),
_Migration20261020125117584_sessions_ended(),
_Migration20261020185750538_recovery_proof_key(),
fix(auth): codes wait after ten wrong in a row, and the owner is told Whoever knew the password could guess codes at 10 an hour forever, unnoticed, each guess checked against every authenticator (the second reading's M4). Every code an account's second factor refuses after a right password (sign-in, a phrase's reset, a change) or from a signed-in device (removing an authenticator, new recovery codes, the code from one confirmed before) now counts, per account, from every network; after every tenth in a row no code is taken (codesWait, saying until when), an hour, then twice as long each time, a day at the most (longestCodeWait, second_factor_waits.dart): at a day, ten guesses a day still have about a 1 % chance a year against one authenticator, so the first wait mails the owner that someone knows their password. The instance keeps no address, so sign-in and the phrase's reset now send the address the device typed (and its language), used only for that mail after its hash is checked against the account's. A code accepted clears it all. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "second_factor_failures" ("id" bigserial PRIMARY KEY, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "waits" bigint NOT NULL, "waitUntil" timestamp, "updatedAt" timestamp NOT NULL) with a unique index on "authUserId": a new table, nothing touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 21:47:38 +02:00
_Migration20261020194228286_second_factor_waits(),
fix(auth): a session's wrong codes end the session, not the account's codes Wrong codes for removing an authenticator, new recovery codes or confirming one counted toward the account's waits (the re-review's third finding): a stolen session alone, without the password, could keep the owner's codes waiting for up to a day at a time, renewed without end, holding them out of signing in and of changing the password, the way to end that session, and nobody was mailed. Those codes now count per session, where the session is (the register's own, or a member's, which forwards the code): each try counted before its code is checked (one statement, so tries sent at once count each), a code accepted clearing the count, and the tenth wrong in a row ending the session (sessionEnded; the device says it was signed out). A try that checked no code (a refusal of something else) is taken back. The account's waits now count only codes after a right password, and the per-account ten tries an hour, which a session could fill as well, go. Migration (module, Learn's and Todo's servers, Learn's device schema): CREATE TABLE "session_code_failures" ("id" bigserial PRIMARY KEY, "sessionId" uuid NOT NULL, "authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "updatedAt" timestamp NOT NULL) with a unique index on "sessionId": a new table, nothing touched. Its rows go with a code accepted, with the person, and hourly once their session is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 22:53:20 +02:00
_Migration20261020204619104_session_code_tries(),
_Migration20261020211224692_email_code_purpose(),
_Migration20261021100422048_session_ended_warning(),
_Migration20261021110159867_second_factor_paths(),
_Migration20261021134803749_reset_hold_outs(),
_Migration20261021184601197_private_studysets(),
_Migration20261023181810112_oidc_module(),
feat(base): SimpleLogin accounts' keys without SRP, signing up in one call (ADR 0020, step 3) An account that signs in with SimpleLogin keeps its keys under a password that signs nobody in (D19): `account_password` keeps the password's own proof key (Ed25519, `passwordProofKey`) in place of SRP's verifier, which becomes nullable. The migration adds the column and drops the NOT NULL, nothing else. - `simpleLogin.signUp(attempt, handle, setup)`: someone new whose identity waits under the attempt gets, in one transaction, an account (identified), the link, a profile with the handle and the keys; then a session (D18). Refused, the identity still waiting, for a setup out of range and a handle refused as saving a profile refuses it. - `simpleLogin.checkHandle(attempt, handle)`: whether someone new could take a handle, at most 30 an hour per attempt and 60 per network. - `simpleLogin.complete` says, for an account, whether it has its keys and its handle, and keeps the sign-in ten minutes under its attempt, the account as its caller: D20's confirmation for the changes to come. - `keys.begin`, `keys.unlock`: the keys for a signed-in device once the password is proven with its proof key, for a nonce, bound to the base URL the device addressed; wrong proofs counted per session as ADR 0017's. - An authenticator is enrolled only for a password that signs in. - Members ask the register for all of it; `RemoteRegister` and the instance API carry the four new operations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 21:49:44 +02:00
_Migration20261023192913178_simplelogin_keys(),
];
/// List of all client-side database migration versions.
static List<String> get versions => migrations.map((m) => m.version).toList();
}