feat: studysets record when they last changed; the library lists them so

Decided 2026-10-01: after the starred ones, the library lists the most
recently changed studysets first. That needs a change date, so studyset
gains updatedAt (ADR 0004): when the studyset itself last changed, its
content, place, star or archive date, written by the device making the
change in the same update as the change; the last writer wins. Studysets
from before have none and come last, the newest of them first.

The branch's migration is still unreleased (not merged, not deployed), so it
is recreated rather than followed by a second one:
20261001064926911-tree-tags-assets replaces 20260930213716558 and differs
only by one line on the server, ALTER TABLE "studyset" ADD COLUMN
"updatedAt" timestamp without time zone (nullable, so existing rows keep
null and no data changes), the same column in the table devices rebuild,
and its version. The model's comment on the payload no longer lists schema
1's fields alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-01 08:51:33 +02:00
commit 85c028fe3c
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
9 changed files with 349 additions and 74 deletions

View file

@ -691,6 +691,12 @@ class Protocol extends _is.DatabaseSerializationManager {
isNullable: true,
dartType: 'DateTime?',
),
_isp.ColumnDefinition(
name: 'updatedAt',
columnType: _isp.ColumnType.timestampWithoutTimeZone,
isNullable: true,
dartType: 'DateTime?',
),
_isp.ColumnDefinition(
name: 'keyId',
columnType: _isp.ColumnType.text,

View file

@ -32,6 +32,7 @@ abstract class Studyset
String? position,
this.archivedAt,
this.starredAt,
this.updatedAt,
this.keyId,
required this.payload,
this.nonce,
@ -45,6 +46,7 @@ abstract class Studyset
String? position,
DateTime? archivedAt,
DateTime? starredAt,
DateTime? updatedAt,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
@ -71,6 +73,9 @@ abstract class Studyset
starredAt: jsonSerialization['starredAt'] == null
? null
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['starredAt']),
updatedAt: jsonSerialization['updatedAt'] == null
? null
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['updatedAt']),
keyId: jsonSerialization['keyId'] as String?,
payload: _is.ByteDataJsonExtension.fromJson(jsonSerialization['payload']),
nonce: jsonSerialization['nonce'] == null
@ -112,11 +117,18 @@ abstract class Studyset
/// not starred. A column of its own, like archivedAt.
DateTime? starredAt;
/// When the studyset itself last changed (its content, place, star or archive
/// date), set by the device making the change, with it; the last writer wins.
/// Null for studysets from before this column. The library lists the most
/// recently changed first (decided 2026-10-01).
DateTime? updatedAt;
/// The encryption key's id; always null, since content isn't end-to-end encrypted
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
String? keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description", …}; the
/// app's studyset_payload.dart describes each schema.
_idt.ByteData payload;
/// The encryption nonce; always null, like keyId.
@ -136,6 +148,7 @@ abstract class Studyset
String? position,
DateTime? archivedAt,
DateTime? starredAt,
DateTime? updatedAt,
String? keyId,
_idt.ByteData? payload,
_idt.ByteData? nonce,
@ -151,6 +164,7 @@ abstract class Studyset
'position': position,
if (archivedAt != null) 'archivedAt': archivedAt?.toJson(),
if (starredAt != null) 'starredAt': starredAt?.toJson(),
if (updatedAt != null) 'updatedAt': updatedAt?.toJson(),
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
@ -168,6 +182,7 @@ abstract class Studyset
'position': position,
if (archivedAt != null) 'archivedAt': archivedAt?.toJson(),
if (starredAt != null) 'starredAt': starredAt?.toJson(),
if (updatedAt != null) 'updatedAt': updatedAt?.toJson(),
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
@ -213,6 +228,7 @@ class _StudysetImpl extends Studyset {
String? position,
DateTime? archivedAt,
DateTime? starredAt,
DateTime? updatedAt,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
@ -224,6 +240,7 @@ class _StudysetImpl extends Studyset {
position: position,
archivedAt: archivedAt,
starredAt: starredAt,
updatedAt: updatedAt,
keyId: keyId,
payload: payload,
nonce: nonce,
@ -241,6 +258,7 @@ class _StudysetImpl extends Studyset {
String? position,
Object? archivedAt = _Undefined,
Object? starredAt = _Undefined,
Object? updatedAt = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
@ -253,6 +271,7 @@ class _StudysetImpl extends Studyset {
position: position ?? this.position,
archivedAt: archivedAt is DateTime? ? archivedAt : this.archivedAt,
starredAt: starredAt is DateTime? ? starredAt : this.starredAt,
updatedAt: updatedAt is DateTime? ? updatedAt : this.updatedAt,
keyId: keyId is String? ? keyId : this.keyId,
payload: payload ?? this.payload.clone(),
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
@ -292,6 +311,12 @@ class StudysetUpdateTable extends _is.UpdateTable<StudysetTable> {
value,
);
_is.ColumnValue<DateTime, DateTime> updatedAt(DateTime? value) =>
_is.ColumnValue(
table.updatedAt,
value,
);
_is.ColumnValue<String, String> keyId(String? value) => _is.ColumnValue(
table.keyId,
value,
@ -334,6 +359,10 @@ class StudysetTable extends _is.Table<_is.UuidValue?> {
'starredAt',
this,
);
updatedAt = _is.ColumnDateTime(
'updatedAt',
this,
);
keyId = _is.ColumnString(
'keyId',
this,
@ -376,11 +405,18 @@ class StudysetTable extends _is.Table<_is.UuidValue?> {
/// not starred. A column of its own, like archivedAt.
late final _is.ColumnDateTime starredAt;
/// When the studyset itself last changed (its content, place, star or archive
/// date), set by the device making the change, with it; the last writer wins.
/// Null for studysets from before this column. The library lists the most
/// recently changed first (decided 2026-10-01).
late final _is.ColumnDateTime updatedAt;
/// The encryption key's id; always null, since content isn't end-to-end encrypted
/// (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
late final _is.ColumnString keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
/// The content as UTF-8 JSON: {"schemaVersion", "title", "description", …}; the
/// app's studyset_payload.dart describes each schema.
late final _is.ColumnByteData payload;
/// The encryption nonce; always null, like keyId.
@ -407,6 +443,7 @@ class StudysetTable extends _is.Table<_is.UuidValue?> {
position,
archivedAt,
starredAt,
updatedAt,
keyId,
payload,
nonce,

View file

@ -25,10 +25,16 @@ fields:
### When the person starred it, to keep it at the top of the library; null when
### not starred. A column of its own, like archivedAt.
starredAt: DateTime?
### When the studyset itself last changed (its content, place, star or archive
### date), set by the device making the change, with it; the last writer wins.
### Null for studysets from before this column. The library lists the most
### recently changed first (decided 2026-10-01).
updatedAt: DateTime?
### The encryption key's id; always null, since content isn't end-to-end encrypted
### (ADR 0002). Kept so opt-in encryption of single studysets stays possible.
keyId: String?
### The content as UTF-8 JSON: {"schemaVersion", "title", "description"}.
### The content as UTF-8 JSON: {"schemaVersion", "title", "description", …}; the
### app's studyset_payload.dart describes each schema.
payload: ByteData
### The encryption nonce; always null, like keyId.
nonce: ByteData?

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,570 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "asset",
"dartName": "Asset",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "sha256",
"fieldName": "sha256",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "mimeType",
"fieldName": "mimeType",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "byteSize",
"fieldName": "byteSize",
"columnType": 6,
"isNullable": false,
"dartType": "int"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "asset_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "asset_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "asset_hash_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "sha256"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "card_tag",
"dartName": "CardTag",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "cardId",
"fieldName": "cardId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "tagId",
"fieldName": "tagId",
"columnType": 7,
"isNullable": false,
"dartType": "UuidValue"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "card_tag_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "card_tag_fk_1",
"columns": [
"cardId"
],
"referenceTable": "card",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
},
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "card_tag_fk_2",
"columns": [
"tagId"
],
"referenceTable": "tag",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "card_tag_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "card_tag_card_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "cardId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
},
{
"__className__": "serverpod.IndexDefinition",
"indexName": "card_tag_tag_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "tagId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "person_settings",
"dartName": "PersonSettings",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "person_settings_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "person_settings_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "alterTable",
"alterTable": {
"__className__": "serverpod.TableMigration",
"name": "review_log",
"schema": "public",
"addColumns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "mode",
"fieldName": "mode",
"columnType": 0,
"isNullable": false,
"columnDefault": "'review'",
"dartType": "String"
}
],
"deleteColumns": [],
"modifyColumns": [],
"addIndexes": [],
"deleteIndexes": [],
"addForeignKeys": [],
"deleteForeignKeys": [],
"warnings": []
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "alterTable",
"alterTable": {
"__className__": "serverpod.TableMigration",
"name": "studyset",
"schema": "public",
"addColumns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "parentId",
"fieldName": "parentId",
"columnType": 7,
"isNullable": true,
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "position",
"fieldName": "position",
"columnType": 0,
"isNullable": false,
"columnDefault": "'a0'",
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "archivedAt",
"fieldName": "archivedAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "starredAt",
"fieldName": "starredAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "updatedAt",
"fieldName": "updatedAt",
"columnType": 4,
"isNullable": true,
"dartType": "DateTime?"
}
],
"deleteColumns": [],
"modifyColumns": [],
"addIndexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "studyset_parent_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "parentId"
},
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "position"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"deleteIndexes": [],
"addForeignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "studyset_fk_1",
"columns": [
"parentId"
],
"referenceTable": "studyset",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4,
"deferrable": "initiallyDeferred"
}
],
"deleteForeignKeys": [],
"warnings": []
}
},
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "tag",
"dartName": "Tag",
"module": "halkyon_learn",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "tag_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "tag_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
}
],
"warnings": [],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,212 @@
BEGIN;
--
-- Function: gen_random_uuid_v7()
-- Source: https://gist.github.com/kjmph/5bd772b2c2df145aa645b837da7eca74
-- License: MIT (copyright notice included on the generator source code).
--
create or replace function gen_random_uuid_v7()
returns uuid
as $$
begin
-- use random v4 uuid as starting point (which has the same variant we need)
-- then overlay timestamp
-- then set version 7 by flipping the 2 and 1 bit in the version 4 string
return encode(
set_bit(
set_bit(
overlay(uuid_send(gen_random_uuid())
placing substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3)
from 1 for 6
),
52, 1
),
53, 1
),
'hex')::uuid;
end
$$
language plpgsql
volatile;
--
-- ACTION CREATE TABLE
--
CREATE TABLE "asset" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"sha256" text NOT NULL,
"mimeType" text NOT NULL,
"byteSize" bigint NOT NULL,
"keyId" text,
"payload" bytea NOT NULL,
"nonce" bytea
);
-- Indexes
CREATE INDEX "asset_space_idx" ON "asset" USING btree ("spaceId");
CREATE INDEX "asset_hash_idx" ON "asset" USING btree ("spaceId", "sha256");
--
-- ACTION CREATE TABLE
--
CREATE TABLE "card_tag" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"cardId" uuid NOT NULL,
"tagId" uuid NOT NULL
);
-- Indexes
CREATE INDEX "card_tag_space_idx" ON "card_tag" USING btree ("spaceId");
CREATE INDEX "card_tag_card_idx" ON "card_tag" USING btree ("cardId");
CREATE INDEX "card_tag_tag_idx" ON "card_tag" USING btree ("tagId");
--
-- ACTION CREATE TABLE
--
CREATE TABLE "person_settings" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"keyId" text,
"payload" bytea NOT NULL,
"nonce" bytea
);
-- Indexes
CREATE INDEX "person_settings_space_idx" ON "person_settings" USING btree ("spaceId");
--
-- ACTION ALTER TABLE
--
ALTER TABLE "review_log" ADD COLUMN "mode" text NOT NULL DEFAULT 'review'::text;
--
-- ACTION ALTER TABLE
--
ALTER TABLE "studyset" ADD COLUMN "parentId" uuid;
ALTER TABLE "studyset" ADD COLUMN "position" text NOT NULL DEFAULT 'a0'::text;
ALTER TABLE "studyset" ADD COLUMN "archivedAt" timestamp without time zone;
ALTER TABLE "studyset" ADD COLUMN "starredAt" timestamp without time zone;
ALTER TABLE "studyset" ADD COLUMN "updatedAt" timestamp without time zone;
CREATE INDEX "studyset_parent_idx" ON "studyset" USING btree ("parentId", "position");
--
-- ACTION CREATE TABLE
--
CREATE TABLE "tag" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
"spaceId" bigint,
"keyId" text,
"payload" bytea NOT NULL,
"nonce" bytea
);
-- Indexes
CREATE INDEX "tag_space_idx" ON "tag" USING btree ("spaceId");
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "asset"
ADD CONSTRAINT "asset_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "card_tag"
ADD CONSTRAINT "card_tag_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
ALTER TABLE ONLY "card_tag"
ADD CONSTRAINT "card_tag_fk_1"
FOREIGN KEY("cardId")
REFERENCES "card"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE ONLY "card_tag"
ADD CONSTRAINT "card_tag_fk_2"
FOREIGN KEY("tagId")
REFERENCES "tag"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "person_settings"
ADD CONSTRAINT "person_settings_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "studyset"
ADD CONSTRAINT "studyset_fk_1"
FOREIGN KEY("parentId")
REFERENCES "studyset"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION
DEFERRABLE INITIALLY DEFERRED;
--
-- ACTION CREATE FOREIGN KEY
--
ALTER TABLE ONLY "tag"
ADD CONSTRAINT "tag_fk_0"
FOREIGN KEY("spaceId")
REFERENCES "offline_sync_spaces"("id")
ON DELETE CASCADE
ON UPDATE NO ACTION;
--
-- MIGRATION VERSION FOR halkyon_learn
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('halkyon_learn', '20261001064926911-tree-tags-assets', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20261001064926911-tree-tags-assets', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod', '20260824182259319', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_idp
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_idp', '20260924105404509', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_offline_sync
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_offline_sync', '20260914143806119', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260914143806119', "timestamp" = now();
--
-- MIGRATION VERSION FOR serverpod_auth_core
--
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
VALUES ('serverpod_auth_core', '20260924105232991', now())
ON CONFLICT ("module")
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
COMMIT;

View file

@ -8,4 +8,4 @@
20260930094729362-studysets
20260930122235111-cards
20260930145136285-reviews
20260930213716558-tree-tags-assets
20261001064926911-tree-tags-assets