feat(privacy): the instance's side of private studysets (ADR 0019)
- `content_key`, a synced table in the person's space, keeps their data keys sealed to their account (a private studyset's, their personal one); `learning_path_answer` gains `keyId` and `nonce` for answers in private studysets. Additive only, on the server and the device. - Publishing refuses a private studyset, and a tree holding a private row (`PublishRefusal.private`); a private studyset elsewhere no longer breaks publishing a readable one. Encrypted tags are named as the device names them (`tagNames`): a name for a tag that isn't the person's, or an encrypted tag of the release without one, refuses it (`tagNamesMismatch`); a readable tag keeps its own name. - The connector leaves private studysets out and says how many there are. - The learner count reads no payload any more: Count me is kept by the instance (`counted_learner`) and the device reports which publications its copies come from (`learning_copy`, accepted only while counted, each report replacing the last); only the reporter's own studysets count. Deleting a person deletes both. - An upload says whether its file is encrypted: only a readable one over 20 MB is checked to be a PDF, and an encrypted one may be 4 KiB larger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
074d05ab4e
commit
a1c5ac3f86
34 changed files with 35261 additions and 198 deletions
|
|
@ -30,16 +30,24 @@ class AssetEndpoint extends Endpoint {
|
|||
/// is checked to be a PDF once stored, and deleted otherwise.
|
||||
static const maxOtherBytes = 20 * 1024 * 1024;
|
||||
|
||||
/// What encrypting adds to a file at most (ADR 0019): its key's ID, the
|
||||
/// nonce, the signature and the cipher's tag, with room to spare.
|
||||
static const encryptionOverhead = 4 * 1024;
|
||||
|
||||
/// How to upload the caller's file with [sha256] (lowercase hex) of
|
||||
/// [byteSize] bytes; null when the server has it already. Throws a
|
||||
/// [FileLimitExceededException] when it doesn't fit (see [FileLimits]).
|
||||
/// [byteSize] bytes, [encrypted] if it is a private studyset's (ADR
|
||||
/// 0019), which may be larger by [encryptionOverhead]; null when the
|
||||
/// server has it already. Throws a [FileLimitExceededException] when it
|
||||
/// doesn't fit (see [FileLimits]).
|
||||
Future<String?> startUpload(
|
||||
Session session,
|
||||
String sha256,
|
||||
int byteSize,
|
||||
) async {
|
||||
int byteSize, {
|
||||
bool? encrypted,
|
||||
}) async {
|
||||
_checkHash(sha256);
|
||||
if (byteSize <= 0 || byteSize > maxBytes) {
|
||||
final most = maxBytes + (encrypted ?? false ? encryptionOverhead : 0);
|
||||
if (byteSize <= 0 || byteSize > most) {
|
||||
throw ArgumentError.value(byteSize, 'byteSize', 'is out of range');
|
||||
}
|
||||
final space = _caller(session);
|
||||
|
|
@ -60,15 +68,21 @@ class AssetEndpoint extends Endpoint {
|
|||
return await session.storage.createUploadDescription(
|
||||
storageId: assetStorage,
|
||||
path: path,
|
||||
options: UploadOptions(contentLength: byteSize, maxFileSize: maxBytes),
|
||||
options: UploadOptions(contentLength: byteSize, maxFileSize: most),
|
||||
);
|
||||
}
|
||||
|
||||
/// Completes the upload of the caller's file with [sha256]; whether it is
|
||||
/// stored now. A file whose content has another hash is deleted, and so is
|
||||
/// one over [maxOtherBytes] that isn't a PDF, and one that no longer fits,
|
||||
/// with a [FileLimitExceededException].
|
||||
Future<bool> finishUpload(Session session, String sha256) async {
|
||||
/// a readable one over [maxOtherBytes] that isn't a PDF, and one that no
|
||||
/// longer fits, with a [FileLimitExceededException]. An [encrypted] file
|
||||
/// (ADR 0019) can't be looked into: its size alone counts, as the quotas'
|
||||
/// sizes do.
|
||||
Future<bool> finishUpload(
|
||||
Session session,
|
||||
String sha256, {
|
||||
bool? encrypted,
|
||||
}) async {
|
||||
_checkHash(sha256);
|
||||
final path = assetPath(_caller(session), sha256);
|
||||
if (!await session.storage.verifyUpload(
|
||||
|
|
@ -82,7 +96,10 @@ class AssetEndpoint extends Endpoint {
|
|||
path: path,
|
||||
);
|
||||
if (_hashOf(bytes) != sha256 ||
|
||||
(bytes.lengthInBytes > maxOtherBytes && !_isPdf(bytes))) {
|
||||
(!(encrypted ?? false) &&
|
||||
bytes.lengthInBytes > maxOtherBytes &&
|
||||
!_isPdf(bytes)) ||
|
||||
bytes.lengthInBytes > maxBytes + encryptionOverhead) {
|
||||
await session.storage.deleteFile(storageId: assetStorage, path: path);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ List<McpTool> connectorTools(Session session, UuidValue person) {
|
|||
"Lists the person's studysets in Halkyon Learn, a flashcard and "
|
||||
'learning app: each with its id, title, where it sits (the '
|
||||
'studysets above it), whether it is archived, and how many cards '
|
||||
'and notes it holds.',
|
||||
'and notes it holds; and how many are private, end-to-end '
|
||||
'encrypted, which nothing here can read.',
|
||||
inputSchema: const {'type': 'object', 'properties': <String, Object?>{}},
|
||||
call: (_) async => _json(await content.studysets()),
|
||||
),
|
||||
|
|
@ -174,14 +175,33 @@ class _Content(final Session _session, final UuidValue _person) {
|
|||
where: (t) => t.spaceId.equals(space),
|
||||
orderBy: (t) => t.position,
|
||||
);
|
||||
final alive = notDeleted(rows);
|
||||
// Private studysets (ADR 0019) are encrypted on the person's devices:
|
||||
// nothing here reads them, and only their number is told.
|
||||
final private = {
|
||||
for (final row in alive)
|
||||
if (row.keyId != null) row.id!,
|
||||
};
|
||||
_private = {
|
||||
for (final row in alive)
|
||||
if (row.keyId != null && !private.contains(row.parentId)) row.id!,
|
||||
};
|
||||
_privateTrees = private;
|
||||
return _studysets = [
|
||||
for (final row in notDeleted(rows))
|
||||
if (_decoded(row.payload) case final payload?)
|
||||
(row: row, payload: payload),
|
||||
for (final row in alive)
|
||||
if (row.keyId == null)
|
||||
if (_decoded(row.payload) case final payload?)
|
||||
(row: row, payload: payload),
|
||||
];
|
||||
}
|
||||
|
||||
Future<List<Map<String, Object?>>> studysets() async {
|
||||
/// The top-level private studysets [_all] left out.
|
||||
Set<UuidValue> _private = const {};
|
||||
|
||||
/// Every studyset of a private tree, the top-level ones and those inside.
|
||||
Set<UuidValue> _privateTrees = const {};
|
||||
|
||||
Future<Map<String, Object?>> studysets() async {
|
||||
final all = await _all();
|
||||
final space = await _spaceId();
|
||||
final cards = await _countBy(
|
||||
|
|
@ -196,21 +216,27 @@ class _Content(final Session _session, final UuidValue _person) {
|
|||
(note) => note.studysetId,
|
||||
all,
|
||||
);
|
||||
return [
|
||||
for (final (:row, :payload) in all)
|
||||
{
|
||||
'id': row.id!.uuid,
|
||||
'title': _title(payload),
|
||||
if (_text(payload['secondaryTitle']) case final subtitle
|
||||
when subtitle.isNotEmpty)
|
||||
'subtitle': subtitle,
|
||||
if (_path(row, all) case final path when path.isNotEmpty)
|
||||
'inside': path,
|
||||
if (row.archivedAt != null) 'archived': true,
|
||||
'cards': cards[row.id] ?? 0,
|
||||
'notes': notes[row.id] ?? 0,
|
||||
},
|
||||
];
|
||||
return {
|
||||
'studysets': [
|
||||
for (final (:row, :payload) in all)
|
||||
{
|
||||
'id': row.id!.uuid,
|
||||
'title': _title(payload),
|
||||
if (_text(payload['secondaryTitle']) case final subtitle
|
||||
when subtitle.isNotEmpty)
|
||||
'subtitle': subtitle,
|
||||
if (_path(row, all) case final path when path.isNotEmpty)
|
||||
'inside': path,
|
||||
if (row.archivedAt != null) 'archived': true,
|
||||
'cards': cards[row.id] ?? 0,
|
||||
'notes': notes[row.id] ?? 0,
|
||||
},
|
||||
],
|
||||
if (_private.isNotEmpty) ...{
|
||||
'private_studysets': _private.length,
|
||||
'private_note': _privateNote,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, Object?>> studyset(
|
||||
|
|
@ -221,7 +247,11 @@ class _Content(final Session _session, final UuidValue _person) {
|
|||
final all = await _all();
|
||||
final found = all.where((each) => each.row.id == id).firstOrNull;
|
||||
if (found == null) {
|
||||
throw const McpToolError('No studyset of the person has this id.');
|
||||
throw McpToolError(
|
||||
_privateTrees.contains(id)
|
||||
? _privateNote
|
||||
: 'No studyset of the person has this id.',
|
||||
);
|
||||
}
|
||||
final (:row, :payload) = found;
|
||||
final cards = await _cards({id});
|
||||
|
|
@ -592,6 +622,12 @@ String _richText(Object? editor) {
|
|||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/// What the tools say of private studysets (ADR 0019).
|
||||
const _privateNote =
|
||||
"Private studysets are end-to-end encrypted on the person's devices: "
|
||||
"the connector can't read them. The person can make one non-private "
|
||||
'in the app to let it be read here.';
|
||||
|
||||
/// A synced payload's JSON; null for a blank one, of a row being deleted.
|
||||
Map<String, Object?>? _decoded(ByteData payload) {
|
||||
if (payload.lengthInBytes == 0) {
|
||||
|
|
|
|||
|
|
@ -94,6 +94,20 @@ Future<AfterDeletion?> deleteLearnData(
|
|||
where: (t) => t.authUserId.equals(authUserId),
|
||||
transaction: transaction,
|
||||
);
|
||||
// Whether they chose to be counted, and the copies their device reported
|
||||
// (ADR 0019, D3).
|
||||
// DELETE FROM "counted_learner" WHERE "authUserId" = $1
|
||||
await CountedLearner.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(authUserId),
|
||||
transaction: transaction,
|
||||
);
|
||||
// DELETE FROM "learning_copy" WHERE "authUserId" = $1
|
||||
await LearningCopy.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(authUserId),
|
||||
transaction: transaction,
|
||||
);
|
||||
if (published.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
### A device's report of the studysets copied from one publication (ADR 0019,
|
||||
### D3): the copy's studysets, its topics with it.
|
||||
class: CopyReport
|
||||
fields:
|
||||
### The publication's public id.
|
||||
publicationId: String
|
||||
### The person's studysets that are part of the copy.
|
||||
studysetIds: List<UuidValue>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
### A person who chose to be counted as learning the published studysets
|
||||
### their copies come from (ADR 0005, rule 8; ADR 0019): kept by the instance,
|
||||
### as their settings are encrypted. Never sent.
|
||||
class: CountedLearner
|
||||
table: counted_learner
|
||||
serverOnly: true
|
||||
fields:
|
||||
### The person.
|
||||
authUserId: UuidValue
|
||||
### When they chose it, in UTC.
|
||||
since: DateTime
|
||||
indexes:
|
||||
counted_learner_person_idx:
|
||||
fields: authUserId
|
||||
unique: true
|
||||
|
|
@ -1,6 +1,3 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_learn_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_learn_server/src/studyset/deleted_studysets.dart';
|
||||
|
|
@ -33,12 +30,14 @@ int? learnersShown(int learners) {
|
|||
|
||||
/// Counts, for every published studyset, the people who learn it on this
|
||||
/// instance (ADR 0005, rule 4; decided 2026-10-09): those with an identity
|
||||
/// who chose to be counted (`countLearning` in their settings) and studied
|
||||
/// a copy of it on [learningDays] or more days of the 30 before [now].
|
||||
/// Learning activity is shown, never ranked, and only a number the instance
|
||||
/// computes itself counts: another instance could claim any. Nothing about
|
||||
/// a person leaves the instance, and a payload it can't read counts for
|
||||
/// nothing.
|
||||
/// who chose to be counted ([CountedLearner]) and studied a copy of it on
|
||||
/// [learningDays] or more days of the 30 before [now]. Which studysets are
|
||||
/// copies of what, their devices report ([LearningCopy], ADR 0019, D3): a
|
||||
/// private copy's origin is encrypted. Only a reported studyset that is the
|
||||
/// person's own and not in Recently deleted counts. Learning activity is
|
||||
/// shown, never ranked, and only a number the instance computes itself
|
||||
/// counts: another instance could claim any. Nothing about a person leaves
|
||||
/// the instance.
|
||||
Future<void> countLearnersOf(Session session, {required DateTime now}) async {
|
||||
// SELECT … FROM "published_studyset"
|
||||
final published = await PublishedStudyset.db.find(session);
|
||||
|
|
@ -49,61 +48,63 @@ Future<void> countLearnersOf(Session session, {required DateTime now}) async {
|
|||
for (final studyset in published) studyset.publicId.uuid,
|
||||
};
|
||||
|
||||
// The people who chose to be counted, by their space: their settings row
|
||||
// of the smallest id, as their devices fold several.
|
||||
// SELECT … FROM "person_settings" ORDER BY "id"
|
||||
final counted = <int>{};
|
||||
final seen = <int>{};
|
||||
for (final settings in await PersonSettings.db.find(
|
||||
session,
|
||||
orderBy: (t) => t.id,
|
||||
)) {
|
||||
if (seen.add(settings.spaceId!) &&
|
||||
_json(settings.payload)?['countLearning'] == true) {
|
||||
counted.add(settings.spaceId!);
|
||||
}
|
||||
}
|
||||
if (counted.isEmpty) {
|
||||
// The people who chose to be counted, of them those with an identity.
|
||||
// SELECT "authUserId" FROM "counted_learner"
|
||||
final counted = {
|
||||
for (final learner in await CountedLearner.db.find(session))
|
||||
learner.authUserId,
|
||||
};
|
||||
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = ANY($1)
|
||||
final people = counted.isEmpty
|
||||
? const <UuidValue>{}
|
||||
: {
|
||||
for (final person in await AuthUser.db.find(
|
||||
session,
|
||||
where: (t) => t.id.inSet(counted),
|
||||
))
|
||||
if (person.scopeNames.contains(identified.name)) person.id!,
|
||||
};
|
||||
if (people.isEmpty) {
|
||||
await _store(session, published, const {});
|
||||
return;
|
||||
}
|
||||
// Of them, those with an identity: a space is its person's.
|
||||
// SELECT … FROM "offline_sync_spaces" WHERE "id" = ANY($1)
|
||||
final people = {
|
||||
// Their spaces: a space is its person's.
|
||||
// SELECT … FROM "offline_sync_spaces" WHERE "uuidSpaceId" = ANY($1)
|
||||
final spaceOfPerson = {
|
||||
for (final space in await OfflineSyncSpace.db.find(
|
||||
session,
|
||||
where: (t) => t.id.inSet(counted),
|
||||
where: (t) => t.uuidSpaceId.inSet(people),
|
||||
))
|
||||
space.id!: space.uuidSpaceId,
|
||||
space.uuidSpaceId: space.id!,
|
||||
};
|
||||
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = ANY($1)
|
||||
final identifiedPeople = {
|
||||
for (final person in await AuthUser.db.find(
|
||||
session,
|
||||
where: (t) => t.id.inSet(people.values.toSet()),
|
||||
// Their studysets not waiting in Recently deleted, by space; read for
|
||||
// their tree alone, never their content.
|
||||
// SELECT … FROM "studyset" WHERE "spaceId" = ANY($1)
|
||||
final alive = {
|
||||
for (final studyset in notDeleted(
|
||||
await Studyset.db.find(
|
||||
session,
|
||||
where: (t) => t.spaceId.inSet(spaceOfPerson.values.toSet()),
|
||||
),
|
||||
))
|
||||
if (person.scopeNames.contains(identified.name)) person.id,
|
||||
};
|
||||
final spaces = {
|
||||
for (final MapEntry(key: space, value: person) in people.entries)
|
||||
if (identifiedPeople.contains(person)) space,
|
||||
studyset.id!: studyset.spaceId!,
|
||||
};
|
||||
|
||||
// Their studysets that are copies, and of which published studyset; a
|
||||
// copy waiting in Recently deleted is no one's learning.
|
||||
// SELECT … FROM "studyset" WHERE "spaceId" = ANY($1)
|
||||
// The copies their devices reported, of a published studyset, each a
|
||||
// studyset of the reporter's own.
|
||||
// SELECT … FROM "learning_copy" WHERE "authUserId" = ANY($1)
|
||||
final publicationOf = <UuidValue, String>{};
|
||||
final spaceOf = <UuidValue, int>{};
|
||||
for (final studyset in notDeleted(
|
||||
await Studyset.db.find(session, where: (t) => t.spaceId.inSet(spaces)),
|
||||
for (final copy in await LearningCopy.db.find(
|
||||
session,
|
||||
where: (t) => t.authUserId.inSet(people),
|
||||
)) {
|
||||
if (_json(studyset.payload)?['published']
|
||||
case {
|
||||
'publication': final String publication,
|
||||
}
|
||||
when publications.contains(publication)) {
|
||||
publicationOf[studyset.id!] = publication;
|
||||
spaceOf[studyset.id!] = studyset.spaceId!;
|
||||
final space = spaceOfPerson[copy.authUserId];
|
||||
if (space != null &&
|
||||
alive[copy.studysetId] == space &&
|
||||
publications.contains(copy.publicationId)) {
|
||||
publicationOf[copy.studysetId] = copy.publicationId;
|
||||
spaceOf[copy.studysetId] = space;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,18 +161,3 @@ Future<void> _store(
|
|||
columns: (t) => [t.learners],
|
||||
);
|
||||
}
|
||||
|
||||
/// A synced payload's JSON; null for a blank one or one that isn't JSON.
|
||||
Map<String, Object?>? _json(ByteData payload) {
|
||||
if (payload.lengthInBytes == 0) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return switch (jsonDecode(utf8.decode(Uint8List.sublistView(payload)))) {
|
||||
final Map<String, Object?> json => json,
|
||||
_ => null,
|
||||
};
|
||||
} on FormatException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
### A studyset of a counted person's that is a copy of a published one, as
|
||||
### their device reports it (ADR 0019, D3): the instance can't read a private
|
||||
### copy's origin. Replaced with each report; gone once they stop being
|
||||
### counted. Never sent.
|
||||
class: LearningCopy
|
||||
table: learning_copy
|
||||
serverOnly: true
|
||||
fields:
|
||||
### Whose copy it is.
|
||||
authUserId: UuidValue
|
||||
### The publication's public id.
|
||||
publicationId: String
|
||||
### The person's studyset that is part of the copy.
|
||||
studysetId: UuidValue
|
||||
indexes:
|
||||
learning_copy_person_idx:
|
||||
fields: authUserId
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import 'package:clock/clock.dart';
|
||||
import 'package:halkyon_base_server/base.dart';
|
||||
import 'package:halkyon_learn_server/src/generated/protocol.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// Whether the caller is counted as learning what their copies come from
|
||||
/// ("Learn this here", ADR 0005, rule 8), and which publications those are
|
||||
/// (ADR 0019, D3): their settings and private copies are encrypted, so their
|
||||
/// device says so here, and only while they chose to be counted. Only
|
||||
/// people with an identity are counted.
|
||||
class LearningCountEndpoint extends Endpoint {
|
||||
@override
|
||||
Set<Scope> get requiredScopes => {identified};
|
||||
|
||||
/// The most publications a report may name.
|
||||
static const maxPublications = 1000;
|
||||
|
||||
/// The most studysets a report may name in all.
|
||||
static const maxStudysets = 10000;
|
||||
|
||||
/// Whether the caller chose to be counted.
|
||||
Future<bool> counted(Session session) async =>
|
||||
// SELECT count(*) FROM "counted_learner" WHERE "authUserId" = $1
|
||||
await CountedLearner.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(session.authenticated!.authUserId),
|
||||
) >
|
||||
0;
|
||||
|
||||
/// Counts the caller from now on, or no longer ([counted] false), which
|
||||
/// also forgets the copies their device reported.
|
||||
Future<void> setCounted(Session session, bool counted) async {
|
||||
final person = session.authenticated!.authUserId;
|
||||
await session.db.transaction((transaction) async {
|
||||
if (counted) {
|
||||
// SELECT … FROM "counted_learner" WHERE "authUserId" = $1
|
||||
final existing = await CountedLearner.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(person),
|
||||
transaction: transaction,
|
||||
);
|
||||
if (existing == null) {
|
||||
// INSERT INTO "counted_learner" ("authUserId", "since") …
|
||||
await CountedLearner.db.insertRow(
|
||||
session,
|
||||
CountedLearner(authUserId: person, since: clock.now().toUtc()),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// DELETE FROM "counted_learner" WHERE "authUserId" = $1
|
||||
await CountedLearner.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(person),
|
||||
transaction: transaction,
|
||||
);
|
||||
// DELETE FROM "learning_copy" WHERE "authUserId" = $1
|
||||
await LearningCopy.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(person),
|
||||
transaction: transaction,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// Replaces the copies the caller's device reported before with
|
||||
/// [copies]; whether they were kept, which they are only while the
|
||||
/// caller chose to be counted. Which of them are really the caller's,
|
||||
/// and still there, the count works out from their own rows.
|
||||
Future<bool> reportCopies(Session session, List<CopyReport> copies) async {
|
||||
if (copies.length > maxPublications ||
|
||||
copies.fold(0, (sum, copy) => sum + copy.studysetIds.length) >
|
||||
maxStudysets) {
|
||||
throw ArgumentError.value(copies.length, 'copies', 'too many');
|
||||
}
|
||||
final person = session.authenticated!.authUserId;
|
||||
return await session.db.transaction((transaction) async {
|
||||
// SELECT count(*) FROM "counted_learner" WHERE "authUserId" = $1
|
||||
if (await CountedLearner.db.count(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(person),
|
||||
transaction: transaction,
|
||||
) ==
|
||||
0) {
|
||||
return false;
|
||||
}
|
||||
// DELETE FROM "learning_copy" WHERE "authUserId" = $1
|
||||
await LearningCopy.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.authUserId.equals(person),
|
||||
transaction: transaction,
|
||||
);
|
||||
// INSERT INTO "learning_copy" ("authUserId", "publicationId",
|
||||
// "studysetId") VALUES …
|
||||
await LearningCopy.db.insert(session, [
|
||||
for (final copy in copies)
|
||||
for (final studyset in copy.studysetIds.toSet())
|
||||
LearningCopy(
|
||||
authUserId: person,
|
||||
publicationId: copy.publicationId,
|
||||
studysetId: studyset,
|
||||
),
|
||||
], transaction: transaction);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,8 @@
|
|||
import 'package:halkyon_base_server/halkyon_base_server.dart' as _ik8molhd;
|
||||
import 'package:halkyon_learn_server/src/generated/discussion/discussion_target.dart'
|
||||
as _ike2gc23;
|
||||
import 'package:halkyon_learn_server/src/generated/explore/copy_report.dart'
|
||||
as _ipludy4j;
|
||||
import 'package:halkyon_learn_server/src/generated/explore/explore_query.dart'
|
||||
as _isbgipyv;
|
||||
import 'package:halkyon_learn_server/src/generated/future_calls.dart'
|
||||
|
|
@ -30,6 +32,7 @@ import '../auth/devices_endpoint.dart' as _iduct7ue;
|
|||
import '../dictionary/dictionary_endpoint.dart' as _ilgtwlto;
|
||||
import '../discussion/discussion_endpoint.dart' as _ienplr78;
|
||||
import '../explore/explore_endpoint.dart' as _i897jtwc;
|
||||
import '../explore/learning_count_endpoint.dart' as _is6pj3q5;
|
||||
import '../feedback/feedback_endpoint.dart' as _i3n4p88h;
|
||||
import '../instance/instance_endpoint.dart' as _i3g8bfc2;
|
||||
import '../publish/publish_endpoint.dart' as _iyiav298;
|
||||
|
|
@ -81,6 +84,12 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
'explore',
|
||||
null,
|
||||
),
|
||||
'learningCount': _is6pj3q5.LearningCountEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
'learningCount',
|
||||
null,
|
||||
),
|
||||
'feedback': _i3n4p88h.FeedbackEndpoint()
|
||||
..initialize(
|
||||
server,
|
||||
|
|
@ -123,6 +132,11 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
type: _is.getType<int>(),
|
||||
nullable: false,
|
||||
),
|
||||
'encrypted': _is.ParameterDescription(
|
||||
name: 'encrypted',
|
||||
type: _is.getType<bool?>(),
|
||||
nullable: true,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
|
|
@ -133,6 +147,7 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
session,
|
||||
params['sha256'],
|
||||
params['byteSize'],
|
||||
encrypted: params['encrypted'],
|
||||
),
|
||||
),
|
||||
'finishUpload': _is.MethodConnector(
|
||||
|
|
@ -143,6 +158,11 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
type: _is.getType<String>(),
|
||||
nullable: false,
|
||||
),
|
||||
'encrypted': _is.ParameterDescription(
|
||||
name: 'encrypted',
|
||||
type: _is.getType<bool?>(),
|
||||
nullable: true,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
|
|
@ -152,6 +172,7 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
(endpoints['asset'] as _ibwtj1i8.AssetEndpoint).finishUpload(
|
||||
session,
|
||||
params['sha256'],
|
||||
encrypted: params['encrypted'],
|
||||
),
|
||||
),
|
||||
'usage': _is.MethodConnector(
|
||||
|
|
@ -981,6 +1002,66 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
),
|
||||
},
|
||||
);
|
||||
connectors['learningCount'] = _is.EndpointConnector(
|
||||
name: 'learningCount',
|
||||
endpoint: endpoints['learningCount']!,
|
||||
methodConnectors: {
|
||||
'counted': _is.MethodConnector(
|
||||
name: 'counted',
|
||||
params: {},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['learningCount']
|
||||
as _is6pj3q5.LearningCountEndpoint)
|
||||
.counted(session),
|
||||
),
|
||||
'setCounted': _is.MethodConnector(
|
||||
name: 'setCounted',
|
||||
params: {
|
||||
'counted': _is.ParameterDescription(
|
||||
name: 'counted',
|
||||
type: _is.getType<bool>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['learningCount']
|
||||
as _is6pj3q5.LearningCountEndpoint)
|
||||
.setCounted(
|
||||
session,
|
||||
params['counted'],
|
||||
),
|
||||
),
|
||||
'reportCopies': _is.MethodConnector(
|
||||
name: 'reportCopies',
|
||||
params: {
|
||||
'copies': _is.ParameterDescription(
|
||||
name: 'copies',
|
||||
type: _is.getType<List<_ipludy4j.CopyReport>>(),
|
||||
nullable: false,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
_is.Session session,
|
||||
Map<String, dynamic> params,
|
||||
) async =>
|
||||
(endpoints['learningCount']
|
||||
as _is6pj3q5.LearningCountEndpoint)
|
||||
.reportCopies(
|
||||
session,
|
||||
params['copies'],
|
||||
),
|
||||
),
|
||||
},
|
||||
);
|
||||
connectors['feedback'] = _is.EndpointConnector(
|
||||
name: 'feedback',
|
||||
endpoint: endpoints['feedback']!,
|
||||
|
|
@ -1196,6 +1277,11 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
type: _is.getType<String?>(),
|
||||
nullable: true,
|
||||
),
|
||||
'tagNames': _is.ParameterDescription(
|
||||
name: 'tagNames',
|
||||
type: _is.getType<Map<String, String>?>(),
|
||||
nullable: true,
|
||||
),
|
||||
},
|
||||
call:
|
||||
(
|
||||
|
|
@ -1208,6 +1294,7 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
params['slug'],
|
||||
params['notes'],
|
||||
description: params['description'],
|
||||
tagNames: params['tagNames'],
|
||||
),
|
||||
),
|
||||
'withdraw': _is.MethodConnector(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/* 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_learn_server/src/generated/protocol.dart' as _i5lsenq5;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// A device's report of the studysets copied from one publication (ADR 0019,
|
||||
/// D3): the copy's studysets, its topics with it.
|
||||
abstract class CopyReport
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
CopyReport._({
|
||||
required this.publicationId,
|
||||
required this.studysetIds,
|
||||
});
|
||||
|
||||
factory CopyReport({
|
||||
required String publicationId,
|
||||
required List<_is.UuidValue> studysetIds,
|
||||
}) = _CopyReportImpl;
|
||||
|
||||
factory CopyReport.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return CopyReport(
|
||||
publicationId: jsonSerialization['publicationId'] as String,
|
||||
studysetIds: _i5lsenq5.Protocol().deserialize<List<_is.UuidValue>>(
|
||||
jsonSerialization['studysetIds'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The publication's public id.
|
||||
String publicationId;
|
||||
|
||||
/// The person's studysets that are part of the copy.
|
||||
List<_is.UuidValue> studysetIds;
|
||||
|
||||
/// Returns a shallow copy of this [CopyReport]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
CopyReport copyWith({
|
||||
String? publicationId,
|
||||
List<_is.UuidValue>? studysetIds,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'CopyReport',
|
||||
'publicationId': publicationId,
|
||||
'studysetIds': studysetIds.toJson(valueToJson: (v) => v.toJson()),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'CopyReport',
|
||||
'publicationId': publicationId,
|
||||
'studysetIds': studysetIds.toJson(valueToJson: (v) => v.toJson()),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyReportImpl extends CopyReport {
|
||||
_CopyReportImpl({
|
||||
required String publicationId,
|
||||
required List<_is.UuidValue> studysetIds,
|
||||
}) : super._(
|
||||
publicationId: publicationId,
|
||||
studysetIds: studysetIds,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [CopyReport]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
CopyReport copyWith({
|
||||
String? publicationId,
|
||||
List<_is.UuidValue>? studysetIds,
|
||||
}) {
|
||||
return CopyReport(
|
||||
publicationId: publicationId ?? this.publicationId,
|
||||
studysetIds: studysetIds ?? this.studysetIds.map((e0) => e0).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,607 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// A person who chose to be counted as learning the published studysets
|
||||
/// their copies come from (ADR 0005, rule 8; ADR 0019): kept by the instance,
|
||||
/// as their settings are encrypted. Never sent.
|
||||
abstract class CountedLearner
|
||||
implements _is.TableRow<int?>, _is.ProtocolSerialization {
|
||||
CountedLearner._({
|
||||
this.id,
|
||||
required this.authUserId,
|
||||
required this.since,
|
||||
});
|
||||
|
||||
factory CountedLearner({
|
||||
int? id,
|
||||
required _is.UuidValue authUserId,
|
||||
required DateTime since,
|
||||
}) = _CountedLearnerImpl;
|
||||
|
||||
factory CountedLearner.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return CountedLearner(
|
||||
id: jsonSerialization['id'] as int?,
|
||||
authUserId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['authUserId'],
|
||||
),
|
||||
since: _is.DateTimeJsonExtension.fromJson(jsonSerialization['since']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = CountedLearnerTable();
|
||||
|
||||
static const db = CountedLearnerRepository._();
|
||||
|
||||
@override
|
||||
int? id;
|
||||
|
||||
/// The person.
|
||||
_is.UuidValue authUserId;
|
||||
|
||||
/// When they chose it, in UTC.
|
||||
DateTime since;
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [CountedLearner]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
CountedLearner copyWith({
|
||||
int? id,
|
||||
_is.UuidValue? authUserId,
|
||||
DateTime? since,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'CountedLearner',
|
||||
if (id != null) 'id': id,
|
||||
'authUserId': authUserId.toJson(),
|
||||
'since': since.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static CountedLearnerInclude include() {
|
||||
return CountedLearnerInclude._();
|
||||
}
|
||||
|
||||
static CountedLearnerIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
CountedLearnerInclude? include,
|
||||
}) {
|
||||
return CountedLearnerIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _CountedLearnerImpl extends CountedLearner {
|
||||
_CountedLearnerImpl({
|
||||
int? id,
|
||||
required _is.UuidValue authUserId,
|
||||
required DateTime since,
|
||||
}) : super._(
|
||||
id: id,
|
||||
authUserId: authUserId,
|
||||
since: since,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [CountedLearner]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
CountedLearner copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? authUserId,
|
||||
DateTime? since,
|
||||
}) {
|
||||
return CountedLearner(
|
||||
id: id is int? ? id : this.id,
|
||||
authUserId: authUserId ?? this.authUserId,
|
||||
since: since ?? this.since,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CountedLearnerUpdateTable extends _is.UpdateTable<CountedLearnerTable> {
|
||||
CountedLearnerUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
|
||||
_is.UuidValue value,
|
||||
) => _is.ColumnValue(
|
||||
table.authUserId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> since(DateTime value) => _is.ColumnValue(
|
||||
table.since,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class CountedLearnerTable extends _is.Table<int?> {
|
||||
CountedLearnerTable({super.tableRelation})
|
||||
: super(tableName: 'counted_learner') {
|
||||
updateTable = CountedLearnerUpdateTable(this);
|
||||
authUserId = _is.ColumnUuid(
|
||||
'authUserId',
|
||||
this,
|
||||
);
|
||||
since = _is.ColumnDateTime(
|
||||
'since',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final CountedLearnerUpdateTable updateTable;
|
||||
|
||||
/// The person.
|
||||
late final _is.ColumnUuid authUserId;
|
||||
|
||||
/// When they chose it, in UTC.
|
||||
late final _is.ColumnDateTime since;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
authUserId,
|
||||
since,
|
||||
];
|
||||
}
|
||||
|
||||
class CountedLearnerInclude extends _is.IncludeObject {
|
||||
CountedLearnerInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => CountedLearner.t;
|
||||
}
|
||||
|
||||
class CountedLearnerIncludeList extends _is.IncludeList {
|
||||
CountedLearnerIncludeList._({
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(CountedLearner.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => CountedLearner.t;
|
||||
}
|
||||
|
||||
class CountedLearnerRepository {
|
||||
const CountedLearnerRepository._();
|
||||
|
||||
/// Returns a list of [CountedLearner]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<CountedLearner>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<CountedLearner>(
|
||||
where: where?.call(CountedLearner.t),
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [CountedLearner] 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<CountedLearner?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<CountedLearner>(
|
||||
where: where?.call(CountedLearner.t),
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [CountedLearner] by its [id] or null if no such row exists.
|
||||
Future<CountedLearner?> findById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<CountedLearner>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [CountedLearner]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [CountedLearner]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<CountedLearner>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<CountedLearner> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<CountedLearner>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [CountedLearner] and returns the inserted row.
|
||||
///
|
||||
/// The returned [CountedLearner] will have its `id` field set.
|
||||
Future<CountedLearner> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
CountedLearner row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<CountedLearner>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [CountedLearner]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 [CountedLearner]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<CountedLearner>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<CountedLearner> rows, {
|
||||
required _is.ColumnSelections<CountedLearnerTable> conflictColumns,
|
||||
_is.ColumnSelections<CountedLearnerTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<CountedLearner>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(CountedLearner.t),
|
||||
updateColumns: updateColumns?.call(CountedLearner.t),
|
||||
updateWhere: updateWhere?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [CountedLearner] 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 [CountedLearner] will have its `id` field set.
|
||||
Future<CountedLearner?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
CountedLearner row, {
|
||||
required _is.ColumnSelections<CountedLearnerTable> conflictColumns,
|
||||
_is.ColumnSelections<CountedLearnerTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<CountedLearnerTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<CountedLearner>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(CountedLearner.t),
|
||||
updateColumns: updateColumns?.call(CountedLearner.t),
|
||||
updateWhere: updateWhere?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [CountedLearner]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<CountedLearner>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<CountedLearner> rows, {
|
||||
_is.ColumnSelections<CountedLearnerTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<CountedLearner>(
|
||||
rows,
|
||||
columns: columns?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [CountedLearner]. 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<CountedLearner> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
CountedLearner row, {
|
||||
_is.ColumnSelections<CountedLearnerTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<CountedLearner>(
|
||||
row,
|
||||
columns: columns?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [CountedLearner] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<CountedLearner?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
required _is.ColumnValueListBuilder<CountedLearnerUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<CountedLearner>(
|
||||
id,
|
||||
columnValues: columnValues(CountedLearner.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [CountedLearner]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<CountedLearner>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<CountedLearnerUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<CountedLearnerTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<CountedLearner>(
|
||||
columnValues: columnValues(CountedLearner.t.updateTable),
|
||||
where: where(CountedLearner.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [CountedLearner]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<CountedLearner>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<CountedLearner> rows, {
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<CountedLearner>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [CountedLearner].
|
||||
Future<CountedLearner> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
CountedLearner row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<CountedLearner>(
|
||||
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<CountedLearner>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<CountedLearnerTable> where,
|
||||
_is.OrderByBuilder<CountedLearnerTable>? orderBy,
|
||||
_is.OrderByListBuilder<CountedLearnerTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<CountedLearner>(
|
||||
where: where(CountedLearner.t),
|
||||
orderBy: orderBy?.call(CountedLearner.t),
|
||||
orderByList: orderByList?.call(CountedLearner.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<CountedLearnerTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<CountedLearner>(
|
||||
where: where?.call(CountedLearner.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [CountedLearner] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<CountedLearnerTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<CountedLearner>(
|
||||
where: where(CountedLearner.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,637 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
|
||||
/// A studyset of a counted person's that is a copy of a published one, as
|
||||
/// their device reports it (ADR 0019, D3): the instance can't read a private
|
||||
/// copy's origin. Replaced with each report; gone once they stop being
|
||||
/// counted. Never sent.
|
||||
abstract class LearningCopy
|
||||
implements _is.TableRow<int?>, _is.ProtocolSerialization {
|
||||
LearningCopy._({
|
||||
this.id,
|
||||
required this.authUserId,
|
||||
required this.publicationId,
|
||||
required this.studysetId,
|
||||
});
|
||||
|
||||
factory LearningCopy({
|
||||
int? id,
|
||||
required _is.UuidValue authUserId,
|
||||
required String publicationId,
|
||||
required _is.UuidValue studysetId,
|
||||
}) = _LearningCopyImpl;
|
||||
|
||||
factory LearningCopy.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return LearningCopy(
|
||||
id: jsonSerialization['id'] as int?,
|
||||
authUserId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['authUserId'],
|
||||
),
|
||||
publicationId: jsonSerialization['publicationId'] as String,
|
||||
studysetId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['studysetId'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = LearningCopyTable();
|
||||
|
||||
static const db = LearningCopyRepository._();
|
||||
|
||||
@override
|
||||
int? id;
|
||||
|
||||
/// Whose copy it is.
|
||||
_is.UuidValue authUserId;
|
||||
|
||||
/// The publication's public id.
|
||||
String publicationId;
|
||||
|
||||
/// The person's studyset that is part of the copy.
|
||||
_is.UuidValue studysetId;
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [LearningCopy]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
LearningCopy copyWith({
|
||||
int? id,
|
||||
_is.UuidValue? authUserId,
|
||||
String? publicationId,
|
||||
_is.UuidValue? studysetId,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'LearningCopy',
|
||||
if (id != null) 'id': id,
|
||||
'authUserId': authUserId.toJson(),
|
||||
'publicationId': publicationId,
|
||||
'studysetId': studysetId.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static LearningCopyInclude include() {
|
||||
return LearningCopyInclude._();
|
||||
}
|
||||
|
||||
static LearningCopyIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
LearningCopyInclude? include,
|
||||
}) {
|
||||
return LearningCopyIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _LearningCopyImpl extends LearningCopy {
|
||||
_LearningCopyImpl({
|
||||
int? id,
|
||||
required _is.UuidValue authUserId,
|
||||
required String publicationId,
|
||||
required _is.UuidValue studysetId,
|
||||
}) : super._(
|
||||
id: id,
|
||||
authUserId: authUserId,
|
||||
publicationId: publicationId,
|
||||
studysetId: studysetId,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [LearningCopy]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
LearningCopy copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? authUserId,
|
||||
String? publicationId,
|
||||
_is.UuidValue? studysetId,
|
||||
}) {
|
||||
return LearningCopy(
|
||||
id: id is int? ? id : this.id,
|
||||
authUserId: authUserId ?? this.authUserId,
|
||||
publicationId: publicationId ?? this.publicationId,
|
||||
studysetId: studysetId ?? this.studysetId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LearningCopyUpdateTable extends _is.UpdateTable<LearningCopyTable> {
|
||||
LearningCopyUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> authUserId(
|
||||
_is.UuidValue value,
|
||||
) => _is.ColumnValue(
|
||||
table.authUserId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> publicationId(String value) =>
|
||||
_is.ColumnValue(
|
||||
table.publicationId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> studysetId(
|
||||
_is.UuidValue value,
|
||||
) => _is.ColumnValue(
|
||||
table.studysetId,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class LearningCopyTable extends _is.Table<int?> {
|
||||
LearningCopyTable({super.tableRelation}) : super(tableName: 'learning_copy') {
|
||||
updateTable = LearningCopyUpdateTable(this);
|
||||
authUserId = _is.ColumnUuid(
|
||||
'authUserId',
|
||||
this,
|
||||
);
|
||||
publicationId = _is.ColumnString(
|
||||
'publicationId',
|
||||
this,
|
||||
);
|
||||
studysetId = _is.ColumnUuid(
|
||||
'studysetId',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final LearningCopyUpdateTable updateTable;
|
||||
|
||||
/// Whose copy it is.
|
||||
late final _is.ColumnUuid authUserId;
|
||||
|
||||
/// The publication's public id.
|
||||
late final _is.ColumnString publicationId;
|
||||
|
||||
/// The person's studyset that is part of the copy.
|
||||
late final _is.ColumnUuid studysetId;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
authUserId,
|
||||
publicationId,
|
||||
studysetId,
|
||||
];
|
||||
}
|
||||
|
||||
class LearningCopyInclude extends _is.IncludeObject {
|
||||
LearningCopyInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => LearningCopy.t;
|
||||
}
|
||||
|
||||
class LearningCopyIncludeList extends _is.IncludeList {
|
||||
LearningCopyIncludeList._({
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(LearningCopy.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => LearningCopy.t;
|
||||
}
|
||||
|
||||
class LearningCopyRepository {
|
||||
const LearningCopyRepository._();
|
||||
|
||||
/// Returns a list of [LearningCopy]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<LearningCopy>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<LearningCopy>(
|
||||
where: where?.call(LearningCopy.t),
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [LearningCopy] 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<LearningCopy?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<LearningCopy>(
|
||||
where: where?.call(LearningCopy.t),
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [LearningCopy] by its [id] or null if no such row exists.
|
||||
Future<LearningCopy?> findById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<LearningCopy>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [LearningCopy]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [LearningCopy]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<LearningCopy>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<LearningCopy> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<LearningCopy>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [LearningCopy] and returns the inserted row.
|
||||
///
|
||||
/// The returned [LearningCopy] will have its `id` field set.
|
||||
Future<LearningCopy> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
LearningCopy row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<LearningCopy>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [LearningCopy]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 [LearningCopy]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<LearningCopy>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<LearningCopy> rows, {
|
||||
required _is.ColumnSelections<LearningCopyTable> conflictColumns,
|
||||
_is.ColumnSelections<LearningCopyTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<LearningCopy>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(LearningCopy.t),
|
||||
updateColumns: updateColumns?.call(LearningCopy.t),
|
||||
updateWhere: updateWhere?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [LearningCopy] 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 [LearningCopy] will have its `id` field set.
|
||||
Future<LearningCopy?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
LearningCopy row, {
|
||||
required _is.ColumnSelections<LearningCopyTable> conflictColumns,
|
||||
_is.ColumnSelections<LearningCopyTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<LearningCopyTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<LearningCopy>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(LearningCopy.t),
|
||||
updateColumns: updateColumns?.call(LearningCopy.t),
|
||||
updateWhere: updateWhere?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [LearningCopy]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<LearningCopy>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<LearningCopy> rows, {
|
||||
_is.ColumnSelections<LearningCopyTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<LearningCopy>(
|
||||
rows,
|
||||
columns: columns?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [LearningCopy]. 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<LearningCopy> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
LearningCopy row, {
|
||||
_is.ColumnSelections<LearningCopyTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<LearningCopy>(
|
||||
row,
|
||||
columns: columns?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [LearningCopy] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<LearningCopy?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
required _is.ColumnValueListBuilder<LearningCopyUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<LearningCopy>(
|
||||
id,
|
||||
columnValues: columnValues(LearningCopy.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [LearningCopy]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<LearningCopy>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<LearningCopyUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<LearningCopyTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<LearningCopy>(
|
||||
columnValues: columnValues(LearningCopy.t.updateTable),
|
||||
where: where(LearningCopy.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [LearningCopy]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<LearningCopy>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<LearningCopy> rows, {
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<LearningCopy>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [LearningCopy].
|
||||
Future<LearningCopy> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
LearningCopy row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<LearningCopy>(
|
||||
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<LearningCopy>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<LearningCopyTable> where,
|
||||
_is.OrderByBuilder<LearningCopyTable>? orderBy,
|
||||
_is.OrderByListBuilder<LearningCopyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<LearningCopy>(
|
||||
where: where(LearningCopy.t),
|
||||
orderBy: orderBy?.call(LearningCopy.t),
|
||||
orderByList: orderByList?.call(LearningCopy.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<LearningCopyTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<LearningCopy>(
|
||||
where: where?.call(LearningCopy.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [LearningCopy] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<LearningCopyTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<LearningCopy>(
|
||||
where: where(LearningCopy.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
// ignore_for_file: dead_code, unnecessary_null_comparison
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:halkyon_learn_server/src/generated/protocol.dart' as _i5lsenq5;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import '../learning_path/learning_path_step.dart' as _i0qbf905;
|
||||
|
|
@ -29,6 +30,8 @@ abstract class LearningPathAnswer
|
|||
required this.answeredAt,
|
||||
required this.outcome,
|
||||
this.answer,
|
||||
this.keyId,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory LearningPathAnswer({
|
||||
|
|
@ -39,6 +42,8 @@ abstract class LearningPathAnswer
|
|||
required DateTime answeredAt,
|
||||
required String outcome,
|
||||
String? answer,
|
||||
String? keyId,
|
||||
_idt.ByteData? nonce,
|
||||
}) = _LearningPathAnswerImpl;
|
||||
|
||||
factory LearningPathAnswer.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
|
|
@ -58,6 +63,10 @@ abstract class LearningPathAnswer
|
|||
),
|
||||
outcome: jsonSerialization['outcome'] as String,
|
||||
answer: jsonSerialization['answer'] as String?,
|
||||
keyId: jsonSerialization['keyId'] as String?,
|
||||
nonce: jsonSerialization['nonce'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -86,9 +95,17 @@ abstract class LearningPathAnswer
|
|||
String outcome;
|
||||
|
||||
/// What was answered, as JSON in the step's exercise schema (the choices made,
|
||||
/// the pairing, the words placed); null for 'continued' and 'reset'.
|
||||
/// the pairing, the words placed); null for 'continued' and 'reset'. In a
|
||||
/// private studyset (ADR 0019), its sealed form in base64: the signature,
|
||||
/// then the ciphertext.
|
||||
String? answer;
|
||||
|
||||
/// The key the answer is encrypted with (ADR 0019); null when readable.
|
||||
String? keyId;
|
||||
|
||||
/// The encryption nonce; null when readable, like keyId.
|
||||
_idt.ByteData? nonce;
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => t;
|
||||
|
||||
|
|
@ -103,6 +120,8 @@ abstract class LearningPathAnswer
|
|||
DateTime? answeredAt,
|
||||
String? outcome,
|
||||
String? answer,
|
||||
String? keyId,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -115,6 +134,8 @@ abstract class LearningPathAnswer
|
|||
'answeredAt': answeredAt.toJson(),
|
||||
'outcome': outcome,
|
||||
if (answer != null) 'answer': answer,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +150,8 @@ abstract class LearningPathAnswer
|
|||
'answeredAt': answeredAt.toJson(),
|
||||
'outcome': outcome,
|
||||
if (answer != null) 'answer': answer,
|
||||
if (keyId != null) 'keyId': keyId,
|
||||
if (nonce != null) 'nonce': nonce?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +196,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
required DateTime answeredAt,
|
||||
required String outcome,
|
||||
String? answer,
|
||||
String? keyId,
|
||||
_idt.ByteData? nonce,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
|
|
@ -181,6 +206,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
answeredAt: answeredAt,
|
||||
outcome: outcome,
|
||||
answer: answer,
|
||||
keyId: keyId,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [LearningPathAnswer]
|
||||
|
|
@ -195,6 +222,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
DateTime? answeredAt,
|
||||
String? outcome,
|
||||
Object? answer = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return LearningPathAnswer(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
|
|
@ -204,6 +233,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
answeredAt: answeredAt ?? this.answeredAt,
|
||||
outcome: outcome ?? this.outcome,
|
||||
answer: answer is String? ? answer : this.answer,
|
||||
keyId: keyId is String? ? keyId : this.keyId,
|
||||
nonce: nonce is _idt.ByteData? ? nonce : this.nonce?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -238,6 +269,17 @@ class LearningPathAnswerUpdateTable
|
|||
table.answer,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> keyId(String? value) => _is.ColumnValue(
|
||||
table.keyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
|
||||
_is.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class LearningPathAnswerTable extends _is.Table<_is.UuidValue?> {
|
||||
|
|
@ -264,6 +306,14 @@ class LearningPathAnswerTable extends _is.Table<_is.UuidValue?> {
|
|||
'answer',
|
||||
this,
|
||||
);
|
||||
keyId = _is.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
nonce = _is.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final LearningPathAnswerUpdateTable updateTable;
|
||||
|
|
@ -286,9 +336,17 @@ class LearningPathAnswerTable extends _is.Table<_is.UuidValue?> {
|
|||
late final _is.ColumnString outcome;
|
||||
|
||||
/// What was answered, as JSON in the step's exercise schema (the choices made,
|
||||
/// the pairing, the words placed); null for 'continued' and 'reset'.
|
||||
/// the pairing, the words placed); null for 'continued' and 'reset'. In a
|
||||
/// private studyset (ADR 0019), its sealed form in base64: the signature,
|
||||
/// then the ciphertext.
|
||||
late final _is.ColumnString answer;
|
||||
|
||||
/// The key the answer is encrypted with (ADR 0019); null when readable.
|
||||
late final _is.ColumnString keyId;
|
||||
|
||||
/// The encryption nonce; null when readable, like keyId.
|
||||
late final _is.ColumnByteData nonce;
|
||||
|
||||
_i0qbf905.LearningPathStepTable get step {
|
||||
if (_step != null) return _step!;
|
||||
_step = _is.createRelationTable(
|
||||
|
|
@ -310,6 +368,8 @@ class LearningPathAnswerTable extends _is.Table<_is.UuidValue?> {
|
|||
answeredAt,
|
||||
outcome,
|
||||
answer,
|
||||
keyId,
|
||||
nonce,
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,735 @@
|
|||
/* 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 data key of the person's (ADR 0019): a private studyset's, which encrypts
|
||||
/// everything in its tree, or their personal key, which encrypts their tags
|
||||
/// and settings. Sealed on a device to the account's X25519 key and signed by
|
||||
/// it (halkyon_keys' `sealDataKey`), so only the person's devices open it;
|
||||
/// the instance keeps it as it is. Synced, so keys arrive with the data, work
|
||||
/// offline and go with the space. Never updated: a studyset made private again
|
||||
/// gets a new key.
|
||||
abstract class ContentKey
|
||||
implements _is.TableRow<_is.UuidValue?>, _is.ProtocolSerialization {
|
||||
ContentKey._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
required this.dataKeyId,
|
||||
required this.kind,
|
||||
this.studysetId,
|
||||
required this.sealedKey,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ContentKey({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
required String dataKeyId,
|
||||
required String kind,
|
||||
_is.UuidValue? studysetId,
|
||||
required _idt.ByteData sealedKey,
|
||||
required DateTime createdAt,
|
||||
}) = _ContentKeyImpl;
|
||||
|
||||
factory ContentKey.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return ContentKey(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
spaceId: jsonSerialization['spaceId'] as int?,
|
||||
dataKeyId: jsonSerialization['dataKeyId'] as String,
|
||||
kind: jsonSerialization['kind'] as String,
|
||||
studysetId: jsonSerialization['studysetId'] == null
|
||||
? null
|
||||
: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['studysetId'],
|
||||
),
|
||||
sealedKey: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['sealedKey'],
|
||||
),
|
||||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = ContentKeyTable();
|
||||
|
||||
static const db = ContentKeyRepository._();
|
||||
|
||||
@override
|
||||
_is.UuidValue? id;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
int? spaceId;
|
||||
|
||||
/// The key's ID, which the rows it encrypts name in their `keyId`.
|
||||
String dataKeyId;
|
||||
|
||||
/// 'studyset' for a private studyset's key, 'personal' for the person's.
|
||||
String kind;
|
||||
|
||||
/// The top-level studyset a studyset key is of; null for the personal key.
|
||||
/// A plain id: the key outlives nothing it encrypts, and arrives in any
|
||||
/// order.
|
||||
_is.UuidValue? studysetId;
|
||||
|
||||
/// The sealed key: the sealer's Ed25519 signature, then the sealed box.
|
||||
_idt.ByteData sealedKey;
|
||||
|
||||
/// When it was made, in UTC.
|
||||
DateTime createdAt;
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [ContentKey]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
ContentKey copyWith({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? dataKeyId,
|
||||
String? kind,
|
||||
_is.UuidValue? studysetId,
|
||||
_idt.ByteData? sealedKey,
|
||||
DateTime? createdAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'ContentKey',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
'dataKeyId': dataKeyId,
|
||||
'kind': kind,
|
||||
if (studysetId != null) 'studysetId': studysetId?.toJson(),
|
||||
'sealedKey': sealedKey.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'ContentKey',
|
||||
if (id != null) 'id': id?.toJson(),
|
||||
if (spaceId != null) 'spaceId': spaceId,
|
||||
'dataKeyId': dataKeyId,
|
||||
'kind': kind,
|
||||
if (studysetId != null) 'studysetId': studysetId?.toJson(),
|
||||
'sealedKey': sealedKey.toJson(),
|
||||
'createdAt': createdAt.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
static ContentKeyInclude include() {
|
||||
return ContentKeyInclude._();
|
||||
}
|
||||
|
||||
static ContentKeyIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
ContentKeyInclude? include,
|
||||
}) {
|
||||
return ContentKeyIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _ContentKeyImpl extends ContentKey {
|
||||
_ContentKeyImpl({
|
||||
_is.UuidValue? id,
|
||||
int? spaceId,
|
||||
required String dataKeyId,
|
||||
required String kind,
|
||||
_is.UuidValue? studysetId,
|
||||
required _idt.ByteData sealedKey,
|
||||
required DateTime createdAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
dataKeyId: dataKeyId,
|
||||
kind: kind,
|
||||
studysetId: studysetId,
|
||||
sealedKey: sealedKey,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [ContentKey]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
ContentKey copyWith({
|
||||
Object? id = _Undefined,
|
||||
Object? spaceId = _Undefined,
|
||||
String? dataKeyId,
|
||||
String? kind,
|
||||
Object? studysetId = _Undefined,
|
||||
_idt.ByteData? sealedKey,
|
||||
DateTime? createdAt,
|
||||
}) {
|
||||
return ContentKey(
|
||||
id: id is _is.UuidValue? ? id : this.id,
|
||||
spaceId: spaceId is int? ? spaceId : this.spaceId,
|
||||
dataKeyId: dataKeyId ?? this.dataKeyId,
|
||||
kind: kind ?? this.kind,
|
||||
studysetId: studysetId is _is.UuidValue? ? studysetId : this.studysetId,
|
||||
sealedKey: sealedKey ?? this.sealedKey.clone(),
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContentKeyUpdateTable extends _is.UpdateTable<ContentKeyTable> {
|
||||
ContentKeyUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<int, int> spaceId(int? value) => _is.ColumnValue(
|
||||
table.spaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> dataKeyId(String value) => _is.ColumnValue(
|
||||
table.dataKeyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> kind(String value) => _is.ColumnValue(
|
||||
table.kind,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> studysetId(
|
||||
_is.UuidValue? value,
|
||||
) => _is.ColumnValue(
|
||||
table.studysetId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> sealedKey(
|
||||
_idt.ByteData value,
|
||||
) => _is.ColumnValue(
|
||||
table.sealedKey,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.createdAt,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class ContentKeyTable extends _is.Table<_is.UuidValue?> {
|
||||
ContentKeyTable({super.tableRelation}) : super(tableName: 'content_key') {
|
||||
updateTable = ContentKeyUpdateTable(this);
|
||||
spaceId = _is.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
dataKeyId = _is.ColumnString(
|
||||
'dataKeyId',
|
||||
this,
|
||||
);
|
||||
kind = _is.ColumnString(
|
||||
'kind',
|
||||
this,
|
||||
);
|
||||
studysetId = _is.ColumnUuid(
|
||||
'studysetId',
|
||||
this,
|
||||
);
|
||||
sealedKey = _is.ColumnByteData(
|
||||
'sealedKey',
|
||||
this,
|
||||
);
|
||||
createdAt = _is.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final ContentKeyUpdateTable updateTable;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
late final _is.ColumnInt spaceId;
|
||||
|
||||
/// The key's ID, which the rows it encrypts name in their `keyId`.
|
||||
late final _is.ColumnString dataKeyId;
|
||||
|
||||
/// 'studyset' for a private studyset's key, 'personal' for the person's.
|
||||
late final _is.ColumnString kind;
|
||||
|
||||
/// The top-level studyset a studyset key is of; null for the personal key.
|
||||
/// A plain id: the key outlives nothing it encrypts, and arrives in any
|
||||
/// order.
|
||||
late final _is.ColumnUuid studysetId;
|
||||
|
||||
/// The sealed key: the sealer's Ed25519 signature, then the sealed box.
|
||||
late final _is.ColumnByteData sealedKey;
|
||||
|
||||
/// When it was made, in UTC.
|
||||
late final _is.ColumnDateTime createdAt;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
spaceId,
|
||||
dataKeyId,
|
||||
kind,
|
||||
studysetId,
|
||||
sealedKey,
|
||||
createdAt,
|
||||
];
|
||||
}
|
||||
|
||||
class ContentKeyInclude extends _is.IncludeObject {
|
||||
ContentKeyInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => ContentKey.t;
|
||||
}
|
||||
|
||||
class ContentKeyIncludeList extends _is.IncludeList {
|
||||
ContentKeyIncludeList._({
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(ContentKey.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<_is.UuidValue?> get table => ContentKey.t;
|
||||
}
|
||||
|
||||
class ContentKeyRepository {
|
||||
const ContentKeyRepository._();
|
||||
|
||||
/// Returns a list of [ContentKey]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<ContentKey>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<ContentKey>(
|
||||
where: where?.call(ContentKey.t),
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [ContentKey] 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<ContentKey?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<ContentKey>(
|
||||
where: where?.call(ContentKey.t),
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [ContentKey] by its [id] or null if no such row exists.
|
||||
Future<ContentKey?> findById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<ContentKey>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [ContentKey]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [ContentKey]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<ContentKey>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<ContentKey>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [ContentKey] and returns the inserted row.
|
||||
///
|
||||
/// The returned [ContentKey] will have its `id` field set.
|
||||
Future<ContentKey> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<ContentKey>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [ContentKey]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 [ContentKey]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<ContentKey>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
required _is.ColumnSelections<ContentKeyTable> conflictColumns,
|
||||
_is.ColumnSelections<ContentKeyTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<ContentKey>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(ContentKey.t),
|
||||
updateColumns: updateColumns?.call(ContentKey.t),
|
||||
updateWhere: updateWhere?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [ContentKey] 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 [ContentKey] will have its `id` field set.
|
||||
Future<ContentKey?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
required _is.ColumnSelections<ContentKeyTable> conflictColumns,
|
||||
_is.ColumnSelections<ContentKeyTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<ContentKeyTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<ContentKey>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(ContentKey.t),
|
||||
updateColumns: updateColumns?.call(ContentKey.t),
|
||||
updateWhere: updateWhere?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [ContentKey]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<ContentKey>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_is.ColumnSelections<ContentKeyTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<ContentKey>(
|
||||
rows,
|
||||
columns: columns?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [ContentKey]. 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<ContentKey> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_is.ColumnSelections<ContentKeyTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<ContentKey>(
|
||||
row,
|
||||
columns: columns?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [ContentKey] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<ContentKey?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
_is.UuidValue id, {
|
||||
required _is.ColumnValueListBuilder<ContentKeyUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<ContentKey>(
|
||||
id,
|
||||
columnValues: columnValues(ContentKey.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [ContentKey]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<ContentKey>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<ContentKeyUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<ContentKey>(
|
||||
columnValues: columnValues(ContentKey.t.updateTable),
|
||||
where: where(ContentKey.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [ContentKey]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<ContentKey>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<ContentKey>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [ContentKey].
|
||||
Future<ContentKey> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<ContentKey>(
|
||||
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<ContentKey>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
_is.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_is.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<ContentKey>(
|
||||
where: where(ContentKey.t),
|
||||
orderBy: orderBy?.call(ContentKey.t),
|
||||
orderByList: orderByList?.call(ContentKey.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<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<ContentKey>(
|
||||
where: where?.call(ContentKey.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [ContentKey] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<ContentKey>(
|
||||
where: where(ContentKey.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,8 @@ import 'package:halkyon_learn_server/src/generated/dictionary/dictionary_source.
|
|||
as _i4at7f2s;
|
||||
import 'package:halkyon_learn_server/src/generated/discussion/discussion_summary.dart'
|
||||
as _iqo3lug2;
|
||||
import 'package:halkyon_learn_server/src/generated/explore/copy_report.dart'
|
||||
as _ipludy4j;
|
||||
import 'package:halkyon_learn_server/src/generated/explore/explore_entry.dart'
|
||||
as _iscq690j;
|
||||
import 'package:halkyon_learn_server/src/generated/feedback/feedback_view.dart'
|
||||
|
|
@ -69,8 +71,11 @@ import 'discussion/discussion_seen.dart' as _iui8gn57;
|
|||
import 'discussion/discussion_summary.dart' as _i5mmlb7v;
|
||||
import 'discussion/discussion_target.dart' as _ijf8cxgd;
|
||||
import 'discussion/discussion_view.dart' as _ixby8mme;
|
||||
import 'explore/copy_report.dart' as _i1e7dzqy;
|
||||
import 'explore/counted_learner.dart' as _iyckmb26;
|
||||
import 'explore/explore_entry.dart' as _iaxdli27;
|
||||
import 'explore/explore_query.dart' as _ibl9h9o9;
|
||||
import 'explore/learning_copy.dart' as _ip8uabek;
|
||||
import 'explore/release_content.dart' as _ievpeb4f;
|
||||
import 'explore/release_item.dart' as _iaswq7ap;
|
||||
import 'explore/release_part.dart' as _i0tkr6he;
|
||||
|
|
@ -91,6 +96,7 @@ import 'note/note.dart' as _i9njjhyv;
|
|||
import 'note/note_revision.dart' as _i6j5dng4;
|
||||
import 'pdf_source/pdf_reading.dart' as _i4l20c7i;
|
||||
import 'pdf_source/pdf_source.dart' as _iz838ro9;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'publish/publication.dart' as _i9o1kyhh;
|
||||
import 'publish/publish_refusal.dart' as _iofgvwaa;
|
||||
import 'publish/publish_refused_exception.dart' as _itdn8966;
|
||||
|
|
@ -135,8 +141,11 @@ export 'discussion/discussion_seen.dart';
|
|||
export 'discussion/discussion_summary.dart';
|
||||
export 'discussion/discussion_target.dart';
|
||||
export 'discussion/discussion_view.dart';
|
||||
export 'explore/copy_report.dart';
|
||||
export 'explore/counted_learner.dart';
|
||||
export 'explore/explore_entry.dart';
|
||||
export 'explore/explore_query.dart';
|
||||
export 'explore/learning_copy.dart';
|
||||
export 'explore/release_content.dart';
|
||||
export 'explore/release_item.dart';
|
||||
export 'explore/release_part.dart';
|
||||
|
|
@ -157,6 +166,7 @@ export 'note/note.dart';
|
|||
export 'note/note_revision.dart';
|
||||
export 'pdf_source/pdf_reading.dart';
|
||||
export 'pdf_source/pdf_source.dart';
|
||||
export 'privacy/content_key.dart';
|
||||
export 'publish/publication.dart';
|
||||
export 'publish/publish_refusal.dart';
|
||||
export 'publish/publish_refused_exception.dart';
|
||||
|
|
@ -1015,6 +1025,129 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'content_key',
|
||||
dartName: 'ContentKey',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random_v7',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'spaceId',
|
||||
columnType: _isp.ColumnType.bigint,
|
||||
isNullable: true,
|
||||
dartType: 'int?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'dataKeyId',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'kind',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'studysetId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: true,
|
||||
dartType: 'UuidValue?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'sealedKey',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: false,
|
||||
dartType: 'dart:typed_data:ByteData',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'createdAt',
|
||||
columnType: _isp.ColumnType.timestampWithoutTimeZone,
|
||||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
constraintName: 'content_key_fk_0',
|
||||
columns: ['spaceId'],
|
||||
referenceTable: 'offline_sync_spaces',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isp.ForeignKeyAction.noAction,
|
||||
onDelete: _isp.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'content_key_space_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'spaceId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'counted_learner',
|
||||
dartName: 'CountedLearner',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.bigint,
|
||||
isNullable: false,
|
||||
dartType: 'int?',
|
||||
columnDefault: 'serial',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'authUserId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'since',
|
||||
columnType: _isp.ColumnType.timestampWithoutTimeZone,
|
||||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
],
|
||||
foreignKeys: [],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'counted_learner_person_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'authUserId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: true,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'dictionary_entry',
|
||||
dartName: 'DictionaryEntry',
|
||||
|
|
@ -2100,6 +2233,56 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'learning_copy',
|
||||
dartName: 'LearningCopy',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
columns: [
|
||||
_isp.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isp.ColumnType.bigint,
|
||||
isNullable: false,
|
||||
dartType: 'int?',
|
||||
columnDefault: 'serial',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'authUserId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'publicationId',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'studysetId',
|
||||
columnType: _isp.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue',
|
||||
),
|
||||
],
|
||||
foreignKeys: [],
|
||||
indexes: [
|
||||
_isp.IndexDefinition(
|
||||
indexName: 'learning_copy_person_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isp.IndexElementDefinition(
|
||||
type: _isp.IndexElementDefinitionType.column,
|
||||
definition: 'authUserId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isp.TableDefinition(
|
||||
name: 'learning_path',
|
||||
dartName: 'LearningPath',
|
||||
|
|
@ -2250,6 +2433,18 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'keyId',
|
||||
columnType: _isp.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isp.ColumnDefinition(
|
||||
name: 'nonce',
|
||||
columnType: _isp.ColumnType.bytea,
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isp.ForeignKeyDefinition(
|
||||
|
|
@ -3933,12 +4128,21 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _ixby8mme.DiscussionView) {
|
||||
return _ixby8mme.DiscussionView.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i1e7dzqy.CopyReport) {
|
||||
return _i1e7dzqy.CopyReport.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iyckmb26.CountedLearner) {
|
||||
return _iyckmb26.CountedLearner.fromJson(data) as T;
|
||||
}
|
||||
if (t == _iaxdli27.ExploreEntry) {
|
||||
return _iaxdli27.ExploreEntry.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ibl9h9o9.ExploreQuery) {
|
||||
return _ibl9h9o9.ExploreQuery.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ip8uabek.LearningCopy) {
|
||||
return _ip8uabek.LearningCopy.fromJson(data) as T;
|
||||
}
|
||||
if (t == _ievpeb4f.ReleaseContent) {
|
||||
return _ievpeb4f.ReleaseContent.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -3999,6 +4203,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _iz838ro9.PdfSource) {
|
||||
return _iz838ro9.PdfSource.fromJson(data) as T;
|
||||
}
|
||||
if (t == _imed3x5d.ContentKey) {
|
||||
return _imed3x5d.ContentKey.fromJson(data) as T;
|
||||
}
|
||||
if (t == _i9o1kyhh.Publication) {
|
||||
return _i9o1kyhh.Publication.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -4171,12 +4378,22 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return (data != null ? _ixby8mme.DiscussionView.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_i1e7dzqy.CopyReport?>()) {
|
||||
return (data != null ? _i1e7dzqy.CopyReport.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_iyckmb26.CountedLearner?>()) {
|
||||
return (data != null ? _iyckmb26.CountedLearner.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<_iaxdli27.ExploreEntry?>()) {
|
||||
return (data != null ? _iaxdli27.ExploreEntry.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ibl9h9o9.ExploreQuery?>()) {
|
||||
return (data != null ? _ibl9h9o9.ExploreQuery.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ip8uabek.LearningCopy?>()) {
|
||||
return (data != null ? _ip8uabek.LearningCopy.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_ievpeb4f.ReleaseContent?>()) {
|
||||
return (data != null ? _ievpeb4f.ReleaseContent.fromJson(data) : null)
|
||||
as T;
|
||||
|
|
@ -4251,6 +4468,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (t == _is.getType<_iz838ro9.PdfSource?>()) {
|
||||
return (data != null ? _iz838ro9.PdfSource.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_imed3x5d.ContentKey?>()) {
|
||||
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _is.getType<_i9o1kyhh.Publication?>()) {
|
||||
return (data != null ? _i9o1kyhh.Publication.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -4396,12 +4616,27 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return (data as List).map((e) => deserialize<_is.UuidValue>(e)).toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_ipludy4j.CopyReport>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_ipludy4j.CopyReport>(e))
|
||||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_iulci38e.FeedbackView>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_iulci38e.FeedbackView>(e))
|
||||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == _is.getType<Map<String, String>?>()) {
|
||||
return (data != null
|
||||
? (data as Map).map(
|
||||
(k, v) =>
|
||||
MapEntry(deserialize<String>(k), deserialize<String>(v)),
|
||||
)
|
||||
: null)
|
||||
as T;
|
||||
}
|
||||
try {
|
||||
return _ik8molhd.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -4453,8 +4688,11 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_i5mmlb7v.DiscussionSummary => 'DiscussionSummary',
|
||||
_ijf8cxgd.DiscussionTarget => 'DiscussionTarget',
|
||||
_ixby8mme.DiscussionView => 'DiscussionView',
|
||||
_i1e7dzqy.CopyReport => 'CopyReport',
|
||||
_iyckmb26.CountedLearner => 'CountedLearner',
|
||||
_iaxdli27.ExploreEntry => 'ExploreEntry',
|
||||
_ibl9h9o9.ExploreQuery => 'ExploreQuery',
|
||||
_ip8uabek.LearningCopy => 'LearningCopy',
|
||||
_ievpeb4f.ReleaseContent => 'ReleaseContent',
|
||||
_iaswq7ap.ReleaseItem => 'ReleaseItem',
|
||||
_i0tkr6he.ReleasePart => 'ReleasePart',
|
||||
|
|
@ -4475,6 +4713,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_i6j5dng4.NoteRevision => 'NoteRevision',
|
||||
_i4l20c7i.PdfReading => 'PdfReading',
|
||||
_iz838ro9.PdfSource => 'PdfSource',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9o1kyhh.Publication => 'Publication',
|
||||
_iofgvwaa.PublishRefusal => 'PublishRefusal',
|
||||
_itdn8966.PublishRefusedException => 'PublishRefusedException',
|
||||
|
|
@ -4567,10 +4806,16 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'DiscussionTarget';
|
||||
case _ixby8mme.DiscussionView():
|
||||
return 'DiscussionView';
|
||||
case _i1e7dzqy.CopyReport():
|
||||
return 'CopyReport';
|
||||
case _iyckmb26.CountedLearner():
|
||||
return 'CountedLearner';
|
||||
case _iaxdli27.ExploreEntry():
|
||||
return 'ExploreEntry';
|
||||
case _ibl9h9o9.ExploreQuery():
|
||||
return 'ExploreQuery';
|
||||
case _ip8uabek.LearningCopy():
|
||||
return 'LearningCopy';
|
||||
case _ievpeb4f.ReleaseContent():
|
||||
return 'ReleaseContent';
|
||||
case _iaswq7ap.ReleaseItem():
|
||||
|
|
@ -4611,6 +4856,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return 'PdfReading';
|
||||
case _iz838ro9.PdfSource():
|
||||
return 'PdfSource';
|
||||
case _imed3x5d.ContentKey():
|
||||
return 'ContentKey';
|
||||
case _i9o1kyhh.Publication():
|
||||
return 'Publication';
|
||||
case _iofgvwaa.PublishRefusal():
|
||||
|
|
@ -4766,12 +5013,21 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'DiscussionView') {
|
||||
return deserialize<_ixby8mme.DiscussionView>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'CopyReport') {
|
||||
return deserialize<_i1e7dzqy.CopyReport>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'CountedLearner') {
|
||||
return deserialize<_iyckmb26.CountedLearner>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ExploreEntry') {
|
||||
return deserialize<_iaxdli27.ExploreEntry>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ExploreQuery') {
|
||||
return deserialize<_ibl9h9o9.ExploreQuery>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'LearningCopy') {
|
||||
return deserialize<_ip8uabek.LearningCopy>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ReleaseContent') {
|
||||
return deserialize<_ievpeb4f.ReleaseContent>(data['data']);
|
||||
}
|
||||
|
|
@ -4832,6 +5088,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
if (dataClassName == 'PdfSource') {
|
||||
return deserialize<_iz838ro9.PdfSource>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ContentKey') {
|
||||
return deserialize<_imed3x5d.ContentKey>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'Publication') {
|
||||
return deserialize<_i9o1kyhh.Publication>(data['data']);
|
||||
}
|
||||
|
|
@ -4964,6 +5223,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return _ig6tsv8f.DiscussionPost.t;
|
||||
case _iui8gn57.DiscussionSeen:
|
||||
return _iui8gn57.DiscussionSeen.t;
|
||||
case _iyckmb26.CountedLearner:
|
||||
return _iyckmb26.CountedLearner.t;
|
||||
case _ip8uabek.LearningCopy:
|
||||
return _ip8uabek.LearningCopy.t;
|
||||
case _is663hjt.FeedbackBlock:
|
||||
return _is663hjt.FeedbackBlock.t;
|
||||
case _idsmz0xa.FeedbackDecision:
|
||||
|
|
@ -4990,6 +5253,8 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return _i4l20c7i.PdfReading.t;
|
||||
case _iz838ro9.PdfSource:
|
||||
return _iz838ro9.PdfSource.t;
|
||||
case _imed3x5d.ContentKey:
|
||||
return _imed3x5d.ContentKey.t;
|
||||
case _irp51t0o.PublishedStudyset:
|
||||
return _irp51t0o.PublishedStudyset.t;
|
||||
case _i6qpha3x.StudysetRelease:
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ explore:
|
|||
- release:
|
||||
- releasePart:
|
||||
- file:
|
||||
learningCount:
|
||||
- counted:
|
||||
- setCounted:
|
||||
- reportCopies:
|
||||
feedback:
|
||||
- threadsOf:
|
||||
inbox:
|
||||
|
|
|
|||
|
|
@ -55,7 +55,16 @@ enum PublishRefusal implements _is.SerializableModel {
|
|||
|
||||
/// It is a copy of a published studyset under a licence that asks
|
||||
/// adaptations to stay under it (ShareAlike): `license` says which.
|
||||
licenseMustStay;
|
||||
licenseMustStay,
|
||||
|
||||
/// The studyset is private (ADR 0019): its content is encrypted, so the
|
||||
/// instance can't read a release from it. Making it non-private opens it.
|
||||
private,
|
||||
|
||||
/// The tag names the device sent don't name exactly the tags the release's
|
||||
/// items carry, or a tag the release needs has no name the instance can
|
||||
/// read: tags are encrypted with the person's key (ADR 0019, D2).
|
||||
tagNamesMismatch;
|
||||
|
||||
static PublishRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
|
|
@ -83,6 +92,10 @@ enum PublishRefusal implements _is.SerializableModel {
|
|||
return PublishRefusal.forkForbidden;
|
||||
case 'licenseMustStay':
|
||||
return PublishRefusal.licenseMustStay;
|
||||
case 'private':
|
||||
return PublishRefusal.private;
|
||||
case 'tagNamesMismatch':
|
||||
return PublishRefusal.tagNamesMismatch;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "PublishRefusal"',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import 'note/note.dart' as _i9njjhyv;
|
|||
import 'note/note_revision.dart' as _i6j5dng4;
|
||||
import 'pdf_source/pdf_reading.dart' as _i4l20c7i;
|
||||
import 'pdf_source/pdf_source.dart' as _iz838ro9;
|
||||
import 'privacy/content_key.dart' as _imed3x5d;
|
||||
import 'review/review_log.dart' as _irzjbdqz;
|
||||
import 'settings/person_settings.dart' as _irnhx5e5;
|
||||
import 'studyset/studyset.dart' as _ikbclmae;
|
||||
|
|
@ -41,6 +42,7 @@ final List<_is.Table> syncTables = [
|
|||
_ipiw1cmx.Card.t,
|
||||
_ipbyuef1.CardRevision.t,
|
||||
_i6jz6nyq.CardTag.t,
|
||||
_imed3x5d.ContentKey.t,
|
||||
_idsmz0xa.FeedbackDecision.t,
|
||||
_iqqrr7at.FeedbackMessage.t,
|
||||
_ihfcaz5v.FeedbackThread.t,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,14 @@ fields:
|
|||
### continued) since its last fresh start.
|
||||
outcome: String
|
||||
### What was answered, as JSON in the step's exercise schema (the choices made,
|
||||
### the pairing, the words placed); null for 'continued' and 'reset'.
|
||||
### the pairing, the words placed); null for 'continued' and 'reset'. In a
|
||||
### private studyset (ADR 0019), its sealed form in base64: the signature,
|
||||
### then the ciphertext.
|
||||
answer: String?
|
||||
### The key the answer is encrypted with (ADR 0019); null when readable.
|
||||
keyId: String?
|
||||
### The encryption nonce; null when readable, like keyId.
|
||||
nonce: ByteData?
|
||||
indexes:
|
||||
# Deleting a space finds its rows through this index (see studyset.spy.yaml).
|
||||
learning_path_answer_space_idx:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
### A data key of the person's (ADR 0019): a private studyset's, which encrypts
|
||||
### everything in its tree, or their personal key, which encrypts their tags
|
||||
### and settings. Sealed on a device to the account's X25519 key and signed by
|
||||
### it (halkyon_keys' `sealDataKey`), so only the person's devices open it;
|
||||
### the instance keeps it as it is. Synced, so keys arrive with the data, work
|
||||
### offline and go with the space. Never updated: a studyset made private again
|
||||
### gets a new key.
|
||||
class: ContentKey
|
||||
table: content_key
|
||||
database: sync
|
||||
fields:
|
||||
### The key's ID, which the rows it encrypts name in their `keyId`.
|
||||
dataKeyId: String
|
||||
### 'studyset' for a private studyset's key, 'personal' for the person's.
|
||||
kind: String
|
||||
### The top-level studyset a studyset key is of; null for the personal key.
|
||||
### A plain id: the key outlives nothing it encrypts, and arrives in any
|
||||
### order.
|
||||
studysetId: UuidValue?
|
||||
### The sealed key: the sealer's Ed25519 signature, then the sealed box.
|
||||
sealedKey: ByteData
|
||||
### When it was made, in UTC.
|
||||
createdAt: DateTime
|
||||
indexes:
|
||||
# Deleting a space finds its rows through this index (see studyset.spy.yaml).
|
||||
content_key_space_idx:
|
||||
fields: spaceId
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue