feat(meditate): the operator's library on the server, and a course's lessons done synced

An operator's folder (MEDITATE_LIBRARY_DIR) of recordings, pictures and a
YAML manifest, library.yaml (commented sample: library.example.yaml), read
as the server starts and every ten minutes, held in memory. Items without
a licence or credit, files outside the folder, unknown categories and
courses pointing at items not served are refused and logged; a manifest
that doesn't read keeps the library served before. bin/check_library.dart
checks a folder before deploying.

The catalogue goes to signed-in people alone (LibraryEndpoint), with a
12-hour access the server signs; the web server's /library/<served name>
serves the files to that access only, by ranges (relic's StaticHandler).
No request path reaches the file system.

lesson_done: a lesson done, sealed and synced, a row each; the migration
creates the table with its spaceId index and the cascading foreign key to
the sync engine's spaces, as dream's did. ADR 0038, D16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 15:36:11 +01:00
commit 929974443d
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
55 changed files with 22541 additions and 7 deletions

View file

@ -0,0 +1,447 @@
{
"__className__": "serverpod.DatabaseDefinition",
"schemaVersion": 2,
"moduleName": "halkyon_meditate",
"tables": [
{
"__className__": "serverpod.TableDefinition",
"name": "content_key",
"dartName": "ContentKey",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "dataKeyId",
"fieldName": "dataKeyId",
"columnType": 0,
"isNullable": false,
"dartType": "String"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "sealedKey",
"fieldName": "sealedKey",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "content_key_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "content_key_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "dream",
"dartName": "Dream",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "dream_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "dream_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "lesson_done",
"dartName": "LessonDone",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "lesson_done_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "lesson_done_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "meditate_settings",
"dartName": "MeditateSettings",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "meditate_settings_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "meditate_settings_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
},
{
"__className__": "serverpod.TableDefinition",
"name": "sitting",
"dartName": "Sitting",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "sitting_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "sitting_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
],
"installedModules": [
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_base",
"version": "20261026140329453-assistant-budgets"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod",
"version": "20260824182259319"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_idp",
"version": "20260924105404509"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_offline_sync",
"version": "20260914143806119"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "halkyon_oidc",
"version": "20261023180644988"
},
{
"__className__": "serverpod.DatabaseMigrationVersion",
"module": "serverpod_auth_core",
"version": "20260924105232991"
}
],
"migrationApiVersion": 1
}

View file

@ -0,0 +1,94 @@
{
"__className__": "serverpod.DatabaseMigration",
"actions": [
{
"__className__": "serverpod.DatabaseMigrationAction",
"type": "createTable",
"createTable": {
"__className__": "serverpod.TableDefinition",
"name": "lesson_done",
"dartName": "LessonDone",
"module": "halkyon_meditate",
"schema": "public",
"columns": [
{
"__className__": "serverpod.ColumnDefinition",
"name": "id",
"fieldName": "id",
"columnType": 7,
"isNullable": false,
"columnDefault": "random_v7",
"dartType": "UuidValue?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "spaceId",
"fieldName": "spaceId",
"columnType": 6,
"isNullable": true,
"dartType": "int?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "keyId",
"fieldName": "keyId",
"columnType": 0,
"isNullable": true,
"dartType": "String?"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "payload",
"fieldName": "payload",
"columnType": 5,
"isNullable": false,
"dartType": "dart:typed_data:ByteData"
},
{
"__className__": "serverpod.ColumnDefinition",
"name": "nonce",
"fieldName": "nonce",
"columnType": 5,
"isNullable": true,
"dartType": "dart:typed_data:ByteData?"
}
],
"foreignKeys": [
{
"__className__": "serverpod.ForeignKeyDefinition",
"constraintName": "lesson_done_fk_0",
"columns": [
"spaceId"
],
"referenceTable": "offline_sync_spaces",
"referenceTableSchema": "public",
"referenceColumns": [
"id"
],
"onUpdate": 3,
"onDelete": 4
}
],
"indexes": [
{
"__className__": "serverpod.IndexDefinition",
"indexName": "lesson_done_space_idx",
"elements": [
{
"__className__": "serverpod.IndexElementDefinition",
"type": 0,
"definition": "spaceId"
}
],
"type": "btree",
"isUnique": false,
"isPrimary": false
}
],
"managed": true
}
}
],
"warnings": [],
"migrationApiVersion": 1
}

View file

@ -6,6 +6,7 @@ import 'package:serverpod_database/serverpod_database.dart';
part '20261103074902604-sittings/migration.dart';
part '20261103130213716-dreams/migration.dart';
part '20261103143338267-library/migration.dart';
/// Migration registry for the client-side database.
class MigrationRegistry {
@ -14,6 +15,7 @@ class MigrationRegistry {
static final List<MigrationVersionSql> migrations = [
_Migration20261103074902604_sittings(),
_Migration20261103130213716_dreams(),
_Migration20261103143338267_library(),
];
/// List of all client-side database migration versions.

View file

@ -12,6 +12,8 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _ida;
import 'package:halkyon_base_client/halkyon_base_client.dart' as _ihk4p5t7;
import 'package:halkyon_meditate_client/src/protocol/library/library_catalogue.dart'
as _i6e8np2y;
import 'package:halkyon_oidc_client/halkyon_oidc_client.dart' as _ibkpwzgl;
import 'package:http/http.dart' as _i85jenna;
import 'package:serverpod_auth_core_client/serverpod_auth_core_client.dart'
@ -26,6 +28,26 @@ import 'protocol.dart' as _il2as5qe;
import 'sync_tables.dart' as _ii3f3u05;
import 'package:halkyon_meditate_client/migrations/migration_registry.dart';
/// The instance's library (ADR 0038, D16), for signed-in people alone: the
/// content is the operator's, under licences that may not allow offering it
/// to the public.
/// {@category Endpoint}
class EndpointLibrary extends _isc.EndpointRef {
EndpointLibrary(_isc.EndpointCaller caller) : super(caller);
@override
String get name => 'library';
/// What the library offers, where its files are and an access to fetch
/// them; empty where the instance has none.
_ida.Future<_i6e8np2y.LibraryCatalogue> catalogue() =>
caller.callServerEndpoint<_i6e8np2y.LibraryCatalogue>(
'library',
'catalogue',
{},
);
}
class Modules {
Modules(Client client) {
base = _ihk4p5t7.Caller(client);
@ -73,13 +95,16 @@ class Client extends _isc.ServerpodClientShared {
disconnectStreamsOnLostInternetConnection,
httpClientOverride: httpClientOverride,
) {
library = EndpointLibrary(this);
modules = Modules(this);
}
late final EndpointLibrary library;
late final Modules modules;
@override
Map<String, _isc.EndpointRef> get endpointRefLookup => {};
Map<String, _isc.EndpointRef> get endpointRefLookup => {'library': library};
@override
Map<String, _isc.ModuleEndpointCaller> get moduleLookup => {

View file

@ -0,0 +1,673 @@
/* 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 'dart:typed_data' as _idt;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import 'package:serverpod_database/serverpod_database.dart' as _isd;
/// A lesson of a library course the person did (ADR 0038, D16): the course's
/// and the item's ids as the operator names them, and when, all in its sealed
/// payload; the instance sees that it exists, its size and when it changed.
/// One row a lesson, under an ID keyed by the account, so every device of
/// theirs writes the same one; deleted by the person alone. Synced.
abstract class LessonDone
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
LessonDone._({
this.id,
this.spaceId,
this.keyId,
required this.payload,
this.nonce,
});
factory LessonDone({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) = _LessonDoneImpl;
factory LessonDone.fromJson(Map<String, dynamic> jsonSerialization) {
return LessonDone(
id: jsonSerialization['id'] == null
? null
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
keyId: jsonSerialization['keyId'] as String?,
payload: _isc.ByteDataJsonExtension.fromJson(
jsonSerialization['payload'],
),
nonce: jsonSerialization['nonce'] == null
? null
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
);
}
static final t = LessonDoneTable();
static const db = LessonDoneRepository._();
@override
_isc.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Meditate,
/// where everything is sealed.
String? keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
_idt.ByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
_idt.ByteData? nonce;
@override
_isd.Table<_isc.UuidValue?> get table => t;
/// Returns a shallow copy of this [LessonDone]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LessonDone copyWith({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
_idt.ByteData? payload,
_idt.ByteData? nonce,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LessonDone',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LessonDone',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
static LessonDoneInclude include() {
return LessonDoneInclude._();
}
static LessonDoneIncludeList includeList({
_isd.WhereExpressionBuilder<LessonDoneTable>? where,
int? limit,
int? offset,
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
LessonDoneInclude? include,
}) {
return LessonDoneIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
include: include,
);
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LessonDoneImpl extends LessonDone {
_LessonDoneImpl({
_isc.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) : super._(
id: id,
spaceId: spaceId,
keyId: keyId,
payload: payload,
nonce: nonce,
);
/// Returns a shallow copy of this [LessonDone]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LessonDone copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
}) {
return LessonDone(
id: id is _isc.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
keyId: keyId is String? ? keyId : this.keyId,
payload: payload ?? this.payload.clone(),
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
);
}
}
class LessonDoneUpdateTable extends _isd.UpdateTable<LessonDoneTable> {
LessonDoneUpdateTable(super.table);
_isd.ColumnValue<int, int> spaceId(int? value) => _isd.ColumnValue(
table.spaceId,
value,
);
_isd.ColumnValue<String, String> keyId(String? value) => _isd.ColumnValue(
table.keyId,
value,
);
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
_isd.ColumnValue(
table.payload,
value,
);
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
_isd.ColumnValue(
table.nonce,
value,
);
}
class LessonDoneTable extends _isd.Table<_isc.UuidValue?> {
LessonDoneTable({super.tableRelation}) : super(tableName: 'lesson_done') {
updateTable = LessonDoneUpdateTable(this);
spaceId = _isd.ColumnInt(
'spaceId',
this,
);
keyId = _isd.ColumnString(
'keyId',
this,
);
payload = _isd.ColumnByteData(
'payload',
this,
);
nonce = _isd.ColumnByteData(
'nonce',
this,
);
}
late final LessonDoneUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _isd.ColumnInt spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Meditate,
/// where everything is sealed.
late final _isd.ColumnString keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
late final _isd.ColumnByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
late final _isd.ColumnByteData nonce;
@override
List<_isd.Column> get columns => [
id,
spaceId,
keyId,
payload,
nonce,
];
}
class LessonDoneInclude extends _isd.IncludeObject {
LessonDoneInclude._();
@override
Map<String, _isd.Include?> get includes => {};
@override
_isd.Table<_isc.UuidValue?> get table => LessonDone.t;
}
class LessonDoneIncludeList extends _isd.IncludeList {
LessonDoneIncludeList._({
_isd.WhereExpressionBuilder<LessonDoneTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(LessonDone.t);
}
@override
Map<String, _isd.Include?> get includes => include?.includes ?? {};
@override
_isd.Table<_isc.UuidValue?> get table => LessonDone.t;
}
class LessonDoneRepository {
const LessonDoneRepository._();
/// Returns a list of [LessonDone]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<LessonDone>> find(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<LessonDoneTable>? where,
int? limit,
int? offset,
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.find<LessonDone>(
where: where?.call(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [LessonDone] 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<LessonDone?> findFirstRow(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<LessonDoneTable>? where,
int? offset,
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<LessonDone>(
where: where?.call(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [LessonDone] by its [id] or null if no such row exists.
Future<LessonDone?> findById(
_isd.DatabaseSession session,
_isc.UuidValue id, {
_isd.Transaction? transaction,
_isd.LockMode? lockMode,
_isd.LockBehavior? lockBehavior,
}) async {
return session.db.findById<LessonDone>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [LessonDone]s in the list and returns the inserted rows.
///
/// The returned [LessonDone]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<LessonDone>> insert(
_isd.DatabaseSession session,
List<LessonDone> rows, {
_isd.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<LessonDone>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [LessonDone] and returns the inserted row.
///
/// The returned [LessonDone] will have its `id` field set.
Future<LessonDone> insertRow(
_isd.DatabaseSession session,
LessonDone row, {
_isd.Transaction? transaction,
}) async {
return session.db.insertRow<LessonDone>(
row,
transaction: transaction,
);
}
/// Upserts all [LessonDone]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 [LessonDone]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<LessonDone>> upsert(
_isd.DatabaseSession session,
List<LessonDone> rows, {
required _isd.ColumnSelections<LessonDoneTable> conflictColumns,
_isd.ColumnSelections<LessonDoneTable>? updateColumns,
_isd.WhereExpressionBuilder<LessonDoneTable>? updateWhere,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<LessonDone>(
rows,
conflictColumns: conflictColumns(LessonDone.t),
updateColumns: updateColumns?.call(LessonDone.t),
updateWhere: updateWhere?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [LessonDone] 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 [LessonDone] will have its `id` field set.
Future<LessonDone?> upsertRow(
_isd.DatabaseSession session,
LessonDone row, {
required _isd.ColumnSelections<LessonDoneTable> conflictColumns,
_isd.ColumnSelections<LessonDoneTable>? updateColumns,
_isd.WhereExpressionBuilder<LessonDoneTable>? updateWhere,
_isd.Transaction? transaction,
}) async {
return session.db.upsertRow<LessonDone>(
row,
conflictColumns: conflictColumns(LessonDone.t),
updateColumns: updateColumns?.call(LessonDone.t),
updateWhere: updateWhere?.call(LessonDone.t),
transaction: transaction,
);
}
/// Updates all [LessonDone]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<LessonDone>> update(
_isd.DatabaseSession session,
List<LessonDone> rows, {
_isd.ColumnSelections<LessonDoneTable>? columns,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<LessonDone>(
rows,
columns: columns?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [LessonDone]. 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<LessonDone> updateRow(
_isd.DatabaseSession session,
LessonDone row, {
_isd.ColumnSelections<LessonDoneTable>? columns,
_isd.Transaction? transaction,
}) async {
return session.db.updateRow<LessonDone>(
row,
columns: columns?.call(LessonDone.t),
transaction: transaction,
);
}
/// Updates a single [LessonDone] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<LessonDone?> updateById(
_isd.DatabaseSession session,
_isc.UuidValue id, {
required _isd.ColumnValueListBuilder<LessonDoneUpdateTable> columnValues,
_isd.Transaction? transaction,
}) async {
return session.db.updateById<LessonDone>(
id,
columnValues: columnValues(LessonDone.t.updateTable),
transaction: transaction,
);
}
/// Updates all [LessonDone]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<LessonDone>> updateWhere(
_isd.DatabaseSession session, {
required _isd.ColumnValueListBuilder<LessonDoneUpdateTable> columnValues,
required _isd.WhereExpressionBuilder<LessonDoneTable> where,
int? limit,
int? offset,
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<LessonDone>(
columnValues: columnValues(LessonDone.t.updateTable),
where: where(LessonDone.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [LessonDone]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<LessonDone>> delete(
_isd.DatabaseSession session,
List<LessonDone> rows, {
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<LessonDone>(
rows,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [LessonDone].
Future<LessonDone> deleteRow(
_isd.DatabaseSession session,
LessonDone row, {
_isd.Transaction? transaction,
}) async {
return session.db.deleteRow<LessonDone>(
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<LessonDone>> deleteWhere(
_isd.DatabaseSession session, {
required _isd.WhereExpressionBuilder<LessonDoneTable> where,
_isd.OrderByBuilder<LessonDoneTable>? orderBy,
_isd.OrderByListBuilder<LessonDoneTable>? orderByList,
_isd.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<LessonDone>(
where: where(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.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(
_isd.DatabaseSession session, {
_isd.WhereExpressionBuilder<LessonDoneTable>? where,
int? limit,
_isd.Transaction? transaction,
}) async {
return session.db.count<LessonDone>(
where: where?.call(LessonDone.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [LessonDone] rows matching the [where] expression.
Future<void> lockRows(
_isd.DatabaseSession session, {
required _isd.WhereExpressionBuilder<LessonDoneTable> where,
required _isd.LockMode lockMode,
required _isd.Transaction transaction,
_isd.LockBehavior lockBehavior = _isd.LockBehavior.wait,
}) async {
return session.db.lockRows<LessonDone>(
where: where(LessonDone.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

View file

@ -0,0 +1,123 @@
/* 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;
/// One recording of a library item (ADR 0038, D16).
abstract class LibraryAudio
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryAudio._({
required this.file,
this.label,
required this.durationSeconds,
required this.size,
});
factory LibraryAudio({
required String file,
String? label,
required int durationSeconds,
required int size,
}) = _LibraryAudioImpl;
factory LibraryAudio.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryAudio(
file: jsonSerialization['file'] as String,
label: jsonSerialization['label'] as String?,
durationSeconds: jsonSerialization['durationSeconds'] as int,
size: jsonSerialization['size'] as int,
);
}
/// Its served name: a hash of where it is in the folder, its size and when
/// it changed, with its extension; a new file is a new name.
String file;
/// What tells it from the item's other recordings (`10 minutes`, `Anna`).
String? label;
/// How long it plays, in seconds, as the manifest says.
int durationSeconds;
/// Its size, in bytes.
int size;
/// Returns a shallow copy of this [LibraryAudio]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryAudio copyWith({
String? file,
String? label,
int? durationSeconds,
int? size,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryAudio',
'file': file,
if (label != null) 'label': label,
'durationSeconds': durationSeconds,
'size': size,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryAudio',
'file': file,
if (label != null) 'label': label,
'durationSeconds': durationSeconds,
'size': size,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LibraryAudioImpl extends LibraryAudio {
_LibraryAudioImpl({
required String file,
String? label,
required int durationSeconds,
required int size,
}) : super._(
file: file,
label: label,
durationSeconds: durationSeconds,
size: size,
);
/// Returns a shallow copy of this [LibraryAudio]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryAudio copyWith({
String? file,
Object? label = _Undefined,
int? durationSeconds,
int? size,
}) {
return LibraryAudio(
file: file ?? this.file,
label: label is String? ? label : this.label,
durationSeconds: durationSeconds ?? this.durationSeconds,
size: size ?? this.size,
);
}
}

View file

@ -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:halkyon_meditate_client/src/protocol/protocol.dart'
as _ijpv54ks;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../library/library_category.dart' as _isl35f3w;
import '../library/library_course.dart' as _i55xxs4r;
import '../library/library_item.dart' as _iva84vsy;
/// The instance's library as a signed-in person's device gets it (ADR 0038,
/// D16): what the operator's manifest offers that passed its checks, the
/// operator's own categories, and where and how the device fetches the files.
/// Empty where the instance has no library. Never stored: the server holds it
/// in memory, read from the operator's folder.
abstract class LibraryCatalogue
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryCatalogue._({
required this.items,
required this.courses,
required this.categories,
this.filesUrl,
this.access,
this.accessUntil,
});
factory LibraryCatalogue({
required List<_iva84vsy.LibraryItem> items,
required List<_i55xxs4r.LibraryCourse> courses,
required List<_isl35f3w.LibraryCategory> categories,
String? filesUrl,
String? access,
DateTime? accessUntil,
}) = _LibraryCatalogueImpl;
factory LibraryCatalogue.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryCatalogue(
items: _ijpv54ks.Protocol().deserialize<List<_iva84vsy.LibraryItem>>(
jsonSerialization['items'],
),
courses: _ijpv54ks.Protocol().deserialize<List<_i55xxs4r.LibraryCourse>>(
jsonSerialization['courses'],
),
categories: _ijpv54ks.Protocol()
.deserialize<List<_isl35f3w.LibraryCategory>>(
jsonSerialization['categories'],
),
filesUrl: jsonSerialization['filesUrl'] as String?,
access: jsonSerialization['access'] as String?,
accessUntil: jsonSerialization['accessUntil'] == null
? null
: _isc.DateTimeJsonExtension.fromJson(
jsonSerialization['accessUntil'],
),
);
}
/// Every item served, as the manifest lists them.
List<_iva84vsy.LibraryItem> items;
/// Every course served, as the manifest lists them.
List<_i55xxs4r.LibraryCourse> courses;
/// The operator's own categories, with their names; Meditate's own are
/// named by the app.
List<_isl35f3w.LibraryCategory> categories;
/// Where the files are: a file's URL is this, `/`, its served name, then
/// `?access=` and [access]. Null where the instance has no web server.
String? filesUrl;
/// What lets the device fetch the files until [accessUntil], signed by the
/// server; null with no library.
String? access;
/// Until when [access] holds.
DateTime? accessUntil;
/// Returns a shallow copy of this [LibraryCatalogue]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryCatalogue copyWith({
List<_iva84vsy.LibraryItem>? items,
List<_i55xxs4r.LibraryCourse>? courses,
List<_isl35f3w.LibraryCategory>? categories,
String? filesUrl,
String? access,
DateTime? accessUntil,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryCatalogue',
'items': items.toJson(valueToJson: (v) => v.toJson()),
'courses': courses.toJson(valueToJson: (v) => v.toJson()),
'categories': categories.toJson(valueToJson: (v) => v.toJson()),
if (filesUrl != null) 'filesUrl': filesUrl,
if (access != null) 'access': access,
if (accessUntil != null) 'accessUntil': accessUntil?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryCatalogue',
'items': items.toJson(valueToJson: (v) => v.toJsonForProtocol()),
'courses': courses.toJson(valueToJson: (v) => v.toJsonForProtocol()),
'categories': categories.toJson(
valueToJson: (v) => v.toJsonForProtocol(),
),
if (filesUrl != null) 'filesUrl': filesUrl,
if (access != null) 'access': access,
if (accessUntil != null) 'accessUntil': accessUntil?.toJson(),
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LibraryCatalogueImpl extends LibraryCatalogue {
_LibraryCatalogueImpl({
required List<_iva84vsy.LibraryItem> items,
required List<_i55xxs4r.LibraryCourse> courses,
required List<_isl35f3w.LibraryCategory> categories,
String? filesUrl,
String? access,
DateTime? accessUntil,
}) : super._(
items: items,
courses: courses,
categories: categories,
filesUrl: filesUrl,
access: access,
accessUntil: accessUntil,
);
/// Returns a shallow copy of this [LibraryCatalogue]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryCatalogue copyWith({
List<_iva84vsy.LibraryItem>? items,
List<_i55xxs4r.LibraryCourse>? courses,
List<_isl35f3w.LibraryCategory>? categories,
Object? filesUrl = _Undefined,
Object? access = _Undefined,
Object? accessUntil = _Undefined,
}) {
return LibraryCatalogue(
items: items ?? this.items.map((e0) => e0.copyWith()).toList(),
courses: courses ?? this.courses.map((e0) => e0.copyWith()).toList(),
categories:
categories ?? this.categories.map((e0) => e0.copyWith()).toList(),
filesUrl: filesUrl is String? ? filesUrl : this.filesUrl,
access: access is String? ? access : this.access,
accessUntil: accessUntil is DateTime? ? accessUntil : this.accessUntil,
);
}
}

View file

@ -0,0 +1,105 @@
/* 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 category of the operator's own (ADR 0038, D16), with its names, beside
/// Meditate's own, which the app names.
abstract class LibraryCategory
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryCategory._({
required this.key,
required this.english,
required this.german,
});
factory LibraryCategory({
required String key,
required String english,
required String german,
}) = _LibraryCategoryImpl;
factory LibraryCategory.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryCategory(
key: jsonSerialization['key'] as String,
english: jsonSerialization['english'] as String,
german: jsonSerialization['german'] as String,
);
}
String key;
String english;
String german;
/// Returns a shallow copy of this [LibraryCategory]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryCategory copyWith({
String? key,
String? english,
String? german,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryCategory',
'key': key,
'english': english,
'german': german,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryCategory',
'key': key,
'english': english,
'german': german,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _LibraryCategoryImpl extends LibraryCategory {
_LibraryCategoryImpl({
required String key,
required String english,
required String german,
}) : super._(
key: key,
english: english,
german: german,
);
/// Returns a shallow copy of this [LibraryCategory]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryCategory copyWith({
String? key,
String? english,
String? german,
}) {
return LibraryCategory(
key: key ?? this.key,
english: english ?? this.english,
german: german ?? this.german,
);
}
}

View file

@ -0,0 +1,163 @@
/* 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:halkyon_meditate_client/src/protocol/protocol.dart'
as _ijpv54ks;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../library/library_lesson.dart' as _i8407zl3;
/// A course of the instance's library (ADR 0038, D16): lessons in order, each
/// a library item.
abstract class LibraryCourse
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryCourse._({
required this.id,
required this.title,
this.description,
required this.category,
required this.language,
this.image,
required this.lessons,
});
factory LibraryCourse({
required String id,
required String title,
String? description,
required String category,
required String language,
String? image,
required List<_i8407zl3.LibraryLesson> lessons,
}) = _LibraryCourseImpl;
factory LibraryCourse.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryCourse(
id: jsonSerialization['id'] as String,
title: jsonSerialization['title'] as String,
description: jsonSerialization['description'] as String?,
category: jsonSerialization['category'] as String,
language: jsonSerialization['language'] as String,
image: jsonSerialization['image'] as String?,
lessons: _ijpv54ks.Protocol().deserialize<List<_i8407zl3.LibraryLesson>>(
jsonSerialization['lessons'],
),
);
}
/// The operator's stable slug, which lessons done refer to.
String id;
String title;
String? description;
String category;
/// Its language, as BCP 47.
String language;
/// Its picture's served name, if it has one.
String? image;
List<_i8407zl3.LibraryLesson> lessons;
/// Returns a shallow copy of this [LibraryCourse]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryCourse copyWith({
String? id,
String? title,
String? description,
String? category,
String? language,
String? image,
List<_i8407zl3.LibraryLesson>? lessons,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryCourse',
'id': id,
'title': title,
if (description != null) 'description': description,
'category': category,
'language': language,
if (image != null) 'image': image,
'lessons': lessons.toJson(valueToJson: (v) => v.toJson()),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryCourse',
'id': id,
'title': title,
if (description != null) 'description': description,
'category': category,
'language': language,
if (image != null) 'image': image,
'lessons': lessons.toJson(valueToJson: (v) => v.toJsonForProtocol()),
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LibraryCourseImpl extends LibraryCourse {
_LibraryCourseImpl({
required String id,
required String title,
String? description,
required String category,
required String language,
String? image,
required List<_i8407zl3.LibraryLesson> lessons,
}) : super._(
id: id,
title: title,
description: description,
category: category,
language: language,
image: image,
lessons: lessons,
);
/// Returns a shallow copy of this [LibraryCourse]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryCourse copyWith({
String? id,
String? title,
Object? description = _Undefined,
String? category,
String? language,
Object? image = _Undefined,
List<_i8407zl3.LibraryLesson>? lessons,
}) {
return LibraryCourse(
id: id ?? this.id,
title: title ?? this.title,
description: description is String? ? description : this.description,
category: category ?? this.category,
language: language ?? this.language,
image: image is String? ? image : this.image,
lessons: lessons ?? this.lessons.map((e0) => e0.copyWith()).toList(),
);
}
}

View file

@ -0,0 +1,220 @@
/* 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:halkyon_meditate_client/src/protocol/protocol.dart'
as _ijpv54ks;
import 'package:serverpod_client/serverpod_client.dart' as _isc;
import '../library/library_audio.dart' as _isi4clkz;
/// An item of the instance's library (ADR 0038, D16): something to listen to,
/// in one or more recordings, with what the operator says of it and the
/// licence and credit it is offered under.
abstract class LibraryItem
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryItem._({
required this.id,
required this.title,
required this.category,
this.description,
this.speaker,
required this.language,
this.image,
required this.audio,
required this.licence,
required this.credit,
this.licenceUrl,
});
factory LibraryItem({
required String id,
required String title,
required String category,
String? description,
String? speaker,
required String language,
String? image,
required List<_isi4clkz.LibraryAudio> audio,
required String licence,
required String credit,
String? licenceUrl,
}) = _LibraryItemImpl;
factory LibraryItem.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryItem(
id: jsonSerialization['id'] as String,
title: jsonSerialization['title'] as String,
category: jsonSerialization['category'] as String,
description: jsonSerialization['description'] as String?,
speaker: jsonSerialization['speaker'] as String?,
language: jsonSerialization['language'] as String,
image: jsonSerialization['image'] as String?,
audio: _ijpv54ks.Protocol().deserialize<List<_isi4clkz.LibraryAudio>>(
jsonSerialization['audio'],
),
licence: jsonSerialization['licence'] as String,
credit: jsonSerialization['credit'] as String,
licenceUrl: jsonSerialization['licenceUrl'] as String?,
);
}
/// The operator's stable slug: sittings, favourites and lessons done refer
/// to it, so it never changes once offered.
String id;
String title;
/// One of Meditate's categories (`meditation`, `sleep-story`, …) or one of
/// the operator's own ([LibraryCatalogue.categories]).
String category;
String? description;
/// Who speaks it, or wrote it.
String? speaker;
/// Its language, as BCP 47 (`en`, `de-CH`).
String language;
/// Its picture's served name, if it has one.
String? image;
/// Its recordings, at least one (by length or voice).
List<_isi4clkz.LibraryAudio> audio;
/// The licence it is offered under, as the operator names it.
String licence;
/// Whom it is credited to, as the licence asks.
String credit;
/// Where the licence's text is, if the operator says.
String? licenceUrl;
/// Returns a shallow copy of this [LibraryItem]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryItem copyWith({
String? id,
String? title,
String? category,
String? description,
String? speaker,
String? language,
String? image,
List<_isi4clkz.LibraryAudio>? audio,
String? licence,
String? credit,
String? licenceUrl,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryItem',
'id': id,
'title': title,
'category': category,
if (description != null) 'description': description,
if (speaker != null) 'speaker': speaker,
'language': language,
if (image != null) 'image': image,
'audio': audio.toJson(valueToJson: (v) => v.toJson()),
'licence': licence,
'credit': credit,
if (licenceUrl != null) 'licenceUrl': licenceUrl,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryItem',
'id': id,
'title': title,
'category': category,
if (description != null) 'description': description,
if (speaker != null) 'speaker': speaker,
'language': language,
if (image != null) 'image': image,
'audio': audio.toJson(valueToJson: (v) => v.toJsonForProtocol()),
'licence': licence,
'credit': credit,
if (licenceUrl != null) 'licenceUrl': licenceUrl,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LibraryItemImpl extends LibraryItem {
_LibraryItemImpl({
required String id,
required String title,
required String category,
String? description,
String? speaker,
required String language,
String? image,
required List<_isi4clkz.LibraryAudio> audio,
required String licence,
required String credit,
String? licenceUrl,
}) : super._(
id: id,
title: title,
category: category,
description: description,
speaker: speaker,
language: language,
image: image,
audio: audio,
licence: licence,
credit: credit,
licenceUrl: licenceUrl,
);
/// Returns a shallow copy of this [LibraryItem]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryItem copyWith({
String? id,
String? title,
String? category,
Object? description = _Undefined,
Object? speaker = _Undefined,
String? language,
Object? image = _Undefined,
List<_isi4clkz.LibraryAudio>? audio,
String? licence,
String? credit,
Object? licenceUrl = _Undefined,
}) {
return LibraryItem(
id: id ?? this.id,
title: title ?? this.title,
category: category ?? this.category,
description: description is String? ? description : this.description,
speaker: speaker is String? ? speaker : this.speaker,
language: language ?? this.language,
image: image is String? ? image : this.image,
audio: audio ?? this.audio.map((e0) => e0.copyWith()).toList(),
licence: licence ?? this.licence,
credit: credit ?? this.credit,
licenceUrl: licenceUrl is String? ? licenceUrl : this.licenceUrl,
);
}
}

View file

@ -0,0 +1,96 @@
/* 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 lesson of a library course (ADR 0038, D16): the item it plays, once in
/// the course, and a title of its own where the course gives one.
abstract class LibraryLesson
implements _isc.SerializableModel, _isc.ProtocolSerialization {
LibraryLesson._({
required this.item,
this.title,
});
factory LibraryLesson({
required String item,
String? title,
}) = _LibraryLessonImpl;
factory LibraryLesson.fromJson(Map<String, dynamic> jsonSerialization) {
return LibraryLesson(
item: jsonSerialization['item'] as String,
title: jsonSerialization['title'] as String?,
);
}
/// The item's id.
String item;
String? title;
/// Returns a shallow copy of this [LibraryLesson]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
LibraryLesson copyWith({
String? item,
String? title,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LibraryLesson',
'item': item,
if (title != null) 'title': title,
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LibraryLesson',
'item': item,
if (title != null) 'title': title,
};
}
@override
String toString() {
return _isc.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LibraryLessonImpl extends LibraryLesson {
_LibraryLessonImpl({
required String item,
String? title,
}) : super._(
item: item,
title: title,
);
/// Returns a shallow copy of this [LibraryLesson]
/// with some or all fields replaced by the given arguments.
@_isc.useResult
@override
LibraryLesson copyWith({
String? item,
Object? title = _Undefined,
}) {
return LibraryLesson(
item: item ?? this.item,
title: title is String? ? title : this.title,
);
}
}

View file

@ -22,10 +22,24 @@ import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'package:serverpod_offline_sync_client/serverpod_offline_sync_client.dart'
as _ipulbpi2;
import 'journal/dream.dart' as _is2va3v7;
import 'library/lesson_done.dart' as _iji1zgj1;
import 'library/library_audio.dart' as _i7e5zeo1;
import 'library/library_catalogue.dart' as _iy25urx6;
import 'library/library_category.dart' as _i5arxw0y;
import 'library/library_course.dart' as _iq670z17;
import 'library/library_item.dart' as _i68e1vb3;
import 'library/library_lesson.dart' as _io7s64zq;
import 'privacy/content_key.dart' as _imed3x5d;
import 'settings/meditate_settings.dart' as _iutacwgf;
import 'sitting/sitting.dart' as _ic8w2fbu;
export 'journal/dream.dart';
export 'library/lesson_done.dart';
export 'library/library_audio.dart';
export 'library/library_catalogue.dart';
export 'library/library_category.dart';
export 'library/library_course.dart';
export 'library/library_item.dart';
export 'library/library_lesson.dart';
export 'privacy/content_key.dart';
export 'settings/meditate_settings.dart';
export 'sitting/sitting.dart';
@ -168,6 +182,73 @@ class Protocol extends _isd.DatabaseSerializationManager {
],
managed: true,
),
_isd.TableDefinition(
name: 'lesson_done',
dartName: 'LessonDone',
schema: 'public',
module: 'halkyon_meditate',
columns: [
_isd.ColumnDefinition(
name: 'id',
columnType: _isd.ColumnType.uuid,
isNullable: false,
dartType: 'UuidValue?',
columnDefault: 'random_v7',
),
_isd.ColumnDefinition(
name: 'spaceId',
columnType: _isd.ColumnType.bigint,
isNullable: true,
dartType: 'int?',
),
_isd.ColumnDefinition(
name: 'keyId',
columnType: _isd.ColumnType.text,
isNullable: true,
dartType: 'String?',
),
_isd.ColumnDefinition(
name: 'payload',
columnType: _isd.ColumnType.bytea,
isNullable: false,
dartType: 'dart:typed_data:ByteData',
),
_isd.ColumnDefinition(
name: 'nonce',
columnType: _isd.ColumnType.bytea,
isNullable: true,
dartType: 'dart:typed_data:ByteData?',
),
],
foreignKeys: [
_isd.ForeignKeyDefinition(
constraintName: 'lesson_done_fk_0',
columns: ['spaceId'],
referenceTable: 'offline_sync_spaces',
referenceTableSchema: 'public',
referenceColumns: ['id'],
onUpdate: _isd.ForeignKeyAction.noAction,
onDelete: _isd.ForeignKeyAction.cascade,
matchType: null,
),
],
indexes: [
_isd.IndexDefinition(
indexName: 'lesson_done_space_idx',
tableSpace: null,
elements: [
_isd.IndexElementDefinition(
type: _isd.IndexElementDefinitionType.column,
definition: 'spaceId',
),
],
type: 'btree',
isUnique: false,
isPrimary: false,
),
],
managed: true,
),
_isd.TableDefinition(
name: 'meditate_settings',
dartName: 'MeditateSettings',
@ -354,6 +435,27 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (t == _is2va3v7.Dream) {
return _is2va3v7.Dream.fromJson(data) as T;
}
if (t == _iji1zgj1.LessonDone) {
return _iji1zgj1.LessonDone.fromJson(data) as T;
}
if (t == _i7e5zeo1.LibraryAudio) {
return _i7e5zeo1.LibraryAudio.fromJson(data) as T;
}
if (t == _iy25urx6.LibraryCatalogue) {
return _iy25urx6.LibraryCatalogue.fromJson(data) as T;
}
if (t == _i5arxw0y.LibraryCategory) {
return _i5arxw0y.LibraryCategory.fromJson(data) as T;
}
if (t == _iq670z17.LibraryCourse) {
return _iq670z17.LibraryCourse.fromJson(data) as T;
}
if (t == _i68e1vb3.LibraryItem) {
return _i68e1vb3.LibraryItem.fromJson(data) as T;
}
if (t == _io7s64zq.LibraryLesson) {
return _io7s64zq.LibraryLesson.fromJson(data) as T;
}
if (t == _imed3x5d.ContentKey) {
return _imed3x5d.ContentKey.fromJson(data) as T;
}
@ -366,6 +468,31 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (t == _isc.getType<_is2va3v7.Dream?>()) {
return (data != null ? _is2va3v7.Dream.fromJson(data) : null) as T;
}
if (t == _isc.getType<_iji1zgj1.LessonDone?>()) {
return (data != null ? _iji1zgj1.LessonDone.fromJson(data) : null) as T;
}
if (t == _isc.getType<_i7e5zeo1.LibraryAudio?>()) {
return (data != null ? _i7e5zeo1.LibraryAudio.fromJson(data) : null) as T;
}
if (t == _isc.getType<_iy25urx6.LibraryCatalogue?>()) {
return (data != null ? _iy25urx6.LibraryCatalogue.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_i5arxw0y.LibraryCategory?>()) {
return (data != null ? _i5arxw0y.LibraryCategory.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_iq670z17.LibraryCourse?>()) {
return (data != null ? _iq670z17.LibraryCourse.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_i68e1vb3.LibraryItem?>()) {
return (data != null ? _i68e1vb3.LibraryItem.fromJson(data) : null) as T;
}
if (t == _isc.getType<_io7s64zq.LibraryLesson?>()) {
return (data != null ? _io7s64zq.LibraryLesson.fromJson(data) : null)
as T;
}
if (t == _isc.getType<_imed3x5d.ContentKey?>()) {
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
}
@ -376,6 +503,36 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (t == _isc.getType<_ic8w2fbu.Sitting?>()) {
return (data != null ? _ic8w2fbu.Sitting.fromJson(data) : null) as T;
}
if (t == List<_i68e1vb3.LibraryItem>) {
return (data as List)
.map((e) => deserialize<_i68e1vb3.LibraryItem>(e))
.toList()
as T;
}
if (t == List<_iq670z17.LibraryCourse>) {
return (data as List)
.map((e) => deserialize<_iq670z17.LibraryCourse>(e))
.toList()
as T;
}
if (t == List<_i5arxw0y.LibraryCategory>) {
return (data as List)
.map((e) => deserialize<_i5arxw0y.LibraryCategory>(e))
.toList()
as T;
}
if (t == List<_io7s64zq.LibraryLesson>) {
return (data as List)
.map((e) => deserialize<_io7s64zq.LibraryLesson>(e))
.toList()
as T;
}
if (t == List<_i7e5zeo1.LibraryAudio>) {
return (data as List)
.map((e) => deserialize<_i7e5zeo1.LibraryAudio>(e))
.toList()
as T;
}
try {
return _ihk4p5t7.Protocol().deserialize<T>(data, t);
} on _isc.DeserializationTypeNotFoundException catch (_) {}
@ -397,6 +554,13 @@ class Protocol extends _isd.DatabaseSerializationManager {
static String? getClassNameForType(Type type) {
return switch (type) {
_is2va3v7.Dream => 'Dream',
_iji1zgj1.LessonDone => 'LessonDone',
_i7e5zeo1.LibraryAudio => 'LibraryAudio',
_iy25urx6.LibraryCatalogue => 'LibraryCatalogue',
_i5arxw0y.LibraryCategory => 'LibraryCategory',
_iq670z17.LibraryCourse => 'LibraryCourse',
_i68e1vb3.LibraryItem => 'LibraryItem',
_io7s64zq.LibraryLesson => 'LibraryLesson',
_imed3x5d.ContentKey => 'ContentKey',
_iutacwgf.MeditateSettings => 'MeditateSettings',
_ic8w2fbu.Sitting => 'Sitting',
@ -419,6 +583,20 @@ class Protocol extends _isd.DatabaseSerializationManager {
switch (data) {
case _is2va3v7.Dream():
return 'Dream';
case _iji1zgj1.LessonDone():
return 'LessonDone';
case _i7e5zeo1.LibraryAudio():
return 'LibraryAudio';
case _iy25urx6.LibraryCatalogue():
return 'LibraryCatalogue';
case _i5arxw0y.LibraryCategory():
return 'LibraryCategory';
case _iq670z17.LibraryCourse():
return 'LibraryCourse';
case _i68e1vb3.LibraryItem():
return 'LibraryItem';
case _io7s64zq.LibraryLesson():
return 'LibraryLesson';
case _imed3x5d.ContentKey():
return 'ContentKey';
case _iutacwgf.MeditateSettings():
@ -464,6 +642,27 @@ class Protocol extends _isd.DatabaseSerializationManager {
if (dataClassName == 'Dream') {
return deserialize<_is2va3v7.Dream>(data['data']);
}
if (dataClassName == 'LessonDone') {
return deserialize<_iji1zgj1.LessonDone>(data['data']);
}
if (dataClassName == 'LibraryAudio') {
return deserialize<_i7e5zeo1.LibraryAudio>(data['data']);
}
if (dataClassName == 'LibraryCatalogue') {
return deserialize<_iy25urx6.LibraryCatalogue>(data['data']);
}
if (dataClassName == 'LibraryCategory') {
return deserialize<_i5arxw0y.LibraryCategory>(data['data']);
}
if (dataClassName == 'LibraryCourse') {
return deserialize<_iq670z17.LibraryCourse>(data['data']);
}
if (dataClassName == 'LibraryItem') {
return deserialize<_i68e1vb3.LibraryItem>(data['data']);
}
if (dataClassName == 'LibraryLesson') {
return deserialize<_io7s64zq.LibraryLesson>(data['data']);
}
if (dataClassName == 'ContentKey') {
return deserialize<_imed3x5d.ContentKey>(data['data']);
}
@ -554,6 +753,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
switch (t) {
case _is2va3v7.Dream:
return _is2va3v7.Dream.t;
case _iji1zgj1.LessonDone:
return _iji1zgj1.LessonDone.t;
case _imed3x5d.ContentKey:
return _imed3x5d.ContentKey.t;
case _iutacwgf.MeditateSettings:

View file

@ -12,6 +12,7 @@
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:serverpod_database/serverpod_database.dart' as _isd;
import 'journal/dream.dart' as _is2va3v7;
import 'library/lesson_done.dart' as _iji1zgj1;
import 'privacy/content_key.dart' as _imed3x5d;
import 'settings/meditate_settings.dart' as _iutacwgf;
import 'sitting/sitting.dart' as _ic8w2fbu;
@ -22,6 +23,7 @@ import 'sitting/sitting.dart' as _ic8w2fbu;
final List<_isd.Table> syncTables = [
_imed3x5d.ContentKey.t,
_is2va3v7.Dream.t,
_iji1zgj1.LessonDone.t,
_iutacwgf.MeditateSettings.t,
_ic8w2fbu.Sitting.t,
];

View file

@ -0,0 +1,29 @@
import 'dart:io';
import 'package:halkyon_meditate_server/src/library/library_manifest.dart';
/// Checks an operator's library folder before it is deployed (ADR 0038,
/// D16), as the server reads it: what it would serve, and every item,
/// course and category it would refuse, with the reason. Exits 1 where
/// anything is refused, 64 without a folder.
///
/// dart run bin/check_library.dart /srv/meditate-library
void main(List<String> args) {
if (args.length != 1) {
stderr.writeln('Usage: dart run bin/check_library.dart <library folder>');
exitCode = 64;
return;
}
final library = readLibrary(Directory(args.single));
for (final problem in library.problems) {
stdout.writeln('✗ $problem');
}
stdout.writeln(
'${library.items.length} items, ${library.courses.length} courses and '
'${library.categories.length} categories of its own would be served; '
'${library.problems.length} problems.',
);
if (library.problems.isNotEmpty) {
exitCode = 1;
}
}

View file

@ -1,5 +1,6 @@
import 'package:halkyon_base_server/base.dart';
import 'package:halkyon_meditate_server/src/generated/serverpod.dart';
import 'package:halkyon_meditate_server/src/library/meditate_library.dart';
import 'package:halkyon_meditate_server/src/meditate_base.dart';
import 'package:halkyon_meditate_server/src/pruning.dart';
import 'package:serverpod_auth_idp_server/core.dart';
@ -27,7 +28,14 @@ Future<void> run(List<String> args) async {
// (ADR 0016); Caddy never forwards them.
addInstanceRoutes(pod);
// The operator's library's files, to devices holding an access the
// catalogue gave them (ADR 0038, D16).
addLibraryRoute(pod);
await pod.start();
await schedulePruning(pod);
// The operator's library, read now and again every ten minutes (D16).
await startLibrary(pod);
}

View file

@ -21,11 +21,36 @@ import 'package:serverpod_auth_idp_server/serverpod_auth_idp_server.dart'
as _iais;
import 'package:serverpod_offline_sync_server/serverpod_offline_sync_server.dart'
as _izehhkf5;
import '../library/library_endpoint.dart' as _in7rjfvw;
export 'future_calls.dart' show ServerpodFutureCallsGetter;
class Endpoints extends _is.EndpointDispatch {
@override
void initializeEndpoints(_is.Server server) {
var endpoints = <String, _is.Endpoint>{
'library': _in7rjfvw.LibraryEndpoint()
..initialize(
server,
'library',
null,
),
};
connectors['library'] = _is.EndpointConnector(
name: 'library',
endpoint: endpoints['library']!,
methodConnectors: {
'catalogue': _is.MethodConnector(
name: 'catalogue',
params: {},
call:
(
_is.Session session,
Map<String, dynamic> params,
) async => (endpoints['library'] as _in7rjfvw.LibraryEndpoint)
.catalogue(session),
),
},
);
modules['halkyon_base'] = _ik8molhd.Endpoints()
..initializeEndpoints(server);
modules['serverpod_auth_idp'] = _iais.Endpoints()

View file

@ -14,6 +14,7 @@
import 'dart:async' as _ida;
import 'package:clock/clock.dart' as _io0w16m8;
import 'package:serverpod/serverpod.dart' as _is;
import '../library/library_reload_future_call.dart' as _ih20wh43;
import '../pruning_future_call.dart' as _ibrplims;
/// Invokes a future call.
@ -58,6 +59,8 @@ class FutureCalls extends _is.FutureCallDispatch<_FutureCallRef> {
String serverId,
) {
var registeredFutureCalls = <String, _is.InvokableFutureCall>{
'LibraryReloadReloadLibraryFutureCall':
LibraryReloadReloadLibraryFutureCall(),
'PruningPruneAccountsFutureCall': PruningPruneAccountsFutureCall(),
'PruningForgetSignUpAttemptsFutureCall':
PruningForgetSignUpAttemptsFutureCall(),
@ -185,9 +188,26 @@ class _FutureCallRef {
final _InvokeFutureCall _invokeFutureCall;
late final libraryReload = _LibraryReloadFutureCallDispatcher(
_invokeFutureCall,
);
late final pruning = _PruningFutureCallDispatcher(_invokeFutureCall);
}
class _LibraryReloadFutureCallDispatcher {
_LibraryReloadFutureCallDispatcher(this._invokeFutureCall);
final _InvokeFutureCall _invokeFutureCall;
Future<void> reloadLibrary() {
return _invokeFutureCall(
'LibraryReloadReloadLibraryFutureCall',
null,
);
}
}
class _PruningFutureCallDispatcher {
_PruningFutureCallDispatcher(this._invokeFutureCall);
@ -222,6 +242,18 @@ class _PruningFutureCallDispatcher {
}
}
/// Reads the library's folder again.
class LibraryReloadReloadLibraryFutureCall extends _is.FutureCall
implements _is.InvokableFutureCall {
@override
_ida.Future<void> invoke(
_is.Session session,
_is.SerializableModel? object,
) async {
await _ih20wh43.LibraryReloadFutureCall().reloadLibrary(session);
}
}
/// Prunes one batch of anonymous accounts and logs how many people went,
/// forgets the resets that lapsed (ADR 0017) and the assistants' figures
/// past their 3 months (Learn's ADR 0021, D41), and deletes the new accounts left

View file

@ -0,0 +1,670 @@
/* 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 'dart:typed_data' as _idt;
import 'package:serverpod/serverpod.dart' as _is;
/// A lesson of a library course the person did (ADR 0038, D16): the course's
/// and the item's ids as the operator names them, and when, all in its sealed
/// payload; the instance sees that it exists, its size and when it changed.
/// One row a lesson, under an ID keyed by the account, so every device of
/// theirs writes the same one; deleted by the person alone. Synced.
abstract class LessonDone
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
LessonDone._({
this.id,
this.spaceId,
this.keyId,
required this.payload,
this.nonce,
});
factory LessonDone({
_is.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) = _LessonDoneImpl;
factory LessonDone.fromJson(Map<String, dynamic> jsonSerialization) {
return LessonDone(
id: jsonSerialization['id'] == null
? null
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
spaceId: jsonSerialization['spaceId'] as int?,
keyId: jsonSerialization['keyId'] as String?,
payload: _is.ByteDataJsonExtension.fromJson(jsonSerialization['payload']),
nonce: jsonSerialization['nonce'] == null
? null
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
);
}
static final t = LessonDoneTable();
static const db = LessonDoneRepository._();
@override
_is.UuidValue? id;
/// The space owning this row. Maintained by the sync engine.
int? spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Meditate,
/// where everything is sealed.
String? keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
_idt.ByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
_idt.ByteData? nonce;
@override
_is.Table<_is.UuidValue?> get table => t;
/// Returns a shallow copy of this [LessonDone]
/// with some or all fields replaced by the given arguments.
@_is.useResult
LessonDone copyWith({
_is.UuidValue? id,
int? spaceId,
String? keyId,
_idt.ByteData? payload,
_idt.ByteData? nonce,
});
@override
Map<String, dynamic> toJson() {
return {
'__className__': 'LessonDone',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
@override
Map<String, dynamic> toJsonForProtocol() {
return {
'__className__': 'LessonDone',
if (id != null) 'id': id?.toJson(),
if (spaceId != null) 'spaceId': spaceId,
if (keyId != null) 'keyId': keyId,
'payload': payload.toJson(),
if (nonce != null) 'nonce': nonce?.toJson(),
};
}
static LessonDoneInclude include() {
return LessonDoneInclude._();
}
static LessonDoneIncludeList includeList({
_is.WhereExpressionBuilder<LessonDoneTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
LessonDoneInclude? include,
}) {
return LessonDoneIncludeList._(
where: where,
limit: limit,
offset: offset,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
include: include,
);
}
@override
String toString() {
return _is.SerializationManager.encode(this);
}
}
class _Undefined {}
class _LessonDoneImpl extends LessonDone {
_LessonDoneImpl({
_is.UuidValue? id,
int? spaceId,
String? keyId,
required _idt.ByteData payload,
_idt.ByteData? nonce,
}) : super._(
id: id,
spaceId: spaceId,
keyId: keyId,
payload: payload,
nonce: nonce,
);
/// Returns a shallow copy of this [LessonDone]
/// with some or all fields replaced by the given arguments.
@_is.useResult
@override
LessonDone copyWith({
Object? id = _Undefined,
Object? spaceId = _Undefined,
Object? keyId = _Undefined,
_idt.ByteData? payload,
Object? nonce = _Undefined,
}) {
return LessonDone(
id: id is _is.UuidValue? ? id : this.id,
spaceId: spaceId is int? ? spaceId : this.spaceId,
keyId: keyId is String? ? keyId : this.keyId,
payload: payload ?? this.payload.clone(),
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
);
}
}
class LessonDoneUpdateTable extends _is.UpdateTable<LessonDoneTable> {
LessonDoneUpdateTable(super.table);
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
table.spaceId,
value,
);
_is.ColumnValue<String, String> keyId(String? value) => _is.ColumnValue(
table.keyId,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> payload(_idt.ByteData value) =>
_is.ColumnValue(
table.payload,
value,
);
_is.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
_is.ColumnValue(
table.nonce,
value,
);
}
class LessonDoneTable extends _is.Table<_is.UuidValue?> {
LessonDoneTable({super.tableRelation}) : super(tableName: 'lesson_done') {
updateTable = LessonDoneUpdateTable(this);
spaceId = _is.ColumnInt(
'spaceId',
this,
);
keyId = _is.ColumnString(
'keyId',
this,
);
payload = _is.ColumnByteData(
'payload',
this,
);
nonce = _is.ColumnByteData(
'nonce',
this,
);
}
late final LessonDoneUpdateTable updateTable;
/// The space owning this row. Maintained by the sync engine.
late final _is.ColumnInt spaceId;
/// The key it is sealed with (ADR 0002's envelope); never null in Meditate,
/// where everything is sealed.
late final _is.ColumnString keyId;
/// The content, sealed: UTF-8 JSON with a `schemaVersion` once opened.
late final _is.ColumnByteData payload;
/// The sealing's nonce (ADR 0002's envelope).
late final _is.ColumnByteData nonce;
@override
List<_is.Column> get columns => [
id,
spaceId,
keyId,
payload,
nonce,
];
}
class LessonDoneInclude extends _is.IncludeObject {
LessonDoneInclude._();
@override
Map<String, _is.Include?> get includes => {};
@override
_is.Table<_is.UuidValue?> get table => LessonDone.t;
}
class LessonDoneIncludeList extends _is.IncludeList {
LessonDoneIncludeList._({
_is.WhereExpressionBuilder<LessonDoneTable>? where,
super.limit,
super.offset,
super.orderBy,
super.orderByList,
super.include,
}) {
super.where = where?.call(LessonDone.t);
}
@override
Map<String, _is.Include?> get includes => include?.includes ?? {};
@override
_is.Table<_is.UuidValue?> get table => LessonDone.t;
}
class LessonDoneRepository {
const LessonDoneRepository._();
/// Returns a list of [LessonDone]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<LessonDone>> find(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<LessonDoneTable>? where,
int? limit,
int? offset,
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.find<LessonDone>(
where: where?.call(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
limit: limit,
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Returns the first matching [LessonDone] 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<LessonDone?> findFirstRow(
_is.DatabaseSession session, {
_is.WhereExpressionBuilder<LessonDoneTable>? where,
int? offset,
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findFirstRow<LessonDone>(
where: where?.call(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
offset: offset,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Finds a single [LessonDone] by its [id] or null if no such row exists.
Future<LessonDone?> findById(
_is.DatabaseSession session,
_is.UuidValue id, {
_is.Transaction? transaction,
_is.LockMode? lockMode,
_is.LockBehavior? lockBehavior,
}) async {
return session.db.findById<LessonDone>(
id,
transaction: transaction,
lockMode: lockMode,
lockBehavior: lockBehavior,
);
}
/// Inserts all [LessonDone]s in the list and returns the inserted rows.
///
/// The returned [LessonDone]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<LessonDone>> insert(
_is.DatabaseSession session,
List<LessonDone> rows, {
_is.Transaction? transaction,
bool ignoreConflicts = false,
bool noReturn = false,
}) async {
return session.db.insert<LessonDone>(
rows,
transaction: transaction,
ignoreConflicts: ignoreConflicts,
noReturn: noReturn,
);
}
/// Inserts a single [LessonDone] and returns the inserted row.
///
/// The returned [LessonDone] will have its `id` field set.
Future<LessonDone> insertRow(
_is.DatabaseSession session,
LessonDone row, {
_is.Transaction? transaction,
}) async {
return session.db.insertRow<LessonDone>(
row,
transaction: transaction,
);
}
/// Upserts all [LessonDone]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 [LessonDone]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<LessonDone>> upsert(
_is.DatabaseSession session,
List<LessonDone> rows, {
required _is.ColumnSelections<LessonDoneTable> conflictColumns,
_is.ColumnSelections<LessonDoneTable>? updateColumns,
_is.WhereExpressionBuilder<LessonDoneTable>? updateWhere,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.upsert<LessonDone>(
rows,
conflictColumns: conflictColumns(LessonDone.t),
updateColumns: updateColumns?.call(LessonDone.t),
updateWhere: updateWhere?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Upserts a single [LessonDone] 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 [LessonDone] will have its `id` field set.
Future<LessonDone?> upsertRow(
_is.DatabaseSession session,
LessonDone row, {
required _is.ColumnSelections<LessonDoneTable> conflictColumns,
_is.ColumnSelections<LessonDoneTable>? updateColumns,
_is.WhereExpressionBuilder<LessonDoneTable>? updateWhere,
_is.Transaction? transaction,
}) async {
return session.db.upsertRow<LessonDone>(
row,
conflictColumns: conflictColumns(LessonDone.t),
updateColumns: updateColumns?.call(LessonDone.t),
updateWhere: updateWhere?.call(LessonDone.t),
transaction: transaction,
);
}
/// Updates all [LessonDone]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<LessonDone>> update(
_is.DatabaseSession session,
List<LessonDone> rows, {
_is.ColumnSelections<LessonDoneTable>? columns,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.update<LessonDone>(
rows,
columns: columns?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Updates a single [LessonDone]. 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<LessonDone> updateRow(
_is.DatabaseSession session,
LessonDone row, {
_is.ColumnSelections<LessonDoneTable>? columns,
_is.Transaction? transaction,
}) async {
return session.db.updateRow<LessonDone>(
row,
columns: columns?.call(LessonDone.t),
transaction: transaction,
);
}
/// Updates a single [LessonDone] by its [id] with the specified [columnValues].
/// Returns the updated row or null if no row with the given id exists.
Future<LessonDone?> updateById(
_is.DatabaseSession session,
_is.UuidValue id, {
required _is.ColumnValueListBuilder<LessonDoneUpdateTable> columnValues,
_is.Transaction? transaction,
}) async {
return session.db.updateById<LessonDone>(
id,
columnValues: columnValues(LessonDone.t.updateTable),
transaction: transaction,
);
}
/// Updates all [LessonDone]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<LessonDone>> updateWhere(
_is.DatabaseSession session, {
required _is.ColumnValueListBuilder<LessonDoneUpdateTable> columnValues,
required _is.WhereExpressionBuilder<LessonDoneTable> where,
int? limit,
int? offset,
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.updateWhere<LessonDone>(
columnValues: columnValues(LessonDone.t.updateTable),
where: where(LessonDone.t),
limit: limit,
offset: offset,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes all [LessonDone]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<LessonDone>> delete(
_is.DatabaseSession session,
List<LessonDone> rows, {
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.delete<LessonDone>(
rows,
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.t),
transaction: transaction,
noReturn: noReturn,
);
}
/// Deletes a single [LessonDone].
Future<LessonDone> deleteRow(
_is.DatabaseSession session,
LessonDone row, {
_is.Transaction? transaction,
}) async {
return session.db.deleteRow<LessonDone>(
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<LessonDone>> deleteWhere(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<LessonDoneTable> where,
_is.OrderByBuilder<LessonDoneTable>? orderBy,
_is.OrderByListBuilder<LessonDoneTable>? orderByList,
_is.Transaction? transaction,
bool noReturn = false,
}) async {
return session.db.deleteWhere<LessonDone>(
where: where(LessonDone.t),
orderBy: orderBy?.call(LessonDone.t),
orderByList: orderByList?.call(LessonDone.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<LessonDoneTable>? where,
int? limit,
_is.Transaction? transaction,
}) async {
return session.db.count<LessonDone>(
where: where?.call(LessonDone.t),
limit: limit,
transaction: transaction,
);
}
/// Acquires row-level locks on [LessonDone] rows matching the [where] expression.
Future<void> lockRows(
_is.DatabaseSession session, {
required _is.WhereExpressionBuilder<LessonDoneTable> where,
required _is.LockMode lockMode,
required _is.Transaction transaction,
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
}) async {
return session.db.lockRows<LessonDone>(
where: where(LessonDone.t),
lockMode: lockMode,
lockBehavior: lockBehavior,
transaction: transaction,
);
}
}

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