feat(base,learn,todo): the operator's assistants, and the instance's privacy page (ADR 0021, step 2)
The server half of the assistants (points 4 to 8, D27 to D46): the operator sets language models in deploy/assistants.env, grants them to people with limits, and the module forwards their questions with the operator's key, counting what each used, never what was asked. - assistantsFrom: ASSISTANTS, ASSISTANT_ID_KEY, ASSISTANT_<ID>_NAME, _PROTOCOL (anthropic, openai, openai-responses), _BASE_URL, _MODEL, _KEY, _IMAGES, _PRICE_INPUT and _PRICE_OUTPUT (per million tokens), ASSISTANT_CURRENCY, ASSISTANT_OWN_KEYS; anything unusable stops the server as it starts, naming the variable. - The assistant endpoint (identified): offered, agree (D40), ask as a Serverpod stream (D35): the text as the model writes it, then the whole turn, sent once the request is counted; figures, the caller's own. Checked in order: at most 2 questions on their way per person (tooManyAtOnce), the grant, the agreement, limits in the pricing's unit now (limitOutdated), the month's and the day's limits (with when they reset, UTC), the question (malformed, imagesNotRead, tooLarge past 400,000 characters, an image over 3,932,160 bytes or more than 20 images); output capped at 16,384 tokens. Each request carries the person's opaque ID (HMAC-SHA256 under ASSISTANT_ID_KEY, D30). Once sent, the provider's reply is read to its end even if the app cancels, and counted on a session of its own; a provider's failure is logged by kind and status, told as providerKey, providerBusy (with its wait), providerRefused or providerFailed. - The assistantOperator endpoint (identified and operator, on the register): setUp, grants, grant (assistantNotSet, invalidLimit, notFound), revoke, figures of a UTC month. - Tables assistant_grant and assistant_figures (per person, assistant and UTC day), in the module's, Learn's and Todo's migrations. The daily pruning keeps this month and the two before (forgetOldAssistantFigures, D41); deletePerson deletes both. A member offers no assistant (D43). - instance.basics() gains assistantOwnKeys. - /privacy (D39, addPrivacyPageRoute, on the register's web server, before the profile pages' route): made from the instance's settings, English or German, no JavaScript: who may sign up, where the data and backups are, what the module keeps (the account, sessions, IP addresses, the server's log), the host's privacyPagePoints (Learn's: studysets synced, private ones encrypted on the devices with what stays readable, what is published, dictionary lookups, the learner count), each assistant with its model and host, what goes and what is counted, and whether personal keys are allowed. Never a key. Tests: 70 on the assistants (grants, limits, refusals in order, streaming, cancelling, counting, concurrency, the operator's denials, deletion, pruning), 19 on the settings, 8 on the privacy page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
038aca5439
commit
dbae802915
24 changed files with 56851 additions and 33 deletions
File diff suppressed because one or more lines are too long
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,236 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "assistant_figures",
|
||||
"dartName": "AssistantFigures",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"columnDefault": "serial",
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "assistant",
|
||||
"fieldName": "assistant",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "day",
|
||||
"fieldName": "day",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "requests",
|
||||
"fieldName": "requests",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "inputTokens",
|
||||
"fieldName": "inputTokens",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "outputTokens",
|
||||
"fieldName": "outputTokens",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "costMicros",
|
||||
"fieldName": "costMicros",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_figures_day_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "assistant"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "day"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_figures_age_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "day"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "assistant_grant",
|
||||
"dartName": "AssistantGrant",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"columnDefault": "serial",
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "assistant",
|
||||
"fieldName": "assistant",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "grantedAt",
|
||||
"fieldName": "grantedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "limitUnit",
|
||||
"fieldName": "limitUnit",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "protocol:AssistantLimitUnit"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "dailyLimit",
|
||||
"fieldName": "dailyLimit",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "monthlyLimit",
|
||||
"fieldName": "monthlyLimit",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "agreedAt",
|
||||
"fieldName": "agreedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": true,
|
||||
"dartType": "DateTime?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "agreedTo",
|
||||
"fieldName": "agreedTo",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_grant_person_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "assistant"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ part '20261021184601197-private-studysets/migration.dart';
|
|||
part '20261023181810112-oidc-module/migration.dart';
|
||||
part '20261023192913178-simplelogin-keys/migration.dart';
|
||||
part '20261025135744595-waiting-accounts/migration.dart';
|
||||
part '20261025165102861-assistants/migration.dart';
|
||||
|
||||
/// Migration registry for the client-side database.
|
||||
class MigrationRegistry {
|
||||
|
|
@ -64,6 +65,7 @@ class MigrationRegistry {
|
|||
_Migration20261023181810112_oidc_module(),
|
||||
_Migration20261023192913178_simplelogin_keys(),
|
||||
_Migration20261025135744595_waiting_accounts(),
|
||||
_Migration20261025165102861_assistants(),
|
||||
];
|
||||
|
||||
/// List of all client-side database migration versions.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ Future<void> run(List<String> args) async {
|
|||
// Where SimpleLogin sends a sign-in back, which forwards it to the app
|
||||
// (ADR 0020): the register's page, as its SimpleLogin app is.
|
||||
addSimpleLoginCallbackRoute(pod);
|
||||
// The instance's privacy page, `/privacy`, made from its settings (ADR
|
||||
// 0021, D39), before the profile pages' route, whose one segment would
|
||||
// take it.
|
||||
addPrivacyPageRoute(pod);
|
||||
// Profile pages, `/@handle`, which profile links link back to (ADR 0011),
|
||||
// halkyon_base's since profiles are the instance's register's (ADR 0016).
|
||||
addProfilePageRoute(pod);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ HalkyonBase learnBase({HoldsRegister accounts = const HoldsRegister()}) =>
|
|||
'Auf Halkyon Learn, einer Lern-App mit verteilter Wiederholung:',
|
||||
link: Uri.parse('https://halkyon.app/learn'),
|
||||
),
|
||||
privacyPagePoints: _privacyPagePoints,
|
||||
accounts: accounts,
|
||||
);
|
||||
|
||||
|
|
@ -50,3 +51,61 @@ HoldsRegister learnAccounts([Map<String, String>? environment]) =>
|
|||
'the other services in INSTANCE_MEMBERS.',
|
||||
),
|
||||
};
|
||||
|
||||
/// What the instance's privacy page says of Learn's data (ADR 0019, point 4,
|
||||
/// and ADR 0021, D39): what a private studyset shows the instance, what is
|
||||
/// published, what goes to it to be answered, and the learner count.
|
||||
const List<PrivacyPagePoint> _privacyPagePoints = [
|
||||
(
|
||||
english:
|
||||
'Your studysets, with their flashcards, notes, learning paths and '
|
||||
'files, your tags and your settings are kept to sync them between '
|
||||
'your devices. A private studyset (new ones are, wherever the device '
|
||||
'has keys) is encrypted on your devices, as tags and settings are: '
|
||||
'the instance sees its shape, size and rhythm, never its content (its '
|
||||
'tree, when it changed, which flashcard was rated how, when, for how '
|
||||
'long and in which mode, the PDF pages reached, the outcomes of path '
|
||||
'answers, file sizes). One not private the instance can read.',
|
||||
german:
|
||||
'Deine Lernsets mit ihren Karteikarten, Notizen, Lernpfaden und '
|
||||
'Dateien, deine Tags und deine Einstellungen werden aufbewahrt, um sie '
|
||||
'zwischen deinen Geräten abzugleichen. Ein privates Lernset (neue sind '
|
||||
'es, wo immer das Gerät Schlüssel hat) wird auf deinen Geräten '
|
||||
'verschlüsselt, wie Tags und Einstellungen: Die Instanz sieht seine '
|
||||
'Form, Größe und seinen Rhythmus, nie seinen Inhalt (seinen Baum, '
|
||||
'wann es sich änderte, welche Karteikarte wie, wann, wie lange und in '
|
||||
'welchem Modus bewertet wurde, die erreichten PDF-Seiten, die '
|
||||
'Ergebnisse von Pfadantworten, Dateigrößen). Eines, das nicht privat '
|
||||
'ist, kann die Instanz lesen.',
|
||||
),
|
||||
(
|
||||
english:
|
||||
'A studyset you publish, and what you write on others’ (feedback, '
|
||||
'questions, answers and comments), everyone signed in here can read.',
|
||||
german:
|
||||
'Ein Lernset, das du veröffentlichst, und was du zu denen anderer '
|
||||
'schreibst (Feedback, Fragen, Antworten und Kommentare), kann jeder '
|
||||
'lesen, der hier angemeldet ist.',
|
||||
),
|
||||
(
|
||||
english:
|
||||
'Dictionary searches, a studyset’s corrections and subtitle lines from '
|
||||
'Jellyfin go to the instance to be answered, unless your device holds '
|
||||
'the dictionaries; it keeps none of them.',
|
||||
german:
|
||||
'Wörterbuchsuchen, die Korrekturen eines Lernsets und Untertitelzeilen '
|
||||
'aus Jellyfin gehen an die Instanz, um beantwortet zu werden, außer '
|
||||
'dein Gerät hat die Wörterbücher; sie bewahrt nichts davon auf.',
|
||||
),
|
||||
(
|
||||
english:
|
||||
'While “Count my learning in Explore” is on, the instance keeps that '
|
||||
'you chose it, and which publications your copies come from, so that '
|
||||
'their authors see how many learn them.',
|
||||
german:
|
||||
'Solange „Mein Lernen in Entdecken mitzählen“ an ist, bewahrt die '
|
||||
'Instanz auf, dass du es gewählt hast und aus welchen '
|
||||
'Veröffentlichungen deine Kopien stammen, damit ihre Autoren sehen, '
|
||||
'wie viele damit lernen.',
|
||||
),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -11,17 +11,20 @@ import 'package:serverpod/serverpod.dart';
|
|||
/// goes too.
|
||||
class PruningFutureCall extends FutureCall {
|
||||
/// Prunes one batch of anonymous accounts and logs how many people went,
|
||||
/// forgets the resets that lapsed (ADR 0017), and deletes the new accounts
|
||||
/// left unapproved for 14 days, with the waiting passes past their time
|
||||
/// forgets the resets that lapsed (ADR 0017) and the assistants' figures
|
||||
/// past their 3 months (ADR 0021, D41), and deletes the new accounts left
|
||||
/// unapproved for 14 days, with the waiting passes past their time
|
||||
/// (ADR 0021, D25).
|
||||
Future<void> pruneAccounts(Session session) async {
|
||||
final now = clock.now();
|
||||
final pruned = await pruneAnonymousPeople(session);
|
||||
final resets = await forgetLapsedResets(session, now: now);
|
||||
final unapproved = await forgetUnapprovedSignUps(session, now: now);
|
||||
final figures = await forgetOldAssistantFigures(session, now: now);
|
||||
session.log(
|
||||
'Pruned $pruned anonymous people, $resets lapsed resets and '
|
||||
'$unapproved accounts never approved.',
|
||||
'Pruned $pruned anonymous people, $resets lapsed resets, '
|
||||
'$unapproved accounts never approved and $figures days of assistant '
|
||||
'figures.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -283,8 +283,9 @@ class FilePruningPruneFilesFutureCall extends _is.FutureCall
|
|||
}
|
||||
|
||||
/// Prunes one batch of anonymous accounts and logs how many people went,
|
||||
/// forgets the resets that lapsed (ADR 0017), and deletes the new accounts
|
||||
/// left unapproved for 14 days, with the waiting passes past their time
|
||||
/// forgets the resets that lapsed (ADR 0017) and the assistants' figures
|
||||
/// past their 3 months (ADR 0021, D41), and deletes the new accounts left
|
||||
/// unapproved for 14 days, with the waiting passes past their time
|
||||
/// (ADR 0021, D25).
|
||||
class PruningPruneAccountsFutureCall extends _is.FutureCall
|
||||
implements _is.InvokableFutureCall {
|
||||
|
|
|
|||
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,236 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "assistant_figures",
|
||||
"dartName": "AssistantFigures",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"columnDefault": "serial",
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "assistant",
|
||||
"fieldName": "assistant",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "day",
|
||||
"fieldName": "day",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "requests",
|
||||
"fieldName": "requests",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "inputTokens",
|
||||
"fieldName": "inputTokens",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "outputTokens",
|
||||
"fieldName": "outputTokens",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "costMicros",
|
||||
"fieldName": "costMicros",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"dartType": "int"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_figures_day_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "assistant"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "day"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_figures_age_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "day"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "assistant_grant",
|
||||
"dartName": "AssistantGrant",
|
||||
"module": "halkyon_base",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 6,
|
||||
"isNullable": false,
|
||||
"columnDefault": "serial",
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "assistant",
|
||||
"fieldName": "assistant",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "grantedAt",
|
||||
"fieldName": "grantedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "limitUnit",
|
||||
"fieldName": "limitUnit",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "protocol:AssistantLimitUnit"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "dailyLimit",
|
||||
"fieldName": "dailyLimit",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "monthlyLimit",
|
||||
"fieldName": "monthlyLimit",
|
||||
"columnType": 6,
|
||||
"isNullable": true,
|
||||
"dartType": "int?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "agreedAt",
|
||||
"fieldName": "agreedAt",
|
||||
"columnType": 4,
|
||||
"isNullable": true,
|
||||
"dartType": "DateTime?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "agreedTo",
|
||||
"fieldName": "agreedTo",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "assistant_grant_person_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "assistant"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
BEGIN;
|
||||
|
||||
--
|
||||
-- ACTION CREATE TABLE
|
||||
--
|
||||
CREATE TABLE "assistant_figures" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"authUserId" uuid NOT NULL,
|
||||
"assistant" text NOT NULL,
|
||||
"day" timestamp without time zone NOT NULL,
|
||||
"requests" bigint NOT NULL,
|
||||
"inputTokens" bigint NOT NULL,
|
||||
"outputTokens" bigint NOT NULL,
|
||||
"costMicros" bigint NOT NULL
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE UNIQUE INDEX "assistant_figures_day_idx" ON "assistant_figures" USING btree ("authUserId", "assistant", "day");
|
||||
CREATE INDEX "assistant_figures_age_idx" ON "assistant_figures" USING btree ("day");
|
||||
|
||||
--
|
||||
-- ACTION CREATE TABLE
|
||||
--
|
||||
CREATE TABLE "assistant_grant" (
|
||||
"id" bigserial PRIMARY KEY,
|
||||
"authUserId" uuid NOT NULL,
|
||||
"assistant" text NOT NULL,
|
||||
"grantedAt" timestamp without time zone NOT NULL,
|
||||
"limitUnit" text NOT NULL,
|
||||
"dailyLimit" bigint,
|
||||
"monthlyLimit" bigint,
|
||||
"agreedAt" timestamp without time zone,
|
||||
"agreedTo" text
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE UNIQUE INDEX "assistant_grant_person_idx" ON "assistant_grant" USING btree ("authUserId", "assistant");
|
||||
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_learn
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_learn', '20261025165102861-assistants', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261025165102861-assistants', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_base
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_base', '20261025165102522-assistants', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261025165102522-assistants', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod', '20260824182259319', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_auth_idp
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_auth_idp', '20260924105404509', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_offline_sync
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_offline_sync', '20260914143806119', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260914143806119', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_oidc
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_oidc', '20261023180644988', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261023180644988', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_auth_core
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_auth_core', '20260924105232991', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
|
||||
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -49,3 +49,4 @@
|
|||
20261023181810112-oidc-module
|
||||
20261023192913178-simplelogin-keys
|
||||
20261025135744595-waiting-accounts
|
||||
20261025165102861-assistants
|
||||
|
|
|
|||
|
|
@ -223,8 +223,9 @@ class _PruningFutureCallDispatcher {
|
|||
}
|
||||
|
||||
/// Prunes one batch of anonymous accounts and logs how many people went,
|
||||
/// forgets the resets that lapsed (ADR 0017), and deletes the new accounts
|
||||
/// left unapproved for 14 days, with the waiting passes past their time
|
||||
/// forgets the resets that lapsed (ADR 0017) and the assistants' figures
|
||||
/// past their 3 months (Learn's ADR 0021, D41), and deletes the new accounts left
|
||||
/// unapproved for 14 days, with the waiting passes past their time
|
||||
/// (Learn's ADR 0021, D25).
|
||||
class PruningPruneAccountsFutureCall extends _is.FutureCall
|
||||
implements _is.InvokableFutureCall {
|
||||
|
|
|
|||
|
|
@ -11,17 +11,20 @@ import 'package:serverpod/serverpod.dart';
|
|||
/// whose account the register no longer knows, daily.
|
||||
class PruningFutureCall extends FutureCall {
|
||||
/// Prunes one batch of anonymous accounts and logs how many people went,
|
||||
/// forgets the resets that lapsed (ADR 0017), and deletes the new accounts
|
||||
/// left unapproved for 14 days, with the waiting passes past their time
|
||||
/// forgets the resets that lapsed (ADR 0017) and the assistants' figures
|
||||
/// past their 3 months (Learn's ADR 0021, D41), and deletes the new accounts left
|
||||
/// unapproved for 14 days, with the waiting passes past their time
|
||||
/// (Learn's ADR 0021, D25).
|
||||
Future<void> pruneAccounts(Session session) async {
|
||||
final now = clock.now();
|
||||
final pruned = await pruneAnonymousPeople(session);
|
||||
final resets = await forgetLapsedResets(session, now: now);
|
||||
final unapproved = await forgetUnapprovedSignUps(session, now: now);
|
||||
final figures = await forgetOldAssistantFigures(session, now: now);
|
||||
session.log(
|
||||
'Pruned $pruned anonymous people, $resets lapsed resets and '
|
||||
'$unapproved accounts never approved.',
|
||||
'Pruned $pruned anonymous people, $resets lapsed resets, '
|
||||
'$unapproved accounts never approved and $figures days of assistant '
|
||||
'figures.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue