feat(base,app,learn): invitations to shared spaces by mail and by link
The instance's half of Halkyon's ADR 0044, step 5 (D37 to D40).
An invitation by mail (sharedSpace.inviteByEmail) goes out through the
register (send-space-invitation), naming who invites by their handle
and nothing of what is shared, with a link to /join/<token>. The
instance keeps only the address's keyed hash, as for sign-in, and the
SHA-256 of the token. Whoever opens it joins with a code mailed to that
address (EmailCodePurpose.joinSpace, proven through the register,
prove-address-code), so a forwarded mail admits nobody else; a code
proving another address is refused. It lapses after 14 days, and the
hourly pruning forgets it; inviting the address again replaces it.
A space's own link (setJoinLink, the Owner's) admits whoever opens it
as a Viewer (joinByLink) while it is on; switched off and on, it gets a
new token. Its token is kept as it is, so that whoever may invite copies
the link again (joinLink). The page at /join/<token>, in English and
German, says who invites and as whom; preview tells the app the same.
A membership someone else made is new to its member until they mark it
seen (isNew, addedBy; mine names who shared it), for the Inbox.
halkyon_app learns the two new refusals (handleNeeded, otherAddress),
which Learn reports as not allowed until its screens offer these ways
in. The ADR records D37 to D40 and how the instance's half is built.
The migration, the same in the module and in each server including it
(Learn, Meditate, Todo, Train):
ALTER TABLE "shared_space" ADD COLUMN "joinToken" text;
CREATE UNIQUE INDEX "shared_space_join_token_idx"
ON "shared_space" ("joinToken");
CREATE TABLE "shared_space_email_invitation" (…), with its unique
indexes on the space and address hash, and on the token's hash,
and its foreign key to "shared_space" ON DELETE CASCADE;
ALTER TABLE "shared_space_member" ADD COLUMN "addedBy" uuid;
ALTER TABLE "shared_space_member"
ADD COLUMN "isNew" boolean NOT NULL DEFAULT false;
Every space gets no token, and the unique index allows any number of
nulls; every membership gets no one who added it and isn't new, so
nothing already shared shows in an Inbox. The new table starts empty.
None of it rewrites a table, and the server before the switch names its
own columns, so it keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8dd3ec0522
commit
554e1604b4
15 changed files with 108488 additions and 42 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,227 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "alterTable",
|
||||
"alterTable": {
|
||||
"__className__": "serverpod.TableMigration",
|
||||
"name": "shared_space",
|
||||
"schema": "public",
|
||||
"addColumns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "joinToken",
|
||||
"fieldName": "joinToken",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
}
|
||||
],
|
||||
"deleteColumns": [],
|
||||
"modifyColumns": [],
|
||||
"addIndexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_join_token_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "joinToken"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"deleteIndexes": [],
|
||||
"addForeignKeys": [],
|
||||
"deleteForeignKeys": [],
|
||||
"warnings": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationWarning",
|
||||
"type": "uniqueIndexCreated",
|
||||
"message": "Unique index \"shared_space_join_token_idx\" is added to table \"shared_space\". If there are existing rows with duplicate values, this migration will fail.",
|
||||
"table": "shared_space",
|
||||
"columns": [
|
||||
"joinToken"
|
||||
],
|
||||
"destrucive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "shared_space_email_invitation",
|
||||
"dartName": "SharedSpaceEmailInvitation",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "sharedSpaceId",
|
||||
"fieldName": "sharedSpaceId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addressHash",
|
||||
"fieldName": "addressHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "tokenHash",
|
||||
"fieldName": "tokenHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "role",
|
||||
"fieldName": "role",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "protocol:SharedSpaceRole"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "invitedBy",
|
||||
"fieldName": "invitedBy",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "invitedAt",
|
||||
"fieldName": "invitedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "shared_space_email_invitation_fk_0",
|
||||
"columns": [
|
||||
"sharedSpaceId"
|
||||
],
|
||||
"referenceTable": "shared_space",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_email_invitation_address_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "sharedSpaceId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "addressHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_email_invitation_token_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "tokenHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "alterTable",
|
||||
"alterTable": {
|
||||
"__className__": "serverpod.TableMigration",
|
||||
"name": "shared_space_member",
|
||||
"schema": "public",
|
||||
"addColumns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addedBy",
|
||||
"fieldName": "addedBy",
|
||||
"columnType": 7,
|
||||
"isNullable": true,
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "isNew",
|
||||
"fieldName": "isNew",
|
||||
"columnType": 1,
|
||||
"isNullable": false,
|
||||
"columnDefault": "false",
|
||||
"dartType": "bool"
|
||||
}
|
||||
],
|
||||
"deleteColumns": [],
|
||||
"modifyColumns": [],
|
||||
"addIndexes": [],
|
||||
"deleteIndexes": [],
|
||||
"addForeignKeys": [],
|
||||
"deleteForeignKeys": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationWarning",
|
||||
"type": "uniqueIndexCreated",
|
||||
"message": "Unique index \"shared_space_join_token_idx\" is added to table \"shared_space\". If there are existing rows with duplicate values, this migration will fail.",
|
||||
"table": "shared_space",
|
||||
"columns": [
|
||||
"joinToken"
|
||||
],
|
||||
"destrucive": false
|
||||
}
|
||||
],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ part '20261111091720823-shared-spaces/migration.dart';
|
|||
part '20261111100124819-shared-space-keys/migration.dart';
|
||||
part '20261111110923694-own-rows-by-id/migration.dart';
|
||||
part '20261111145608988-shared-space-key-roll/migration.dart';
|
||||
part '20261111170119332-shared-space-invitations/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -82,6 +83,7 @@ class MigrationRegistry {
|
|||
_Migration20261111100124819_shared_space_keys(),
|
||||
_Migration20261111110923694_own_rows_by_id(),
|
||||
_Migration20261111145608988_shared_space_key_roll(),
|
||||
_Migration20261111170119332_shared_space_invitations(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -460,6 +460,8 @@ void _report(BuildContext context, SpaceAction outcome) {
|
|||
SpaceRefusal.ownerRole ||
|
||||
SpaceRefusal.self ||
|
||||
SpaceRefusal.nextOwnerNeeded ||
|
||||
SpaceRefusal.handleNeeded ||
|
||||
SpaceRefusal.otherAddress ||
|
||||
SpaceRefusal.notFound => l10n.shareRefusedNotAllowed,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ Future<void> run(List<String> args) async {
|
|||
// 0021, D39), before the profile pages' route, whose one segment would
|
||||
// take it.
|
||||
addPrivacyPageRoute(pod);
|
||||
// The pages of links to join a shared studyset, `/join/<token>` (Halkyon's
|
||||
// ADR 0044, D39).
|
||||
addJoinPageRoute(pod);
|
||||
// Profile pages, `/@handle`, which profile links link back to (ADR 0011),
|
||||
// halkyon_base's since profiles are the instance's register's (ADR 0016).
|
||||
addProfilePageRoute(pod);
|
||||
|
|
|
|||
|
|
@ -31,8 +31,9 @@ class PruningFutureCall extends FutureCall {
|
|||
/// Forgets the sign-up attempts the rate limit is done with, with their IP
|
||||
/// addresses, the email sign-in codes past their time, the password
|
||||
/// sign-ins never finished and authenticators never confirmed (ADR 0017),
|
||||
/// and the SimpleLogin sign-ins never finished (ADR 0020), and logs how
|
||||
/// many.
|
||||
/// the SimpleLogin sign-ins never finished (ADR 0020) and the invitations
|
||||
/// to shared spaces mailed that lapsed (Halkyon's ADR 0044, D37), and
|
||||
/// logs how many.
|
||||
Future<void> forgetSignUpAttempts(Session session) async {
|
||||
final now = clock.now();
|
||||
final forgotten = await forgetOldSignUpAttempts(session, now: now);
|
||||
|
|
@ -42,10 +43,12 @@ class PruningFutureCall extends FutureCall {
|
|||
now: now,
|
||||
);
|
||||
final simpleLogins = await forgetUnfinishedSimpleLogins(session, now: now);
|
||||
final invitations = await forgetLapsedSpaceInvitations(session, now: now);
|
||||
session.log(
|
||||
'Forgot $forgotten sign-up attempts, $codes email codes, '
|
||||
'$unfinished unfinished sign-ins and authenticators and '
|
||||
'$simpleLogins unfinished SimpleLogin sign-ins.',
|
||||
'$unfinished unfinished sign-ins and authenticators, '
|
||||
'$simpleLogins unfinished SimpleLogin sign-ins and $invitations '
|
||||
'lapsed invitations by mail.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -301,8 +301,9 @@ class PruningPruneAccountsFutureCall extends _is.FutureCall
|
|||
/// Forgets the sign-up attempts the rate limit is done with, with their IP
|
||||
/// addresses, the email sign-in codes past their time, the password
|
||||
/// sign-ins never finished and authenticators never confirmed (ADR 0017),
|
||||
/// and the SimpleLogin sign-ins never finished (ADR 0020), and logs how
|
||||
/// many.
|
||||
/// the SimpleLogin sign-ins never finished (ADR 0020) and the invitations
|
||||
/// to shared spaces mailed that lapsed (Halkyon's ADR 0044, D37), and
|
||||
/// logs how many.
|
||||
class PruningForgetSignUpAttemptsFutureCall extends _is.FutureCall
|
||||
implements _is.InvokableFutureCall {
|
||||
@override
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,227 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "alterTable",
|
||||
"alterTable": {
|
||||
"__className__": "serverpod.TableMigration",
|
||||
"name": "shared_space",
|
||||
"schema": "public",
|
||||
"addColumns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "joinToken",
|
||||
"fieldName": "joinToken",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
}
|
||||
],
|
||||
"deleteColumns": [],
|
||||
"modifyColumns": [],
|
||||
"addIndexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_join_token_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "joinToken"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"deleteIndexes": [],
|
||||
"addForeignKeys": [],
|
||||
"deleteForeignKeys": [],
|
||||
"warnings": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationWarning",
|
||||
"type": "uniqueIndexCreated",
|
||||
"message": "Unique index \"shared_space_join_token_idx\" is added to table \"shared_space\". If there are existing rows with duplicate values, this migration will fail.",
|
||||
"table": "shared_space",
|
||||
"columns": [
|
||||
"joinToken"
|
||||
],
|
||||
"destrucive": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "shared_space_email_invitation",
|
||||
"dartName": "SharedSpaceEmailInvitation",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "sharedSpaceId",
|
||||
"fieldName": "sharedSpaceId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addressHash",
|
||||
"fieldName": "addressHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "tokenHash",
|
||||
"fieldName": "tokenHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "role",
|
||||
"fieldName": "role",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "protocol:SharedSpaceRole"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "invitedBy",
|
||||
"fieldName": "invitedBy",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "invitedAt",
|
||||
"fieldName": "invitedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "shared_space_email_invitation_fk_0",
|
||||
"columns": [
|
||||
"sharedSpaceId"
|
||||
],
|
||||
"referenceTable": "shared_space",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_email_invitation_address_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "sharedSpaceId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "addressHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "shared_space_email_invitation_token_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "tokenHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "alterTable",
|
||||
"alterTable": {
|
||||
"__className__": "serverpod.TableMigration",
|
||||
"name": "shared_space_member",
|
||||
"schema": "public",
|
||||
"addColumns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addedBy",
|
||||
"fieldName": "addedBy",
|
||||
"columnType": 7,
|
||||
"isNullable": true,
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "isNew",
|
||||
"fieldName": "isNew",
|
||||
"columnType": 1,
|
||||
"isNullable": false,
|
||||
"columnDefault": "false",
|
||||
"dartType": "bool"
|
||||
}
|
||||
],
|
||||
"deleteColumns": [],
|
||||
"modifyColumns": [],
|
||||
"addIndexes": [],
|
||||
"deleteIndexes": [],
|
||||
"addForeignKeys": [],
|
||||
"deleteForeignKeys": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationWarning",
|
||||
"type": "uniqueIndexCreated",
|
||||
"message": "Unique index \"shared_space_join_token_idx\" is added to table \"shared_space\". If there are existing rows with duplicate values, this migration will fail.",
|
||||
"table": "shared_space",
|
||||
"columns": [
|
||||
"joinToken"
|
||||
],
|
||||
"destrucive": false
|
||||
}
|
||||
],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
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 ALTER TABLE
|
||||
--
|
||||
ALTER TABLE "shared_space" ADD COLUMN "joinToken" text;
|
||||
CREATE UNIQUE INDEX "shared_space_join_token_idx" ON "shared_space" USING btree ("joinToken");
|
||||
--
|
||||
-- ACTION CREATE TABLE
|
||||
--
|
||||
CREATE TABLE "shared_space_email_invitation" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
|
||||
"sharedSpaceId" uuid NOT NULL,
|
||||
"addressHash" text NOT NULL,
|
||||
"tokenHash" text NOT NULL,
|
||||
"role" text NOT NULL,
|
||||
"invitedBy" uuid NOT NULL,
|
||||
"invitedAt" timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE UNIQUE INDEX "shared_space_email_invitation_address_idx" ON "shared_space_email_invitation" USING btree ("sharedSpaceId", "addressHash");
|
||||
CREATE UNIQUE INDEX "shared_space_email_invitation_token_idx" ON "shared_space_email_invitation" USING btree ("tokenHash");
|
||||
|
||||
--
|
||||
-- ACTION ALTER TABLE
|
||||
--
|
||||
ALTER TABLE "shared_space_member" ADD COLUMN "addedBy" uuid;
|
||||
ALTER TABLE "shared_space_member" ADD COLUMN "isNew" boolean NOT NULL DEFAULT false;
|
||||
--
|
||||
-- ACTION CREATE FOREIGN KEY
|
||||
--
|
||||
ALTER TABLE ONLY "shared_space_email_invitation"
|
||||
ADD CONSTRAINT "shared_space_email_invitation_fk_0"
|
||||
FOREIGN KEY("sharedSpaceId")
|
||||
REFERENCES "shared_space"("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE NO ACTION;
|
||||
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_learn
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_learn', '20261111170119332-shared-space-invitations', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261111170119332-shared-space-invitations', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_base
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_base', '20261111170118451-shared-space-invitations', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261111170118451-shared-space-invitations', "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 halkyon_oidc
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_oidc', '20261023180644988', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261023180644988', "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;
|
||||
|
|
@ -57,3 +57,4 @@
|
|||
20261111100124819-shared-space-keys
|
||||
20261111110923694-own-rows-by-id
|
||||
20261111145608988-shared-space-key-roll
|
||||
20261111170119332-shared-space-invitations
|
||||
|
|
|
|||
Loading…
Reference in a new issue