feat(base,app,learn): invitations to shared spaces by mail and by link
The instance's half of Halkyon's ADR 0044, step 5 (D37 to D40).
An invitation by mail (sharedSpace.inviteByEmail) goes out through the
register (send-space-invitation), naming who invites by their handle
and nothing of what is shared, with a link to /join/<token>. The
instance keeps only the address's keyed hash, as for sign-in, and the
SHA-256 of the token. Whoever opens it joins with a code mailed to that
address (EmailCodePurpose.joinSpace, proven through the register,
prove-address-code), so a forwarded mail admits nobody else; a code
proving another address is refused. It lapses after 14 days, and the
hourly pruning forgets it; inviting the address again replaces it.
A space's own link (setJoinLink, the Owner's) admits whoever opens it
as a Viewer (joinByLink) while it is on; switched off and on, it gets a
new token. Its token is kept as it is, so that whoever may invite copies
the link again (joinLink). The page at /join/<token>, in English and
German, says who invites and as whom; preview tells the app the same.
A membership someone else made is new to its member until they mark it
seen (isNew, addedBy; mine names who shared it), for the Inbox.
halkyon_app learns the two new refusals (handleNeeded, otherAddress),
which Learn reports as not allowed until its screens offer these ways
in. The ADR records D37 to D40 and how the instance's half is built.
The migration, the same in the module and in each server including it
(Learn, Meditate, Todo, Train):
ALTER TABLE "shared_space" ADD COLUMN "joinToken" text;
CREATE UNIQUE INDEX "shared_space_join_token_idx"
ON "shared_space" ("joinToken");
CREATE TABLE "shared_space_email_invitation" (…), with its unique
indexes on the space and address hash, and on the token's hash,
and its foreign key to "shared_space" ON DELETE CASCADE;
ALTER TABLE "shared_space_member" ADD COLUMN "addedBy" uuid;
ALTER TABLE "shared_space_member"
ADD COLUMN "isNew" boolean NOT NULL DEFAULT false;
Every space gets no token, and the unique index allows any number of
nulls; every membership gets no one who added it and isn't new, so
nothing already shared shows in an Inbox. The new table starts empty.
None of it rewrites a table, and the server before the switch names its
own columns, so it keeps working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
8dd3ec0522
commit
554e1604b4
53 changed files with 108488 additions and 42 deletions
|
|
@ -73,6 +73,8 @@ class ServerpodSharedSpaceGateway(final protocol.Caller _base)
|
|||
protocol.SharedSpaceRefusal.nextOwnerNeeded =>
|
||||
SpaceRefusal.nextOwnerNeeded,
|
||||
protocol.SharedSpaceRefusal.lastMember => SpaceRefusal.lastMember,
|
||||
protocol.SharedSpaceRefusal.handleNeeded => SpaceRefusal.handleNeeded,
|
||||
protocol.SharedSpaceRefusal.otherAddress => SpaceRefusal.otherAddress,
|
||||
});
|
||||
} on protocol.SharedSpaceNotFoundException {
|
||||
throw const SpaceRefused(SpaceRefusal.notFound);
|
||||
|
|
|
|||
|
|
@ -67,6 +67,13 @@ enum SpaceRefusal() {
|
|||
/// The only member doesn't leave: they delete the space.
|
||||
lastMember,
|
||||
|
||||
/// Inviting by mail names who invites by their handle: the person has
|
||||
/// none (ADR 0044, D37).
|
||||
handleNeeded,
|
||||
|
||||
/// The code proved another address than the one invited (D37).
|
||||
otherAddress,
|
||||
|
||||
/// No such space the person is in, or no such member in it.
|
||||
notFound,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ enum EmailCodePurpose implements _isc.SerializableModel {
|
|||
|
||||
/// Proving, signed in, the account's own address to give it its first
|
||||
/// password (the fourth reading: a session alone sets none).
|
||||
firstPassword;
|
||||
firstPassword,
|
||||
|
||||
/// Proving the address a space's invitation was mailed to, to join it
|
||||
/// (Halkyon's ADR 0044, D37).
|
||||
joinSpace;
|
||||
|
||||
static EmailCodePurpose fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -52,6 +56,8 @@ enum EmailCodePurpose implements _isc.SerializableModel {
|
|||
return EmailCodePurpose.deletion;
|
||||
case 'firstPassword':
|
||||
return EmailCodePurpose.firstPassword;
|
||||
case 'joinSpace':
|
||||
return EmailCodePurpose.joinSpace;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "EmailCodePurpose"',
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ import 'package:halkyon_base_client/src/protocol/profile/profile_refusal.dart'
|
|||
as _ih722tf9;
|
||||
import 'package:halkyon_base_client/src/protocol/shared_space/shared_space_info.dart'
|
||||
as _i9dv6y4h;
|
||||
import 'package:halkyon_base_client/src/protocol/shared_space/shared_space_join_preview.dart'
|
||||
as _iti36awa;
|
||||
import 'package:halkyon_base_client/src/protocol/shared_space/shared_space_key_need.dart'
|
||||
as _ixm5azhv;
|
||||
import 'package:halkyon_base_client/src/protocol/shared_space/shared_space_member_info.dart'
|
||||
|
|
@ -1322,7 +1324,8 @@ class EndpointProfiles extends _isc.EndpointRef {
|
|||
}
|
||||
|
||||
/// Spaces you share with others (ADR 0044): making one, its members and
|
||||
/// their roles, inviting by handle, handing it on, leaving and deleting it.
|
||||
/// their roles, inviting by handle, by mail (D37, D38) and by its link
|
||||
/// (D39), handing it on, leaving and deleting it, and what is new (D40).
|
||||
/// 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
|
||||
|
|
@ -1351,6 +1354,15 @@ class EndpointSharedSpace extends _isc.EndpointRef {
|
|||
{},
|
||||
);
|
||||
|
||||
/// Marks [space] as seen by you (D40): the Inbox no longer lists it, nor
|
||||
/// the library marks it New. Nothing for a space you aren't in.
|
||||
_ida.Future<void> markSeen(_isc.UuidValue space) =>
|
||||
caller.callServerEndpoint<void>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'markSeen',
|
||||
{'space': space},
|
||||
);
|
||||
|
||||
/// The members of [space] and those invited, the Owner first, then as
|
||||
/// they joined.
|
||||
_ida.Future<List<_iyon0cg8.SharedSpaceMemberInfo>> members(
|
||||
|
|
@ -1381,6 +1393,97 @@ class EndpointSharedSpace extends _isc.EndpointRef {
|
|||
},
|
||||
);
|
||||
|
||||
/// Mails an invitation to [space] as [role] to [address], worded in
|
||||
/// [language] (D37, D38): whoever opens its link and proves the address
|
||||
/// with a mailed code joins, within 14 days. Who may invite by handle may
|
||||
/// invite so, naming themselves by their handle in the mail
|
||||
/// ([SharedSpaceRefusal.handleNeeded] without one). The instance keeps
|
||||
/// only the address's keyed hash: the space's members see "invited by
|
||||
/// email", and inviting the address again mails a new link, the old one
|
||||
/// ending. A mail's refusals are `EmailSignInRefusedException`'s, as a
|
||||
/// code's: no address, too many mails, a mail that couldn't be sent.
|
||||
_ida.Future<_iyon0cg8.SharedSpaceMemberInfo> inviteByEmail(
|
||||
_isc.UuidValue space,
|
||||
String address,
|
||||
_i1fyjuki.SharedSpaceRole role,
|
||||
String language,
|
||||
) => caller.callServerEndpoint<_iyon0cg8.SharedSpaceMemberInfo>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'inviteByEmail',
|
||||
{
|
||||
'space': space,
|
||||
'address': address,
|
||||
'role': role,
|
||||
'language': language,
|
||||
},
|
||||
);
|
||||
|
||||
/// What the link of [token] invites to (D39): a mailed invitation still
|
||||
/// holding, or a space's own link while it is on; whether you are in it
|
||||
/// already. [SharedSpaceNotFoundException] (`invitation`) for neither,
|
||||
/// alike for a token that never was, has lapsed or was switched off.
|
||||
_ida.Future<_iti36awa.SharedSpaceJoinPreview> preview(String token) =>
|
||||
caller.callServerEndpoint<_iti36awa.SharedSpaceJoinPreview>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'preview',
|
||||
{'token': token},
|
||||
);
|
||||
|
||||
/// Joins the space the invitation mailed with [token] is to, as its role,
|
||||
/// once [code], mailed for [request] (`emailCode.sendCode`, for
|
||||
/// `EmailCodePurpose.joinSpace`), proves the address it was mailed to
|
||||
/// (D37); the invitation goes. Refused for a code proving another address
|
||||
/// ([SharedSpaceRefusal.otherAddress]), and as `proveAddressCode` for a
|
||||
/// wrong code; [SharedSpaceNotFoundException] (`invitation`) as [preview].
|
||||
/// You keep your role where you are a member already.
|
||||
_ida.Future<_i9dv6y4h.SharedSpaceInfo> acceptEmailInvitation(
|
||||
String token,
|
||||
_isc.UuidValue request,
|
||||
String code,
|
||||
) => caller.callServerEndpoint<_i9dv6y4h.SharedSpaceInfo>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'acceptEmailInvitation',
|
||||
{
|
||||
'token': token,
|
||||
'request': request,
|
||||
'code': code,
|
||||
},
|
||||
);
|
||||
|
||||
/// The link of [space] while someone may join by it (D39); null while
|
||||
/// not. Whoever may invite reads it.
|
||||
_ida.Future<String?> joinLink(_isc.UuidValue space) =>
|
||||
caller.callServerEndpoint<String?>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'joinLink',
|
||||
{'space': space},
|
||||
);
|
||||
|
||||
/// Switches [space]'s link on, with a new token, or off, ending it for
|
||||
/// good (D5, D39); the Owner's. Returns the link while on.
|
||||
_ida.Future<String?> setJoinLink(
|
||||
_isc.UuidValue space,
|
||||
bool on,
|
||||
) => caller.callServerEndpoint<String?>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'setJoinLink',
|
||||
{
|
||||
'space': space,
|
||||
'on': on,
|
||||
},
|
||||
);
|
||||
|
||||
/// Joins the space whose link [token] is, as a Viewer (D5, D39); you keep
|
||||
/// your role where you are a member already.
|
||||
/// [SharedSpaceNotFoundException] (`invitation`) for no such link, or one
|
||||
/// switched off.
|
||||
_ida.Future<_i9dv6y4h.SharedSpaceInfo> joinByLink(String token) =>
|
||||
caller.callServerEndpoint<_i9dv6y4h.SharedSpaceInfo>(
|
||||
'halkyon_base.sharedSpace',
|
||||
'joinByLink',
|
||||
{'token': token},
|
||||
);
|
||||
|
||||
/// Gives the member [member] of [space] the role [role]; the Owner's.
|
||||
/// The Owner's own role passes only by handing on ([handOn]).
|
||||
_ida.Future<void> setRole(
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ import 'profile/profile_not_found_exception.dart' as _iim3hfcf;
|
|||
import 'profile/profile_refusal.dart' as _i6j6efd1;
|
||||
import 'profile/profile_refused_exception.dart' as _idvc5e7p;
|
||||
import 'shared_space/shared_space_info.dart' as _i1rvc02f;
|
||||
import 'shared_space/shared_space_join_preview.dart' as _if1ehrh0;
|
||||
import 'shared_space/shared_space_key_need.dart' as _imxqlbwp;
|
||||
import 'shared_space/shared_space_member_info.dart' as _iz8z2oju;
|
||||
import 'shared_space/shared_space_member_keys.dart' as _iqkqjhh4;
|
||||
|
|
@ -178,6 +179,7 @@ export 'profile/profile_not_found_exception.dart';
|
|||
export 'profile/profile_refusal.dart';
|
||||
export 'profile/profile_refused_exception.dart';
|
||||
export 'shared_space/shared_space_info.dart';
|
||||
export 'shared_space/shared_space_join_preview.dart';
|
||||
export 'shared_space/shared_space_key_need.dart';
|
||||
export 'shared_space/shared_space_member_info.dart';
|
||||
export 'shared_space/shared_space_member_keys.dart';
|
||||
|
|
@ -434,6 +436,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (t == _i1rvc02f.SharedSpaceInfo) {
|
||||
return _i1rvc02f.SharedSpaceInfo.fromJson(data) as T;
|
||||
}
|
||||
if (t == _if1ehrh0.SharedSpaceJoinPreview) {
|
||||
return _if1ehrh0.SharedSpaceJoinPreview.fromJson(data) as T;
|
||||
}
|
||||
if (t == _imxqlbwp.SharedSpaceKeyNeed) {
|
||||
return _imxqlbwp.SharedSpaceKeyNeed.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -743,6 +748,12 @@ class Protocol extends _isc.SerializationManager {
|
|||
return (data != null ? _i1rvc02f.SharedSpaceInfo.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_if1ehrh0.SharedSpaceJoinPreview?>()) {
|
||||
return (data != null
|
||||
? _if1ehrh0.SharedSpaceJoinPreview.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_imxqlbwp.SharedSpaceKeyNeed?>()) {
|
||||
return (data != null ? _imxqlbwp.SharedSpaceKeyNeed.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -994,6 +1005,7 @@ class Protocol extends _isc.SerializationManager {
|
|||
_i6j6efd1.ProfileRefusal => 'ProfileRefusal',
|
||||
_idvc5e7p.ProfileRefusedException => 'ProfileRefusedException',
|
||||
_i1rvc02f.SharedSpaceInfo => 'SharedSpaceInfo',
|
||||
_if1ehrh0.SharedSpaceJoinPreview => 'SharedSpaceJoinPreview',
|
||||
_imxqlbwp.SharedSpaceKeyNeed => 'SharedSpaceKeyNeed',
|
||||
_iz8z2oju.SharedSpaceMemberInfo => 'SharedSpaceMemberInfo',
|
||||
_iqkqjhh4.SharedSpaceMemberKeys => 'SharedSpaceMemberKeys',
|
||||
|
|
@ -1154,6 +1166,8 @@ class Protocol extends _isc.SerializationManager {
|
|||
return 'ProfileRefusedException';
|
||||
case _i1rvc02f.SharedSpaceInfo():
|
||||
return 'SharedSpaceInfo';
|
||||
case _if1ehrh0.SharedSpaceJoinPreview():
|
||||
return 'SharedSpaceJoinPreview';
|
||||
case _imxqlbwp.SharedSpaceKeyNeed():
|
||||
return 'SharedSpaceKeyNeed';
|
||||
case _iz8z2oju.SharedSpaceMemberInfo():
|
||||
|
|
@ -1384,6 +1398,9 @@ class Protocol extends _isc.SerializationManager {
|
|||
if (dataClassName == 'SharedSpaceInfo') {
|
||||
return deserialize<_i1rvc02f.SharedSpaceInfo>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceJoinPreview') {
|
||||
return deserialize<_if1ehrh0.SharedSpaceJoinPreview>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceKeyNeed') {
|
||||
return deserialize<_imxqlbwp.SharedSpaceKeyNeed>(data['data']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,17 @@ abstract class SharedSpaceInfo
|
|||
required this.kind,
|
||||
required this.role,
|
||||
required this.createdAt,
|
||||
});
|
||||
bool? isNew,
|
||||
this.sharedBy,
|
||||
}) : isNew = isNew ?? false;
|
||||
|
||||
factory SharedSpaceInfo({
|
||||
required _isc.UuidValue id,
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required DateTime createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
}) = _SharedSpaceInfoImpl;
|
||||
|
||||
factory SharedSpaceInfo.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
|
|
@ -40,6 +44,10 @@ abstract class SharedSpaceInfo
|
|||
createdAt: _isc.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
isNew: jsonSerialization['isNew'] == null
|
||||
? null
|
||||
: _isc.BoolJsonExtension.fromJson(jsonSerialization['isNew']),
|
||||
sharedBy: jsonSerialization['sharedBy'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +62,14 @@ abstract class SharedSpaceInfo
|
|||
|
||||
DateTime createdAt;
|
||||
|
||||
/// Whether the caller hasn't seen it since someone else made them a
|
||||
/// member (D40).
|
||||
bool isNew;
|
||||
|
||||
/// The handle of whoever made the caller a member, while it [isNew];
|
||||
/// null without a public profile, or otherwise.
|
||||
String? sharedBy;
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
|
|
@ -62,6 +78,8 @@ abstract class SharedSpaceInfo
|
|||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
DateTime? createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -71,6 +89,8 @@ abstract class SharedSpaceInfo
|
|||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
'isNew': isNew,
|
||||
if (sharedBy != null) 'sharedBy': sharedBy,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +102,8 @@ abstract class SharedSpaceInfo
|
|||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
'isNew': isNew,
|
||||
if (sharedBy != null) 'sharedBy': sharedBy,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -91,17 +113,23 @@ abstract class SharedSpaceInfo
|
|||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SharedSpaceInfoImpl extends SharedSpaceInfo {
|
||||
_SharedSpaceInfoImpl({
|
||||
required _isc.UuidValue id,
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required DateTime createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
}) : super._(
|
||||
id: id,
|
||||
kind: kind,
|
||||
role: role,
|
||||
createdAt: createdAt,
|
||||
isNew: isNew,
|
||||
sharedBy: sharedBy,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceInfo]
|
||||
|
|
@ -113,12 +141,16 @@ class _SharedSpaceInfoImpl extends SharedSpaceInfo {
|
|||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
DateTime? createdAt,
|
||||
bool? isNew,
|
||||
Object? sharedBy = _Undefined,
|
||||
}) {
|
||||
return SharedSpaceInfo(
|
||||
id: id ?? this.id,
|
||||
kind: kind ?? this.kind,
|
||||
role: role ?? this.role,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
isNew: isNew ?? this.isNew,
|
||||
sharedBy: sharedBy is String? ? sharedBy : this.sharedBy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
/* 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_client/serverpod_client.dart' as _isc;
|
||||
import '../shared_space/shared_space_role.dart' as _igsxp9el;
|
||||
|
||||
/// What a link to join a shared space says before anyone joins (ADR 0044,
|
||||
/// D39): what it holds and as whom, and who invites, never its content's
|
||||
/// names, which are sealed.
|
||||
abstract class SharedSpaceJoinPreview
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
SharedSpaceJoinPreview._({
|
||||
required this.kind,
|
||||
required this.role,
|
||||
required this.byEmail,
|
||||
this.inviterHandle,
|
||||
required this.inviterName,
|
||||
this.lapsesAt,
|
||||
required this.member,
|
||||
});
|
||||
|
||||
factory SharedSpaceJoinPreview({
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required bool byEmail,
|
||||
String? inviterHandle,
|
||||
required String inviterName,
|
||||
DateTime? lapsesAt,
|
||||
required bool member,
|
||||
}) = _SharedSpaceJoinPreviewImpl;
|
||||
|
||||
factory SharedSpaceJoinPreview.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return SharedSpaceJoinPreview(
|
||||
kind: jsonSerialization['kind'] as String,
|
||||
role: _igsxp9el.SharedSpaceRole.fromJson(
|
||||
(jsonSerialization['role'] as String),
|
||||
),
|
||||
byEmail: _isc.BoolJsonExtension.fromJson(jsonSerialization['byEmail']),
|
||||
inviterHandle: jsonSerialization['inviterHandle'] as String?,
|
||||
inviterName: jsonSerialization['inviterName'] as String,
|
||||
lapsesAt: jsonSerialization['lapsesAt'] == null
|
||||
? null
|
||||
: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['lapsesAt']),
|
||||
member: _isc.BoolJsonExtension.fromJson(jsonSerialization['member']),
|
||||
);
|
||||
}
|
||||
|
||||
/// What the space holds, in the host's words.
|
||||
String kind;
|
||||
|
||||
/// The role joining gives.
|
||||
_igsxp9el.SharedSpaceRole role;
|
||||
|
||||
/// Whether it is an invitation mailed to an address, which joining
|
||||
/// proves with a mailed code (D37); else the space's own link.
|
||||
bool byEmail;
|
||||
|
||||
/// The handle of who invites: whoever mailed it, or the space's Owner
|
||||
/// for its link; null without a public profile.
|
||||
String? inviterHandle;
|
||||
|
||||
/// Their display name; empty without one.
|
||||
String inviterName;
|
||||
|
||||
/// When a mailed invitation lapses; null for a link.
|
||||
DateTime? lapsesAt;
|
||||
|
||||
/// Whether the caller is a member already; false for nobody signed in.
|
||||
bool member;
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceJoinPreview]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
SharedSpaceJoinPreview copyWith({
|
||||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
bool? byEmail,
|
||||
String? inviterHandle,
|
||||
String? inviterName,
|
||||
DateTime? lapsesAt,
|
||||
bool? member,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.SharedSpaceJoinPreview',
|
||||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'byEmail': byEmail,
|
||||
if (inviterHandle != null) 'inviterHandle': inviterHandle,
|
||||
'inviterName': inviterName,
|
||||
if (lapsesAt != null) 'lapsesAt': lapsesAt?.toJson(),
|
||||
'member': member,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.SharedSpaceJoinPreview',
|
||||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'byEmail': byEmail,
|
||||
if (inviterHandle != null) 'inviterHandle': inviterHandle,
|
||||
'inviterName': inviterName,
|
||||
if (lapsesAt != null) 'lapsesAt': lapsesAt?.toJson(),
|
||||
'member': member,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SharedSpaceJoinPreviewImpl extends SharedSpaceJoinPreview {
|
||||
_SharedSpaceJoinPreviewImpl({
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required bool byEmail,
|
||||
String? inviterHandle,
|
||||
required String inviterName,
|
||||
DateTime? lapsesAt,
|
||||
required bool member,
|
||||
}) : super._(
|
||||
kind: kind,
|
||||
role: role,
|
||||
byEmail: byEmail,
|
||||
inviterHandle: inviterHandle,
|
||||
inviterName: inviterName,
|
||||
lapsesAt: lapsesAt,
|
||||
member: member,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceJoinPreview]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
SharedSpaceJoinPreview copyWith({
|
||||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
bool? byEmail,
|
||||
Object? inviterHandle = _Undefined,
|
||||
String? inviterName,
|
||||
Object? lapsesAt = _Undefined,
|
||||
bool? member,
|
||||
}) {
|
||||
return SharedSpaceJoinPreview(
|
||||
kind: kind ?? this.kind,
|
||||
role: role ?? this.role,
|
||||
byEmail: byEmail ?? this.byEmail,
|
||||
inviterHandle: inviterHandle is String?
|
||||
? inviterHandle
|
||||
: this.inviterHandle,
|
||||
inviterName: inviterName ?? this.inviterName,
|
||||
lapsesAt: lapsesAt is DateTime? ? lapsesAt : this.lapsesAt,
|
||||
member: member ?? this.member,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,8 @@ abstract class SharedSpaceMemberInfo
|
|||
this.joinedAt,
|
||||
required this.invited,
|
||||
required this.you,
|
||||
});
|
||||
bool? byEmail,
|
||||
}) : byEmail = byEmail ?? false;
|
||||
|
||||
factory SharedSpaceMemberInfo({
|
||||
required _isc.UuidValue id,
|
||||
|
|
@ -36,6 +37,7 @@ abstract class SharedSpaceMemberInfo
|
|||
DateTime? joinedAt,
|
||||
required bool invited,
|
||||
required bool you,
|
||||
bool? byEmail,
|
||||
}) = _SharedSpaceMemberInfoImpl;
|
||||
|
||||
factory SharedSpaceMemberInfo.fromJson(
|
||||
|
|
@ -53,6 +55,9 @@ abstract class SharedSpaceMemberInfo
|
|||
: _isc.DateTimeJsonExtension.fromJson(jsonSerialization['joinedAt']),
|
||||
invited: _isc.BoolJsonExtension.fromJson(jsonSerialization['invited']),
|
||||
you: _isc.BoolJsonExtension.fromJson(jsonSerialization['you']),
|
||||
byEmail: jsonSerialization['byEmail'] == null
|
||||
? null
|
||||
: _isc.BoolJsonExtension.fromJson(jsonSerialization['byEmail']),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -77,6 +82,10 @@ abstract class SharedSpaceMemberInfo
|
|||
/// Whether this is the caller.
|
||||
bool you;
|
||||
|
||||
/// Whether this is an invitation mailed to an address (D38), which the
|
||||
/// space's members never see: neither handle nor name.
|
||||
bool byEmail;
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceMemberInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
|
|
@ -88,6 +97,7 @@ abstract class SharedSpaceMemberInfo
|
|||
DateTime? joinedAt,
|
||||
bool? invited,
|
||||
bool? you,
|
||||
bool? byEmail,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -100,6 +110,7 @@ abstract class SharedSpaceMemberInfo
|
|||
if (joinedAt != null) 'joinedAt': joinedAt?.toJson(),
|
||||
'invited': invited,
|
||||
'you': you,
|
||||
'byEmail': byEmail,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -114,6 +125,7 @@ abstract class SharedSpaceMemberInfo
|
|||
if (joinedAt != null) 'joinedAt': joinedAt?.toJson(),
|
||||
'invited': invited,
|
||||
'you': you,
|
||||
'byEmail': byEmail,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +146,7 @@ class _SharedSpaceMemberInfoImpl extends SharedSpaceMemberInfo {
|
|||
DateTime? joinedAt,
|
||||
required bool invited,
|
||||
required bool you,
|
||||
bool? byEmail,
|
||||
}) : super._(
|
||||
id: id,
|
||||
handle: handle,
|
||||
|
|
@ -142,6 +155,7 @@ class _SharedSpaceMemberInfoImpl extends SharedSpaceMemberInfo {
|
|||
joinedAt: joinedAt,
|
||||
invited: invited,
|
||||
you: you,
|
||||
byEmail: byEmail,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceMemberInfo]
|
||||
|
|
@ -156,6 +170,7 @@ class _SharedSpaceMemberInfoImpl extends SharedSpaceMemberInfo {
|
|||
Object? joinedAt = _Undefined,
|
||||
bool? invited,
|
||||
bool? you,
|
||||
bool? byEmail,
|
||||
}) {
|
||||
return SharedSpaceMemberInfo(
|
||||
id: id ?? this.id,
|
||||
|
|
@ -165,6 +180,7 @@ class _SharedSpaceMemberInfoImpl extends SharedSpaceMemberInfo {
|
|||
joinedAt: joinedAt is DateTime? ? joinedAt : this.joinedAt,
|
||||
invited: invited ?? this.invited,
|
||||
you: you ?? this.you,
|
||||
byEmail: byEmail ?? this.byEmail,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,14 @@ enum SharedSpaceRefusal implements _isc.SerializableModel {
|
|||
nextOwnerNeeded,
|
||||
|
||||
/// The only member doesn't leave: they delete the space.
|
||||
lastMember;
|
||||
lastMember,
|
||||
|
||||
/// Invitations by mail name who invites by their handle: the caller has
|
||||
/// none.
|
||||
handleNeeded,
|
||||
|
||||
/// The code proved another address than the one invited (D37).
|
||||
otherAddress;
|
||||
|
||||
static SharedSpaceRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -56,6 +63,10 @@ enum SharedSpaceRefusal implements _isc.SerializableModel {
|
|||
return SharedSpaceRefusal.nextOwnerNeeded;
|
||||
case 'lastMember':
|
||||
return SharedSpaceRefusal.lastMember;
|
||||
case 'handleNeeded':
|
||||
return SharedSpaceRefusal.handleNeeded;
|
||||
case 'otherAddress':
|
||||
return SharedSpaceRefusal.otherAddress;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "SharedSpaceRefusal"',
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ 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/join_page.dart' show addJoinPageRoute, joinPage;
|
||||
export 'src/shared_space/shared_spaces.dart' show forgetLapsedSpaceInvitations;
|
||||
export 'src/shared_space/space_grants.dart';
|
||||
export 'src/simplelogin/handle_suggestion.dart'
|
||||
show handleFromName, handlesToTry, suggestable;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,19 @@ abstract interface class CodeMailer {
|
|||
required DateTime until,
|
||||
required String language,
|
||||
});
|
||||
|
||||
/// Mails [to], worded in [language], that [inviter] invites them to
|
||||
/// something they share in [app], to be joined by [link] until
|
||||
/// [lapsesAt] ([invitationMail]; Halkyon's ADR 0044, D37).
|
||||
Future<void> sendInvitation(
|
||||
Session session, {
|
||||
required String to,
|
||||
required String inviter,
|
||||
required String app,
|
||||
required Uri link,
|
||||
required DateTime lapsesAt,
|
||||
required String language,
|
||||
});
|
||||
}
|
||||
|
||||
/// Writes codes into the server's log, for development, where nobody has a
|
||||
|
|
@ -76,6 +89,17 @@ final class LogCodeMailer implements CodeMailer {
|
|||
required DateTime until,
|
||||
required String language,
|
||||
}) async => session.log('Codes wait for $to until ${_utc(until)}');
|
||||
|
||||
@override
|
||||
Future<void> sendInvitation(
|
||||
Session session, {
|
||||
required String to,
|
||||
required String inviter,
|
||||
required String app,
|
||||
required Uri link,
|
||||
required DateTime lapsesAt,
|
||||
required String language,
|
||||
}) async => session.log('Invitation for $to from $inviter: $link');
|
||||
}
|
||||
|
||||
/// A code's mail, worded in [language] (`en`, else German for `de`): its
|
||||
|
|
@ -103,6 +127,7 @@ final class LogCodeMailer implements CodeMailer {
|
|||
EmailCodePurpose.accountReset => 'um dein Konto zurückzusetzen',
|
||||
EmailCodePurpose.deletion => 'um dein Konto zu löschen',
|
||||
EmailCodePurpose.firstPassword => 'um deinem Konto ein Passwort zu geben',
|
||||
EmailCodePurpose.joinSpace => 'um einer Einladung zu folgen',
|
||||
}}',
|
||||
body:
|
||||
'$code\n'
|
||||
|
|
@ -119,6 +144,8 @@ final class LogCodeMailer implements CodeMailer {
|
|||
'dann verloren',
|
||||
EmailCodePurpose.deletion => 'um dein Konto auf $instance mit allem darin zu löschen',
|
||||
EmailCodePurpose.firstPassword => 'um deinem Konto auf $instance sein erstes Passwort zu geben',
|
||||
EmailCodePurpose.joinSpace => 'um mit deinem Konto auf $instance dem beizutreten, wozu '
|
||||
'diese Adresse eingeladen wurde',
|
||||
}}. Er gilt 15 Minuten.\n'
|
||||
'\n'
|
||||
'Gib ihn nur in $app auf $instance ein, nirgends sonst, auch wenn '
|
||||
|
|
@ -139,6 +166,7 @@ final class LogCodeMailer implements CodeMailer {
|
|||
EmailCodePurpose.accountReset => 'reset your account',
|
||||
EmailCodePurpose.deletion => 'delete your account',
|
||||
EmailCodePurpose.firstPassword => 'give your account a password',
|
||||
EmailCodePurpose.joinSpace => 'accept an invitation',
|
||||
}}',
|
||||
body:
|
||||
'$code\n'
|
||||
|
|
@ -153,6 +181,8 @@ final class LogCodeMailer implements CodeMailer {
|
|||
'keys, and what was encrypted with the old ones is lost',
|
||||
EmailCodePurpose.deletion => 'to delete your account on $instance with everything in it',
|
||||
EmailCodePurpose.firstPassword => 'to give your account on $instance its first password',
|
||||
EmailCodePurpose.joinSpace => 'to join, with your account on $instance, what this address '
|
||||
'was invited to',
|
||||
}}. It is valid for 15 minutes.\n'
|
||||
'\n'
|
||||
'Type it only in $app on $instance, nowhere else, even if a page or '
|
||||
|
|
@ -269,6 +299,60 @@ final class LogCodeMailer implements CodeMailer {
|
|||
};
|
||||
}
|
||||
|
||||
/// An invitation to a shared space (Halkyon's ADR 0044, D37), worded in
|
||||
/// [language] (`en`, else German for `de`): its subject and its body.
|
||||
/// [inviter] names who invites (`Alice (@alice)`), [app] the app it is
|
||||
/// shared in, [link] the page that opens it, which holds until [lapsesAt]
|
||||
/// (shown in UTC). It names nothing of what is shared, which is sealed,
|
||||
/// and says that joining takes a code mailed here, so a link forwarded
|
||||
/// admits nobody else.
|
||||
({String subject, String body}) invitationMail({
|
||||
required String inviter,
|
||||
required String app,
|
||||
required Uri link,
|
||||
required DateTime lapsesAt,
|
||||
required String language,
|
||||
}) {
|
||||
final when = _utc(lapsesAt);
|
||||
final host = link.host;
|
||||
return switch (language) {
|
||||
'de' => (
|
||||
subject: '$inviter lädt dich in $app ein',
|
||||
body:
|
||||
'$inviter möchte in $app etwas mit dir teilen, auf der Instanz '
|
||||
'$host.\n'
|
||||
'\n'
|
||||
'Öffne diesen Link, um beizutreten, auf einem Gerät mit $app: '
|
||||
'$link\n'
|
||||
'\n'
|
||||
'Die App fragt nach dieser Adresse und schickt einen Code an sie: '
|
||||
'Nur wer dieses Postfach liest, kann beitreten. Die Einladung gilt '
|
||||
'bis $when.\n'
|
||||
'\n'
|
||||
'Kennst du sie nicht, ignoriere diese Mail: Ohne dich geschieht '
|
||||
'nichts.\n'
|
||||
'\n'
|
||||
'Diese Instanz behält nur einen Fingerabdruck deiner Adresse.\n',
|
||||
),
|
||||
_ => (
|
||||
subject: '$inviter invites you to $app',
|
||||
body:
|
||||
'$inviter wants to share something with you in $app, on the '
|
||||
'instance $host.\n'
|
||||
'\n'
|
||||
'Open this link to join, on a device with $app: $link\n'
|
||||
'\n'
|
||||
'The app asks for this address and mails a code to it: only whoever '
|
||||
'reads this mailbox can join. The invitation holds until $when.\n'
|
||||
'\n'
|
||||
"If you don't know them, ignore this mail: nothing happens without "
|
||||
'you.\n'
|
||||
'\n'
|
||||
'This instance keeps only a fingerprint of your address.\n',
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// [time] as a reset's notice names it: to the minute, in UTC.
|
||||
String _utc(DateTime time) {
|
||||
final utc = time.toUtc();
|
||||
|
|
@ -372,6 +456,26 @@ final class SmtpCodeMailer implements CodeMailer {
|
|||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> sendInvitation(
|
||||
Session session, {
|
||||
required String to,
|
||||
required String inviter,
|
||||
required String app,
|
||||
required Uri link,
|
||||
required DateTime lapsesAt,
|
||||
required String language,
|
||||
}) => _deliver(
|
||||
to,
|
||||
invitationMail(
|
||||
inviter: inviter,
|
||||
app: app,
|
||||
link: link,
|
||||
lapsesAt: lapsesAt,
|
||||
language: language,
|
||||
),
|
||||
);
|
||||
|
||||
/// Mails [code] for [purpose] to [to], worded in [language]; throws what
|
||||
/// the service answered when it took no mail.
|
||||
Future<void> mail({
|
||||
|
|
|
|||
|
|
@ -220,6 +220,58 @@ Future<UuidValue> sendSignInCode(
|
|||
});
|
||||
}
|
||||
|
||||
/// Mails an invitation to a shared space to [address] (Halkyon's ADR 0044,
|
||||
/// D37: [inviter] invites to something shared in [app], joined by [link]
|
||||
/// until [lapsesAt]) and returns the address's keyed hash, which alone the
|
||||
/// invitation keeps (D38). Counted with the codes mailed to the address and
|
||||
/// asked for from [network], so invitations flood no mailbox. Refused as
|
||||
/// [sendSignInCode] is: no address, too many mails, or a mail that couldn't
|
||||
/// be sent ([EmailSignInRefusedException]).
|
||||
Future<String> sendSpaceInvitation(
|
||||
Session session, {
|
||||
required String address,
|
||||
required String language,
|
||||
required String inviter,
|
||||
required String app,
|
||||
required Uri link,
|
||||
required DateTime lapsesAt,
|
||||
required String network,
|
||||
}) async {
|
||||
final pod = session.serverpod;
|
||||
final mailer = EmailCodes.of(pod).mailer;
|
||||
if (mailer == null || !emailSignInAvailable(pod)) {
|
||||
throw EmailSignInRefusedException(reason: EmailSignInRefusal.unavailable);
|
||||
}
|
||||
final to = normalizedAddress(address);
|
||||
if (to == null) {
|
||||
throw EmailSignInRefusedException(reason: EmailSignInRefusal.notAnAddress);
|
||||
}
|
||||
final hash = addressHash(pod, to);
|
||||
if (!await _codesPerAddress.tryRecordAttempt(session, key: hash) ||
|
||||
!await _codesPerNetwork.tryRecordAttempt(session, key: network)) {
|
||||
throw EmailSignInRefusedException(reason: EmailSignInRefusal.tooManyCodes);
|
||||
}
|
||||
try {
|
||||
await mailer.sendInvitation(
|
||||
session,
|
||||
to: to,
|
||||
inviter: inviter,
|
||||
app: app,
|
||||
link: link,
|
||||
lapsesAt: lapsesAt,
|
||||
language: language == 'de' ? 'de' : 'en',
|
||||
);
|
||||
} on Exception catch (error) {
|
||||
// Its type only: what the mail service answered may name the address.
|
||||
session.log(
|
||||
'Mailing an invitation failed: ${error.runtimeType}',
|
||||
level: LogLevel.warning,
|
||||
);
|
||||
throw EmailSignInRefusedException(reason: EmailSignInRefusal.mailFailed);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// The keyed hash of the address [code] proves for [request], once: the
|
||||
/// request goes as it is accepted. A code by mail signs nobody in (ADR
|
||||
/// 0017): it proves an address for a password's setup, a sign-up, a setup
|
||||
|
|
|
|||
|
|
@ -20,3 +20,6 @@ values:
|
|||
### Proving, signed in, the account's own address to give it its first
|
||||
### password (the fourth reading: a session alone sets none).
|
||||
- firstPassword
|
||||
### Proving the address a space's invitation was mailed to, to join it
|
||||
### (Halkyon's ADR 0044, D37).
|
||||
- joinSpace
|
||||
|
|
|
|||
|
|
@ -36,7 +36,11 @@ enum EmailCodePurpose implements _is.SerializableModel {
|
|||
|
||||
/// Proving, signed in, the account's own address to give it its first
|
||||
/// password (the fourth reading: a session alone sets none).
|
||||
firstPassword;
|
||||
firstPassword,
|
||||
|
||||
/// Proving the address a space's invitation was mailed to, to join it
|
||||
/// (Halkyon's ADR 0044, D37).
|
||||
joinSpace;
|
||||
|
||||
static EmailCodePurpose fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -52,6 +56,8 @@ enum EmailCodePurpose implements _is.SerializableModel {
|
|||
return EmailCodePurpose.deletion;
|
||||
case 'firstPassword':
|
||||
return EmailCodePurpose.firstPassword;
|
||||
case 'joinSpace':
|
||||
return EmailCodePurpose.joinSpace;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "EmailCodePurpose"',
|
||||
|
|
|
|||
|
|
@ -1959,6 +1959,26 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.mine(session),
|
||||
),
|
||||
'markSeen': _is.MethodConnector(
|
||||
name: 'markSeen',
|
||||
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)
|
||||
.markSeen(
|
||||
session,
|
||||
params['space'],
|
||||
),
|
||||
),
|
||||
'members': _is.MethodConnector(
|
||||
name: 'members',
|
||||
params: {
|
||||
|
|
@ -2011,6 +2031,162 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
params['role'],
|
||||
),
|
||||
),
|
||||
'inviteByEmail': _is.MethodConnector(
|
||||
name: 'inviteByEmail',
|
||||
params: {
|
||||
'space': _is.ParameterDescription(
|
||||
name: 'space',
|
||||
type: _is.getType<_is.UuidValue>(),
|
||||
nullable: false,
|
||||
),
|
||||
'address': _is.ParameterDescription(
|
||||
name: 'address',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'role': _is.ParameterDescription(
|
||||
name: 'role',
|
||||
type: _is.getType<_i4jk9by9.SharedSpaceRole>(),
|
||||
nullable: false,
|
||||
),
|
||||
'language': _is.ParameterDescription(
|
||||
name: 'language',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.inviteByEmail(
|
||||
session,
|
||||
params['space'],
|
||||
params['address'],
|
||||
params['role'],
|
||||
params['language'],
|
||||
),
|
||||
),
|
||||
'preview': _is.MethodConnector(
|
||||
name: 'preview',
|
||||
params: {
|
||||
'token': _is.ParameterDescription(
|
||||
name: 'token',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.preview(
|
||||
session,
|
||||
params['token'],
|
||||
),
|
||||
),
|
||||
'acceptEmailInvitation': _is.MethodConnector(
|
||||
name: 'acceptEmailInvitation',
|
||||
params: {
|
||||
'token': _is.ParameterDescription(
|
||||
name: 'token',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'request': _is.ParameterDescription(
|
||||
name: 'request',
|
||||
type: _is.getType<_is.UuidValue>(),
|
||||
nullable: false,
|
||||
),
|
||||
'code': _is.ParameterDescription(
|
||||
name: 'code',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.acceptEmailInvitation(
|
||||
session,
|
||||
params['token'],
|
||||
params['request'],
|
||||
params['code'],
|
||||
),
|
||||
),
|
||||
'joinLink': _is.MethodConnector(
|
||||
name: 'joinLink',
|
||||
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)
|
||||
.joinLink(
|
||||
session,
|
||||
params['space'],
|
||||
),
|
||||
),
|
||||
'setJoinLink': _is.MethodConnector(
|
||||
name: 'setJoinLink',
|
||||
params: {
|
||||
'space': _is.ParameterDescription(
|
||||
name: 'space',
|
||||
type: _is.getType<_is.UuidValue>(),
|
||||
nullable: false,
|
||||
),
|
||||
'on': _is.ParameterDescription(
|
||||
name: 'on',
|
||||
type: _is.getType<bool>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.setJoinLink(
|
||||
session,
|
||||
params['space'],
|
||||
params['on'],
|
||||
),
|
||||
),
|
||||
'joinByLink': _is.MethodConnector(
|
||||
name: 'joinByLink',
|
||||
params: {
|
||||
'token': _is.ParameterDescription(
|
||||
name: 'token',
|
||||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['sharedSpace'] as _i858tn7r.SharedSpaceEndpoint)
|
||||
.joinByLink(
|
||||
session,
|
||||
params['token'],
|
||||
),
|
||||
),
|
||||
'setRole': _is.MethodConnector(
|
||||
name: 'setRole',
|
||||
params: {
|
||||
|
|
|
|||
|
|
@ -121,8 +121,10 @@ 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_email_invitation.dart' as _i2x2lml3;
|
||||
import 'shared_space/shared_space_info.dart' as _i1rvc02f;
|
||||
import 'shared_space/shared_space_invitation.dart' as _i7h4we7t;
|
||||
import 'shared_space/shared_space_join_preview.dart' as _if1ehrh0;
|
||||
import 'shared_space/shared_space_key.dart' as _i9bjihm2;
|
||||
import 'shared_space/shared_space_key_need.dart' as _imxqlbwp;
|
||||
import 'shared_space/shared_space_member.dart' as _iw7ixstr;
|
||||
|
|
@ -222,8 +224,10 @@ 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_email_invitation.dart';
|
||||
export 'shared_space/shared_space_info.dart';
|
||||
export 'shared_space/shared_space_invitation.dart';
|
||||
export 'shared_space/shared_space_join_preview.dart';
|
||||
export 'shared_space/shared_space_key.dart';
|
||||
export 'shared_space/shared_space_key_need.dart';
|
||||
export 'shared_space/shared_space_member.dart';
|
||||
|
|
@ -1316,6 +1320,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
dartType: 'bool',
|
||||
columnDefault: 'false',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'joinToken',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'membersInvite',
|
||||
columnType: _isp.ColumnType.boolean,
|
||||
|
|
@ -1332,7 +1342,117 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
),
|
||||
],
|
||||
foreignKeys: [],
|
||||
indexes: [],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'shared_space_join_token_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'joinToken',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'shared_space_email_invitation',
|
||||
dartName: 'SharedSpaceEmailInvitation',
|
||||
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: 'addressHash',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'tokenHash',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_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_email_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_email_invitation_address_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'sharedSpaceId',
|
||||
),
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'addressHash',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'shared_space_email_invitation_token_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'tokenHash',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
|
|
@ -1562,6 +1682,19 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'addedBy',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: true,
|
||||
dartType: 'UuidValue?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'isNew',
|
||||
columnType: _isp.ColumnType.boolean,
|
||||
isNullable: false,
|
||||
dartType: 'bool',
|
||||
columnDefault: 'false',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
|
|
@ -2160,12 +2293,18 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _iampeqql.SharedSpace) {
|
||||
return _iampeqql.SharedSpace.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i2x2lml3.SharedSpaceEmailInvitation) {
|
||||
return _i2x2lml3.SharedSpaceEmailInvitation.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 == _if1ehrh0.SharedSpaceJoinPreview) {
|
||||
return _if1ehrh0.SharedSpaceJoinPreview.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i9bjihm2.SharedSpaceKey) {
|
||||
return _i9bjihm2.SharedSpaceKey.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -2564,6 +2703,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _is.getType<_iampeqql.SharedSpace?>()) {
|
||||
return (data != null ? _iampeqql.SharedSpace.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_i2x2lml3.SharedSpaceEmailInvitation?>()) {
|
||||
return (data != null
|
||||
? _i2x2lml3.SharedSpaceEmailInvitation.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_i1rvc02f.SharedSpaceInfo?>()) {
|
||||
return (data != null ? _i1rvc02f.SharedSpaceInfo.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -2574,6 +2719,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_if1ehrh0.SharedSpaceJoinPreview?>()) {
|
||||
return (data != null
|
||||
? _if1ehrh0.SharedSpaceJoinPreview.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_i9bjihm2.SharedSpaceKey?>()) {
|
||||
return (data != null ? _i9bjihm2.SharedSpaceKey.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -2859,8 +3010,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_i2lyzpm9.AccountDeletionNotice => 'AccountDeletionNotice',
|
||||
_io5c3tod.SessionsEndedNotice => 'SessionsEndedNotice',
|
||||
_iampeqql.SharedSpace => 'SharedSpace',
|
||||
_i2x2lml3.SharedSpaceEmailInvitation => 'SharedSpaceEmailInvitation',
|
||||
_i1rvc02f.SharedSpaceInfo => 'SharedSpaceInfo',
|
||||
_i7h4we7t.SharedSpaceInvitation => 'SharedSpaceInvitation',
|
||||
_if1ehrh0.SharedSpaceJoinPreview => 'SharedSpaceJoinPreview',
|
||||
_i9bjihm2.SharedSpaceKey => 'SharedSpaceKey',
|
||||
_imxqlbwp.SharedSpaceKeyNeed => 'SharedSpaceKeyNeed',
|
||||
_iw7ixstr.SharedSpaceMember => 'SharedSpaceMember',
|
||||
|
|
@ -3062,10 +3215,14 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'SessionsEndedNotice';
|
||||
case _iampeqql.SharedSpace():
|
||||
return 'SharedSpace';
|
||||
case _i2x2lml3.SharedSpaceEmailInvitation():
|
||||
return 'SharedSpaceEmailInvitation';
|
||||
case _i1rvc02f.SharedSpaceInfo():
|
||||
return 'SharedSpaceInfo';
|
||||
case _i7h4we7t.SharedSpaceInvitation():
|
||||
return 'SharedSpaceInvitation';
|
||||
case _if1ehrh0.SharedSpaceJoinPreview():
|
||||
return 'SharedSpaceJoinPreview';
|
||||
case _i9bjihm2.SharedSpaceKey():
|
||||
return 'SharedSpaceKey';
|
||||
case _imxqlbwp.SharedSpaceKeyNeed():
|
||||
|
|
@ -3363,12 +3520,18 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'SharedSpace') {
|
||||
return deserialize<_iampeqql.SharedSpace>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceEmailInvitation') {
|
||||
return deserialize<_i2x2lml3.SharedSpaceEmailInvitation>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceInfo') {
|
||||
return deserialize<_i1rvc02f.SharedSpaceInfo>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceInvitation') {
|
||||
return deserialize<_i7h4we7t.SharedSpaceInvitation>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceJoinPreview') {
|
||||
return deserialize<_if1ehrh0.SharedSpaceJoinPreview>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SharedSpaceKey') {
|
||||
return deserialize<_i9bjihm2.SharedSpaceKey>(data['data']);
|
||||
}
|
||||
|
|
@ -3542,6 +3705,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return _io5c3tod.SessionsEndedNotice.t;
|
||||
case _iampeqql.SharedSpace:
|
||||
return _iampeqql.SharedSpace.t;
|
||||
case _i2x2lml3.SharedSpaceEmailInvitation:
|
||||
return _i2x2lml3.SharedSpaceEmailInvitation.t;
|
||||
case _i7h4we7t.SharedSpaceInvitation:
|
||||
return _i7h4we7t.SharedSpaceInvitation.t;
|
||||
case _i9bjihm2.SharedSpaceKey:
|
||||
|
|
|
|||
|
|
@ -87,8 +87,15 @@ profiles:
|
|||
sharedSpace:
|
||||
- create:
|
||||
- mine:
|
||||
- markSeen:
|
||||
- members:
|
||||
- invite:
|
||||
- inviteByEmail:
|
||||
- preview:
|
||||
- acceptEmailInvitation:
|
||||
- joinLink:
|
||||
- setJoinLink:
|
||||
- joinByLink:
|
||||
- setRole:
|
||||
- remove:
|
||||
- handOn:
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ abstract class SharedSpace
|
|||
required this.kind,
|
||||
required this.createdAt,
|
||||
bool? joinByLink,
|
||||
this.joinToken,
|
||||
bool? membersInvite,
|
||||
bool? keyRollDue,
|
||||
}) : joinByLink = joinByLink ?? false,
|
||||
|
|
@ -34,6 +35,7 @@ abstract class SharedSpace
|
|||
required String kind,
|
||||
required DateTime createdAt,
|
||||
bool? joinByLink,
|
||||
String? joinToken,
|
||||
bool? membersInvite,
|
||||
bool? keyRollDue,
|
||||
}) = _SharedSpaceImpl;
|
||||
|
|
@ -50,6 +52,7 @@ abstract class SharedSpace
|
|||
joinByLink: jsonSerialization['joinByLink'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['joinByLink']),
|
||||
joinToken: jsonSerialization['joinToken'] as String?,
|
||||
membersInvite: jsonSerialization['membersInvite'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['membersInvite']),
|
||||
|
|
@ -74,6 +77,12 @@ abstract class SharedSpace
|
|||
/// Whether someone may join by its link (D5); off at first.
|
||||
bool joinByLink;
|
||||
|
||||
/// The link's token while someone may join by it (D39): kept as it is,
|
||||
/// so that whoever may invite copies the link again; a new one each
|
||||
/// time the link is switched on, so switching it off ends a link that
|
||||
/// went too far for good.
|
||||
String? joinToken;
|
||||
|
||||
/// Whether members other than the Owner may invite others (D5); off at
|
||||
/// first.
|
||||
bool membersInvite;
|
||||
|
|
@ -94,6 +103,7 @@ abstract class SharedSpace
|
|||
String? kind,
|
||||
DateTime? createdAt,
|
||||
bool? joinByLink,
|
||||
String? joinToken,
|
||||
bool? membersInvite,
|
||||
bool? keyRollDue,
|
||||
});
|
||||
|
|
@ -105,6 +115,7 @@ abstract class SharedSpace
|
|||
'kind': kind,
|
||||
'createdAt': createdAt.toJson(),
|
||||
'joinByLink': joinByLink,
|
||||
if (joinToken != null) 'joinToken': joinToken,
|
||||
'membersInvite': membersInvite,
|
||||
'keyRollDue': keyRollDue,
|
||||
};
|
||||
|
|
@ -151,6 +162,7 @@ class _SharedSpaceImpl extends SharedSpace {
|
|||
required String kind,
|
||||
required DateTime createdAt,
|
||||
bool? joinByLink,
|
||||
String? joinToken,
|
||||
bool? membersInvite,
|
||||
bool? keyRollDue,
|
||||
}) : super._(
|
||||
|
|
@ -158,6 +170,7 @@ class _SharedSpaceImpl extends SharedSpace {
|
|||
kind: kind,
|
||||
createdAt: createdAt,
|
||||
joinByLink: joinByLink,
|
||||
joinToken: joinToken,
|
||||
membersInvite: membersInvite,
|
||||
keyRollDue: keyRollDue,
|
||||
);
|
||||
|
|
@ -171,6 +184,7 @@ class _SharedSpaceImpl extends SharedSpace {
|
|||
String? kind,
|
||||
DateTime? createdAt,
|
||||
bool? joinByLink,
|
||||
Object? joinToken = _Undefined,
|
||||
bool? membersInvite,
|
||||
bool? keyRollDue,
|
||||
}) {
|
||||
|
|
@ -179,6 +193,7 @@ class _SharedSpaceImpl extends SharedSpace {
|
|||
kind: kind ?? this.kind,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
joinByLink: joinByLink ?? this.joinByLink,
|
||||
joinToken: joinToken is String? ? joinToken : this.joinToken,
|
||||
membersInvite: membersInvite ?? this.membersInvite,
|
||||
keyRollDue: keyRollDue ?? this.keyRollDue,
|
||||
);
|
||||
|
|
@ -204,6 +219,11 @@ class SharedSpaceUpdateTable extends _is.UpdateTable<SharedSpaceTable> {
|
|||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> joinToken(String? value) => _is.ColumnValue(
|
||||
table.joinToken,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<bool, bool> membersInvite(bool value) => _is.ColumnValue(
|
||||
table.membersInvite,
|
||||
value,
|
||||
|
|
@ -231,6 +251,10 @@ class SharedSpaceTable extends _is.Table<_is.UuidValue?> {
|
|||
this,
|
||||
hasDefault: true,
|
||||
);
|
||||
joinToken = _is.ColumnString(
|
||||
'joinToken',
|
||||
this,
|
||||
);
|
||||
membersInvite = _is.ColumnBool(
|
||||
'membersInvite',
|
||||
this,
|
||||
|
|
@ -253,6 +277,12 @@ class SharedSpaceTable extends _is.Table<_is.UuidValue?> {
|
|||
/// Whether someone may join by its link (D5); off at first.
|
||||
late final _is.ColumnBool joinByLink;
|
||||
|
||||
/// The link's token while someone may join by it (D39): kept as it is,
|
||||
/// so that whoever may invite copies the link again; a new one each
|
||||
/// time the link is switched on, so switching it off ends a link that
|
||||
/// went too far for good.
|
||||
late final _is.ColumnString joinToken;
|
||||
|
||||
/// Whether members other than the Owner may invite others (D5); off at
|
||||
/// first.
|
||||
late final _is.ColumnBool membersInvite;
|
||||
|
|
@ -268,6 +298,7 @@ class SharedSpaceTable extends _is.Table<_is.UuidValue?> {
|
|||
kind,
|
||||
createdAt,
|
||||
joinByLink,
|
||||
joinToken,
|
||||
membersInvite,
|
||||
keyRollDue,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,728 @@
|
|||
/* 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 to a shared space mailed to an address (ADR 0044, D37,
|
||||
/// D38): whoever opens its link and proves the address with a mailed code
|
||||
/// joins, until it lapses 14 days after it was mailed. The instance keeps
|
||||
/// only the address's keyed hash, as it does for sign-in (Halkyon Learn's
|
||||
/// ADR 0010), and the hash of the link's token: inviting again means
|
||||
/// typing the address again.
|
||||
abstract class SharedSpaceEmailInvitation
|
||||
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
|
||||
SharedSpaceEmailInvitation._({
|
||||
this.id,
|
||||
required this.sharedSpaceId,
|
||||
required this.addressHash,
|
||||
required this.tokenHash,
|
||||
required this.role,
|
||||
required this.invitedBy,
|
||||
required this.invitedAt,
|
||||
});
|
||||
|
||||
factory SharedSpaceEmailInvitation({
|
||||
_is.UuidValue? id,
|
||||
required _is.UuidValue sharedSpaceId,
|
||||
required String addressHash,
|
||||
required String tokenHash,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required _is.UuidValue invitedBy,
|
||||
required DateTime invitedAt,
|
||||
}) = _SharedSpaceEmailInvitationImpl;
|
||||
|
||||
factory SharedSpaceEmailInvitation.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return SharedSpaceEmailInvitation(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
sharedSpaceId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['sharedSpaceId'],
|
||||
),
|
||||
addressHash: jsonSerialization['addressHash'] as String,
|
||||
tokenHash: jsonSerialization['tokenHash'] as String,
|
||||
role: _igsxp9el.SharedSpaceRole.fromJson(
|
||||
(jsonSerialization['role'] as String),
|
||||
),
|
||||
invitedBy: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['invitedBy'],
|
||||
),
|
||||
invitedAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['invitedAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = SharedSpaceEmailInvitationTable();
|
||||
|
||||
static const db = SharedSpaceEmailInvitationRepository._();
|
||||
|
||||
@override
|
||||
_is.UuidValue? id;
|
||||
|
||||
_is.UuidValue sharedSpaceId;
|
||||
|
||||
/// HMAC-SHA256 of the address, keyed with the register's pepper.
|
||||
String addressHash;
|
||||
|
||||
/// SHA-256 of the link's token, in hex.
|
||||
String tokenHash;
|
||||
|
||||
/// 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 [SharedSpaceEmailInvitation]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
SharedSpaceEmailInvitation copyWith({
|
||||
_is.UuidValue? id,
|
||||
_is.UuidValue? sharedSpaceId,
|
||||
String? addressHash,
|
||||
String? tokenHash,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
_is.UuidValue? invitedBy,
|
||||
DateTime? invitedAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.SharedSpaceEmailInvitation',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
'sharedSpaceId': sharedSpaceId.toJson(),
|
||||
'addressHash': addressHash,
|
||||
'tokenHash': tokenHash,
|
||||
'role': role.toJson(),
|
||||
'invitedBy': invitedBy.toJson(),
|
||||
'invitedAt': invitedAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static SharedSpaceEmailInvitationInclude include() {
|
||||
return SharedSpaceEmailInvitationInclude._();
|
||||
}
|
||||
|
||||
static SharedSpaceEmailInvitationIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
SharedSpaceEmailInvitationInclude? include,
|
||||
}) {
|
||||
return SharedSpaceEmailInvitationIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SharedSpaceEmailInvitationImpl extends SharedSpaceEmailInvitation {
|
||||
_SharedSpaceEmailInvitationImpl({
|
||||
_is.UuidValue? id,
|
||||
required _is.UuidValue sharedSpaceId,
|
||||
required String addressHash,
|
||||
required String tokenHash,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required _is.UuidValue invitedBy,
|
||||
required DateTime invitedAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
sharedSpaceId: sharedSpaceId,
|
||||
addressHash: addressHash,
|
||||
tokenHash: tokenHash,
|
||||
role: role,
|
||||
invitedBy: invitedBy,
|
||||
invitedAt: invitedAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceEmailInvitation]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
SharedSpaceEmailInvitation copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? sharedSpaceId,
|
||||
String? addressHash,
|
||||
String? tokenHash,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
_is.UuidValue? invitedBy,
|
||||
DateTime? invitedAt,
|
||||
}) {
|
||||
return SharedSpaceEmailInvitation(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
sharedSpaceId: sharedSpaceId ?? this.sharedSpaceId,
|
||||
addressHash: addressHash ?? this.addressHash,
|
||||
tokenHash: tokenHash ?? this.tokenHash,
|
||||
role: role ?? this.role,
|
||||
invitedBy: invitedBy ?? this.invitedBy,
|
||||
invitedAt: invitedAt ?? this.invitedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SharedSpaceEmailInvitationUpdateTable
|
||||
extends _is.UpdateTable<SharedSpaceEmailInvitationTable> {
|
||||
SharedSpaceEmailInvitationUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> sharedSpaceId(
|
||||
_is.UuidValue value,
|
||||
) => _is.ColumnValue(
|
||||
table.sharedSpaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> addressHash(String value) => _is.ColumnValue(
|
||||
table.addressHash,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> tokenHash(String value) => _is.ColumnValue(
|
||||
table.tokenHash,
|
||||
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 SharedSpaceEmailInvitationTable extends _is.Table<_is.UuidValue?> {
|
||||
SharedSpaceEmailInvitationTable({super.tableRelation})
|
||||
: super(tableName: 'shared_space_email_invitation') {
|
||||
updateTable = SharedSpaceEmailInvitationUpdateTable(this);
|
||||
sharedSpaceId = _is.ColumnUuid(
|
||||
'sharedSpaceId',
|
||||
this,
|
||||
);
|
||||
addressHash = _is.ColumnString(
|
||||
'addressHash',
|
||||
this,
|
||||
);
|
||||
tokenHash = _is.ColumnString(
|
||||
'tokenHash',
|
||||
this,
|
||||
);
|
||||
role = _is.ColumnEnum(
|
||||
'role',
|
||||
this,
|
||||
_is.EnumSerialization.byName,
|
||||
);
|
||||
invitedBy = _is.ColumnUuid(
|
||||
'invitedBy',
|
||||
this,
|
||||
);
|
||||
invitedAt = _is.ColumnDateTime(
|
||||
'invitedAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final SharedSpaceEmailInvitationUpdateTable updateTable;
|
||||
|
||||
late final _is.ColumnUuid sharedSpaceId;
|
||||
|
||||
/// HMAC-SHA256 of the address, keyed with the register's pepper.
|
||||
late final _is.ColumnString addressHash;
|
||||
|
||||
/// SHA-256 of the link's token, in hex.
|
||||
late final _is.ColumnString tokenHash;
|
||||
|
||||
/// 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,
|
||||
addressHash,
|
||||
tokenHash,
|
||||
role,
|
||||
invitedBy,
|
||||
invitedAt,
|
||||
];
|
||||
}
|
||||
|
||||
class SharedSpaceEmailInvitationInclude extends _is.IncludeObject {
|
||||
SharedSpaceEmailInvitationInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => SharedSpaceEmailInvitation.t;
|
||||
}
|
||||
|
||||
class SharedSpaceEmailInvitationIncludeList extends _is.IncludeList {
|
||||
SharedSpaceEmailInvitationIncludeList._({
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(SharedSpaceEmailInvitation.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => SharedSpaceEmailInvitation.t;
|
||||
}
|
||||
|
||||
class SharedSpaceEmailInvitationRepository {
|
||||
const SharedSpaceEmailInvitationRepository._();
|
||||
|
||||
/// Returns a list of [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<SharedSpaceEmailInvitation>(
|
||||
where: where?.call(SharedSpaceEmailInvitation.t),
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [SharedSpaceEmailInvitation] 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<SharedSpaceEmailInvitation?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<SharedSpaceEmailInvitation>(
|
||||
where: where?.call(SharedSpaceEmailInvitation.t),
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [SharedSpaceEmailInvitation] by its [id] or null if no such row exists.
|
||||
Future<SharedSpaceEmailInvitation?> findById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<SharedSpaceEmailInvitation>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [SharedSpaceEmailInvitation]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<SharedSpaceEmailInvitation> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<SharedSpaceEmailInvitation>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [SharedSpaceEmailInvitation] and returns the inserted row.
|
||||
///
|
||||
/// The returned [SharedSpaceEmailInvitation] will have its `id` field set.
|
||||
Future<SharedSpaceEmailInvitation> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
SharedSpaceEmailInvitation row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<SharedSpaceEmailInvitation>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [SharedSpaceEmailInvitation]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 [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<SharedSpaceEmailInvitation> rows, {
|
||||
required _is.ColumnSelections<SharedSpaceEmailInvitationTable>
|
||||
conflictColumns,
|
||||
_is.ColumnSelections<SharedSpaceEmailInvitationTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<SharedSpaceEmailInvitation>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(SharedSpaceEmailInvitation.t),
|
||||
updateColumns: updateColumns?.call(SharedSpaceEmailInvitation.t),
|
||||
updateWhere: updateWhere?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [SharedSpaceEmailInvitation] 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 [SharedSpaceEmailInvitation] will have its `id` field set.
|
||||
Future<SharedSpaceEmailInvitation?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
SharedSpaceEmailInvitation row, {
|
||||
required _is.ColumnSelections<SharedSpaceEmailInvitationTable>
|
||||
conflictColumns,
|
||||
_is.ColumnSelections<SharedSpaceEmailInvitationTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<SharedSpaceEmailInvitation>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(SharedSpaceEmailInvitation.t),
|
||||
updateColumns: updateColumns?.call(SharedSpaceEmailInvitation.t),
|
||||
updateWhere: updateWhere?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<SharedSpaceEmailInvitation> rows, {
|
||||
_is.ColumnSelections<SharedSpaceEmailInvitationTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<SharedSpaceEmailInvitation>(
|
||||
rows,
|
||||
columns: columns?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SharedSpaceEmailInvitation]. 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<SharedSpaceEmailInvitation> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
SharedSpaceEmailInvitation row, {
|
||||
_is.ColumnSelections<SharedSpaceEmailInvitationTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<SharedSpaceEmailInvitation>(
|
||||
row,
|
||||
columns: columns?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [SharedSpaceEmailInvitation] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<SharedSpaceEmailInvitation?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
required _is.ColumnValueListBuilder<SharedSpaceEmailInvitationUpdateTable>
|
||||
columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<SharedSpaceEmailInvitation>(
|
||||
id,
|
||||
columnValues: columnValues(SharedSpaceEmailInvitation.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<SharedSpaceEmailInvitationUpdateTable>
|
||||
columnValues,
|
||||
required _is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<SharedSpaceEmailInvitation>(
|
||||
columnValues: columnValues(SharedSpaceEmailInvitation.t.updateTable),
|
||||
where: where(SharedSpaceEmailInvitation.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [SharedSpaceEmailInvitation]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<SharedSpaceEmailInvitation>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<SharedSpaceEmailInvitation> rows, {
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<SharedSpaceEmailInvitation>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [SharedSpaceEmailInvitation].
|
||||
Future<SharedSpaceEmailInvitation> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
SharedSpaceEmailInvitation row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<SharedSpaceEmailInvitation>(
|
||||
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<SharedSpaceEmailInvitation>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable> where,
|
||||
_is.OrderByBuilder<SharedSpaceEmailInvitationTable>? orderBy,
|
||||
_is.OrderByListBuilder<SharedSpaceEmailInvitationTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<SharedSpaceEmailInvitation>(
|
||||
where: where(SharedSpaceEmailInvitation.t),
|
||||
orderBy: orderBy?.call(SharedSpaceEmailInvitation.t),
|
||||
orderByList: orderByList?.call(SharedSpaceEmailInvitation.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<SharedSpaceEmailInvitationTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<SharedSpaceEmailInvitation>(
|
||||
where: where?.call(SharedSpaceEmailInvitation.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [SharedSpaceEmailInvitation] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<SharedSpaceEmailInvitationTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<SharedSpaceEmailInvitation>(
|
||||
where: where(SharedSpaceEmailInvitation.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,13 +21,17 @@ abstract class SharedSpaceInfo
|
|||
required this.kind,
|
||||
required this.role,
|
||||
required this.createdAt,
|
||||
});
|
||||
bool? isNew,
|
||||
this.sharedBy,
|
||||
}) : isNew = isNew ?? false;
|
||||
|
||||
factory SharedSpaceInfo({
|
||||
required _is.UuidValue id,
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required DateTime createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
}) = _SharedSpaceInfoImpl;
|
||||
|
||||
factory SharedSpaceInfo.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
|
|
@ -40,6 +44,10 @@ abstract class SharedSpaceInfo
|
|||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
isNew: jsonSerialization['isNew'] == null
|
||||
? null
|
||||
: _is.BoolJsonExtension.fromJson(jsonSerialization['isNew']),
|
||||
sharedBy: jsonSerialization['sharedBy'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +62,14 @@ abstract class SharedSpaceInfo
|
|||
|
||||
DateTime createdAt;
|
||||
|
||||
/// Whether the caller hasn't seen it since someone else made them a
|
||||
/// member (D40).
|
||||
bool isNew;
|
||||
|
||||
/// The handle of whoever made the caller a member, while it [isNew];
|
||||
/// null without a public profile, or otherwise.
|
||||
String? sharedBy;
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
|
|
@ -62,6 +78,8 @@ abstract class SharedSpaceInfo
|
|||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
DateTime? createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -71,6 +89,8 @@ abstract class SharedSpaceInfo
|
|||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
'isNew': isNew,
|
||||
if (sharedBy != null) 'sharedBy': sharedBy,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +102,8 @@ abstract class SharedSpaceInfo
|
|||
'kind': kind,
|
||||
'role': role.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
'isNew': isNew,
|
||||
if (sharedBy != null) 'sharedBy': sharedBy,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -91,17 +113,23 @@ abstract class SharedSpaceInfo
|
|||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _SharedSpaceInfoImpl extends SharedSpaceInfo {
|
||||
_SharedSpaceInfoImpl({
|
||||
required _is.UuidValue id,
|
||||
required String kind,
|
||||
required _igsxp9el.SharedSpaceRole role,
|
||||
required DateTime createdAt,
|
||||
bool? isNew,
|
||||
String? sharedBy,
|
||||
}) : super._(
|
||||
id: id,
|
||||
kind: kind,
|
||||
role: role,
|
||||
createdAt: createdAt,
|
||||
isNew: isNew,
|
||||
sharedBy: sharedBy,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SharedSpaceInfo]
|
||||
|
|
@ -113,12 +141,16 @@ class _SharedSpaceInfoImpl extends SharedSpaceInfo {
|
|||
String? kind,
|
||||
_igsxp9el.SharedSpaceRole? role,
|
||||
DateTime? createdAt,
|
||||
bool? isNew,
|
||||
Object? sharedBy = _Undefined,
|
||||
}) {
|
||||
return SharedSpaceInfo(
|
||||
id: id ?? this.id,
|
||||
kind: kind ?? this.kind,
|
||||
role: role ?? this.role,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
isNew: isNew ?? this.isNew,
|
||||
sharedBy: sharedBy is String? ? sharedBy : this.sharedBy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue