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
12 changed files with 35261 additions and 198 deletions
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,144 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "content_key",
|
||||
"dartName": "ContentKey",
|
||||
"module": "halkyon_learn",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "spaceId",
|
||||
"fieldName": "spaceId",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "dataKeyId",
|
||||
"fieldName": "dataKeyId",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "kind",
|
||||
"fieldName": "kind",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "studysetId",
|
||||
"fieldName": "studysetId",
|
||||
"columnType": 7,
|
||||
"isNullable": true,
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "sealedKey",
|
||||
"fieldName": "sealedKey",
|
||||
"columnType": 5,
|
||||
"isNullable": false,
|
||||
"dartType": "dart:typed_data:ByteData"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "createdAt",
|
||||
"fieldName": "createdAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "content_key_fk_0",
|
||||
"columns": [
|
||||
"spaceId"
|
||||
],
|
||||
"referenceTable": "offline_sync_spaces",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "content_key_space_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "spaceId"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "alterTable",
|
||||
"alterTable": {
|
||||
"__className__": "serverpod.TableMigration",
|
||||
"name": "learning_path_answer",
|
||||
"schema": "public",
|
||||
"addColumns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "keyId",
|
||||
"fieldName": "keyId",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 5,
|
||||
"isNullable": true,
|
||||
"dartType": "dart:typed_data:ByteData?"
|
||||
}
|
||||
],
|
||||
"deleteColumns": [],
|
||||
"modifyColumns": [],
|
||||
"addIndexes": [],
|
||||
"deleteIndexes": [],
|
||||
"addForeignKeys": [],
|
||||
"deleteForeignKeys": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ part '20261020211224692-email-code-purpose/migration.dart';
|
|||
part '20261021100422048-session-ended-warning/migration.dart';
|
||||
part '20261021110159867-second-factor-paths/migration.dart';
|
||||
part '20261021134803749-reset-hold-outs/migration.dart';
|
||||
part '20261021184601197-private-studysets/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -56,6 +57,7 @@ class MigrationRegistry {
|
|||
_Migration20261021100422048_session_ended_warning(),
|
||||
_Migration20261021110159867_second_factor_paths(),
|
||||
_Migration20261021134803749_reset_hold_outs(),
|
||||
_Migration20261021184601197_private_studysets(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ import 'package:halkyon_learn_client/src/protocol/discussion/discussion_target.d
|
|||
as _iy0rtcfg;
|
||||
import 'package:halkyon_learn_client/src/protocol/discussion/discussion_view.dart'
|
||||
as _i31gsvwz;
|
||||
import 'package:halkyon_learn_client/src/protocol/explore/copy_report.dart'
|
||||
as _i3wii6lt;
|
||||
import 'package:halkyon_learn_client/src/protocol/explore/explore_entry.dart'
|
||||
as _iyigb6vc;
|
||||
import 'package:halkyon_learn_client/src/protocol/explore/explore_query.dart'
|
||||
|
|
@ -82,30 +84,41 @@ class EndpointAsset extends _isc.EndpointRef {
|
|||
String get name => 'asset';
|
||||
|
||||
/// 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]).
|
||||
_ida.Future<String?> startUpload(
|
||||
String sha256,
|
||||
int byteSize,
|
||||
) => caller.callServerEndpoint<String?>(
|
||||
int byteSize, {
|
||||
bool? encrypted,
|
||||
}) => caller.callServerEndpoint<String?>(
|
||||
'asset',
|
||||
'startUpload',
|
||||
{
|
||||
'sha256': sha256,
|
||||
'byteSize': byteSize,
|
||||
'encrypted': encrypted,
|
||||
},
|
||||
);
|
||||
|
||||
/// 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].
|
||||
_ida.Future<bool> finishUpload(String sha256) =>
|
||||
caller.callServerEndpoint<bool>(
|
||||
'asset',
|
||||
'finishUpload',
|
||||
{'sha256': sha256},
|
||||
);
|
||||
/// 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.
|
||||
_ida.Future<bool> finishUpload(
|
||||
String sha256, {
|
||||
bool? encrypted,
|
||||
}) => caller.callServerEndpoint<bool>(
|
||||
'asset',
|
||||
'finishUpload',
|
||||
{
|
||||
'sha256': sha256,
|
||||
'encrypted': encrypted,
|
||||
},
|
||||
);
|
||||
|
||||
/// How much room the caller's files take, and may take.
|
||||
_ida.Future<_iimptq0q.FileUsage> usage() =>
|
||||
|
|
@ -646,6 +659,45 @@ class EndpointExplore extends _isc.EndpointRef {
|
|||
);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// {@category Endpoint}
|
||||
class EndpointLearningCount extends _isc.EndpointRef {
|
||||
EndpointLearningCount(_isc.EndpointCaller caller) : super(caller);
|
||||
|
||||
@override
|
||||
String get name => 'learningCount';
|
||||
|
||||
/// Whether the caller chose to be counted.
|
||||
_ida.Future<bool> counted() => caller.callServerEndpoint<bool>(
|
||||
'learningCount',
|
||||
'counted',
|
||||
{},
|
||||
);
|
||||
|
||||
/// Counts the caller from now on, or no longer ([counted] false), which
|
||||
/// also forgets the copies their device reported.
|
||||
_ida.Future<void> setCounted(bool counted) => caller.callServerEndpoint<void>(
|
||||
'learningCount',
|
||||
'setCounted',
|
||||
{'counted': counted},
|
||||
);
|
||||
|
||||
/// 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.
|
||||
_ida.Future<bool> reportCopies(List<_i3wii6lt.CopyReport> copies) =>
|
||||
caller.callServerEndpoint<bool>(
|
||||
'learningCount',
|
||||
'reportCopies',
|
||||
{'copies': copies},
|
||||
);
|
||||
}
|
||||
|
||||
/// The reports and suggestions on a published studyset (ADR 0005, rule 6),
|
||||
/// for everyone signed in to read: they are public, but for those its author
|
||||
/// hid. Writing them is the sync engine's: each person writes in their own
|
||||
|
|
@ -808,7 +860,9 @@ class EndpointPublish extends _isc.EndpointRef {
|
|||
/// when given: a studyset may introduce itself to strangers otherwise
|
||||
/// than in the library (decided 2026-10-14); without, the studyset's
|
||||
/// own description. One that isn't rich text, or is larger than
|
||||
/// [maxExploreDescription], counts as not given.
|
||||
/// [maxExploreDescription], counts as not given. [tagNames] names the
|
||||
/// tags its items carry (tag id to name), as the device reads them: the
|
||||
/// person's tags are encrypted (ADR 0019, D2).
|
||||
///
|
||||
/// Throws [PublishRefusedException] when it can't be published (see
|
||||
/// [PublishRefusal]), and [NotFoundException] for a studyset that isn't
|
||||
|
|
@ -818,6 +872,7 @@ class EndpointPublish extends _isc.EndpointRef {
|
|||
String slug,
|
||||
String notes, {
|
||||
String? description,
|
||||
Map<String, String>? tagNames,
|
||||
}) => caller.callServerEndpoint<_ij0cqo11.Publication>(
|
||||
'publish',
|
||||
'publish',
|
||||
|
|
@ -826,6 +881,7 @@ class EndpointPublish extends _isc.EndpointRef {
|
|||
'slug': slug,
|
||||
'notes': notes,
|
||||
'description': description,
|
||||
'tagNames': tagNames,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -892,6 +948,7 @@ class Client extends _isc.ServerpodClientShared {
|
|||
discussions = EndpointDiscussions(this);
|
||||
discussion = EndpointDiscussion(this);
|
||||
explore = EndpointExplore(this);
|
||||
learningCount = EndpointLearningCount(this);
|
||||
feedback = EndpointFeedback(this);
|
||||
inbox = EndpointInbox(this);
|
||||
instance = EndpointInstance(this);
|
||||
|
|
@ -913,6 +970,8 @@ class Client extends _isc.ServerpodClientShared {
|
|||
|
||||
late final EndpointExplore explore;
|
||||
|
||||
late final EndpointLearningCount learningCount;
|
||||
|
||||
late final EndpointFeedback feedback;
|
||||
|
||||
late final EndpointInbox inbox;
|
||||
|
|
@ -932,6 +991,7 @@ class Client extends _isc.ServerpodClientShared {
|
|||
'discussions': discussions,
|
||||
'discussion': discussion,
|
||||
'explore': explore,
|
||||
'learningCount': learningCount,
|
||||
'feedback': feedback,
|
||||
'inbox': inbox,
|
||||
'instance': instance,
|
||||
|
|
|
|||
|
|
@ -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_client/src/protocol/protocol.dart' as _itx5j3f5;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
|
||||
/// 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 _isc.SerializableModel, _isc.ProtocolSerialization {
|
||||
CopyReport._({
|
||||
required this.publicationId,
|
||||
required this.studysetIds,
|
||||
});
|
||||
|
||||
factory CopyReport({
|
||||
required String publicationId,
|
||||
required List<_isc.UuidValue> studysetIds,
|
||||
}) = _CopyReportImpl;
|
||||
|
||||
factory CopyReport.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return CopyReport(
|
||||
publicationId: jsonSerialization['publicationId'] as String,
|
||||
studysetIds: _itx5j3f5.Protocol().deserialize<List<_isc.UuidValue>>(
|
||||
jsonSerialization['studysetIds'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The publication's public id.
|
||||
String publicationId;
|
||||
|
||||
/// The person's studysets that are part of the copy.
|
||||
List<_isc.UuidValue> studysetIds;
|
||||
|
||||
/// Returns a shallow copy of this [CopyReport]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
CopyReport copyWith({
|
||||
String? publicationId,
|
||||
List<_isc.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 _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyReportImpl extends CopyReport {
|
||||
_CopyReportImpl({
|
||||
required String publicationId,
|
||||
required List<_isc.UuidValue> studysetIds,
|
||||
}) : super._(
|
||||
publicationId: publicationId,
|
||||
studysetIds: studysetIds,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [CopyReport]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
@override
|
||||
CopyReport copyWith({
|
||||
String? publicationId,
|
||||
List<_isc.UuidValue>? studysetIds,
|
||||
}) {
|
||||
return CopyReport(
|
||||
publicationId: publicationId ?? this.publicationId,
|
||||
studysetIds: studysetIds ?? this.studysetIds.map((e0) => e0).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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_client/src/protocol/protocol.dart' as _itx5j3f5;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
|
|
@ -30,6 +31,8 @@ abstract class LearningPathAnswer
|
|||
required this.answeredAt,
|
||||
required this.outcome,
|
||||
this.answer,
|
||||
this.keyId,
|
||||
this.nonce,
|
||||
});
|
||||
|
||||
factory LearningPathAnswer({
|
||||
|
|
@ -40,6 +43,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) {
|
||||
|
|
@ -59,6 +64,10 @@ abstract class LearningPathAnswer
|
|||
),
|
||||
outcome: jsonSerialization['outcome'] as String,
|
||||
answer: jsonSerialization['answer'] as String?,
|
||||
keyId: jsonSerialization['keyId'] as String?,
|
||||
nonce: jsonSerialization['nonce'] == null
|
||||
? null
|
||||
: _isc.ByteDataJsonExtension.fromJson(jsonSerialization['nonce']),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -87,9 +96,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
|
||||
_isd.Table<_isc.UuidValue?> get table => t;
|
||||
|
||||
|
|
@ -104,6 +121,8 @@ abstract class LearningPathAnswer
|
|||
DateTime? answeredAt,
|
||||
String? outcome,
|
||||
String? answer,
|
||||
String? keyId,
|
||||
_idt.ByteData? nonce,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -116,6 +135,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(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -130,6 +151,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(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +197,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
required DateTime answeredAt,
|
||||
required String outcome,
|
||||
String? answer,
|
||||
String? keyId,
|
||||
_idt.ByteData? nonce,
|
||||
}) : super._(
|
||||
id: id,
|
||||
spaceId: spaceId,
|
||||
|
|
@ -182,6 +207,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
answeredAt: answeredAt,
|
||||
outcome: outcome,
|
||||
answer: answer,
|
||||
keyId: keyId,
|
||||
nonce: nonce,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [LearningPathAnswer]
|
||||
|
|
@ -196,6 +223,8 @@ class _LearningPathAnswerImpl extends LearningPathAnswer {
|
|||
DateTime? answeredAt,
|
||||
String? outcome,
|
||||
Object? answer = _Undefined,
|
||||
Object? keyId = _Undefined,
|
||||
Object? nonce = _Undefined,
|
||||
}) {
|
||||
return LearningPathAnswer(
|
||||
id: id is _isc.UuidValue? ? id : this.id,
|
||||
|
|
@ -205,6 +234,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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +271,17 @@ class LearningPathAnswerUpdateTable
|
|||
table.answer,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<String, String> keyId(String? value) => _isd.ColumnValue(
|
||||
table.keyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> nonce(_idt.ByteData? value) =>
|
||||
_isd.ColumnValue(
|
||||
table.nonce,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class LearningPathAnswerTable extends _isd.Table<_isc.UuidValue?> {
|
||||
|
|
@ -266,6 +308,14 @@ class LearningPathAnswerTable extends _isd.Table<_isc.UuidValue?> {
|
|||
'answer',
|
||||
this,
|
||||
);
|
||||
keyId = _isd.ColumnString(
|
||||
'keyId',
|
||||
this,
|
||||
);
|
||||
nonce = _isd.ColumnByteData(
|
||||
'nonce',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final LearningPathAnswerUpdateTable updateTable;
|
||||
|
|
@ -288,9 +338,17 @@ class LearningPathAnswerTable extends _isd.Table<_isc.UuidValue?> {
|
|||
late final _isd.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 _isd.ColumnString answer;
|
||||
|
||||
/// The key the answer is encrypted with (ADR 0019); null when readable.
|
||||
late final _isd.ColumnString keyId;
|
||||
|
||||
/// The encryption nonce; null when readable, like keyId.
|
||||
late final _isd.ColumnByteData nonce;
|
||||
|
||||
_i0qbf905.LearningPathStepTable get step {
|
||||
if (_step != null) return _step!;
|
||||
_step = _isd.createRelationTable(
|
||||
|
|
@ -312,6 +370,8 @@ class LearningPathAnswerTable extends _isd.Table<_isc.UuidValue?> {
|
|||
answeredAt,
|
||||
outcome,
|
||||
answer,
|
||||
keyId,
|
||||
nonce,
|
||||
];
|
||||
|
||||
@override
|
||||
|
|
|
|||
|
|
@ -0,0 +1,736 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'dart:typed_data' as _idt;
|
||||
import 'package:serverpod_client/serverpod_client.dart' as _isc;
|
||||
import 'package:serverpod_database/serverpod_database.dart' as _isd;
|
||||
|
||||
/// A 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 _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
|
||||
ContentKey._({
|
||||
this.id,
|
||||
this.spaceId,
|
||||
required this.dataKeyId,
|
||||
required this.kind,
|
||||
this.studysetId,
|
||||
required this.sealedKey,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory ContentKey({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
required String dataKeyId,
|
||||
required String kind,
|
||||
_isc.UuidValue? studysetId,
|
||||
required _idt.ByteData sealedKey,
|
||||
required DateTime createdAt,
|
||||
}) = _ContentKeyImpl;
|
||||
|
||||
factory ContentKey.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return ContentKey(
|
||||
id: jsonSerialization['id'] == null
|
||||
? null
|
||||
: _isc.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
spaceId: jsonSerialization['spaceId'] as int?,
|
||||
dataKeyId: jsonSerialization['dataKeyId'] as String,
|
||||
kind: jsonSerialization['kind'] as String,
|
||||
studysetId: jsonSerialization['studysetId'] == null
|
||||
? null
|
||||
: _isc.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['studysetId'],
|
||||
),
|
||||
sealedKey: _isc.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['sealedKey'],
|
||||
),
|
||||
createdAt: _isc.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = ContentKeyTable();
|
||||
|
||||
static const db = ContentKeyRepository._();
|
||||
|
||||
@override
|
||||
_isc.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.
|
||||
_isc.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
|
||||
_isd.Table<_isc.UuidValue?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [ContentKey]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_isc.useResult
|
||||
ContentKey copyWith({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
String? dataKeyId,
|
||||
String? kind,
|
||||
_isc.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({
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.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 _isc.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _ContentKeyImpl extends ContentKey {
|
||||
_ContentKeyImpl({
|
||||
_isc.UuidValue? id,
|
||||
int? spaceId,
|
||||
required String dataKeyId,
|
||||
required String kind,
|
||||
_isc.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.
|
||||
@_isc.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 _isc.UuidValue? ? id : this.id,
|
||||
spaceId: spaceId is int? ? spaceId : this.spaceId,
|
||||
dataKeyId: dataKeyId ?? this.dataKeyId,
|
||||
kind: kind ?? this.kind,
|
||||
studysetId: studysetId is _isc.UuidValue? ? studysetId : this.studysetId,
|
||||
sealedKey: sealedKey ?? this.sealedKey.clone(),
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ContentKeyUpdateTable extends _isd.UpdateTable<ContentKeyTable> {
|
||||
ContentKeyUpdateTable(super.table);
|
||||
|
||||
_isd.ColumnValue<int, int> spaceId(int? value) => _isd.ColumnValue(
|
||||
table.spaceId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<String, String> dataKeyId(String value) => _isd.ColumnValue(
|
||||
table.dataKeyId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<String, String> kind(String value) => _isd.ColumnValue(
|
||||
table.kind,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_isc.UuidValue, _isc.UuidValue> studysetId(
|
||||
_isc.UuidValue? value,
|
||||
) => _isd.ColumnValue(
|
||||
table.studysetId,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<_idt.ByteData, _idt.ByteData> sealedKey(
|
||||
_idt.ByteData value,
|
||||
) => _isd.ColumnValue(
|
||||
table.sealedKey,
|
||||
value,
|
||||
);
|
||||
|
||||
_isd.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_isd.ColumnValue(
|
||||
table.createdAt,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class ContentKeyTable extends _isd.Table<_isc.UuidValue?> {
|
||||
ContentKeyTable({super.tableRelation}) : super(tableName: 'content_key') {
|
||||
updateTable = ContentKeyUpdateTable(this);
|
||||
spaceId = _isd.ColumnInt(
|
||||
'spaceId',
|
||||
this,
|
||||
);
|
||||
dataKeyId = _isd.ColumnString(
|
||||
'dataKeyId',
|
||||
this,
|
||||
);
|
||||
kind = _isd.ColumnString(
|
||||
'kind',
|
||||
this,
|
||||
);
|
||||
studysetId = _isd.ColumnUuid(
|
||||
'studysetId',
|
||||
this,
|
||||
);
|
||||
sealedKey = _isd.ColumnByteData(
|
||||
'sealedKey',
|
||||
this,
|
||||
);
|
||||
createdAt = _isd.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final ContentKeyUpdateTable updateTable;
|
||||
|
||||
/// The space owning this row. Maintained by the sync engine.
|
||||
late final _isd.ColumnInt spaceId;
|
||||
|
||||
/// The key's ID, which the rows it encrypts name in their `keyId`.
|
||||
late final _isd.ColumnString dataKeyId;
|
||||
|
||||
/// 'studyset' for a private studyset's key, 'personal' for the person's.
|
||||
late final _isd.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 _isd.ColumnUuid studysetId;
|
||||
|
||||
/// The sealed key: the sealer's Ed25519 signature, then the sealed box.
|
||||
late final _isd.ColumnByteData sealedKey;
|
||||
|
||||
/// When it was made, in UTC.
|
||||
late final _isd.ColumnDateTime createdAt;
|
||||
|
||||
@override
|
||||
List<_isd.Column> get columns => [
|
||||
id,
|
||||
spaceId,
|
||||
dataKeyId,
|
||||
kind,
|
||||
studysetId,
|
||||
sealedKey,
|
||||
createdAt,
|
||||
];
|
||||
}
|
||||
|
||||
class ContentKeyInclude extends _isd.IncludeObject {
|
||||
ContentKeyInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.UuidValue?> get table => ContentKey.t;
|
||||
}
|
||||
|
||||
class ContentKeyIncludeList extends _isd.IncludeList {
|
||||
ContentKeyIncludeList._({
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(ContentKey.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _isd.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_isd.Table<_isc.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(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
_isd.Transaction? transaction,
|
||||
_isd.LockMode? lockMode,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
required _isd.ColumnSelections<ContentKeyTable> conflictColumns,
|
||||
_isd.ColumnSelections<ContentKeyTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? updateWhere,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
required _isd.ColumnSelections<ContentKeyTable> conflictColumns,
|
||||
_isd.ColumnSelections<ContentKeyTable>? updateColumns,
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? updateWhere,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_isd.ColumnSelections<ContentKeyTable>? columns,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_isd.ColumnSelections<ContentKeyTable>? columns,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
_isc.UuidValue id, {
|
||||
required _isd.ColumnValueListBuilder<ContentKeyUpdateTable> columnValues,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.ColumnValueListBuilder<ContentKeyUpdateTable> columnValues,
|
||||
required _isd.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
List<ContentKey> rows, {
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session,
|
||||
ContentKey row, {
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
_isd.OrderByBuilder<ContentKeyTable>? orderBy,
|
||||
_isd.OrderByListBuilder<ContentKeyTable>? orderByList,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session, {
|
||||
_isd.WhereExpressionBuilder<ContentKeyTable>? where,
|
||||
int? limit,
|
||||
_isd.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(
|
||||
_isd.DatabaseSession session, {
|
||||
required _isd.WhereExpressionBuilder<ContentKeyTable> where,
|
||||
required _isd.LockMode lockMode,
|
||||
required _isd.Transaction transaction,
|
||||
_isd.LockBehavior lockBehavior = _isd.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_client/src/protocol/dictionary/dictionary_source.d
|
|||
as _i8vj64sp;
|
||||
import 'package:halkyon_learn_client/src/protocol/discussion/discussion_summary.dart'
|
||||
as _i7lxdcg2;
|
||||
import 'package:halkyon_learn_client/src/protocol/explore/copy_report.dart'
|
||||
as _i3wii6lt;
|
||||
import 'package:halkyon_learn_client/src/protocol/explore/explore_entry.dart'
|
||||
as _iyigb6vc;
|
||||
import 'package:halkyon_learn_client/src/protocol/feedback/feedback_view.dart'
|
||||
|
|
@ -62,6 +64,7 @@ import 'discussion/discussion_refused_exception.dart' as _i1nsp6ib;
|
|||
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/explore_entry.dart' as _iaxdli27;
|
||||
import 'explore/explore_query.dart' as _ibl9h9o9;
|
||||
import 'explore/release_content.dart' as _ievpeb4f;
|
||||
|
|
@ -82,6 +85,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;
|
||||
|
|
@ -117,6 +121,7 @@ export 'discussion/discussion_refused_exception.dart';
|
|||
export 'discussion/discussion_summary.dart';
|
||||
export 'discussion/discussion_target.dart';
|
||||
export 'discussion/discussion_view.dart';
|
||||
export 'explore/copy_report.dart';
|
||||
export 'explore/explore_entry.dart';
|
||||
export 'explore/explore_query.dart';
|
||||
export 'explore/release_content.dart';
|
||||
|
|
@ -137,6 +142,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';
|
||||
|
|
@ -580,6 +586,85 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'content_key',
|
||||
dartName: 'ContentKey',
|
||||
schema: 'public',
|
||||
module: 'halkyon_learn',
|
||||
columns: [
|
||||
_isd.ColumnDefinition(
|
||||
name: 'id',
|
||||
columnType: _isd.ColumnType.uuid,
|
||||
isNullable: false,
|
||||
dartType: 'UuidValue?',
|
||||
columnDefault: 'random_v7',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'spaceId',
|
||||
columnType: _isd.ColumnType.bigint,
|
||||
isNullable: true,
|
||||
dartType: 'int?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'dataKeyId',
|
||||
columnType: _isd.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'kind',
|
||||
columnType: _isd.ColumnType.text,
|
||||
isNullable: false,
|
||||
dartType: 'String',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'studysetId',
|
||||
columnType: _isd.ColumnType.uuid,
|
||||
isNullable: true,
|
||||
dartType: 'UuidValue?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'sealedKey',
|
||||
columnType: _isd.ColumnType.bytea,
|
||||
isNullable: false,
|
||||
dartType: 'dart:typed_data:ByteData',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'createdAt',
|
||||
columnType: _isd.ColumnType.timestampWithoutTimeZone,
|
||||
isNullable: false,
|
||||
dartType: 'DateTime',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isd.ForeignKeyDefinition(
|
||||
constraintName: 'content_key_fk_0',
|
||||
columns: ['spaceId'],
|
||||
referenceTable: 'offline_sync_spaces',
|
||||
referenceTableSchema: 'public',
|
||||
referenceColumns: ['id'],
|
||||
onUpdate: _isd.ForeignKeyAction.noAction,
|
||||
onDelete: _isd.ForeignKeyAction.cascade,
|
||||
matchType: null,
|
||||
),
|
||||
],
|
||||
indexes: [
|
||||
_isd.IndexDefinition(
|
||||
indexName: 'content_key_space_idx',
|
||||
tableSpace: null,
|
||||
elements: [
|
||||
_isd.IndexElementDefinition(
|
||||
type: _isd.IndexElementDefinitionType.column,
|
||||
definition: 'spaceId',
|
||||
),
|
||||
],
|
||||
type: 'btree',
|
||||
isUnique: false,
|
||||
isPrimary: false,
|
||||
),
|
||||
],
|
||||
managed: true,
|
||||
),
|
||||
_isd.TableDefinition(
|
||||
name: 'feedback_decision',
|
||||
dartName: 'FeedbackDecision',
|
||||
|
|
@ -982,6 +1067,18 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'keyId',
|
||||
columnType: _isd.ColumnType.text,
|
||||
isNullable: true,
|
||||
dartType: 'String?',
|
||||
),
|
||||
_isd.ColumnDefinition(
|
||||
name: 'nonce',
|
||||
columnType: _isd.ColumnType.bytea,
|
||||
isNullable: true,
|
||||
dartType: 'dart:typed_data:ByteData?',
|
||||
),
|
||||
],
|
||||
foreignKeys: [
|
||||
_isd.ForeignKeyDefinition(
|
||||
|
|
@ -2372,6 +2469,9 @@ class Protocol extends _isd.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 == _iaxdli27.ExploreEntry) {
|
||||
return _iaxdli27.ExploreEntry.fromJson(data) as T;
|
||||
}
|
||||
|
|
@ -2432,6 +2532,9 @@ class Protocol extends _isd.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;
|
||||
}
|
||||
|
|
@ -2566,6 +2669,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return (data != null ? _ixby8mme.DiscussionView.fromJson(data) : null)
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.getType<_i1e7dzqy.CopyReport?>()) {
|
||||
return (data != null ? _i1e7dzqy.CopyReport.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_iaxdli27.ExploreEntry?>()) {
|
||||
return (data != null ? _iaxdli27.ExploreEntry.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -2639,6 +2745,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (t == _isc.getType<_iz838ro9.PdfSource?>()) {
|
||||
return (data != null ? _iz838ro9.PdfSource.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_imed3x5d.ContentKey?>()) {
|
||||
return (data != null ? _imed3x5d.ContentKey.fromJson(data) : null) as T;
|
||||
}
|
||||
if (t == _isc.getType<_i9o1kyhh.Publication?>()) {
|
||||
return (data != null ? _i9o1kyhh.Publication.fromJson(data) : null) as T;
|
||||
}
|
||||
|
|
@ -2776,12 +2885,27 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return (data as List).map((e) => deserialize<_isc.UuidValue>(e)).toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_i3wii6lt.CopyReport>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_i3wii6lt.CopyReport>(e))
|
||||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == List<_idpwh21d.FeedbackView>) {
|
||||
return (data as List)
|
||||
.map((e) => deserialize<_idpwh21d.FeedbackView>(e))
|
||||
.toList()
|
||||
as T;
|
||||
}
|
||||
if (t == _isc.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 _ihk4p5t7.Protocol().deserialize<T>(data, t);
|
||||
} on _isc.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -2823,6 +2947,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_i5mmlb7v.DiscussionSummary => 'DiscussionSummary',
|
||||
_ijf8cxgd.DiscussionTarget => 'DiscussionTarget',
|
||||
_ixby8mme.DiscussionView => 'DiscussionView',
|
||||
_i1e7dzqy.CopyReport => 'CopyReport',
|
||||
_iaxdli27.ExploreEntry => 'ExploreEntry',
|
||||
_ibl9h9o9.ExploreQuery => 'ExploreQuery',
|
||||
_ievpeb4f.ReleaseContent => 'ReleaseContent',
|
||||
|
|
@ -2843,6 +2968,7 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
_i6j5dng4.NoteRevision => 'NoteRevision',
|
||||
_i4l20c7i.PdfReading => 'PdfReading',
|
||||
_iz838ro9.PdfSource => 'PdfSource',
|
||||
_imed3x5d.ContentKey => 'ContentKey',
|
||||
_i9o1kyhh.Publication => 'Publication',
|
||||
_iofgvwaa.PublishRefusal => 'PublishRefusal',
|
||||
_itdn8966.PublishRefusedException => 'PublishRefusedException',
|
||||
|
|
@ -2919,6 +3045,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'DiscussionTarget';
|
||||
case _ixby8mme.DiscussionView():
|
||||
return 'DiscussionView';
|
||||
case _i1e7dzqy.CopyReport():
|
||||
return 'CopyReport';
|
||||
case _iaxdli27.ExploreEntry():
|
||||
return 'ExploreEntry';
|
||||
case _ibl9h9o9.ExploreQuery():
|
||||
|
|
@ -2959,6 +3087,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return 'PdfReading';
|
||||
case _iz838ro9.PdfSource():
|
||||
return 'PdfSource';
|
||||
case _imed3x5d.ContentKey():
|
||||
return 'ContentKey';
|
||||
case _i9o1kyhh.Publication():
|
||||
return 'Publication';
|
||||
case _iofgvwaa.PublishRefusal():
|
||||
|
|
@ -3085,6 +3215,9 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
if (dataClassName == 'DiscussionView') {
|
||||
return deserialize<_ixby8mme.DiscussionView>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'CopyReport') {
|
||||
return deserialize<_i1e7dzqy.CopyReport>(data['data']);
|
||||
}
|
||||
if (dataClassName == 'ExploreEntry') {
|
||||
return deserialize<_iaxdli27.ExploreEntry>(data['data']);
|
||||
}
|
||||
|
|
@ -3145,6 +3278,9 @@ class Protocol extends _isd.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']);
|
||||
}
|
||||
|
|
@ -3271,6 +3407,8 @@ class Protocol extends _isd.DatabaseSerializationManager {
|
|||
return _i4l20c7i.PdfReading.t;
|
||||
case _iz838ro9.PdfSource:
|
||||
return _iz838ro9.PdfSource.t;
|
||||
case _imed3x5d.ContentKey:
|
||||
return _imed3x5d.ContentKey.t;
|
||||
case _irzjbdqz.ReviewLog:
|
||||
return _irzjbdqz.ReviewLog.t;
|
||||
case _irnhx5e5.PersonSettings:
|
||||
|
|
|
|||
|
|
@ -55,7 +55,16 @@ enum PublishRefusal implements _isc.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 _isc.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<_isd.Table> syncTables = [
|
|||
_ipiw1cmx.Card.t,
|
||||
_ipbyuef1.CardRevision.t,
|
||||
_i6jz6nyq.CardTag.t,
|
||||
_imed3x5d.ContentKey.t,
|
||||
_idsmz0xa.FeedbackDecision.t,
|
||||
_iqqrr7at.FeedbackMessage.t,
|
||||
_ihfcaz5v.FeedbackThread.t,
|
||||
|
|
|
|||
Loading…
Reference in a new issue