refactor(auth)!: sign-in and the account come from halkyon_base
Sign-in (anonymous, GitHub, email codes), an account's ways in and its deletion, the pruning and the instance's basics move into the halkyon_base module (ADR 0014), which every Halkyon server includes. Learn configures it in server.dart and deletes its own data with a person through the module's hook, in the order it did. The identity tables move unchanged: the migration records versions only. The app calls the module's endpoints; instance.info() stays as it was. BREAKING CHANGE: the sign-in endpoints moved into the module, so apps up to build 20 can no longer sign in or start; minimumAppBuild is 21. Deploy this only together with the release of build 21. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f43b92e4db
commit
a463f24444
101 changed files with 28050 additions and 6268 deletions
16
AGENTS.md
16
AGENTS.md
File diff suppressed because one or more lines are too long
86
docs/adr/0014-halkyon-base-module.md
Normal file
86
docs/adr/0014-halkyon-base-module.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# ADR 0014: What every Halkyon server has, as a Serverpod module of its own
|
||||
|
||||
- **Status:** accepted
|
||||
- **Date:** 2026-10-19
|
||||
|
||||
## Context
|
||||
|
||||
Halkyon is to have other services beside Learn (a to-do app, a training app), each a
|
||||
Flutter app with a Serverpod server of its own, run by whoever runs an instance. Their
|
||||
look already comes from `halkyon_ui` in halkyon_shared. What each of their servers needs
|
||||
besides its own features is what Learn's has built since September: signing in
|
||||
anonymously, with GitHub and with a code by mail (ADR 0010), the account's ways in and its
|
||||
deletion in ADR 0001 rule 6's order, the pruning of what nobody can return to, and what an
|
||||
instance tells its apps before anyone signs in (its sign-in ways, its source, the oldest
|
||||
app build it works with, where it keeps people's data).
|
||||
|
||||
The app's side of signing in (its gateway, sessions, the start screen) can only be shared
|
||||
once these endpoints are shared: an app's generated client calls its own server's
|
||||
endpoints, under their names. The developer chose to share the server side first (19 Oct),
|
||||
rather than letting each app's server copy Learn's.
|
||||
|
||||
Serverpod's way of sharing server code with endpoints, models and tables is a module: a
|
||||
server package and a generated client package, which a host project names in its
|
||||
`generator.yaml` and reaches as `client.modules.<nickname>`. Learn already runs
|
||||
Serverpod's own modules (`serverpod_auth_idp`, `serverpod_auth_core`) and the sync
|
||||
engine's.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **`halkyon_base`, a Serverpod module in halkyon_shared.** `halkyon_base_server` and
|
||||
`halkyon_base_client`, nickname `base`, pinned by git to a signed commit of
|
||||
halkyon_shared's main as Learn pins the sync fork. It holds the anonymous, GitHub and
|
||||
email sign-ins with their endpoints, models and tables (`email_identity`,
|
||||
`email_code_request`), the scopes and `grantIdentified`, the code mails, the account's
|
||||
endpoint (`delete`, `signInWays`), `deletePerson`, the pruning functions, and the
|
||||
instance's basics (`instance.basics()`, `InstanceBasics`).
|
||||
2. **A host configures it in one place, before its pod starts.**
|
||||
`HalkyonBase.configure(pod, HalkyonBase(...))`: the app's name, which sign-in mails
|
||||
name; where the project publishes its source, which the instance names unless its
|
||||
operator sets `SOURCE_URL`; the oldest app build the server works with; and what the
|
||||
host deletes of a person who goes. Learn's is `learnBase()`
|
||||
(`lib/src/auth/learn_base.dart`), set up in `server.dart`.
|
||||
3. **A person's own data is the host's, deleted through a hook.** `deletePerson` ends the
|
||||
person's sessions first, then runs the host's deletions inside its transaction, then
|
||||
deletes their identities and the person; what must wait until the deletion holds
|
||||
(Learn's release repositories) runs after the commit. Learn's hook,
|
||||
`deleteLearnData`, deletes their space and its files, profile, discussions, published
|
||||
studysets, blocks, what they read, assistant connections and file allowance, as
|
||||
`deletePerson` did before. The pruning goes through the same deletion. Each host
|
||||
schedules the pruning in its own future call.
|
||||
4. **The tables move as they are.** Serverpod (checked with v4.0.4, in a throwaway
|
||||
project and module, then on Learn) matches tables by name alone when it compares a
|
||||
migration's definitions, and ignores which module owns them. So the moved models keep
|
||||
their class, table, column and index names, and Learn's migration
|
||||
(`20261019121406578-halkyon-base`, made with `create-migration --empty`) holds no SQL
|
||||
but the rows recording each module's version; its definition gives the two tables to
|
||||
`halkyon_base`. The rows stay where they are, and the password keys keep their names
|
||||
(`emailSignInPepper` among them), so every address's hash stays valid. The device's
|
||||
local migration creates the two tables, empty, as it holds every module's tables
|
||||
(Serverpod's own auth tables among them); nothing on a device reads them.
|
||||
5. **Released apps are told to update, rather than served by wrappers.** A module's
|
||||
endpoints are called under its nickname, and its models go over the wire under its
|
||||
name (`halkyon_base.EmailSignInRefusedException`), so Learn's released apps (up to
|
||||
v0.17.0, build 20) call endpoints that are gone and can't read the refusals. Learn
|
||||
keeps no wrappers: `minimumAppBuild` is 21, the first build on the module, and
|
||||
Learn's own `instance.info()` stays with every field it had, as released apps read
|
||||
`minimumAppBuild` there. Its other Learn fields (the assistant connector, Jellyfin)
|
||||
stay Learn's; the fields it shares with `InstanceBasics` are filled from the module.
|
||||
6. **Licences as Learn's.** The server package is under the SSPL, as Learn's server; the
|
||||
client package under the AGPL, as Learn's client and app; each combines with its
|
||||
counterpart in every app as Learn's own packages do.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The server with the module deploys only together with the release of build 21.**
|
||||
Until people update, released apps can't sign in, switch accounts or start afresh, and
|
||||
say the server needs a newer app. Not before 22 Oct 2026 either: the hackathon's judges
|
||||
use v0.17.0 until then.
|
||||
- Another Halkyon server gets all of this by naming the module and configuring it, and its
|
||||
app can share the app side of signing in (`halkyon_app`, next) on the module's client.
|
||||
- A change to sign-in, deletion or the instance's basics is made in halkyon_shared and
|
||||
reaches Learn by moving the pin; Learn's tests of its own deletion and its
|
||||
`instance.info()` set the module up as the server does (`useLearnBase`).
|
||||
- Serverpod's version moves in the module too, together with every other package.
|
||||
- A table holding a person's data in a host gets a line in that host's deletion hook;
|
||||
the module can't know it.
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "email_code_request",
|
||||
"dartName": "EmailCodeRequest",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addressHash",
|
||||
"fieldName": "addressHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "challengeId",
|
||||
"fieldName": "challengeId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "switchChallengeId",
|
||||
"fieldName": "switchChallengeId",
|
||||
"columnType": 7,
|
||||
"isNullable": true,
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "createdAt",
|
||||
"fieldName": "createdAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "email_code_request_fk_0",
|
||||
"columns": [
|
||||
"challengeId"
|
||||
],
|
||||
"referenceTable": "serverpod_auth_idp_secret_challenge",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "email_code_request_fk_1",
|
||||
"columns": [
|
||||
"switchChallengeId"
|
||||
],
|
||||
"referenceTable": "serverpod_auth_idp_secret_challenge",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "email_code_request_address_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "addressHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "email_identity",
|
||||
"dartName": "EmailIdentity",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"columnDefault": "serial",
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "addressHash",
|
||||
"fieldName": "addressHash",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "createdAt",
|
||||
"fieldName": "createdAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "email_identity_person_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "email_identity_address_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "addressHash"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ part '20261007205440868-card-buried-at/migration.dart';
|
|||
part '20261008165051407-pdf-position/migration.dart';
|
||||
part '20261009224606274-feedback/migration.dart';
|
||||
part '20261012162411835-studyset-deleted-at/migration.dart';
|
||||
part '20261019121406578-halkyon-base/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -32,6 +33,7 @@ class MigrationRegistry {
|
|||
_Migration20261008165051407_pdf_position(),
|
||||
_Migration20261009224606274_feedback(),
|
||||
_Migration20261012162411835_studyset_deleted_at(),
|
||||
_Migration20261019121406578_halkyon_base(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
/* 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;
|
||||
|
||||
/// A code accepted: the address is now the signed-in person's (or already
|
||||
/// was), or it is another person's and [switchToken] signs in as them.
|
||||
abstract class EmailCodeAccepted
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
EmailCodeAccepted._({this.switchToken});
|
||||
|
||||
factory EmailCodeAccepted({String? switchToken}) = _EmailCodeAcceptedImpl;
|
||||
|
||||
factory EmailCodeAccepted.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return EmailCodeAccepted(
|
||||
switchToken: jsonSerialization['switchToken'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Null when the address is the signed-in person's; else what signs in as
|
||||
/// the person who has it, once, within the code's 15 minutes
|
||||
/// (`EmailSignInEndpoint.signIn`).
|
||||
String? switchToken;
|
||||
|
||||
/// Returns a shallow copy of this [EmailCodeAccepted]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
EmailCodeAccepted copyWith({String? switchToken});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'EmailCodeAccepted',
|
||||
if (switchToken != null) 'switchToken': switchToken,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'EmailCodeAccepted',
|
||||
if (switchToken != null) 'switchToken': switchToken,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _EmailCodeAcceptedImpl extends EmailCodeAccepted {
|
||||
_EmailCodeAcceptedImpl({String? switchToken})
|
||||
: super._(switchToken: switchToken);
|
||||
|
||||
/// Returns a shallow copy of this [EmailCodeAccepted]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
EmailCodeAccepted copyWith({Object? switchToken = _Undefined}) {
|
||||
return EmailCodeAccepted(
|
||||
switchToken: switchToken is String? ? switchToken : this.switchToken,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/* 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;
|
||||
|
||||
/// Why mailing a code, accepting it, or signing in with an email address
|
||||
/// was refused.
|
||||
enum EmailSignInRefusal implements _isc.SerializableModel {
|
||||
/// This instance has no email sign-in (no emailSignInPepper in its
|
||||
/// passwords, or no way to send mail).
|
||||
unavailable,
|
||||
|
||||
/// What was typed isn't an email address.
|
||||
notAnAddress,
|
||||
|
||||
/// Too many codes asked for, for this address or from this network, in
|
||||
/// the last hour.
|
||||
tooManyCodes,
|
||||
|
||||
/// The code is wrong.
|
||||
wrongCode,
|
||||
|
||||
/// The code was typed wrong too often; a new one is needed.
|
||||
tooManyTries,
|
||||
|
||||
/// The code is no longer valid: past its time, used, or replaced by a
|
||||
/// newer one.
|
||||
codeExpired,
|
||||
|
||||
/// The person already signs in with another address.
|
||||
otherAddressLinked,
|
||||
|
||||
/// The mail couldn't be sent; nothing was kept of the request.
|
||||
mailFailed;
|
||||
|
||||
static EmailSignInRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'unavailable':
|
||||
return EmailSignInRefusal.unavailable;
|
||||
case 'notAnAddress':
|
||||
return EmailSignInRefusal.notAnAddress;
|
||||
case 'tooManyCodes':
|
||||
return EmailSignInRefusal.tooManyCodes;
|
||||
case 'wrongCode':
|
||||
return EmailSignInRefusal.wrongCode;
|
||||
case 'tooManyTries':
|
||||
return EmailSignInRefusal.tooManyTries;
|
||||
case 'codeExpired':
|
||||
return EmailSignInRefusal.codeExpired;
|
||||
case 'otherAddressLinked':
|
||||
return EmailSignInRefusal.otherAddressLinked;
|
||||
case 'mailFailed':
|
||||
return EmailSignInRefusal.mailFailed;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "EmailSignInRefusal"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/* 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 '../auth/email_sign_in_refusal.dart' as _in9x16lz;
|
||||
|
||||
/// Mailing a code, accepting it, or signing in with an email address was
|
||||
/// refused; nothing changed.
|
||||
abstract class EmailSignInRefusedException
|
||||
implements
|
||||
_isc.SerializableException,
|
||||
_isc.SerializableModel,
|
||||
_isc.ProtocolSerialization {
|
||||
EmailSignInRefusedException._({required this.reason});
|
||||
|
||||
factory EmailSignInRefusedException({
|
||||
required _in9x16lz.EmailSignInRefusal reason,
|
||||
}) = _EmailSignInRefusedExceptionImpl;
|
||||
|
||||
factory EmailSignInRefusedException.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return EmailSignInRefusedException(
|
||||
reason: _in9x16lz.EmailSignInRefusal.fromJson(
|
||||
(jsonSerialization['reason'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Why it was refused.
|
||||
_in9x16lz.EmailSignInRefusal reason;
|
||||
|
||||
/// Returns a shallow copy of this [EmailSignInRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
EmailSignInRefusedException copyWith({_in9x16lz.EmailSignInRefusal? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'EmailSignInRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'EmailSignInRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'EmailSignInRefusedException(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _EmailSignInRefusedExceptionImpl extends EmailSignInRefusedException {
|
||||
_EmailSignInRefusedExceptionImpl({
|
||||
required _in9x16lz.EmailSignInRefusal reason,
|
||||
}) : super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [EmailSignInRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
EmailSignInRefusedException copyWith({_in9x16lz.EmailSignInRefusal? reason}) {
|
||||
return EmailSignInRefusedException(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/* 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;
|
||||
|
||||
/// Why signing in with GitHub, or linking a GitHub account, was refused.
|
||||
enum GitHubSignInRefusal implements _isc.SerializableModel {
|
||||
/// This instance has no GitHub OAuth app (no githubClientId and
|
||||
/// githubClientSecret in its passwords).
|
||||
unavailable,
|
||||
|
||||
/// The GitHub account already belongs to another person.
|
||||
takenByAnotherPerson,
|
||||
|
||||
/// The person already has a different GitHub account.
|
||||
otherGitHubAccountLinked,
|
||||
|
||||
/// No person has this GitHub account; only linking one creates it.
|
||||
unknownAccount;
|
||||
|
||||
static GitHubSignInRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'unavailable':
|
||||
return GitHubSignInRefusal.unavailable;
|
||||
case 'takenByAnotherPerson':
|
||||
return GitHubSignInRefusal.takenByAnotherPerson;
|
||||
case 'otherGitHubAccountLinked':
|
||||
return GitHubSignInRefusal.otherGitHubAccountLinked;
|
||||
case 'unknownAccount':
|
||||
return GitHubSignInRefusal.unknownAccount;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "GitHubSignInRefusal"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/* 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 '../auth/github_sign_in_refusal.dart' as _irgbwy1y;
|
||||
|
||||
/// Signing in with GitHub, or linking a GitHub account, was refused; nothing
|
||||
/// changed.
|
||||
abstract class GitHubSignInRefusedException
|
||||
implements
|
||||
_isc.SerializableException,
|
||||
_isc.SerializableModel,
|
||||
_isc.ProtocolSerialization {
|
||||
GitHubSignInRefusedException._({required this.reason});
|
||||
|
||||
factory GitHubSignInRefusedException({
|
||||
required _irgbwy1y.GitHubSignInRefusal reason,
|
||||
}) = _GitHubSignInRefusedExceptionImpl;
|
||||
|
||||
factory GitHubSignInRefusedException.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return GitHubSignInRefusedException(
|
||||
reason: _irgbwy1y.GitHubSignInRefusal.fromJson(
|
||||
(jsonSerialization['reason'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Why it was refused.
|
||||
_irgbwy1y.GitHubSignInRefusal reason;
|
||||
|
||||
/// Returns a shallow copy of this [GitHubSignInRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
GitHubSignInRefusedException copyWith({
|
||||
_irgbwy1y.GitHubSignInRefusal? reason,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'GitHubSignInRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'GitHubSignInRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'GitHubSignInRefusedException(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _GitHubSignInRefusedExceptionImpl extends GitHubSignInRefusedException {
|
||||
_GitHubSignInRefusedExceptionImpl({
|
||||
required _irgbwy1y.GitHubSignInRefusal reason,
|
||||
}) : super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [GitHubSignInRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
GitHubSignInRefusedException copyWith({
|
||||
_irgbwy1y.GitHubSignInRefusal? reason,
|
||||
}) {
|
||||
return GitHubSignInRefusedException(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/* 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;
|
||||
|
||||
/// The ways a person signs in, besides an anonymous account's session.
|
||||
abstract class SignInWays
|
||||
implements _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
SignInWays._({
|
||||
required this.gitHub,
|
||||
required this.email,
|
||||
});
|
||||
|
||||
factory SignInWays({
|
||||
required bool gitHub,
|
||||
required bool email,
|
||||
}) = _SignInWaysImpl;
|
||||
|
||||
factory SignInWays.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return SignInWays(
|
||||
gitHub: _isc.BoolJsonExtension.fromJson(jsonSerialization['gitHub']),
|
||||
email: _isc.BoolJsonExtension.fromJson(jsonSerialization['email']),
|
||||
);
|
||||
}
|
||||
|
||||
bool gitHub;
|
||||
|
||||
bool email;
|
||||
|
||||
/// Returns a shallow copy of this [SignInWays]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
SignInWays copyWith({
|
||||
bool? gitHub,
|
||||
bool? email,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'SignInWays',
|
||||
'gitHub': gitHub,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'SignInWays',
|
||||
'gitHub': gitHub,
|
||||
'email': email,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _SignInWaysImpl extends SignInWays {
|
||||
_SignInWaysImpl({
|
||||
required bool gitHub,
|
||||
required bool email,
|
||||
}) : super._(
|
||||
gitHub: gitHub,
|
||||
email: email,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [SignInWays]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
SignInWays copyWith({
|
||||
bool? gitHub,
|
||||
bool? email,
|
||||
}) {
|
||||
return SignInWays(
|
||||
gitHub: gitHub ?? this.gitHub,
|
||||
email: email ?? this.email,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:async' as _ida;
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_learn_client/src/protocol/asset/file_usage.dart'
|
||||
as _iimptq0q;
|
||||
import 'package:halkyon_learn_client/src/protocol/assistant/assistant_code.dart'
|
||||
|
|
@ -20,10 +21,6 @@ import 'package:halkyon_learn_client/src/protocol/assistant/assistant_connection
|
|||
as _iplrdti2;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/device.dart'
|
||||
as _ieqh4yr9;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/email_code_accepted.dart'
|
||||
as _ivlnmdt4;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/sign_in_ways.dart'
|
||||
as _ixy30i05;
|
||||
import 'package:halkyon_learn_client/src/protocol/dictionary/device_dictionary_file.dart'
|
||||
as _ijp9kwtx;
|
||||
import 'package:halkyon_learn_client/src/protocol/dictionary/dictionary_hit.dart'
|
||||
|
|
@ -170,56 +167,6 @@ class EndpointAssistant extends _isc.EndpointRef {
|
|||
);
|
||||
}
|
||||
|
||||
/// The signed-in person's account.
|
||||
/// {@category Endpoint}
|
||||
class EndpointAccount extends _isc.EndpointRef {
|
||||
EndpointAccount(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'account';
|
||||
|
||||
/// Deletes the caller with everything they have (see [deletePerson]). The
|
||||
/// app also calls it when an anonymous account is given up (decided
|
||||
/// 2026-09-30). Their other devices learn at once that their sessions
|
||||
/// ended.
|
||||
_ida.Future<void> delete() => caller.callServerEndpoint<void>(
|
||||
'account',
|
||||
'delete',
|
||||
{},
|
||||
);
|
||||
|
||||
/// How the caller signs in besides their sessions: with GitHub, with an
|
||||
/// email address, both, or neither (anonymous).
|
||||
_ida.Future<_ixy30i05.SignInWays> signInWays() =>
|
||||
caller.callServerEndpoint<_ixy30i05.SignInWays>(
|
||||
'account',
|
||||
'signInWays',
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
/// Signs people in anonymously; the app calls it on first launch. Creating an
|
||||
/// account is rate-limited per IP address (see `auth_config.dart`).
|
||||
/// {@category Endpoint}
|
||||
class EndpointAnonymousIdp extends _iaic.EndpointAnonymousIdpBase {
|
||||
EndpointAnonymousIdp(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'anonymousIdp';
|
||||
|
||||
/// Creates a new anonymous account and returns its session.
|
||||
///
|
||||
/// Invokes the [AnonymousIdp.beforeAnonymousAccount] callback if configured,
|
||||
/// which may prevent account creation if the endpoint is protected.
|
||||
@override
|
||||
_ida.Future<_iacc.AuthSuccess> login({String? token}) =>
|
||||
caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||
'anonymousIdp',
|
||||
'login',
|
||||
{'token': token},
|
||||
);
|
||||
}
|
||||
|
||||
/// Your devices: the sessions you're signed in with, and signing one of them
|
||||
/// out, for example a lost phone.
|
||||
///
|
||||
|
|
@ -252,121 +199,6 @@ class EndpointDevices extends _isc.EndpointRef {
|
|||
);
|
||||
}
|
||||
|
||||
/// Signing in with an email address, by a code mailed to it (ADR 0010):
|
||||
/// the way to keep an anonymous account, as linking GitHub is, and a second
|
||||
/// way in for someone who signs in with GitHub. Signed in only, as every
|
||||
/// device is from its first start.
|
||||
/// {@category Endpoint}
|
||||
class EndpointEmailCode extends _isc.EndpointRef {
|
||||
EndpointEmailCode(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'emailCode';
|
||||
|
||||
/// Mails a code to [address], worded in [language] (`en`, `de`), and
|
||||
/// returns the request it answers; refusals as in [sendSignInCode].
|
||||
_ida.Future<_isc.UuidValue> sendCode({
|
||||
required String address,
|
||||
required String language,
|
||||
}) => caller.callServerEndpoint<_isc.UuidValue>(
|
||||
'emailCode',
|
||||
'sendCode',
|
||||
{
|
||||
'address': address,
|
||||
'language': language,
|
||||
},
|
||||
);
|
||||
|
||||
/// Accepts [code] for [request]: the address becomes the caller's unless
|
||||
/// it is another person's, whose token then signs in as them
|
||||
/// (`EmailSignInEndpoint.signIn`); refusals as in [acceptSignInCode].
|
||||
_ida.Future<_ivlnmdt4.EmailCodeAccepted> verifyCode({
|
||||
required _isc.UuidValue request,
|
||||
required String code,
|
||||
}) => caller.callServerEndpoint<_ivlnmdt4.EmailCodeAccepted>(
|
||||
'emailCode',
|
||||
'verifyCode',
|
||||
{
|
||||
'request': request,
|
||||
'code': code,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Signs in the person whose address a mailed code proved, when the device
|
||||
/// switches to them. Called without a session: Serverpod issues no session
|
||||
/// for another person inside a signed-in call.
|
||||
/// {@category Endpoint}
|
||||
class EndpointEmailSignIn extends _isc.EndpointRef {
|
||||
EndpointEmailSignIn(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'emailSignIn';
|
||||
|
||||
/// A session for the person [switchToken] names; refusals as in
|
||||
/// [signInWithEmail].
|
||||
_ida.Future<_iacc.AuthSuccess> signIn({required String switchToken}) =>
|
||||
caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||
'emailSignIn',
|
||||
'signIn',
|
||||
{'switchToken': switchToken},
|
||||
);
|
||||
}
|
||||
|
||||
/// Signs in the person who already has a GitHub account, e.g. when they switch
|
||||
/// to it on another device. Called without a session: Serverpod issues no
|
||||
/// session for another person inside a signed-in call.
|
||||
/// {@category Endpoint}
|
||||
class EndpointGitHubSignIn extends _isc.EndpointRef {
|
||||
EndpointGitHubSignIn(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'gitHubSignIn';
|
||||
|
||||
/// A session for the person with the GitHub account that [code] proves;
|
||||
/// refusals as in [signInWithGitHubAccount].
|
||||
_ida.Future<_iacc.AuthSuccess> signIn({
|
||||
required String code,
|
||||
required String codeVerifier,
|
||||
required String redirectUri,
|
||||
}) => caller.callServerEndpoint<_iacc.AuthSuccess>(
|
||||
'gitHubSignIn',
|
||||
'signIn',
|
||||
{
|
||||
'code': code,
|
||||
'codeVerifier': codeVerifier,
|
||||
'redirectUri': redirectUri,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Links a GitHub account to the signed-in person: the lossless upgrade from
|
||||
/// an anonymous account. The person, their spaces and their studysets stay
|
||||
/// where they are; only the way they sign in changes.
|
||||
/// {@category Endpoint}
|
||||
class EndpointGitHubUpgrade extends _isc.EndpointRef {
|
||||
EndpointGitHubUpgrade(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'gitHubUpgrade';
|
||||
|
||||
/// Links the GitHub account that [code] proves to the caller; refusals as in
|
||||
/// [linkGitHubAccount].
|
||||
_ida.Future<void> upgrade({
|
||||
required String code,
|
||||
required String codeVerifier,
|
||||
required String redirectUri,
|
||||
}) => caller.callServerEndpoint<void>(
|
||||
'gitHubUpgrade',
|
||||
'upgrade',
|
||||
{
|
||||
'code': code,
|
||||
'codeVerifier': codeVerifier,
|
||||
'redirectUri': redirectUri,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// The dictionaries the instance holds, to look words up in and to take
|
||||
/// words from by a label (all JLPT N5 words). A dictionary is one source
|
||||
/// in one meaning language (`jmdict` in `en`). Reference data: any
|
||||
|
|
@ -918,6 +750,8 @@ class EndpointInbox extends _isc.EndpointRef {
|
|||
}
|
||||
|
||||
/// What this instance offers. Public: the app asks before anyone is signed in.
|
||||
/// Released apps read the oldest build the server works with here, so it
|
||||
/// keeps every field it had: halkyon_base's own basics fill those it shares.
|
||||
/// {@category Endpoint}
|
||||
class EndpointInstance extends _isc.EndpointRef {
|
||||
EndpointInstance(_isc.EndpointCaller caller) : super(caller);
|
||||
|
|
@ -1128,11 +962,14 @@ class EndpointPublish extends _isc.EndpointRef {
|
|||
|
||||
class Modules {
|
||||
Modules(Client client) {
|
||||
base = _ihk4p5t7.Caller(client);
|
||||
serverpod_auth_idp = _iaic.Caller(client);
|
||||
offline_sync = _ipulbpi2.Caller(client);
|
||||
serverpod_auth_core = _iacc.Caller(client);
|
||||
}
|
||||
|
||||
late final _ihk4p5t7.Caller base;
|
||||
|
||||
late final _iaic.Caller serverpod_auth_idp;
|
||||
|
||||
late final _ipulbpi2.Caller offline_sync;
|
||||
|
|
@ -1169,13 +1006,7 @@ class Client extends _isc.ServerpodClientShared {
|
|||
) {
|
||||
asset = EndpointAsset(this);
|
||||
assistant = EndpointAssistant(this);
|
||||
account = EndpointAccount(this);
|
||||
anonymousIdp = EndpointAnonymousIdp(this);
|
||||
devices = EndpointDevices(this);
|
||||
emailCode = EndpointEmailCode(this);
|
||||
emailSignIn = EndpointEmailSignIn(this);
|
||||
gitHubSignIn = EndpointGitHubSignIn(this);
|
||||
gitHubUpgrade = EndpointGitHubUpgrade(this);
|
||||
dictionary = EndpointDictionary(this);
|
||||
discussions = EndpointDiscussions(this);
|
||||
discussion = EndpointDiscussion(this);
|
||||
|
|
@ -1193,20 +1024,8 @@ class Client extends _isc.ServerpodClientShared {
|
|||
|
||||
late final EndpointAssistant assistant;
|
||||
|
||||
late final EndpointAccount account;
|
||||
|
||||
late final EndpointAnonymousIdp anonymousIdp;
|
||||
|
||||
late final EndpointDevices devices;
|
||||
|
||||
late final EndpointEmailCode emailCode;
|
||||
|
||||
late final EndpointEmailSignIn emailSignIn;
|
||||
|
||||
late final EndpointGitHubSignIn gitHubSignIn;
|
||||
|
||||
late final EndpointGitHubUpgrade gitHubUpgrade;
|
||||
|
||||
late final EndpointDictionary dictionary;
|
||||
|
||||
late final EndpointDiscussions discussions;
|
||||
|
|
@ -1233,13 +1052,7 @@ class Client extends _isc.ServerpodClientShared {
|
|||
Map<String, _isc.EndpointRef> get endpointRefLookup => {
|
||||
'asset': asset,
|
||||
'assistant': assistant,
|
||||
'account': account,
|
||||
'anonymousIdp': anonymousIdp,
|
||||
'devices': devices,
|
||||
'emailCode': emailCode,
|
||||
'emailSignIn': emailSignIn,
|
||||
'gitHubSignIn': gitHubSignIn,
|
||||
'gitHubUpgrade': gitHubUpgrade,
|
||||
'dictionary': dictionary,
|
||||
'discussions': discussions,
|
||||
'discussion': discussion,
|
||||
|
|
@ -1254,6 +1067,7 @@ class Client extends _isc.ServerpodClientShared {
|
|||
|
||||
@override
|
||||
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {
|
||||
'base': modules.base,
|
||||
'serverpod_auth_idp': modules.serverpod_auth_idp,
|
||||
'offline_sync': modules.offline_sync,
|
||||
'serverpod_auth_core': modules.serverpod_auth_core,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
// ignore_for_file: dead_code, unnecessary_type_check
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
|
||||
import 'package:halkyon_learn_client/src/protocol/assistant/assistant_connection_info.dart'
|
||||
as _iplrdti2;
|
||||
import 'package:halkyon_learn_client/src/protocol/auth/device.dart'
|
||||
|
|
@ -44,12 +45,6 @@ import 'asset/file_usage.dart' as _iurwo9k8;
|
|||
import 'assistant/assistant_code.dart' as _i8xi9jqo;
|
||||
import 'assistant/assistant_connection_info.dart' as _icukzjbz;
|
||||
import 'auth/device.dart' as _i6c5ny7n;
|
||||
import 'auth/email_code_accepted.dart' as _iys46ui1;
|
||||
import 'auth/email_sign_in_refusal.dart' as _ieyvcag1;
|
||||
import 'auth/email_sign_in_refused_exception.dart' as _iztfgag5;
|
||||
import 'auth/github_sign_in_refusal.dart' as _ir1gd9p5;
|
||||
import 'auth/github_sign_in_refused_exception.dart' as _i0xfcu5n;
|
||||
import 'auth/sign_in_ways.dart' as _ie6tbh85;
|
||||
import 'card/card.dart' as _ipiw1cmx;
|
||||
import 'card/card_revision.dart' as _ipbyuef1;
|
||||
import 'common/not_found_exception.dart' as _i8oee73b;
|
||||
|
|
@ -108,12 +103,6 @@ export 'asset/file_usage.dart';
|
|||
export 'assistant/assistant_code.dart';
|
||||
export 'assistant/assistant_connection_info.dart';
|
||||
export 'auth/device.dart';
|
||||
export 'auth/email_code_accepted.dart';
|
||||
export 'auth/email_sign_in_refusal.dart';
|
||||
export 'auth/email_sign_in_refused_exception.dart';
|
||||
export 'auth/github_sign_in_refusal.dart';
|
||||
export 'auth/github_sign_in_refused_exception.dart';
|
||||
export 'auth/sign_in_ways.dart';
|
||||
export 'card/card.dart';
|
||||
export 'card/card_revision.dart';
|
||||
export 'common/not_found_exception.dart';
|
||||
|
|
@ -2272,6 +2261,10 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
..._ihk4p5t7.Protocol() is _isd.DatabaseSerializationManager
|
||||
? (_ihk4p5t7.Protocol() as _isd.DatabaseSerializationManager)
|
||||
.getTargetTableDefinitions()
|
||||
: [],
|
||||
..._iaic.Protocol() is _isd.DatabaseSerializationManager
|
||||
? (_iaic.Protocol() as _isd.DatabaseSerializationManager)
|
||||
.getTargetTableDefinitions()
|
||||
|
|
@ -2334,24 +2327,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _i6c5ny7n.Device) {
|
||||
return _i6c5ny7n.Device.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iys46ui1.EmailCodeAccepted) {
|
||||
return _iys46ui1.EmailCodeAccepted.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ieyvcag1.EmailSignInRefusal) {
|
||||
return _ieyvcag1.EmailSignInRefusal.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iztfgag5.EmailSignInRefusedException) {
|
||||
return _iztfgag5.EmailSignInRefusedException.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ir1gd9p5.GitHubSignInRefusal) {
|
||||
return _ir1gd9p5.GitHubSignInRefusal.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i0xfcu5n.GitHubSignInRefusedException) {
|
||||
return _i0xfcu5n.GitHubSignInRefusedException.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ie6tbh85.SignInWays) {
|
||||
return _ie6tbh85.SignInWays.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ipiw1cmx.Card) {
|
||||
return _ipiw1cmx.Card.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -2534,35 +2509,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_i6c5ny7n.Device?>()) {
|
||||
return (data != null ? _i6c5ny7n.Device.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_iys46ui1.EmailCodeAccepted?>()) {
|
||||
return (data != null ? _iys46ui1.EmailCodeAccepted.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ieyvcag1.EmailSignInRefusal?>()) {
|
||||
return (data != null ? _ieyvcag1.EmailSignInRefusal.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_iztfgag5.EmailSignInRefusedException?>()) {
|
||||
return (data != null
|
||||
? _iztfgag5.EmailSignInRefusedException.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ir1gd9p5.GitHubSignInRefusal?>()) {
|
||||
return (data != null
|
||||
? _ir1gd9p5.GitHubSignInRefusal.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_i0xfcu5n.GitHubSignInRefusedException?>()) {
|
||||
return (data != null
|
||||
? _i0xfcu5n.GitHubSignInRefusedException.fromJson(data)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_ie6tbh85.SignInWays?>()) {
|
||||
return (data != null ? _ie6tbh85.SignInWays.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_ipiw1cmx.Card?>()) {
|
||||
return (data != null ? _ipiw1cmx.Card.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -2870,6 +2816,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
.toList()
|
||||
as T;
|
||||
}
|
||||
try {
|
||||
return _ihk4p5t7.Protocol().deserialize<T>(data, t);
|
||||
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _iaic.Protocol().deserialize<T>(data, t);
|
||||
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -2891,12 +2840,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_i8xi9jqo.AssistantCode => 'AssistantCode',
|
||||
_icukzjbz.AssistantConnectionInfo => 'AssistantConnectionInfo',
|
||||
_i6c5ny7n.Device => 'Device',
|
||||
_iys46ui1.EmailCodeAccepted => 'EmailCodeAccepted',
|
||||
_ieyvcag1.EmailSignInRefusal => 'EmailSignInRefusal',
|
||||
_iztfgag5.EmailSignInRefusedException => 'EmailSignInRefusedException',
|
||||
_ir1gd9p5.GitHubSignInRefusal => 'GitHubSignInRefusal',
|
||||
_i0xfcu5n.GitHubSignInRefusedException => 'GitHubSignInRefusedException',
|
||||
_ie6tbh85.SignInWays => 'SignInWays',
|
||||
_ipiw1cmx.Card => 'Card',
|
||||
_ipbyuef1.CardRevision => 'CardRevision',
|
||||
_i8oee73b.NotFoundException => 'NotFoundException',
|
||||
|
|
@ -2979,18 +2922,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'AssistantConnectionInfo';
|
||||
case _i6c5ny7n.Device():
|
||||
return 'Device';
|
||||
case _iys46ui1.EmailCodeAccepted():
|
||||
return 'EmailCodeAccepted';
|
||||
case _ieyvcag1.EmailSignInRefusal():
|
||||
return 'EmailSignInRefusal';
|
||||
case _iztfgag5.EmailSignInRefusedException():
|
||||
return 'EmailSignInRefusedException';
|
||||
case _ir1gd9p5.GitHubSignInRefusal():
|
||||
return 'GitHubSignInRefusal';
|
||||
case _i0xfcu5n.GitHubSignInRefusedException():
|
||||
return 'GitHubSignInRefusedException';
|
||||
case _ie6tbh85.SignInWays():
|
||||
return 'SignInWays';
|
||||
case _ipiw1cmx.Card():
|
||||
return 'Card';
|
||||
case _ipbyuef1.CardRevision():
|
||||
|
|
@ -3094,6 +3025,10 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
case _ie8t3ar4.Tag():
|
||||
return 'Tag';
|
||||
}
|
||||
className = _ihk4p5t7.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.') ? className : 'halkyon_base.$className';
|
||||
}
|
||||
className = _iaic.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.')
|
||||
|
|
@ -3142,24 +3077,6 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Device') {
|
||||
return deserialize<_i6c5ny7n.Device>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'EmailCodeAccepted') {
|
||||
return deserialize<_iys46ui1.EmailCodeAccepted>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'EmailSignInRefusal') {
|
||||
return deserialize<_ieyvcag1.EmailSignInRefusal>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'EmailSignInRefusedException') {
|
||||
return deserialize<_iztfgag5.EmailSignInRefusedException>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'GitHubSignInRefusal') {
|
||||
return deserialize<_ir1gd9p5.GitHubSignInRefusal>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'GitHubSignInRefusedException') {
|
||||
return deserialize<_i0xfcu5n.GitHubSignInRefusedException>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'SignInWays') {
|
||||
return deserialize<_ie6tbh85.SignInWays>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Card') {
|
||||
return deserialize<_ipiw1cmx.Card>(data['data']);
|
||||
}
|
||||
|
|
@ -3313,6 +3230,10 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'Tag') {
|
||||
return deserialize<_ie8t3ar4.Tag>(data['data']);
|
||||
}
|
||||
if (dataClassName.startsWith('halkyon_base.')) {
|
||||
data['className'] = dataClassName.substring(13);
|
||||
return _ihk4p5t7.Protocol().deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_auth_idp.')) {
|
||||
data['className'] = dataClassName.substring(19);
|
||||
return _iaic.Protocol().deserializeByClassName(data);
|
||||
|
|
@ -3329,6 +3250,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
}
|
||||
|
||||
void _registerHostProtocols() {
|
||||
_ihk4p5t7.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iaic.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_ipulbpi2.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iacc.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
|
|
@ -3336,6 +3258,15 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
|
||||
@override
|
||||
_isd.Table? getTableForType(Type t) {
|
||||
{
|
||||
var protocol = _ihk4p5t7.Protocol();
|
||||
var table = protocol is _isd.DatabaseSerializationManager
|
||||
? (protocol as _isd.DatabaseSerializationManager).getTableForType(t)
|
||||
: null;
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var protocol = _iaic.Protocol();
|
||||
var table = protocol is _isd.DatabaseSerializationManager
|
||||
|
|
@ -3426,6 +3357,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return _ihk4p5t7.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _iaic.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,12 @@ environment:
|
|||
resolution: workspace
|
||||
|
||||
dependencies:
|
||||
# The client side of halkyon_base (ADR 0014), as the server's.
|
||||
halkyon_base_client:
|
||||
git:
|
||||
url: https://code.halkyon.app/project-halkyon/halkyon_shared.git
|
||||
path: packages/halkyon_base_client
|
||||
ref: af1de799f402ef76ad4513052c3e0008155a92fb
|
||||
serverpod_auth_idp_client: 4.0.4
|
||||
serverpod_client: 4.0.4
|
||||
serverpod_offline_sync_client: 0.0.10
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'dart:async';
|
|||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:halkyon_learn_client/halkyon_learn_client.dart'
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart'
|
||||
show GitHubSignInRefusal, GitHubSignInRefusedException;
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_repository.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/signed_out_mark.dart';
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import 'dart:developer';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_web_auth_2_platform_interface/flutter_web_auth_2_platform_interface.dart';
|
||||
import 'package:halkyon_learn_client/halkyon_learn_client.dart'
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart'
|
||||
show
|
||||
Client,
|
||||
EmailSignInRefusal,
|
||||
EmailSignInRefusedException,
|
||||
GitHubSignInRefusal,
|
||||
GitHubSignInRefusedException;
|
||||
import 'package:halkyon_learn_client/halkyon_learn_client.dart' show Client;
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/auth_gateway.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/data/loopback_web_auth.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/auth/model/auth_status.dart';
|
||||
|
|
@ -19,7 +19,8 @@ import 'package:halkyon_learn_flutter/src/feature/auth/model/sign_in_ways.dart';
|
|||
import 'package:serverpod_auth_idp_flutter/serverpod_auth_idp_flutter.dart';
|
||||
|
||||
/// [AuthGateway] on top of the Serverpod client and its session manager,
|
||||
/// which keeps the session on the device (secure storage).
|
||||
/// which keeps the session on the device (secure storage). Signing in and the
|
||||
/// account are halkyon_base's (ADR 0014): `client.modules.base`.
|
||||
class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
||||
this {
|
||||
_client.auth.authInfoListenable.addListener(_onAuthInfoChanged);
|
||||
|
|
@ -69,7 +70,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
|
||||
@override
|
||||
Future<void> signInAnonymously() async {
|
||||
final authSuccess = await _client.anonymousIdp.login();
|
||||
final authSuccess = await _client.modules.base.anonymousIdp.login();
|
||||
await _client.auth.updateSignedInUser(authSuccess);
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +81,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
|
||||
@override
|
||||
Future<void> deleteAccount() async {
|
||||
await _client.account.delete();
|
||||
await _client.modules.base.account.delete();
|
||||
// The server ended every session of the person; forget this device's
|
||||
// copy.
|
||||
await _client.auth.updateSignedInUser(null);
|
||||
|
|
@ -97,7 +98,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
return GitHubLinkOutcome.cancelled;
|
||||
}
|
||||
try {
|
||||
await _client.gitHubUpgrade.upgrade(
|
||||
await _client.modules.base.gitHubUpgrade.upgrade(
|
||||
code: authorization.code,
|
||||
codeVerifier: authorization.codeVerifier,
|
||||
redirectUri: authorization.redirectUri,
|
||||
|
|
@ -135,7 +136,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
final signedOut = Client(_client.host);
|
||||
final AuthSuccess person;
|
||||
try {
|
||||
person = await signedOut.gitHubSignIn.signIn(
|
||||
person = await signedOut.modules.base.gitHubSignIn.signIn(
|
||||
code: authorization.code,
|
||||
codeVerifier: authorization.codeVerifier,
|
||||
redirectUri: authorization.redirectUri,
|
||||
|
|
@ -217,7 +218,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
String address, {
|
||||
required String language,
|
||||
}) => _asEmailFailure(() async {
|
||||
final request = await _client.emailCode.sendCode(
|
||||
final request = await _client.modules.base.emailCode.sendCode(
|
||||
address: address,
|
||||
language: language,
|
||||
);
|
||||
|
|
@ -227,7 +228,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
@override
|
||||
Future<EmailCodeOutcome> verifyEmailCode(String request, String code) =>
|
||||
_asEmailFailure(() async {
|
||||
final accepted = await _client.emailCode.verifyCode(
|
||||
final accepted = await _client.modules.base.emailCode.verifyCode(
|
||||
request: UuidValue.withValidation(request),
|
||||
code: code,
|
||||
);
|
||||
|
|
@ -244,7 +245,9 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
final signedOut = Client(_client.host);
|
||||
final AuthSuccess person;
|
||||
try {
|
||||
person = await signedOut.emailSignIn.signIn(switchToken: switchToken);
|
||||
person = await signedOut.modules.base.emailSignIn.signIn(
|
||||
switchToken: switchToken,
|
||||
);
|
||||
} finally {
|
||||
signedOut.close();
|
||||
}
|
||||
|
|
@ -254,7 +257,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
|
||||
@override
|
||||
Future<SignInWays> signInWays() async {
|
||||
final ways = await _client.account.signInWays();
|
||||
final ways = await _client.modules.base.account.signInWays();
|
||||
return SignInWays(gitHub: ways.gitHub, email: ways.email);
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +291,7 @@ class ServerpodAuthGateway(final Client _client) implements AuthGateway {
|
|||
final client = Client(_client.host)..authSessionManager = sessionManager;
|
||||
try {
|
||||
await sessionManager.updateSignedInUser(anonymous);
|
||||
await client.account.delete();
|
||||
await client.modules.base.account.delete();
|
||||
} on Exception catch (error, st) {
|
||||
log(
|
||||
'Deleting the anonymous account left behind failed',
|
||||
|
|
|
|||
|
|
@ -73,6 +73,14 @@ dependencies:
|
|||
# schedules.
|
||||
fsrs: 2.0.1
|
||||
go_router: ^18.0.2
|
||||
# The client side of halkyon_base (ADR 0014): what the app calls to sign in
|
||||
# and to manage the account, and the refusals it reads. As the server's,
|
||||
# pinned to a signed commit of halkyon_shared's main.
|
||||
halkyon_base_client:
|
||||
git:
|
||||
url: https://code.halkyon.app/project-halkyon/halkyon_shared.git
|
||||
path: packages/halkyon_base_client
|
||||
ref: af1de799f402ef76ad4513052c3e0008155a92fb
|
||||
halkyon_learn_client:
|
||||
path: ../halkyon_learn_client
|
||||
# Halkyon's look, shared by its apps (halkyon_shared): the theme, the bundled Noto
|
||||
|
|
@ -83,7 +91,7 @@ dependencies:
|
|||
git:
|
||||
url: https://code.halkyon.app/project-halkyon/halkyon_shared.git
|
||||
path: packages/halkyon_ui
|
||||
ref: 80fa7b96286b7a00791c4b7ea42af408f4ea548a
|
||||
ref: af1de799f402ef76ad4513052c3e0008155a92fb
|
||||
# Anki's fields are HTML; the editor brings this parser along already.
|
||||
html: ^0.15.6
|
||||
# Talks to Jellyfin servers; Serverpod's client brings it along already.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const _serverUrl = 'http://localhost:8080/';
|
|||
Future<AuthSuccess> signInAnonymously() async {
|
||||
final client = protocol.Client(_serverUrl);
|
||||
try {
|
||||
return await client.anonymousIdp.login();
|
||||
return await client.modules.base.anonymousIdp.login();
|
||||
} finally {
|
||||
client.close();
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue