feat(base,learn): spaces people share, kept by halkyon_base

ADR 0044, step 2 (D1, D2, D11, D13, and D15 to D17, decided today).
halkyon_base keeps the spaces several people share, for every app:
shared_space, its members with their roles (Owner, Editor, Viewer), and
invitations by handle waiting for someone with no account on the
service yet. The sharedSpace endpoint creates a space, lists its
members, invites by handle, changes roles, removes members, hands the
space on, leaves and deletes it.

Who may do what:
- Only people with an identity reach the endpoint.
- A space the caller isn't in can't be told from one that doesn't
  exist.
- Members are named by their membership, never by their account's ID.
- Only the Owner manages the members, unless the space lets members
  invite.
- The Owner's role passes only by handing the space on, and the Owner
  leaves only naming who takes it on.
- An invitation counts as an answer about a handle, as it tells whether
  one exists.
Every rule has a test asserting the refusal.

The module stays free of the sync engine: a host hands it a SpaceGrants,
and every call runs in the module's transaction, so the engine's grants
follow the roles or nothing changes. The Owner and an Editor write; a
Viewer reads. Learn hands over SyncSpaceGrants on OfflineSyncSpaces. Todo
hands over none yet, so its server refuses until its projects are
shared.

Deleting an account hands each space the person owns to the Editor who
joined first, else the Viewer who joined first. A space with no other
member goes. This runs before the host's own deletions. A person's first
sign-in on a service makes their waiting invitations memberships, in the
same transaction that makes them a person there.

Migrations: the module's and every host's (Learn, Todo, Train, Meditate)
create the three tables, their indexes and the two cascading foreign
keys to shared_space, and nothing else. The old server keeps working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-11 10:33:12 +01:00
commit d08d8ad545
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
38 changed files with 108233 additions and 18 deletions

View file

@ -29,6 +29,7 @@ Endpoints, under the nickname `base` in a host's client (`client.modules.base.em
| `assistantOperator` | `setUp`: the assistants set, without their keys, with their effort, and how many people have any; `grantees` (by the start of a handle, 50 from an offset) and `granteesOf(accounts)`: each person's assistants, budget and what they used; `grant(accounts, assistants, budget)`: each person the assistants they lack, the agreements of the others kept, and the budget (D58); `change(account, assistants, budget)`: exactly those; `revoke(account)`: every assistant and the budget; `figures(month)`: everyone's of a UTC month, per person and assistant | `identified` and `operator`, on the register |
| `profile` | `mine`, `save`, `checkHandle` (with every answer about a new handle, at most 30 an hour per person and 60 per network), `checkLinks` (at most 10 an hour), `setAvatar`, `removeAvatar`, `remove` (what it shows goes, the handle stays, private again: Halkyon Learn's ADR 0020, D27): the caller's own public profile | `identified` |
| `profiles` | `byHandle`: anyone's public profile once they went public, whatever the handle's case | signed in |
| `sharedSpace` | spaces people share (Halkyon's ADR 0044, step 2), on the host's sync engine through its `SpaceGrants` (refused, `unavailable`, without one): `create(kind)`, the caller its Owner; `mine`; `members(space)`, by membership ID and public profile, never by account, those invited and waiting last; `invite(space, handle, role)`, the Owner's (or any member's where the space lets members invite), a member at once where the person has an account on this service, else invited until they first sign in there (D13, D17), counted as an answer about a handle; `setRole`, `remove`, `handOn`, `delete`, the Owner's; `leave`, the Owner naming who takes the space on, its only member deleting it instead. A space the caller isn't in can't be told from one that doesn't exist (`SharedSpaceNotFoundException`); Owner and Editor write in the engine, a Viewer reads | `identified` |
- Email keeps only HMAC-SHA256 of the address, keyed with `emailSignInPepper` (`email_identity`), and its
codes as Argon2id hashes (`email_code_request`). Codes are eight Crockford characters,
@ -520,11 +521,17 @@ Built on the three parts above, on the register (members forward every step thro
the other.
4. A host's deletion (`PersonDataDeletion`) runs inside `deletePerson`'s transaction,
after the person's sessions ended and before their identities and the person go, in
after the person's sessions ended and their shared spaces were handed on (each to the
Editor who joined first, else the Viewer, Halkyon's ADR 0044, D11) or deleted with no
other member, and before their identities and the person go, in
the order given; it deletes the person's rows in the host's tables, and returns what
has to wait until the deletion is committed (files outside the database), or null.
Pruning deletes through the same function.
A host keeping spaces people share hands over its sync engine (`HalkyonBase.spaces`, a
`SpaceGrants` on `OfflineSyncSpaces`: Halkyon Learn's `SyncSpaceGrants`); every call
runs in the module's transaction.
5. Create the host's migration. A host taking the tables over from its own models (as
Halkyon Learn does) finds no change once its copies are gone: Serverpod matches tables
by name, and these are identical. `create-migration --empty` then records the module's

View file

@ -5,8 +5,8 @@
/// (ADR 0016), signing in with a password and TOTP and getting back into an
/// account (ADR 0017), signing in with SimpleLogin and its forwarding page
/// (ADR 0020), the ways in an operator offers, the operator's assistants
/// (Halkyon Learn's ADR 0021), people's files (ADR 0043), and the module's
/// models.
/// (Halkyon Learn's ADR 0021), people's files (ADR 0043), the spaces people
/// share (ADR 0044), and the module's models.
library;
export 'src/admission/admission.dart'
@ -111,6 +111,7 @@ export 'src/register/member_pruning.dart';
export 'src/register/people.dart' show issueSession;
export 'src/register/sessions_ended.dart'
show AccountSessionsEnded, deliverSessionNotices;
export 'src/shared_space/space_grants.dart';
export 'src/simplelogin/handle_suggestion.dart'
show handleFromName, handlesToTry, suggestable;
export 'src/simplelogin/simplelogin.dart' show forgetUnfinishedSimpleLogins;

View file

@ -5,13 +5,15 @@ import 'package:halkyon_base_server/src/halkyon_base.dart';
import 'package:halkyon_base_server/src/profile/profile_rules.dart';
import 'package:halkyon_base_server/src/register/accounts_mode.dart';
import 'package:halkyon_base_server/src/register/deletion_notices.dart';
import 'package:halkyon_base_server/src/shared_space/shared_spaces.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
/// Deletes [authUserId] with everything they have, in one transaction, in
/// this order: their sessions end first, since a device with a valid session
/// would upload everything again; then the host deletes their data in its
/// own tables ([HalkyonBase.deletePersonData], in the order given); then
/// own tables ([HalkyonBase.deletePersonData], in the order given), after
/// their shared spaces are handed on or deleted (`leaveSharedSpacesOf`); then
/// their public profile with its avatar, their address's hash, their
/// password with their keys, authenticators and recovery codes, their
/// assistants' grants and figures, their wait for an operator, and the
@ -60,6 +62,9 @@ Future<void> deletePerson(
where: (t) => t.authUserId.equals(authUserId),
transaction: transaction,
);
// Their shared spaces, before the host's own deletions: one they own
// passes on to another member, or goes with them (ADR 0044, D11).
await leaveSharedSpacesOf(session, authUserId, transaction: transaction);
final afterwards = <AfterDeletion>[];
for (final deletion in deletions) {
if (await deletion(session, authUserId, transaction) case final after?) {

View file

@ -35,6 +35,8 @@ import 'package:halkyon_base_server/src/generated/password/password_setup.dart'
as _ikhyee47;
import 'package:halkyon_base_server/src/generated/password/wrapped_keys.dart'
as _iji0wmu8;
import 'package:halkyon_base_server/src/generated/shared_space/shared_space_role.dart'
as _i4jk9by9;
import 'package:halkyon_base_server/src/generated/simplelogin/simplelogin_return.dart'
as _igmh0yku;
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
@ -57,6 +59,7 @@ import '../password/authenticator_endpoint.dart' as _i3xqawxj;
import '../password/password_endpoint.dart' as _ig7fyg1b;
import '../password/password_sign_in_endpoint.dart' as _ik13m562;
import '../profile/profile_endpoint.dart' as _i6ky944g;
import '../shared_space/shared_space_endpoint.dart' as _i858tn7r;
import '../simplelogin/simplelogin_endpoint.dart' as _ia8c9alv;
class Endpoints extends _is.EndpointDispatch {
@ -153,6 +156,12 @@ class Endpoints extends _is.EndpointDispatch {
'profiles',
'halkyon_base',
),
'sharedSpace': _i858tn7r.SharedSpaceEndpoint()
..initialize(
server,
'sharedSpace',
'halkyon_base',
),
'simpleLogin': _ia8c9alv.SimpleLoginEndpoint()
..initialize(
server,
@ -1906,6 +1915,225 @@ class Endpoints extends _is.EndpointDispatch {
),
},
);
connectors['sharedSpace'] = _is.EndpointConnector(
name: 'sharedSpace',
endpoint: endpoints['sharedSpace']!,
methodConnectors: {
'create': _is.MethodConnector(
name: 'create',
params: {
'kind': _is.ParameterDescription(
name: 'kind',
type: _is.getType<String>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.create(
session,
params['kind'],
),
),
'mine': _is.MethodConnector(
name: 'mine',
params: {},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.mine(session),
),
'members': _is.MethodConnector(
name: 'members',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.members(
session,
params['space'],
),
),
'invite': _is.MethodConnector(
name: 'invite',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'handle': _is.ParameterDescription(
name: 'handle',
type: _is.getType<String>(),
nullable: false,
),
'role': _is.ParameterDescription(
name: 'role',
type: _is.getType<_i4jk9by9.SharedSpaceRole>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.invite(
session,
params['space'],
params['handle'],
params['role'],
),
),
'setRole': _is.MethodConnector(
name: 'setRole',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'member': _is.ParameterDescription(
name: 'member',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'role': _is.ParameterDescription(
name: 'role',
type: _is.getType<_i4jk9by9.SharedSpaceRole>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.setRole(
session,
params['space'],
params['member'],
params['role'],
),
),
'remove': _is.MethodConnector(
name: 'remove',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'member': _is.ParameterDescription(
name: 'member',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.remove(
session,
params['space'],
params['member'],
),
),
'handOn': _is.MethodConnector(
name: 'handOn',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'member': _is.ParameterDescription(
name: 'member',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.handOn(
session,
params['space'],
params['member'],
),
),
'leave': _is.MethodConnector(
name: 'leave',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
'nextOwner': _is.ParameterDescription(
name: 'nextOwner',
type: _is.getType<_is.UuidValue?>(),
nullable: true,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.leave(
session,
params['space'],
nextOwner: params['nextOwner'],
),
),
'delete': _is.MethodConnector(
name: 'delete',
params: {
'space': _is.ParameterDescription(
name: 'space',
type: _is.getType<_is.UuidValue>(),
nullable: false,
),
},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async =>
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
.delete(
session,
params['space'],
),
),
},
);
connectors['simpleLogin'] = _is.EndpointConnector(
name: 'simpleLogin',
endpoint: endpoints['simpleLogin']!,

View file

@ -19,6 +19,10 @@ import 'package:halkyon_base_server/src/generated/operator/account_overview.dart
as _iiuvgbjo;
import 'package:halkyon_base_server/src/generated/password/authenticator.dart'
as _ip34xal6;
import 'package:halkyon_base_server/src/generated/shared_space/shared_space_info.dart'
as _i582118y;
import 'package:halkyon_base_server/src/generated/shared_space/shared_space_member_info.dart'
as _izxzwa6x;
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
import 'package:serverpod/protocol.dart' as _isp;
import 'package:serverpod/serverpod.dart' as _is;
@ -109,6 +113,15 @@ import 'profile/profile_refused_exception.dart' as _idvc5e7p;
import 'profile/public_profile.dart' as _ioppe2ep;
import 'register/account_deletion_notice.dart' as _i2lyzpm9;
import 'register/sessions_ended_notice.dart' as _io5c3tod;
import 'shared_space/shared_space.dart' as _iampeqql;
import 'shared_space/shared_space_info.dart' as _i1rvc02f;
import 'shared_space/shared_space_invitation.dart' as _i7h4we7t;
import 'shared_space/shared_space_member.dart' as _iw7ixstr;
import 'shared_space/shared_space_member_info.dart' as _iz8z2oju;
import 'shared_space/shared_space_not_found_exception.dart' as _ixm56fil;
import 'shared_space/shared_space_refusal.dart' as _irg3s7i7;
import 'shared_space/shared_space_refused_exception.dart' as _iooym5cy;
import 'shared_space/shared_space_role.dart' as _i2fude1g;
import 'simplelogin/simplelogin_completed.dart' as _i51ry5s2;
import 'simplelogin/simplelogin_outcome.dart' as _i5ar1hzl;
import 'simplelogin/simplelogin_proof.dart' as _iauqtzog;
@ -196,6 +209,15 @@ export 'profile/profile_refused_exception.dart';
export 'profile/public_profile.dart';
export 'register/account_deletion_notice.dart';
export 'register/sessions_ended_notice.dart';
export 'shared_space/shared_space.dart';
export 'shared_space/shared_space_info.dart';
export 'shared_space/shared_space_invitation.dart';
export 'shared_space/shared_space_member.dart';
export 'shared_space/shared_space_member_info.dart';
export 'shared_space/shared_space_not_found_exception.dart';
export 'shared_space/shared_space_refusal.dart';
export 'shared_space/shared_space_refused_exception.dart';
export 'shared_space/shared_space_role.dart';
export 'simplelogin/simplelogin_completed.dart';
export 'simplelogin/simplelogin_outcome.dart';
export 'simplelogin/simplelogin_proof.dart';
@ -1246,6 +1268,224 @@ class Protocol extends _is.DatabaseSerializationManager {
],
managed: true,
),
_isp.TableDefinition(
name: 'shared_space',
dartName: 'SharedSpace',
schema: 'public',
module: 'halkyon_base',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'kind',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'String',
),
_isp.ColumnDefinition(
name: 'createdAt',
columnType: _isp.ColumnType.timestampWithoutTimeZone,
isNullable: false,
dartType: 'DateTime',
),
_isp.ColumnDefinition(
name: 'joinByLink',
columnType: _isp.ColumnType.boolean,
isNullable: false,
dartType: 'bool',
columnDefault: 'false',
),
_isp.ColumnDefinition(
name: 'membersInvite',
columnType: _isp.ColumnType.boolean,
isNullable: false,
dartType: 'bool',
columnDefault: 'false',
),
],
foreignKeys: [],
indexes: [],
managed: true,
),
_isp.TableDefinition(
name: 'shared_space_invitation',
dartName: 'SharedSpaceInvitation',
schema: 'public',
module: 'halkyon_base',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'sharedSpaceId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'authUserId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'role',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'protocol:SharedSpaceRole',
),
_isp.ColumnDefinition(
name: 'invitedBy',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'invitedAt',
columnType: _isp.ColumnType.timestampWithoutTimeZone,
isNullable: false,
dartType: 'DateTime',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'shared_space_invitation_fk_0',
columns: ['sharedSpaceId'],
referenceTable: 'shared_space',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'shared_space_invitation_space_person_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'sharedSpaceId',
),
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'authUserId',
),
],
type: 'btree',
isUnique: true,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'shared_space_invitation_person_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'authUserId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isp.TableDefinition(
name: 'shared_space_member',
dartName: 'SharedSpaceMember',
schema: 'public',
module: 'halkyon_base',
columns: [
_isp.ColumnDefinition(
name: 'id',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isp.ColumnDefinition(
name: 'sharedSpaceId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'authUserId',
columnType: _isp.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue',
),
_isp.ColumnDefinition(
name: 'role',
columnType: _isp.ColumnType.text,
isNullable: false,
dartType: 'protocol:SharedSpaceRole',
),
_isp.ColumnDefinition(
name: 'joinedAt',
columnType: _isp.ColumnType.timestampWithoutTimeZone,
isNullable: false,
dartType: 'DateTime',
),
],
foreignKeys: [
_isp.ForeignKeyDefinition(
constraintName: 'shared_space_member_fk_0',
columns: ['sharedSpaceId'],
referenceTable: 'shared_space',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isp.ForeignKeyAction.noAction,
onDelete: _isp.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isp.IndexDefinition(
indexName: 'shared_space_member_space_person_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'sharedSpaceId',
),
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'authUserId',
),
],
type: 'btree',
isUnique: true,
isPrimary: false,
),
_isp.IndexDefinition(
indexName: 'shared_space_member_person_idx',
tableSpace: null,
elements: [
_isp.IndexElementDefinition(
type: _isp.IndexElementDefinitionType.column,
definition: 'authUserId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isp.TableDefinition(
name: 'sign_in_challenge',
dartName: 'SignInChallenge',
@ -1791,6 +2031,33 @@ class Protocol extends _is.DatabaseSerializationManager {
if (t == _io5c3tod.SessionsEndedNotice) {
return _io5c3tod.SessionsEndedNotice.fromJson(data) as T;
}
if (t == _iampeqql.SharedSpace) {
return _iampeqql.SharedSpace.fromJson(data) as T;
}
if (t == _i1rvc02f.SharedSpaceInfo) {
return _i1rvc02f.SharedSpaceInfo.fromJson(data) as T;
}
if (t == _i7h4we7t.SharedSpaceInvitation) {
return _i7h4we7t.SharedSpaceInvitation.fromJson(data) as T;
}
if (t == _iw7ixstr.SharedSpaceMember) {
return _iw7ixstr.SharedSpaceMember.fromJson(data) as T;
}
if (t == _iz8z2oju.SharedSpaceMemberInfo) {
return _iz8z2oju.SharedSpaceMemberInfo.fromJson(data) as T;
}
if (t == _ixm56fil.SharedSpaceNotFoundException) {
return _ixm56fil.SharedSpaceNotFoundException.fromJson(data) as T;
}
if (t == _irg3s7i7.SharedSpaceRefusal) {
return _irg3s7i7.SharedSpaceRefusal.fromJson(data) as T;
}
if (t == _iooym5cy.SharedSpaceRefusedException) {
return _iooym5cy.SharedSpaceRefusedException.fromJson(data) as T;
}
if (t == _i2fude1g.SharedSpaceRole) {
return _i2fude1g.SharedSpaceRole.fromJson(data) as T;
}
if (t == _i51ry5s2.SimpleLoginCompleted) {
return _i51ry5s2.SimpleLoginCompleted.fromJson(data) as T;
}
@ -2152,6 +2419,49 @@ class Protocol extends _is.DatabaseSerializationManager {
: null)
as T;
}
if (t == _is.getType<_iampeqql.SharedSpace?>()) {
return (data != null ? _iampeqql.SharedSpace.fromJson(data) : null) as T;
}
if (t == _is.getType<_i1rvc02f.SharedSpaceInfo?>()) {
return (data != null ? _i1rvc02f.SharedSpaceInfo.fromJson(data) : null)
as T;
}
if (t == _is.getType<_i7h4we7t.SharedSpaceInvitation?>()) {
return (data != null
? _i7h4we7t.SharedSpaceInvitation.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_iw7ixstr.SharedSpaceMember?>()) {
return (data != null ? _iw7ixstr.SharedSpaceMember.fromJson(data) : null)
as T;
}
if (t == _is.getType<_iz8z2oju.SharedSpaceMemberInfo?>()) {
return (data != null
? _iz8z2oju.SharedSpaceMemberInfo.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_ixm56fil.SharedSpaceNotFoundException?>()) {
return (data != null
? _ixm56fil.SharedSpaceNotFoundException.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_irg3s7i7.SharedSpaceRefusal?>()) {
return (data != null ? _irg3s7i7.SharedSpaceRefusal.fromJson(data) : null)
as T;
}
if (t == _is.getType<_iooym5cy.SharedSpaceRefusedException?>()) {
return (data != null
? _iooym5cy.SharedSpaceRefusedException.fromJson(data)
: null)
as T;
}
if (t == _is.getType<_i2fude1g.SharedSpaceRole?>()) {
return (data != null ? _i2fude1g.SharedSpaceRole.fromJson(data) : null)
as T;
}
if (t == _is.getType<_i51ry5s2.SimpleLoginCompleted?>()) {
return (data != null
? _i51ry5s2.SimpleLoginCompleted.fromJson(data)
@ -2255,6 +2565,18 @@ class Protocol extends _is.DatabaseSerializationManager {
.toList()
as T;
}
if (t == List<_i582118y.SharedSpaceInfo>) {
return (data as List)
.map((e) => deserialize<_i582118y.SharedSpaceInfo>(e))
.toList()
as T;
}
if (t == List<_izxzwa6x.SharedSpaceMemberInfo>) {
return (data as List)
.map((e) => deserialize<_izxzwa6x.SharedSpaceMemberInfo>(e))
.toList()
as T;
}
try {
return _ipoqfiqr.Protocol().deserialize<T>(data, t);
} on _is.DeserializationTypeNotFoundException catch (_) {}
@ -2355,6 +2677,15 @@ class Protocol extends _is.DatabaseSerializationManager {
_ioppe2ep.PublicProfile => 'PublicProfile',
_i2lyzpm9.AccountDeletionNotice => 'AccountDeletionNotice',
_io5c3tod.SessionsEndedNotice => 'SessionsEndedNotice',
_iampeqql.SharedSpace => 'SharedSpace',
_i1rvc02f.SharedSpaceInfo => 'SharedSpaceInfo',
_i7h4we7t.SharedSpaceInvitation => 'SharedSpaceInvitation',
_iw7ixstr.SharedSpaceMember => 'SharedSpaceMember',
_iz8z2oju.SharedSpaceMemberInfo => 'SharedSpaceMemberInfo',
_ixm56fil.SharedSpaceNotFoundException => 'SharedSpaceNotFoundException',
_irg3s7i7.SharedSpaceRefusal => 'SharedSpaceRefusal',
_iooym5cy.SharedSpaceRefusedException => 'SharedSpaceRefusedException',
_i2fude1g.SharedSpaceRole => 'SharedSpaceRole',
_i51ry5s2.SimpleLoginCompleted => 'SimpleLoginCompleted',
_i5ar1hzl.SimpleLoginOutcome => 'SimpleLoginOutcome',
_iauqtzog.SimpleLoginProof => 'SimpleLoginProof',
@ -2542,6 +2873,24 @@ class Protocol extends _is.DatabaseSerializationManager {
return 'AccountDeletionNotice';
case _io5c3tod.SessionsEndedNotice():
return 'SessionsEndedNotice';
case _iampeqql.SharedSpace():
return 'SharedSpace';
case _i1rvc02f.SharedSpaceInfo():
return 'SharedSpaceInfo';
case _i7h4we7t.SharedSpaceInvitation():
return 'SharedSpaceInvitation';
case _iw7ixstr.SharedSpaceMember():
return 'SharedSpaceMember';
case _iz8z2oju.SharedSpaceMemberInfo():
return 'SharedSpaceMemberInfo';
case _ixm56fil.SharedSpaceNotFoundException():
return 'SharedSpaceNotFoundException';
case _irg3s7i7.SharedSpaceRefusal():
return 'SharedSpaceRefusal';
case _iooym5cy.SharedSpaceRefusedException():
return 'SharedSpaceRefusedException';
case _i2fude1g.SharedSpaceRole():
return 'SharedSpaceRole';
case _i51ry5s2.SimpleLoginCompleted():
return 'SimpleLoginCompleted';
case _i5ar1hzl.SimpleLoginOutcome():
@ -2813,6 +3162,33 @@ class Protocol extends _is.DatabaseSerializationManager {
if (dataClassName == 'SessionsEndedNotice') {
return deserialize<_io5c3tod.SessionsEndedNotice>(data['data']);
}
if (dataClassName == 'SharedSpace') {
return deserialize<_iampeqql.SharedSpace>(data['data']);
}
if (dataClassName == 'SharedSpaceInfo') {
return deserialize<_i1rvc02f.SharedSpaceInfo>(data['data']);
}
if (dataClassName == 'SharedSpaceInvitation') {
return deserialize<_i7h4we7t.SharedSpaceInvitation>(data['data']);
}
if (dataClassName == 'SharedSpaceMember') {
return deserialize<_iw7ixstr.SharedSpaceMember>(data['data']);
}
if (dataClassName == 'SharedSpaceMemberInfo') {
return deserialize<_iz8z2oju.SharedSpaceMemberInfo>(data['data']);
}
if (dataClassName == 'SharedSpaceNotFoundException') {
return deserialize<_ixm56fil.SharedSpaceNotFoundException>(data['data']);
}
if (dataClassName == 'SharedSpaceRefusal') {
return deserialize<_irg3s7i7.SharedSpaceRefusal>(data['data']);
}
if (dataClassName == 'SharedSpaceRefusedException') {
return deserialize<_iooym5cy.SharedSpaceRefusedException>(data['data']);
}
if (dataClassName == 'SharedSpaceRole') {
return deserialize<_i2fude1g.SharedSpaceRole>(data['data']);
}
if (dataClassName == 'SimpleLoginCompleted') {
return deserialize<_i51ry5s2.SimpleLoginCompleted>(data['data']);
}
@ -2954,6 +3330,12 @@ class Protocol extends _is.DatabaseSerializationManager {
return _i2lyzpm9.AccountDeletionNotice.t;
case _io5c3tod.SessionsEndedNotice:
return _io5c3tod.SessionsEndedNotice.t;
case _iampeqql.SharedSpace:
return _iampeqql.SharedSpace.t;
case _i7h4we7t.SharedSpaceInvitation:
return _i7h4we7t.SharedSpaceInvitation.t;
case _iw7ixstr.SharedSpaceMember:
return _iw7ixstr.SharedSpaceMember.t;
}
return null;
}

View file

@ -84,6 +84,16 @@ profile:
- remove:
profiles:
- byHandle:
sharedSpace:
- create:
- mine:
- members:
- invite:
- setRole:
- remove:
- handOn:
- leave:
- delete:
simpleLogin:
- begin:
- complete:

View file

@ -0,0 +1,665 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
/// A space several people share (ADR 0044, D1): the host's sync engine
/// space of the same ID, which this mirrors with who may do what in it.
/// Kept on the service the space belongs to (D16), read by the server
/// alone.
abstract class SharedSpace
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
SharedSpace._({
this.id,
required this.kind,
required this.createdAt,
bool? joinByLink,
bool? membersInvite,
}) : joinByLink = joinByLink ?? false,
membersInvite = membersInvite ?? false;
factory SharedSpace({
_is.UuidValue? id,
required String kind,
required DateTime createdAt,
bool? joinByLink,
bool? membersInvite,
}) = _SharedSpaceImpl;
factory SharedSpace.fromJson(Map<String, dynamic> jsonSerialization) {
return SharedSpace(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
kind: jsonSerialization['kind'] as String,
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
joinByLink: jsonSerialization['joinByLink'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['joinByLink']),
membersInvite: jsonSerialization['membersInvite'] == null
? null
: _is.BoolJsonExtension.fromJson(jsonSerialization['membersInvite']),
);
}
static final t = SharedSpaceTable();
static const db = SharedSpaceRepository._();
@override
_is.UuidValue? id;
/// What the space holds, in the host's words (`studyset`).
String kind;
DateTime createdAt;
/// Whether someone may join by its link (D5); off at first.
bool joinByLink;
/// Whether members other than the Owner may invite others (D5); off at
/// first.
bool membersInvite;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [SharedSpace]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpace copyWith({
_is.UuidValue? id,
String? kind,
DateTime? createdAt,
bool? joinByLink,
bool? membersInvite,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpace',
if (id != null) 'id': id?.toJson(),
'kind': kind,
'createdAt': createdAt.toJson(),
'joinByLink': joinByLink,
'membersInvite': membersInvite,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {};
}
static SharedSpaceInclude include() {
return SharedSpaceInclude._();
}
static SharedSpaceIncludeList includeList({
_is.WhereExpressionBuilder<SharedSpaceTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
SharedSpaceInclude? include,
}) {
return SharedSpaceIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _SharedSpaceImpl extends SharedSpace {
_SharedSpaceImpl({
_is.UuidValue? id,
required String kind,
required DateTime createdAt,
bool? joinByLink,
bool? membersInvite,
}) : super._(
id: id,
kind: kind,
createdAt: createdAt,
joinByLink: joinByLink,
membersInvite: membersInvite,
);
/// Returns a shallow copy of this [SharedSpace]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpace copyWith({
Object? id = _Undefined,
String? kind,
DateTime? createdAt,
bool? joinByLink,
bool? membersInvite,
}) {
return SharedSpace(
id: id is _is.UuidValue? ? id : this.id,
kind: kind ?? this.kind,
createdAt: createdAt ?? this.createdAt,
joinByLink: joinByLink ?? this.joinByLink,
membersInvite: membersInvite ?? this.membersInvite,
);
}
}
class SharedSpaceUpdateTable extends _is.UpdateTable<SharedSpaceTable> {
SharedSpaceUpdateTable(super.table);
_is.ColumnValue<String, String> kind(String value) => _is.ColumnValue(
table.kind,
value,
);
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
_is.ColumnValue(
table.createdAt,
value,
);
_is.ColumnValue<bool, bool> joinByLink(bool value) => _is.ColumnValue(
table.joinByLink,
value,
);
_is.ColumnValue<bool, bool> membersInvite(bool value) => _is.ColumnValue(
table.membersInvite,
value,
);
}
class SharedSpaceTable extends _is.Table<_is.UuidValue?> {
SharedSpaceTable({super.tableRelation}) : super(tableName: 'shared_space') {
updateTable = SharedSpaceUpdateTable(this);
kind = _is.ColumnString(
'kind',
this,
);
createdAt = _is.ColumnDateTime(
'createdAt',
this,
);
joinByLink = _is.ColumnBool(
'joinByLink',
this,
hasDefault: true,
);
membersInvite = _is.ColumnBool(
'membersInvite',
this,
hasDefault: true,
);
}
late final SharedSpaceUpdateTable updateTable;
/// What the space holds, in the host's words (`studyset`).
late final _is.ColumnString kind;
late final _is.ColumnDateTime createdAt;
/// Whether someone may join by its link (D5); off at first.
late final _is.ColumnBool joinByLink;
/// Whether members other than the Owner may invite others (D5); off at
/// first.
late final _is.ColumnBool membersInvite;
@override
List<_is.Column> get columns => [
id,
kind,
createdAt,
joinByLink,
membersInvite,
];
}
class SharedSpaceInclude extends _is.IncludeObject {
SharedSpaceInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpace.t;
}
class SharedSpaceIncludeList extends _is.IncludeList {
SharedSpaceIncludeList._({
_is.WhereExpressionBuilder<SharedSpaceTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(SharedSpace.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpace.t;
}
class SharedSpaceRepository {
const SharedSpaceRepository._();
/// Returns a list of [SharedSpace]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<SharedSpace>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<SharedSpace>(
where: where?.call(SharedSpace.t),
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [SharedSpace] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<SharedSpace?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceTable>? where,
int? offset,
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<SharedSpace>(
where: where?.call(SharedSpace.t),
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [SharedSpace] by its [id] or null if no such row exists.
Future<SharedSpace?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<SharedSpace>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [SharedSpace]s in the list and returns the inserted rows.
///
/// The returned [SharedSpace]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> insert(
_is.DatabaseSession session,
List<SharedSpace> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<SharedSpace>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [SharedSpace] and returns the inserted row.
///
/// The returned [SharedSpace] will have its `id` field set.
Future<SharedSpace> insertRow(
_is.DatabaseSession session,
SharedSpace row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<SharedSpace>(
row,
transaction: transaction,
);
}
/// Upserts all [SharedSpace]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [SharedSpace]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> upsert(
_is.DatabaseSession session,
List<SharedSpace> rows, {
required _is.ColumnSelections<SharedSpaceTable> conflictColumns,
_is.ColumnSelections<SharedSpaceTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<SharedSpace>(
rows,
conflictColumns: conflictColumns(SharedSpace.t),
updateColumns: updateColumns?.call(SharedSpace.t),
updateWhere: updateWhere?.call(SharedSpace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [SharedSpace] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [SharedSpace] will have its `id` field set.
Future<SharedSpace?> upsertRow(
_is.DatabaseSession session,
SharedSpace row, {
required _is.ColumnSelections<SharedSpaceTable> conflictColumns,
_is.ColumnSelections<SharedSpaceTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<SharedSpace>(
row,
conflictColumns: conflictColumns(SharedSpace.t),
updateColumns: updateColumns?.call(SharedSpace.t),
updateWhere: updateWhere?.call(SharedSpace.t),
transaction: transaction,
);
}
/// Updates all [SharedSpace]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> update(
_is.DatabaseSession session,
List<SharedSpace> rows, {
_is.ColumnSelections<SharedSpaceTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<SharedSpace>(
rows,
columns: columns?.call(SharedSpace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [SharedSpace]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<SharedSpace> updateRow(
_is.DatabaseSession session,
SharedSpace row, {
_is.ColumnSelections<SharedSpaceTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<SharedSpace>(
row,
columns: columns?.call(SharedSpace.t),
transaction: transaction,
);
}
/// Updates a single [SharedSpace] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<SharedSpace?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<SharedSpaceUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<SharedSpace>(
id,
columnValues: columnValues(SharedSpace.t.updateTable),
transaction: transaction,
);
}
/// Updates all [SharedSpace]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<SharedSpaceUpdateTable> columnValues,
required _is.WhereExpressionBuilder<SharedSpaceTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<SharedSpace>(
columnValues: columnValues(SharedSpace.t.updateTable),
where: where(SharedSpace.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [SharedSpace]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> delete(
_is.DatabaseSession session,
List<SharedSpace> rows, {
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<SharedSpace>(
rows,
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [SharedSpace].
Future<SharedSpace> deleteRow(
_is.DatabaseSession session,
SharedSpace row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<SharedSpace>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpace>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceTable> where,
_is.OrderByBuilder<SharedSpaceTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<SharedSpace>(
where: where(SharedSpace.t),
orderBy: orderBy?.call(SharedSpace.t),
orderByList: orderByList?.call(SharedSpace.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<SharedSpace>(
where: where?.call(SharedSpace.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [SharedSpace] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<SharedSpace>(
where: where(SharedSpace.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,124 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import '../shared_space/shared_space_role.dart' as _igsxp9el;
/// A shared space as its member sees it.
abstract class SharedSpaceInfo
implements _is.SerializableModel, _is.ProtocolSerialization {
SharedSpaceInfo._({
required this.id,
required this.kind,
required this.role,
required this.createdAt,
});
factory SharedSpaceInfo({
required _is.UuidValue id,
required String kind,
required _igsxp9el.SharedSpaceRole role,
required DateTime createdAt,
}) = _SharedSpaceInfoImpl;
factory SharedSpaceInfo.fromJson(Map<String, dynamic> jsonSerialization) {
return SharedSpaceInfo(
id: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
kind: jsonSerialization['kind'] as String,
role: _igsxp9el.SharedSpaceRole.fromJson(
(jsonSerialization['role'] as String),
),
createdAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['createdAt'],
),
);
}
/// The sync engine's space.
_is.UuidValue id;
/// What it holds, in the host's words.
String kind;
/// The caller's role in it.
_igsxp9el.SharedSpaceRole role;
DateTime createdAt;
/// Returns a shallow copy of this [SharedSpaceInfo]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceInfo copyWith({
_is.UuidValue? id,
String? kind,
_igsxp9el.SharedSpaceRole? role,
DateTime? createdAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceInfo',
'id': id.toJson(),
'kind': kind,
'role': role.toJson(),
'createdAt': createdAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SharedSpaceInfo',
'id': id.toJson(),
'kind': kind,
'role': role.toJson(),
'createdAt': createdAt.toJson(),
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _SharedSpaceInfoImpl extends SharedSpaceInfo {
_SharedSpaceInfoImpl({
required _is.UuidValue id,
required String kind,
required _igsxp9el.SharedSpaceRole role,
required DateTime createdAt,
}) : super._(
id: id,
kind: kind,
role: role,
createdAt: createdAt,
);
/// Returns a shallow copy of this [SharedSpaceInfo]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceInfo copyWith({
_is.UuidValue? id,
String? kind,
_igsxp9el.SharedSpaceRole? role,
DateTime? createdAt,
}) {
return SharedSpaceInfo(
id: id ?? this.id,
kind: kind ?? this.kind,
role: role ?? this.role,
createdAt: createdAt ?? this.createdAt,
);
}
}

View file

@ -0,0 +1,702 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import '../shared_space/shared_space_role.dart' as _igsxp9el;
/// An invitation by @handle waiting for someone with no account on this
/// service yet (ADR 0044, D13 and D17): made a membership the first time
/// they sign in here.
abstract class SharedSpaceInvitation
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
SharedSpaceInvitation._({
this.id,
required this.sharedSpaceId,
required this.authUserId,
required this.role,
required this.invitedBy,
required this.invitedAt,
});
factory SharedSpaceInvitation({
_is.UuidValue? id,
required _is.UuidValue sharedSpaceId,
required _is.UuidValue authUserId,
required _igsxp9el.SharedSpaceRole role,
required _is.UuidValue invitedBy,
required DateTime invitedAt,
}) = _SharedSpaceInvitationImpl;
factory SharedSpaceInvitation.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return SharedSpaceInvitation(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
sharedSpaceId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['sharedSpaceId'],
),
authUserId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authUserId'],
),
role: _igsxp9el.SharedSpaceRole.fromJson(
(jsonSerialization['role'] as String),
),
invitedBy: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['invitedBy'],
),
invitedAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['invitedAt'],
),
);
}
static final t = SharedSpaceInvitationTable();
static const db = SharedSpaceInvitationRepository._();
@override
_is.UuidValue? id;
_is.UuidValue sharedSpaceId;
/// The person invited: the register's account ID.
_is.UuidValue authUserId;
/// The role they will have; never the Owner's.
_igsxp9el.SharedSpaceRole role;
/// Who invited them.
_is.UuidValue invitedBy;
DateTime invitedAt;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [SharedSpaceInvitation]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceInvitation copyWith({
_is.UuidValue? id,
_is.UuidValue? sharedSpaceId,
_is.UuidValue? authUserId,
_igsxp9el.SharedSpaceRole? role,
_is.UuidValue? invitedBy,
DateTime? invitedAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceInvitation',
if (id != null) 'id': id?.toJson(),
'sharedSpaceId': sharedSpaceId.toJson(),
'authUserId': authUserId.toJson(),
'role': role.toJson(),
'invitedBy': invitedBy.toJson(),
'invitedAt': invitedAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {};
}
static SharedSpaceInvitationInclude include() {
return SharedSpaceInvitationInclude._();
}
static SharedSpaceInvitationIncludeList includeList({
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
SharedSpaceInvitationInclude? include,
}) {
return SharedSpaceInvitationIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _SharedSpaceInvitationImpl extends SharedSpaceInvitation {
_SharedSpaceInvitationImpl({
_is.UuidValue? id,
required _is.UuidValue sharedSpaceId,
required _is.UuidValue authUserId,
required _igsxp9el.SharedSpaceRole role,
required _is.UuidValue invitedBy,
required DateTime invitedAt,
}) : super._(
id: id,
sharedSpaceId: sharedSpaceId,
authUserId: authUserId,
role: role,
invitedBy: invitedBy,
invitedAt: invitedAt,
);
/// Returns a shallow copy of this [SharedSpaceInvitation]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceInvitation copyWith({
Object? id = _Undefined,
_is.UuidValue? sharedSpaceId,
_is.UuidValue? authUserId,
_igsxp9el.SharedSpaceRole? role,
_is.UuidValue? invitedBy,
DateTime? invitedAt,
}) {
return SharedSpaceInvitation(
id: id is _is.UuidValue? ? id : this.id,
sharedSpaceId: sharedSpaceId ?? this.sharedSpaceId,
authUserId: authUserId ?? this.authUserId,
role: role ?? this.role,
invitedBy: invitedBy ?? this.invitedBy,
invitedAt: invitedAt ?? this.invitedAt,
);
}
}
class SharedSpaceInvitationUpdateTable
extends _is.UpdateTable<SharedSpaceInvitationTable> {
SharedSpaceInvitationUpdateTable(super.table);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> sharedSpaceId(
_is.UuidValue value,
) => _is.ColumnValue(
table.sharedSpaceId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
_is.UuidValue value,
) => _is.ColumnValue(
table.authUserId,
value,
);
_is.ColumnValue<_igsxp9el.SharedSpaceRole, _igsxp9el.SharedSpaceRole> role(
_igsxp9el.SharedSpaceRole value,
) => _is.ColumnValue(
table.role,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> invitedBy(
_is.UuidValue value,
) => _is.ColumnValue(
table.invitedBy,
value,
);
_is.ColumnValue<DateTime, DateTime> invitedAt(DateTime value) =>
_is.ColumnValue(
table.invitedAt,
value,
);
}
class SharedSpaceInvitationTable extends _is.Table<_is.UuidValue?> {
SharedSpaceInvitationTable({super.tableRelation})
: super(tableName: 'shared_space_invitation') {
updateTable = SharedSpaceInvitationUpdateTable(this);
sharedSpaceId = _is.ColumnUuid(
'sharedSpaceId',
this,
);
authUserId = _is.ColumnUuid(
'authUserId',
this,
);
role = _is.ColumnEnum(
'role',
this,
_is.EnumSerialization.byName,
);
invitedBy = _is.ColumnUuid(
'invitedBy',
this,
);
invitedAt = _is.ColumnDateTime(
'invitedAt',
this,
);
}
late final SharedSpaceInvitationUpdateTable updateTable;
late final _is.ColumnUuid sharedSpaceId;
/// The person invited: the register's account ID.
late final _is.ColumnUuid authUserId;
/// The role they will have; never the Owner's.
late final _is.ColumnEnum<_igsxp9el.SharedSpaceRole> role;
/// Who invited them.
late final _is.ColumnUuid invitedBy;
late final _is.ColumnDateTime invitedAt;
@override
List<_is.Column> get columns => [
id,
sharedSpaceId,
authUserId,
role,
invitedBy,
invitedAt,
];
}
class SharedSpaceInvitationInclude extends _is.IncludeObject {
SharedSpaceInvitationInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpaceInvitation.t;
}
class SharedSpaceInvitationIncludeList extends _is.IncludeList {
SharedSpaceInvitationIncludeList._({
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(SharedSpaceInvitation.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpaceInvitation.t;
}
class SharedSpaceInvitationRepository {
const SharedSpaceInvitationRepository._();
/// Returns a list of [SharedSpaceInvitation]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<SharedSpaceInvitation>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<SharedSpaceInvitation>(
where: where?.call(SharedSpaceInvitation.t),
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [SharedSpaceInvitation] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<SharedSpaceInvitation?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? where,
int? offset,
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<SharedSpaceInvitation>(
where: where?.call(SharedSpaceInvitation.t),
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [SharedSpaceInvitation] by its [id] or null if no such row exists.
Future<SharedSpaceInvitation?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<SharedSpaceInvitation>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [SharedSpaceInvitation]s in the list and returns the inserted rows.
///
/// The returned [SharedSpaceInvitation]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> insert(
_is.DatabaseSession session,
List<SharedSpaceInvitation> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<SharedSpaceInvitation>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [SharedSpaceInvitation] and returns the inserted row.
///
/// The returned [SharedSpaceInvitation] will have its `id` field set.
Future<SharedSpaceInvitation> insertRow(
_is.DatabaseSession session,
SharedSpaceInvitation row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<SharedSpaceInvitation>(
row,
transaction: transaction,
);
}
/// Upserts all [SharedSpaceInvitation]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [SharedSpaceInvitation]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> upsert(
_is.DatabaseSession session,
List<SharedSpaceInvitation> rows, {
required _is.ColumnSelections<SharedSpaceInvitationTable> conflictColumns,
_is.ColumnSelections<SharedSpaceInvitationTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<SharedSpaceInvitation>(
rows,
conflictColumns: conflictColumns(SharedSpaceInvitation.t),
updateColumns: updateColumns?.call(SharedSpaceInvitation.t),
updateWhere: updateWhere?.call(SharedSpaceInvitation.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [SharedSpaceInvitation] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [SharedSpaceInvitation] will have its `id` field set.
Future<SharedSpaceInvitation?> upsertRow(
_is.DatabaseSession session,
SharedSpaceInvitation row, {
required _is.ColumnSelections<SharedSpaceInvitationTable> conflictColumns,
_is.ColumnSelections<SharedSpaceInvitationTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<SharedSpaceInvitation>(
row,
conflictColumns: conflictColumns(SharedSpaceInvitation.t),
updateColumns: updateColumns?.call(SharedSpaceInvitation.t),
updateWhere: updateWhere?.call(SharedSpaceInvitation.t),
transaction: transaction,
);
}
/// Updates all [SharedSpaceInvitation]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> update(
_is.DatabaseSession session,
List<SharedSpaceInvitation> rows, {
_is.ColumnSelections<SharedSpaceInvitationTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<SharedSpaceInvitation>(
rows,
columns: columns?.call(SharedSpaceInvitation.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [SharedSpaceInvitation]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<SharedSpaceInvitation> updateRow(
_is.DatabaseSession session,
SharedSpaceInvitation row, {
_is.ColumnSelections<SharedSpaceInvitationTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<SharedSpaceInvitation>(
row,
columns: columns?.call(SharedSpaceInvitation.t),
transaction: transaction,
);
}
/// Updates a single [SharedSpaceInvitation] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<SharedSpaceInvitation?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<SharedSpaceInvitationUpdateTable>
columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<SharedSpaceInvitation>(
id,
columnValues: columnValues(SharedSpaceInvitation.t.updateTable),
transaction: transaction,
);
}
/// Updates all [SharedSpaceInvitation]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<SharedSpaceInvitationUpdateTable>
columnValues,
required _is.WhereExpressionBuilder<SharedSpaceInvitationTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<SharedSpaceInvitation>(
columnValues: columnValues(SharedSpaceInvitation.t.updateTable),
where: where(SharedSpaceInvitation.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [SharedSpaceInvitation]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> delete(
_is.DatabaseSession session,
List<SharedSpaceInvitation> rows, {
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<SharedSpaceInvitation>(
rows,
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [SharedSpaceInvitation].
Future<SharedSpaceInvitation> deleteRow(
_is.DatabaseSession session,
SharedSpaceInvitation row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<SharedSpaceInvitation>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceInvitation>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceInvitationTable> where,
_is.OrderByBuilder<SharedSpaceInvitationTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceInvitationTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<SharedSpaceInvitation>(
where: where(SharedSpaceInvitation.t),
orderBy: orderBy?.call(SharedSpaceInvitation.t),
orderByList: orderByList?.call(SharedSpaceInvitation.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceInvitationTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<SharedSpaceInvitation>(
where: where?.call(SharedSpaceInvitation.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [SharedSpaceInvitation] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceInvitationTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<SharedSpaceInvitation>(
where: where(SharedSpaceInvitation.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,671 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import '../shared_space/shared_space_role.dart' as _igsxp9el;
/// A person's membership of a shared space (ADR 0044, D2), with their
/// role; the sync engine's grant is kept in step with it. Exactly one
/// member of a space is its Owner, which the module's code holds to.
abstract class SharedSpaceMember
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
SharedSpaceMember._({
this.id,
required this.sharedSpaceId,
required this.authUserId,
required this.role,
required this.joinedAt,
});
factory SharedSpaceMember({
_is.UuidValue? id,
required _is.UuidValue sharedSpaceId,
required _is.UuidValue authUserId,
required _igsxp9el.SharedSpaceRole role,
required DateTime joinedAt,
}) = _SharedSpaceMemberImpl;
factory SharedSpaceMember.fromJson(Map<String, dynamic> jsonSerialization) {
return SharedSpaceMember(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
sharedSpaceId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['sharedSpaceId'],
),
authUserId: _is.UuidValueJsonExtension.fromJson(
jsonSerialization['authUserId'],
),
role: _igsxp9el.SharedSpaceRole.fromJson(
(jsonSerialization['role'] as String),
),
joinedAt: _is.DateTimeJsonExtension.fromJson(
jsonSerialization['joinedAt'],
),
);
}
static final t = SharedSpaceMemberTable();
static const db = SharedSpaceMemberRepository._();
@override
_is.UuidValue? id;
_is.UuidValue sharedSpaceId;
/// The person: the register's account ID (ADR 0016).
_is.UuidValue authUserId;
_igsxp9el.SharedSpaceRole role;
/// When they joined, which decides who takes ownership on (D11).
DateTime joinedAt;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [SharedSpaceMember]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceMember copyWith({
_is.UuidValue? id,
_is.UuidValue? sharedSpaceId,
_is.UuidValue? authUserId,
_igsxp9el.SharedSpaceRole? role,
DateTime? joinedAt,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceMember',
if (id != null) 'id': id?.toJson(),
'sharedSpaceId': sharedSpaceId.toJson(),
'authUserId': authUserId.toJson(),
'role': role.toJson(),
'joinedAt': joinedAt.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {};
}
static SharedSpaceMemberInclude include() {
return SharedSpaceMemberInclude._();
}
static SharedSpaceMemberIncludeList includeList({
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
SharedSpaceMemberInclude? include,
}) {
return SharedSpaceMemberIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _SharedSpaceMemberImpl extends SharedSpaceMember {
_SharedSpaceMemberImpl({
_is.UuidValue? id,
required _is.UuidValue sharedSpaceId,
required _is.UuidValue authUserId,
required _igsxp9el.SharedSpaceRole role,
required DateTime joinedAt,
}) : super._(
id: id,
sharedSpaceId: sharedSpaceId,
authUserId: authUserId,
role: role,
joinedAt: joinedAt,
);
/// Returns a shallow copy of this [SharedSpaceMember]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceMember copyWith({
Object? id = _Undefined,
_is.UuidValue? sharedSpaceId,
_is.UuidValue? authUserId,
_igsxp9el.SharedSpaceRole? role,
DateTime? joinedAt,
}) {
return SharedSpaceMember(
id: id is _is.UuidValue? ? id : this.id,
sharedSpaceId: sharedSpaceId ?? this.sharedSpaceId,
authUserId: authUserId ?? this.authUserId,
role: role ?? this.role,
joinedAt: joinedAt ?? this.joinedAt,
);
}
}
class SharedSpaceMemberUpdateTable
extends _is.UpdateTable<SharedSpaceMemberTable> {
SharedSpaceMemberUpdateTable(super.table);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> sharedSpaceId(
_is.UuidValue value,
) => _is.ColumnValue(
table.sharedSpaceId,
value,
);
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
_is.UuidValue value,
) => _is.ColumnValue(
table.authUserId,
value,
);
_is.ColumnValue<_igsxp9el.SharedSpaceRole, _igsxp9el.SharedSpaceRole> role(
_igsxp9el.SharedSpaceRole value,
) => _is.ColumnValue(
table.role,
value,
);
_is.ColumnValue<DateTime, DateTime> joinedAt(DateTime value) =>
_is.ColumnValue(
table.joinedAt,
value,
);
}
class SharedSpaceMemberTable extends _is.Table<_is.UuidValue?> {
SharedSpaceMemberTable({super.tableRelation})
: super(tableName: 'shared_space_member') {
updateTable = SharedSpaceMemberUpdateTable(this);
sharedSpaceId = _is.ColumnUuid(
'sharedSpaceId',
this,
);
authUserId = _is.ColumnUuid(
'authUserId',
this,
);
role = _is.ColumnEnum(
'role',
this,
_is.EnumSerialization.byName,
);
joinedAt = _is.ColumnDateTime(
'joinedAt',
this,
);
}
late final SharedSpaceMemberUpdateTable updateTable;
late final _is.ColumnUuid sharedSpaceId;
/// The person: the register's account ID (ADR 0016).
late final _is.ColumnUuid authUserId;
late final _is.ColumnEnum<_igsxp9el.SharedSpaceRole> role;
/// When they joined, which decides who takes ownership on (D11).
late final _is.ColumnDateTime joinedAt;
@override
List<_is.Column> get columns => [
id,
sharedSpaceId,
authUserId,
role,
joinedAt,
];
}
class SharedSpaceMemberInclude extends _is.IncludeObject {
SharedSpaceMemberInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpaceMember.t;
}
class SharedSpaceMemberIncludeList extends _is.IncludeList {
SharedSpaceMemberIncludeList._({
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(SharedSpaceMember.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => SharedSpaceMember.t;
}
class SharedSpaceMemberRepository {
const SharedSpaceMemberRepository._();
/// Returns a list of [SharedSpaceMember]s matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order of the items use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// The maximum number of items can be set by [limit]. If no limit is set,
/// all items matching the query will be returned.
///
/// [offset] defines how many items to skip, after which [limit] (or all)
/// items are read from the database.
///
/// ```dart
/// var persons = await Persons.db.find(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.firstName,
/// limit: 100,
/// );
/// ```
Future<List<SharedSpaceMember>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<SharedSpaceMember>(
where: where?.call(SharedSpaceMember.t),
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [SharedSpaceMember] matching the given query parameters.
///
/// Use [where] to specify which items to include in the return value.
/// If none is specified, all items will be returned.
///
/// To specify the order use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// [offset] defines how many items to skip, after which the next one will be picked.
///
/// ```dart
/// var youngestPerson = await Persons.db.findFirstRow(
/// session,
/// where: (t) => t.lastName.equals('Jones'),
/// orderBy: (t) => t.age,
/// );
/// ```
Future<SharedSpaceMember?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? where,
int? offset,
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<SharedSpaceMember>(
where: where?.call(SharedSpaceMember.t),
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [SharedSpaceMember] by its [id] or null if no such row exists.
Future<SharedSpaceMember?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<SharedSpaceMember>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [SharedSpaceMember]s in the list and returns the inserted rows.
///
/// The returned [SharedSpaceMember]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails to
/// insert, none of the rows will be inserted.
///
/// If [ignoreConflicts] is set to `true`, rows that conflict with existing
/// rows are silently skipped, and only the successfully inserted rows are
/// returned.
///
/// If [noReturn] is set to `true`, the inserted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> insert(
_is.DatabaseSession session,
List<SharedSpaceMember> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<SharedSpaceMember>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [SharedSpaceMember] and returns the inserted row.
///
/// The returned [SharedSpaceMember] will have its `id` field set.
Future<SharedSpaceMember> insertRow(
_is.DatabaseSession session,
SharedSpaceMember row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<SharedSpaceMember>(
row,
transaction: transaction,
);
}
/// Upserts all [SharedSpaceMember]s in the list and returns the resulting rows.
///
/// If a row conflicts on the given [conflictColumns], the existing row is
/// updated with the new values. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies to rows matching the
/// given expression. Conflicting rows that don't match are skipped and not
/// returned, so the resulting list may be shorter than [rows].
///
/// The returned [SharedSpaceMember]s will have their `id` fields set.
///
/// This is an atomic operation, meaning that if one of the rows fails,
/// none of the rows will be affected.
///
/// If [noReturn] is set to `true`, the resulting rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> upsert(
_is.DatabaseSession session,
List<SharedSpaceMember> rows, {
required _is.ColumnSelections<SharedSpaceMemberTable> conflictColumns,
_is.ColumnSelections<SharedSpaceMemberTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<SharedSpaceMember>(
rows,
conflictColumns: conflictColumns(SharedSpaceMember.t),
updateColumns: updateColumns?.call(SharedSpaceMember.t),
updateWhere: updateWhere?.call(SharedSpaceMember.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [SharedSpaceMember] and returns the resulting row.
///
/// If the row conflicts on the given [conflictColumns], the existing row is
/// updated. Otherwise, a new row is inserted.
///
/// If [updateColumns] is provided, only those columns will be updated on
/// conflict. If null, all non-conflict, non-id columns are updated.
///
/// If [updateWhere] is provided, the update only applies when the existing
/// row matches the expression. Returns `null` if no row was affected — for
/// example when [updateWhere] does not match the conflicting row.
///
/// The returned [SharedSpaceMember] will have its `id` field set.
Future<SharedSpaceMember?> upsertRow(
_is.DatabaseSession session,
SharedSpaceMember row, {
required _is.ColumnSelections<SharedSpaceMemberTable> conflictColumns,
_is.ColumnSelections<SharedSpaceMemberTable>? updateColumns,
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<SharedSpaceMember>(
row,
conflictColumns: conflictColumns(SharedSpaceMember.t),
updateColumns: updateColumns?.call(SharedSpaceMember.t),
updateWhere: updateWhere?.call(SharedSpaceMember.t),
transaction: transaction,
);
}
/// Updates all [SharedSpaceMember]s in the list and returns the updated rows. If
/// [columns] is provided, only those columns will be updated. Defaults to
/// all columns.
/// This is an atomic operation, meaning that if one of the rows fails to
/// update, none of the rows will be updated.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> update(
_is.DatabaseSession session,
List<SharedSpaceMember> rows, {
_is.ColumnSelections<SharedSpaceMemberTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<SharedSpaceMember>(
rows,
columns: columns?.call(SharedSpaceMember.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [SharedSpaceMember]. The row needs to have its id set.
/// Optionally, a list of [columns] can be provided to only update those
/// columns. Defaults to all columns.
Future<SharedSpaceMember> updateRow(
_is.DatabaseSession session,
SharedSpaceMember row, {
_is.ColumnSelections<SharedSpaceMemberTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<SharedSpaceMember>(
row,
columns: columns?.call(SharedSpaceMember.t),
transaction: transaction,
);
}
/// Updates a single [SharedSpaceMember] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<SharedSpaceMember?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<SharedSpaceMemberUpdateTable>
columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<SharedSpaceMember>(
id,
columnValues: columnValues(SharedSpaceMember.t.updateTable),
transaction: transaction,
);
}
/// Updates all [SharedSpaceMember]s matching the [where] expression with the specified [columnValues].
/// Returns the list of updated rows.
///
/// If [noReturn] is set to `true`, the updated rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<SharedSpaceMemberUpdateTable>
columnValues,
required _is.WhereExpressionBuilder<SharedSpaceMemberTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<SharedSpaceMember>(
columnValues: columnValues(SharedSpaceMember.t.updateTable),
where: where(SharedSpaceMember.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [SharedSpaceMember]s in the list and returns the deleted rows.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// This is an atomic operation, meaning that if one of the rows fail to
/// be deleted, none of the rows will be deleted.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> delete(
_is.DatabaseSession session,
List<SharedSpaceMember> rows, {
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<SharedSpaceMember>(
rows,
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [SharedSpaceMember].
Future<SharedSpaceMember> deleteRow(
_is.DatabaseSession session,
SharedSpaceMember row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<SharedSpaceMember>(
row,
transaction: transaction,
);
}
/// Deletes all rows matching the [where] expression.
///
/// To specify the order of the returned rows use [orderBy] or [orderByList]
/// when sorting by multiple columns.
///
/// If [noReturn] is set to `true`, the deleted rows are not read back from
/// the database and an empty list is returned. This avoids the overhead of
/// transferring and deserializing the rows when the result is not needed.
Future<List<SharedSpaceMember>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceMemberTable> where,
_is.OrderByBuilder<SharedSpaceMemberTable>? orderBy,
_is.OrderByListBuilder<SharedSpaceMemberTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<SharedSpaceMember>(
where: where(SharedSpaceMember.t),
orderBy: orderBy?.call(SharedSpaceMember.t),
orderByList: orderByList?.call(SharedSpaceMember.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Counts the number of rows matching the [where] expression. If omitted,
/// will return the count of all rows in the table.
Future<int> count(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<SharedSpaceMemberTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<SharedSpaceMember>(
where: where?.call(SharedSpaceMember.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [SharedSpaceMember] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<SharedSpaceMemberTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<SharedSpaceMember>(
where: where(SharedSpaceMember.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,170 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import '../shared_space/shared_space_role.dart' as _igsxp9el;
/// A member of a shared space, or someone invited who hasn't joined yet,
/// as the space's members see them: by their public profile, never by
/// their account's ID.
abstract class SharedSpaceMemberInfo
implements _is.SerializableModel, _is.ProtocolSerialization {
SharedSpaceMemberInfo._({
required this.id,
this.handle,
required this.displayName,
required this.role,
this.joinedAt,
required this.invited,
required this.you,
});
factory SharedSpaceMemberInfo({
required _is.UuidValue id,
String? handle,
required String displayName,
required _igsxp9el.SharedSpaceRole role,
DateTime? joinedAt,
required bool invited,
required bool you,
}) = _SharedSpaceMemberInfoImpl;
factory SharedSpaceMemberInfo.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return SharedSpaceMemberInfo(
id: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
handle: jsonSerialization['handle'] as String?,
displayName: jsonSerialization['displayName'] as String,
role: _igsxp9el.SharedSpaceRole.fromJson(
(jsonSerialization['role'] as String),
),
joinedAt: jsonSerialization['joinedAt'] == null
? null
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['joinedAt']),
invited: _is.BoolJsonExtension.fromJson(jsonSerialization['invited']),
you: _is.BoolJsonExtension.fromJson(jsonSerialization['you']),
);
}
/// What names them in calls about the space: their membership, or the
/// invitation while it waits.
_is.UuidValue id;
/// Their handle; null without a public profile.
String? handle;
/// Their display name; empty without one.
String displayName;
_igsxp9el.SharedSpaceRole role;
/// When they joined; null while invited.
DateTime? joinedAt;
/// Whether the invitation waits for them to use the app.
bool invited;
/// Whether this is the caller.
bool you;
/// Returns a shallow copy of this [SharedSpaceMemberInfo]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceMemberInfo copyWith({
_is.UuidValue? id,
String? handle,
String? displayName,
_igsxp9el.SharedSpaceRole? role,
DateTime? joinedAt,
bool? invited,
bool? you,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceMemberInfo',
'id': id.toJson(),
if (handle != null) 'handle': handle,
'displayName': displayName,
'role': role.toJson(),
if (joinedAt != null) 'joinedAt': joinedAt?.toJson(),
'invited': invited,
'you': you,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SharedSpaceMemberInfo',
'id': id.toJson(),
if (handle != null) 'handle': handle,
'displayName': displayName,
'role': role.toJson(),
if (joinedAt != null) 'joinedAt': joinedAt?.toJson(),
'invited': invited,
'you': you,
};
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _SharedSpaceMemberInfoImpl extends SharedSpaceMemberInfo {
_SharedSpaceMemberInfoImpl({
required _is.UuidValue id,
String? handle,
required String displayName,
required _igsxp9el.SharedSpaceRole role,
DateTime? joinedAt,
required bool invited,
required bool you,
}) : super._(
id: id,
handle: handle,
displayName: displayName,
role: role,
joinedAt: joinedAt,
invited: invited,
you: you,
);
/// Returns a shallow copy of this [SharedSpaceMemberInfo]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceMemberInfo copyWith({
_is.UuidValue? id,
Object? handle = _Undefined,
String? displayName,
_igsxp9el.SharedSpaceRole? role,
Object? joinedAt = _Undefined,
bool? invited,
bool? you,
}) {
return SharedSpaceMemberInfo(
id: id ?? this.id,
handle: handle is String? ? handle : this.handle,
displayName: displayName ?? this.displayName,
role: role ?? this.role,
joinedAt: joinedAt is DateTime? ? joinedAt : this.joinedAt,
invited: invited ?? this.invited,
you: you ?? this.you,
);
}
}

View file

@ -0,0 +1,76 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
/// No shared space of that ID that the caller is a member of, or no member
/// of that ID in it: said alike for each, so a caller learns nothing of
/// spaces they are not in.
abstract class SharedSpaceNotFoundException
implements
_is.SerializableException,
_is.SerializableModel,
_is.ProtocolSerialization {
SharedSpaceNotFoundException._({required this.what});
factory SharedSpaceNotFoundException({required String what}) =
_SharedSpaceNotFoundExceptionImpl;
factory SharedSpaceNotFoundException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return SharedSpaceNotFoundException(
what: jsonSerialization['what'] as String,
);
}
/// `space` or `member`.
String what;
/// Returns a shallow copy of this [SharedSpaceNotFoundException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceNotFoundException copyWith({String? what});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceNotFoundException',
'what': what,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SharedSpaceNotFoundException',
'what': what,
};
}
@override
String toString() {
return 'SharedSpaceNotFoundException(what: $what)';
}
}
class _SharedSpaceNotFoundExceptionImpl extends SharedSpaceNotFoundException {
_SharedSpaceNotFoundExceptionImpl({required String what})
: super._(what: what);
/// Returns a shallow copy of this [SharedSpaceNotFoundException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceNotFoundException copyWith({String? what}) {
return SharedSpaceNotFoundException(what: what ?? this.what);
}
}

View file

@ -0,0 +1,71 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
/// Why a call about a shared space was refused (ADR 0044); nothing changed.
enum SharedSpaceRefusal implements _is.SerializableModel {
/// The host keeps no shared spaces.
unavailable,
/// Only the Owner does that (or a member who may invite, inviting).
notAllowed,
/// No account has that handle.
noSuchHandle,
/// They are a member or invited already.
alreadyMember,
/// The Owner's role passes only by handing the space on.
ownerRole,
/// Not on oneself: the Owner hands on or leaves instead.
self,
/// The Owner leaves only naming who takes the space on.
nextOwnerNeeded,
/// The only member doesn't leave: they delete the space.
lastMember;
static SharedSpaceRefusal fromJson(String name) {
switch (name) {
case 'unavailable':
return SharedSpaceRefusal.unavailable;
case 'notAllowed':
return SharedSpaceRefusal.notAllowed;
case 'noSuchHandle':
return SharedSpaceRefusal.noSuchHandle;
case 'alreadyMember':
return SharedSpaceRefusal.alreadyMember;
case 'ownerRole':
return SharedSpaceRefusal.ownerRole;
case 'self':
return SharedSpaceRefusal.self;
case 'nextOwnerNeeded':
return SharedSpaceRefusal.nextOwnerNeeded;
case 'lastMember':
return SharedSpaceRefusal.lastMember;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "SharedSpaceRefusal"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -0,0 +1,78 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
import '../shared_space/shared_space_refusal.dart' as _i7m30y33;
/// A call about a shared space was refused; nothing changed.
abstract class SharedSpaceRefusedException
implements
_is.SerializableException,
_is.SerializableModel,
_is.ProtocolSerialization {
SharedSpaceRefusedException._({required this.reason});
factory SharedSpaceRefusedException({
required _i7m30y33.SharedSpaceRefusal reason,
}) = _SharedSpaceRefusedExceptionImpl;
factory SharedSpaceRefusedException.fromJson(
Map<String, dynamic> jsonSerialization,
) {
return SharedSpaceRefusedException(
reason: _i7m30y33.SharedSpaceRefusal.fromJson(
(jsonSerialization['reason'] as String),
),
);
}
_i7m30y33.SharedSpaceRefusal reason;
/// Returns a shallow copy of this [SharedSpaceRefusedException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
SharedSpaceRefusedException copyWith({_i7m30y33.SharedSpaceRefusal? reason});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'halkyon_base.SharedSpaceRefusedException',
'reason': reason.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'halkyon_base.SharedSpaceRefusedException',
'reason': reason.toJson(),
};
}
@override
String toString() {
return 'SharedSpaceRefusedException(reason: $reason)';
}
}
class _SharedSpaceRefusedExceptionImpl extends SharedSpaceRefusedException {
_SharedSpaceRefusedExceptionImpl({
required _i7m30y33.SharedSpaceRefusal reason,
}) : super._(reason: reason);
/// Returns a shallow copy of this [SharedSpaceRefusedException]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
SharedSpaceRefusedException copyWith({_i7m30y33.SharedSpaceRefusal? reason}) {
return SharedSpaceRefusedException(reason: reason ?? this.reason);
}
}

View file

@ -0,0 +1,46 @@
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
/* To generate run: "serverpod generate" */
// ignore_for_file: implementation_imports
// ignore_for_file: library_private_types_in_public_api
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: public_member_api_docs
// ignore_for_file: type_literal_in_constant_pattern
// ignore_for_file: use_super_parameters
// ignore_for_file: invalid_use_of_internal_member
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod/serverpod.dart' as _is;
/// What a member may do in a shared space (ADR 0044, D2).
enum SharedSpaceRole implements _is.SerializableModel {
/// Everything: manage the members, hand the space on, delete it.
owner,
/// Changes its content.
editor,
/// Reads and studies it.
viewer;
static SharedSpaceRole fromJson(String name) {
switch (name) {
case 'owner':
return SharedSpaceRole.owner;
case 'editor':
return SharedSpaceRole.editor;
case 'viewer':
return SharedSpaceRole.viewer;
default:
throw ArgumentError(
'Value "$name" cannot be converted to "SharedSpaceRole"',
);
}
}
@override
String toJson() => name;
@override
String toString() => name;
}

View file

@ -1,5 +1,6 @@
import 'package:halkyon_base_server/src/files/file_store.dart';
import 'package:halkyon_base_server/src/register/accounts_mode.dart';
import 'package:halkyon_base_server/src/shared_space/space_grants.dart';
import 'package:serverpod/serverpod.dart';
/// What a host deletes of a person who goes, besides what halkyon_base
@ -42,6 +43,7 @@ final class HalkyonBase {
this.privacyPagePoints = const [],
this.accounts = const HoldsRegister(),
this.files,
this.spaces,
});
/// Which app this server serves, as its apps know it (`learn` for Halkyon
@ -86,6 +88,10 @@ final class HalkyonBase {
/// endpoint refuses.
final FileStore? files;
/// The host's sync engine, for the spaces people share (ADR 0044, D15);
/// none where null, and the `sharedSpace` endpoint refuses.
final SpaceGrants? spaces;
/// Sets [pod] up with [base]. Call it before the pod starts; calling it
/// again replaces the configuration (tests do).
static void configure(Serverpod pod, HalkyonBase base) => _ofPod[pod] = base;

View file

@ -3,6 +3,7 @@ import 'package:halkyon_base_server/src/auth/identified.dart';
import 'package:halkyon_base_server/src/auth/scopes.dart';
import 'package:halkyon_base_server/src/register/account_register.dart';
import 'package:halkyon_base_server/src/register/sessions_ended.dart';
import 'package:halkyon_base_server/src/shared_space/shared_spaces.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
@ -10,7 +11,9 @@ import 'package:serverpod_auth_idp_server/core.dart';
/// it is one already, with [scopes] (ADR 0016): on the register, a member's
/// person adding a way in keeps their ID; on a member, the register's
/// account signs in here. Serverpod makes people with an ID of its
/// own choosing, so the row is written here with the one given.
/// own choosing, so the row is written here with the one given. The
/// invitations to shared spaces waiting for them become memberships in the
/// same transaction (ADR 0044, D13 and D17).
Future<void> ensurePerson(
Session session,
UuidValue account, {
@ -26,16 +29,25 @@ Future<void> ensurePerson(
if (existing != null) {
return;
}
// INSERT INTO "serverpod_auth_core_user" ("id", "createdAt", "scopeNames",
// "blocked") VALUES ($1, $2, $3, false)
await AuthUser.db.insertRow(
session,
AuthUser(
id: account,
scopeNames: {for (final scope in scopes) ?scope.name},
),
transaction: transaction,
);
Future<void> make(Transaction transaction) async {
// INSERT INTO "serverpod_auth_core_user" ("id", "createdAt",
// "scopeNames", "blocked") VALUES ($1, $2, $3, false)
await AuthUser.db.insertRow(
session,
AuthUser(
id: account,
scopeNames: {for (final scope in scopes) ?scope.name},
),
transaction: transaction,
);
await takeInvitationsOf(session, account, transaction: transaction);
}
if (transaction != null) {
await make(transaction);
} else {
await session.db.transaction(make);
}
}
/// A new session for [account], which a way in proved, by [method]: made a

View file

@ -0,0 +1,18 @@
### A space several people share (ADR 0044, D1): the host's sync engine
### space of the same ID, which this mirrors with who may do what in it.
### Kept on the service the space belongs to (D16), read by the server
### alone.
class: SharedSpace
serverOnly: true
table: shared_space
fields:
### The sync engine's space: the ID it was created under.
id: UuidValue?, defaultPersist=random_v7
### What the space holds, in the host's words (`studyset`).
kind: String
createdAt: DateTime
### Whether someone may join by its link (D5); off at first.
joinByLink: bool, default=false
### Whether members other than the Owner may invite others (D5); off at
### first.
membersInvite: bool, default=false

View file

@ -0,0 +1,527 @@
import 'package:clock/clock.dart';
import 'package:halkyon_base_server/src/auth/scopes.dart';
import 'package:halkyon_base_server/src/common/network.dart';
import 'package:halkyon_base_server/src/generated/protocol.dart';
import 'package:halkyon_base_server/src/profile/handle_checks.dart';
import 'package:halkyon_base_server/src/profile/profiles.dart';
import 'package:halkyon_base_server/src/shared_space/shared_spaces.dart';
import 'package:halkyon_base_server/src/shared_space/space_grants.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
/// Spaces you share with others (ADR 0044): making one, its members and
/// their roles, inviting by handle, handing it on, leaving and deleting it.
/// Only for people with an identity. The person always comes from the
/// session: a space you aren't a member of can't be told from one that
/// doesn't exist ([SharedSpaceNotFoundException]), and members are named
/// by their membership, never by their account's ID.
class SharedSpaceEndpoint extends Endpoint {
@override
Set<Scope> get requiredScopes => {identified};
/// Makes a shared space holding [kind] (the app's word, `studyset`), with
/// you its Owner.
Future<SharedSpaceInfo> create(Session session, String kind) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
return await session.db.transaction((transaction) async {
final id = await grants.create(
session,
owner: person,
transaction: transaction,
);
final now = clock.now().toUtc();
// INSERT INTO "shared_space" ("id", "kind", "createdAt", …) VALUES (…)
final space = await SharedSpace.db.insertRow(
session,
SharedSpace(id: id, kind: kind, createdAt: now),
transaction: transaction,
);
// INSERT INTO "shared_space_member" (…) VALUES (…)
await SharedSpaceMember.db.insertRow(
session,
SharedSpaceMember(
sharedSpaceId: id,
authUserId: person,
role: SharedSpaceRole.owner,
joinedAt: now,
),
transaction: transaction,
);
return _info(space, SharedSpaceRole.owner);
});
}
/// The shared spaces you are a member of, with your role in each.
Future<List<SharedSpaceInfo>> mine(Session session) async {
final person = _caller(session);
// SELECT … FROM "shared_space_member" WHERE "authUserId" = $1
final memberships = await SharedSpaceMember.db.find(
session,
where: (t) => t.authUserId.equals(person),
);
// SELECT … FROM "shared_space" WHERE "id" IN (…)
final spaces = {
for (final space in await SharedSpace.db.find(
session,
// A set of non-null IDs, which the nullable `id` column takes alone.
where: (t) => t.id.inSet(<UuidValue>{
for (final membership in memberships) membership.sharedSpaceId,
}),
))
space.id!: space,
};
return [
for (final membership in memberships)
if (spaces[membership.sharedSpaceId] case final space?)
_info(space, membership.role),
];
}
/// The members of [space] and those invited, the Owner first, then as
/// they joined.
Future<List<SharedSpaceMemberInfo>> members(
Session session,
UuidValue space,
) async {
final person = _caller(session);
// SELECT … FROM "shared_space_member" WHERE "sharedSpaceId" = $1
// ORDER BY "joinedAt", "id"
final members = await SharedSpaceMember.db.find(
session,
where: (t) => t.sharedSpaceId.equals(space),
orderBy: (t) => t.joinedAt,
);
if (!members.any((member) => member.authUserId == person)) {
throw SharedSpaceNotFoundException(what: 'space');
}
// SELECT … FROM "shared_space_invitation" WHERE "sharedSpaceId" = $1
// ORDER BY "invitedAt"
final invitations = await SharedSpaceInvitation.db.find(
session,
where: (t) => t.sharedSpaceId.equals(space),
orderBy: (t) => t.invitedAt,
);
final profiles = await profilesOf(session, {
for (final member in members) member.authUserId,
for (final invitation in invitations) invitation.authUserId,
});
SharedSpaceMemberInfo info(
UuidValue id,
UuidValue who,
SharedSpaceRole role,
DateTime? joinedAt,
) => SharedSpaceMemberInfo(
id: id,
handle: profiles[who]?.handle,
displayName: profiles[who]?.displayName ?? '',
role: role,
joinedAt: joinedAt,
invited: joinedAt == null,
you: who == person,
);
return [
for (final member in [
...members.where((m) => m.role == SharedSpaceRole.owner),
...members.where((m) => m.role != SharedSpaceRole.owner),
])
info(member.id!, member.authUserId, member.role, member.joinedAt),
for (final invitation in invitations)
info(invitation.id!, invitation.authUserId, invitation.role, null),
];
}
/// Invites the person with [handle] (whatever its case) to [space] as
/// [role]: a member at once where they have an account here, else
/// invited until they first sign in here (ADR 0044, D13). The Owner's,
/// or any member's where the space lets members invite. Counts as an
/// answer about a handle (`countHandleCheck`), as it tells whether one
/// exists.
Future<SharedSpaceMemberInfo> invite(
Session session,
UuidValue space,
String handle,
SharedSpaceRole role,
) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
if (role == SharedSpaceRole.owner) {
throw SharedSpaceRefusedException(reason: SharedSpaceRefusal.ownerRole);
}
// Allowed to invite, asked before the handle is counted, so that a
// refusal here counts nothing; the transaction asks again, locked.
await session.db.transaction(
(transaction) => _mayInvite(session, space, person, transaction),
);
// Counted outside the transaction, which a refusal rolls back.
await countHandleCheck(
session,
asker: person.uuid,
network: networkOf(session),
);
// Asked of the register, over the network on a member: before the
// transaction, which holds the space's row.
final invitee = await personWithHandle(session, handle.trim());
if (invitee == null) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.noSuchHandle,
);
}
await session.db.transaction((transaction) async {
final members = await _mayInvite(session, space, person, transaction);
// SELECT count(*) FROM "shared_space_invitation"
// WHERE "sharedSpaceId" = $1 AND "authUserId" = $2
if (members.any((member) => member.authUserId == invitee) ||
await SharedSpaceInvitation.db.count(
session,
where: (t) =>
t.sharedSpaceId.equals(space) &
t.authUserId.equals(invitee),
transaction: transaction,
) >
0) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.alreadyMember,
);
}
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = $1
if (await AuthUser.db.findById(
session,
invitee,
transaction: transaction,
) ==
null) {
// INSERT INTO "shared_space_invitation" (…) VALUES (…)
await SharedSpaceInvitation.db.insertRow(
session,
SharedSpaceInvitation(
sharedSpaceId: space,
authUserId: invitee,
role: role,
invitedBy: person,
invitedAt: clock.now().toUtc(),
),
transaction: transaction,
);
} else {
await addMember(
session,
grants,
space,
invitee,
role,
transaction: transaction,
);
}
});
return (await members(
session,
space,
)).lastWhere(
(member) => member.handle?.toLowerCase() == handle.trim().toLowerCase(),
);
}
/// Gives the member [member] of [space] the role [role]; the Owner's.
/// The Owner's own role passes only by handing on ([handOn]).
Future<void> setRole(
Session session,
UuidValue space,
UuidValue member,
SharedSpaceRole role,
) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
await session.db.transaction((transaction) async {
final target = await _asOwner(
session,
space,
person,
member,
transaction: transaction,
);
if (role == SharedSpaceRole.owner) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.ownerRole,
);
}
switch (target) {
case final SharedSpaceMember target:
await changeRole(
session,
grants,
target,
role,
transaction: transaction,
);
case final SharedSpaceInvitation invitation:
// UPDATE "shared_space_invitation" SET "role" = $1 WHERE "id" = $2
await SharedSpaceInvitation.db.updateRow(
session,
invitation.copyWith(role: role),
columns: (t) => [t.role],
transaction: transaction,
);
}
});
}
/// Removes the member or invitation [member] from [space]; the Owner's.
Future<void> remove(
Session session,
UuidValue space,
UuidValue member,
) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
await session.db.transaction((transaction) async {
switch (await _asOwner(
session,
space,
person,
member,
transaction: transaction,
)) {
case final SharedSpaceMember target:
await removeMember(
session,
grants,
target,
transaction: transaction,
);
case final SharedSpaceInvitation invitation:
// DELETE FROM "shared_space_invitation" WHERE "id" = $1
await SharedSpaceInvitation.db.deleteRow(
session,
invitation,
transaction: transaction,
);
}
});
}
/// Makes the member [member] the Owner of [space], and you, its Owner, an
/// Editor.
Future<void> handOn(
Session session,
UuidValue space,
UuidValue member,
) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
await session.db.transaction(
(transaction) => _handOn(
session,
grants,
space,
person,
member,
transaction: transaction,
),
);
}
/// Leaves [space]. Its Owner names the member who takes it on
/// ([nextOwner]) and leaves only then (ADR 0044, D11); its only member
/// deletes it instead ([delete]).
Future<void> leave(
Session session,
UuidValue space, {
UuidValue? nextOwner,
}) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
await session.db.transaction((transaction) async {
final members = await _membersAs(
session,
space,
person,
transaction: transaction,
);
if (members.length == 1) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.lastMember,
);
}
final caller = members.firstWhere((m) => m.authUserId == person);
if (caller.role == SharedSpaceRole.owner) {
if (nextOwner == null) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.nextOwnerNeeded,
);
}
await _handOn(
session,
grants,
space,
person,
nextOwner,
transaction: transaction,
);
}
await removeMember(session, grants, caller, transaction: transaction);
});
}
/// Deletes [space] with everything in it; its Owner's.
Future<void> delete(Session session, UuidValue space) async {
final person = _caller(session);
final grants = spaceGrantsOf(session);
await session.db.transaction((transaction) async {
final members = await _membersAs(
session,
space,
person,
transaction: transaction,
);
if (members.firstWhere((m) => m.authUserId == person).role !=
SharedSpaceRole.owner) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.notAllowed,
);
}
await deleteSpace(session, grants, space, transaction: transaction);
});
}
/// The members of [space], locked, where [person] may invite to it: its
/// Owner, or any member where it lets members invite.
Future<List<SharedSpaceMember>> _mayInvite(
Session session,
UuidValue space,
UuidValue person,
Transaction transaction,
) async {
final members = await _membersAs(
session,
space,
person,
transaction: transaction,
);
if (members.firstWhere((m) => m.authUserId == person).role !=
SharedSpaceRole.owner) {
// SELECT … FROM "shared_space" WHERE "id" = $1
final row = await SharedSpace.db.findById(
session,
space,
transaction: transaction,
);
if (!(row?.membersInvite ?? false)) {
throw SharedSpaceRefusedException(
reason: SharedSpaceRefusal.notAllowed,
);
}
}
return members;
}
Future<void> _handOn(
Session session,
SpaceGrants grants,
UuidValue space,
UuidValue person,
UuidValue member, {
required Transaction transaction,
}) async {
final target = await _asOwner(
session,
space,
person,
member,
transaction: transaction,
);
if (target is! SharedSpaceMember) {
// Someone invited isn't a member yet.
throw SharedSpaceNotFoundException(what: 'member');
}
// SELECT … FROM "shared_space_member"
// WHERE "sharedSpaceId" = $1 AND "authUserId" = $2
final owner = (await SharedSpaceMember.db.findFirstRow(
session,
where: (t) => t.sharedSpaceId.equals(space) & t.authUserId.equals(person),
transaction: transaction,
))!;
await changeRole(
session,
grants,
owner,
SharedSpaceRole.editor,
transaction: transaction,
);
await changeRole(
session,
grants,
target,
SharedSpaceRole.owner,
transaction: transaction,
);
}
/// The members of [space], locked, where [person] is one of them;
/// [SharedSpaceNotFoundException] otherwise.
Future<List<SharedSpaceMember>> _membersAs(
Session session,
UuidValue space,
UuidValue person, {
required Transaction transaction,
}) async {
final members = await lockedMembers(
session,
space,
transaction: transaction,
);
if (members == null || !members.any((m) => m.authUserId == person)) {
throw SharedSpaceNotFoundException(what: 'space');
}
return members;
}
/// The member or invitation [id] of [space], where [person] is its Owner
/// and [id] isn't them: [SharedSpaceNotFoundException] for a space they
/// aren't in or no such member there, refused for a member who isn't the
/// Owner, or for the Owner themselves.
Future<Object> _asOwner(
Session session,
UuidValue space,
UuidValue person,
UuidValue id, {
required Transaction transaction,
}) async {
final members = await _membersAs(
session,
space,
person,
transaction: transaction,
);
if (members.firstWhere((m) => m.authUserId == person).role !=
SharedSpaceRole.owner) {
throw SharedSpaceRefusedException(reason: SharedSpaceRefusal.notAllowed);
}
for (final member in members) {
if (member.id == id) {
if (member.authUserId == person) {
throw SharedSpaceRefusedException(reason: SharedSpaceRefusal.self);
}
return member;
}
}
// SELECT … FROM "shared_space_invitation"
// WHERE "id" = $1 AND "sharedSpaceId" = $2
final invitation = await SharedSpaceInvitation.db.findFirstRow(
session,
where: (t) => t.id.equals(id) & t.sharedSpaceId.equals(space),
transaction: transaction,
);
return invitation ?? (throw SharedSpaceNotFoundException(what: 'member'));
}
SharedSpaceInfo _info(SharedSpace space, SharedSpaceRole role) =>
SharedSpaceInfo(
id: space.id!,
kind: space.kind,
role: role,
createdAt: space.createdAt,
);
UuidValue _caller(Session session) => session.authenticated!.authUserId;
}

View file

@ -0,0 +1,10 @@
### A shared space as its member sees it.
class: SharedSpaceInfo
fields:
### The sync engine's space.
id: UuidValue
### What it holds, in the host's words.
kind: String
### The caller's role in it.
role: SharedSpaceRole
createdAt: DateTime

Some files were not shown because too many files have changed in this diff Show more