feat(base,app,learn,todo,train,meditate): agent accounts with tokens
ADR 0044's step 6a (D42, D43, D45): accounts for programs, such as AI assistants, that an identified person makes from the Profile. halkyon_base keeps them on the instance's register, which its members ask: an agent is an account (agent_account: its owner, its name, its public keys, set by itself once) with one token (agent_token: 32 random bytes, kept as their SHA-256 alone, shown once). agents.create, mine, newToken, revoke, delete and setKeys serve its owner; agentSignIn.signIn exchanges the token for an ordinary server-side session of a day, with the scopes identified and agent, at most 60 sign-ins an hour from a network. Revoking a token or making a new one ends the agent's sessions; a token unused for 90 days lapses in every service's pruning. Up to 5 agents a person, where the operator didn't switch them off (AGENTS=off; said on the privacy page and in instance.basics). An agent makes no agents, creates or owns no shared space, is invited and raised as a Viewer alone, is never handed a space on, is named no operator and granted no assistants; each refusal has its test. Deleting a person deletes their agents first; an agent's public profile names whose agent it is. In the apps, every Profile has an Agents card (halkyon_app's AgentsSection): made with a name and a handle, its token shown once to copy, a new token, the token taken back, deleted. Learn's Share sheet marks an agent among the members and offers it no role above Viewer. The migrations, the module's and each host's (Learn, Meditate, Todo, Train), are additive alone: CREATE TABLE "agent_account" with a unique index on "agentId" (one row an agent account) and one on "ownerId" (a person's agents, and deleting them with the person), and CREATE TABLE "agent_token" with unique indexes on "agentId" (one token an agent) and "tokenHash" (the sign-in finds the token by its hash). No existing table is touched, so the server before the deploy keeps working on the migrated database. Whats-New: Make agents in your Profile: accounts for programs such as AI assistants, which sign in with a token and see only what you share with them. Whats-New-De: Leg in deinem Profil Agenten an: Konten für Programme wie KI-Assistenten, die sich mit einem Token anmelden und nur sehen, was du mit ihnen teilst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
fb6b78fa19
commit
7aee78bd87
44 changed files with 113257 additions and 50 deletions
|
|
@ -17,6 +17,15 @@ export 'src/admission/admission.dart'
|
|||
unapprovedSignUpLifetime,
|
||||
waitingPassLifetime;
|
||||
export 'src/admission/sign_up_config.dart';
|
||||
export 'src/agent/agents.dart'
|
||||
show
|
||||
agentSessionLifetime,
|
||||
agentSignInMethod,
|
||||
agentTokenLapsesAfter,
|
||||
agentTokenPrefix,
|
||||
forgetLapsedAgentTokens,
|
||||
maxAgentsPerPerson;
|
||||
export 'src/agent/agents_config.dart';
|
||||
export 'src/assistant/assistant_conversation.dart'
|
||||
show
|
||||
assistantImageMaxBytes,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
### An agent (Halkyon's ADR 0044, D6, D45): an account of the register that
|
||||
### its owner made from their Profile, signing in with a token alone
|
||||
### (`agent_token`), never a password. Its public keys are its own, set by
|
||||
### the agent once where it runs; it has no others.
|
||||
class: AgentAccount
|
||||
serverOnly: true
|
||||
table: agent_account
|
||||
fields:
|
||||
### The agent's account: its AuthUser's id.
|
||||
agentId: UuidValue
|
||||
### The person who made it, and whose it is.
|
||||
ownerId: UuidValue
|
||||
### What its owner called it.
|
||||
name: String
|
||||
createdAt: DateTime
|
||||
### X25519's public key, which shared keys are sealed to; null until the
|
||||
### agent set its keys.
|
||||
encryptionPublicKey: ByteData?
|
||||
### Ed25519's public key, which its signatures check against.
|
||||
signingPublicKey: ByteData?
|
||||
indexes:
|
||||
agent_account_agent_idx:
|
||||
fields: agentId
|
||||
unique: true
|
||||
agent_account_owner_idx:
|
||||
fields: ownerId
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
### An agent as its owner's Profile lists it (Halkyon's ADR 0044, D45).
|
||||
class: AgentInfo
|
||||
fields:
|
||||
### The agent's account.
|
||||
id: UuidValue
|
||||
name: String
|
||||
handle: String
|
||||
createdAt: DateTime
|
||||
### When it last signed in with its token; null while never.
|
||||
lastUsedAt: DateTime?
|
||||
### Whether it has a token: revoked, it has none until a new one is made.
|
||||
hasToken: bool
|
||||
### Whether it set its public keys.
|
||||
hasKeys: bool
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
### An agent just made, or given a new token: the token, shown this once.
|
||||
class: AgentMade
|
||||
fields:
|
||||
agent: AgentInfo
|
||||
token: String
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
### Why a call about agents was refused (Halkyon's ADR 0044, D45).
|
||||
enum: AgentRefusal
|
||||
serialized: byName
|
||||
values:
|
||||
### The operator switched agents off (`AGENTS=off`).
|
||||
- agentsOff
|
||||
### The person has as many agents as they may (5).
|
||||
- tooMany
|
||||
### An agent asked for what only people do, or a person for what only
|
||||
### agents do.
|
||||
- notForAgents
|
||||
### No agent of yours is that: another person's answers alike.
|
||||
- noSuchAgent
|
||||
### No token is that, or it lapsed.
|
||||
- notValid
|
||||
### The agent set its keys already.
|
||||
- keysSet
|
||||
### Too many sign-ins from this network this hour.
|
||||
- tooManyAttempts
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
### A call about agents was refused; nothing changed.
|
||||
exception: AgentRefusedException
|
||||
fields:
|
||||
reason: AgentRefusal
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:halkyon_base_server/src/agent/agents.dart';
|
||||
import 'package:halkyon_base_server/src/common/network.dart';
|
||||
import 'package:halkyon_base_server/src/register/account_register.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// An agent signing in with its token (Halkyon's ADR 0044, D43): the
|
||||
/// register checks the token and stamps its use, and this server makes a
|
||||
/// session lasting a day, as an agent.
|
||||
class AgentSignInEndpoint extends Endpoint {
|
||||
/// A session for the agent [token] is of. Refused for a token that is
|
||||
/// none or lapsed (`AgentRefusal.notValid`), where the operator switched
|
||||
/// agents off (`agentsOff`), and past this network's sign-ins this hour
|
||||
/// (`tooManyAttempts`).
|
||||
Future<AuthSuccess> signIn(Session session, {required String token}) async {
|
||||
final agent = await AccountRegister.of(
|
||||
session.serverpod,
|
||||
).agentWithToken(session, token: token.trim(), network: networkOf(session));
|
||||
return await issueAgentSession(session, agent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
### The token an agent signs in with (Halkyon's ADR 0044, D12, D43, D45):
|
||||
### one an agent, 32 random bytes shown to its owner once, kept as their
|
||||
### SHA-256 alone. It lapses 90 days after it was last used.
|
||||
class: AgentToken
|
||||
serverOnly: true
|
||||
table: agent_token
|
||||
fields:
|
||||
### The agent's account.
|
||||
agentId: UuidValue
|
||||
### SHA-256 of the token as the agent sends it, in hex.
|
||||
tokenHash: String
|
||||
createdAt: DateTime
|
||||
### When the agent last signed in with it; null while never.
|
||||
lastUsedAt: DateTime?
|
||||
indexes:
|
||||
agent_token_agent_idx:
|
||||
fields: agentId
|
||||
unique: true
|
||||
agent_token_hash_idx:
|
||||
fields: tokenHash
|
||||
unique: true
|
||||
525
packages/halkyon_base_server/lib/src/agent/agents.dart
Normal file
525
packages/halkyon_base_server/lib/src/agent/agents.dart
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:halkyon_base_server/src/agent/agents_config.dart';
|
||||
import 'package:halkyon_base_server/src/auth/account.dart';
|
||||
import 'package:halkyon_base_server/src/auth/scopes.dart';
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/profile/first_handle.dart';
|
||||
import 'package:halkyon_base_server/src/profile/profile_rules.dart';
|
||||
import 'package:halkyon_base_server/src/register/people.dart';
|
||||
import 'package:halkyon_base_server/src/register/sessions_ended.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
// Agents as the register keeps them (Halkyon's ADR 0044, D6, D12, D43,
|
||||
// D45): what `LocalRegister` answers, and a member asks it for.
|
||||
|
||||
/// How many agents one person may have.
|
||||
const maxAgentsPerPerson = 5;
|
||||
|
||||
/// How long a token stays valid after it was last used (D12), or made.
|
||||
const agentTokenLapsesAfter = Duration(days: 90);
|
||||
|
||||
/// How long a session an agent signed in for lasts: it signs in at each
|
||||
/// start, so a stolen session is good for a day at most.
|
||||
const agentSessionLifetime = Duration(days: 1);
|
||||
|
||||
/// What a token begins with, so that it is recognised as one, by people and
|
||||
/// by secret scanners alike.
|
||||
const agentTokenPrefix = 'hkagent_';
|
||||
|
||||
/// The method an agent's sessions are made by, as Serverpod names it.
|
||||
const agentSignInMethod = 'agent';
|
||||
|
||||
/// How many times one network (IP address) signs an agent in within an
|
||||
/// hour. A token has 256 random bits, so nobody guesses one: this only
|
||||
/// keeps a flood of sign-ins off the database.
|
||||
final _signInsPerNetwork = DatabaseRateLimiter(
|
||||
RateLimiterConfig(
|
||||
domain: 'agent_sign_in',
|
||||
source: 'per_network',
|
||||
maxAttempts: 60,
|
||||
timeframe: const Duration(hours: 1),
|
||||
),
|
||||
);
|
||||
|
||||
Never _refused(AgentRefusal reason) =>
|
||||
throw AgentRefusedException(reason: reason);
|
||||
|
||||
/// A new token: [agentTokenPrefix] and 32 random bytes, base64url.
|
||||
String _newToken() {
|
||||
final random = Random.secure();
|
||||
return agentTokenPrefix +
|
||||
base64Url
|
||||
.encode([for (var i = 0; i < 32; i++) random.nextInt(256)])
|
||||
.replaceAll('=', '');
|
||||
}
|
||||
|
||||
/// What the register keeps of [token]: SHA-256, in hex. The token is random
|
||||
/// enough that no pepper is needed.
|
||||
String _hashOf(String token) => sha256.convert(utf8.encode(token)).toString();
|
||||
|
||||
/// Whether [token], last used or made as it says, has lapsed at [now].
|
||||
bool _lapsed(AgentToken token, DateTime now) =>
|
||||
!(token.lastUsedAt ?? token.createdAt)
|
||||
.add(agentTokenLapsesAfter)
|
||||
.isAfter(now);
|
||||
|
||||
/// Makes an agent of [owner] called [name] with [handle] (D45), and its
|
||||
/// token, which the answer alone holds. Refused where the operator
|
||||
/// switched agents off, for an agent, past [maxAgentsPerPerson], and for a
|
||||
/// handle or name a profile couldn't have (`ProfileRefusedException`); an
|
||||
/// answer about a handle counts as one (`countHandleCheck`, from
|
||||
/// [network]).
|
||||
Future<AgentMade> createAgent(
|
||||
Session session, {
|
||||
required UuidValue owner,
|
||||
required String name,
|
||||
required String handle,
|
||||
required String network,
|
||||
}) async {
|
||||
if (!agentsOfferedOn(session.serverpod)) {
|
||||
_refused(AgentRefusal.agentsOff);
|
||||
}
|
||||
final displayName = name.trim().isEmpty ? handle : name.trim();
|
||||
if (profileRefusal(
|
||||
handle: handle,
|
||||
displayName: displayName,
|
||||
bio: '',
|
||||
links: const [],
|
||||
)
|
||||
case final reason?) {
|
||||
throw ProfileRefusedException(reason: reason);
|
||||
}
|
||||
await _refuseAgentOwner(session, owner);
|
||||
await _refuseTooMany(session, owner);
|
||||
if (await refusalOfNewHandle(
|
||||
session,
|
||||
handle,
|
||||
asker: owner.uuid,
|
||||
network: network,
|
||||
)
|
||||
case final reason?) {
|
||||
throw ProfileRefusedException(reason: reason);
|
||||
}
|
||||
final token = _newToken();
|
||||
final agent = const Uuid().v7obj();
|
||||
final now = clock.now().toUtc();
|
||||
await session.db.transaction((transaction) async {
|
||||
// The owner's row, locked, so that two agents made at once count each
|
||||
// other.
|
||||
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = $1 FOR UPDATE
|
||||
await AuthUser.db.findById(
|
||||
session,
|
||||
owner,
|
||||
transaction: transaction,
|
||||
lockMode: LockMode.forUpdate,
|
||||
);
|
||||
await _refuseTooMany(session, owner, transaction: transaction);
|
||||
await ensurePerson(
|
||||
session,
|
||||
agent,
|
||||
scopes: {identified, agentScope},
|
||||
transaction: transaction,
|
||||
);
|
||||
// Its profile is public from the start, under the name its owner gave
|
||||
// it: members see whom they invite.
|
||||
// INSERT INTO "public_profile" (…) VALUES (…)
|
||||
await PublicProfile.db.insertRow(
|
||||
session,
|
||||
PublicProfile(
|
||||
authUserId: agent,
|
||||
handle: handle,
|
||||
handleKey: handle.toLowerCase(),
|
||||
displayName: displayName,
|
||||
bio: '',
|
||||
links: const [],
|
||||
verifiedLinks: const [],
|
||||
updatedAt: now,
|
||||
),
|
||||
transaction: transaction,
|
||||
);
|
||||
// INSERT INTO "agent_account" (…) VALUES (…)
|
||||
await AgentAccount.db.insertRow(
|
||||
session,
|
||||
AgentAccount(
|
||||
agentId: agent,
|
||||
ownerId: owner,
|
||||
name: displayName,
|
||||
createdAt: now,
|
||||
),
|
||||
transaction: transaction,
|
||||
);
|
||||
// INSERT INTO "agent_token" (…) VALUES (…)
|
||||
await AgentToken.db.insertRow(
|
||||
session,
|
||||
AgentToken(agentId: agent, tokenHash: _hashOf(token), createdAt: now),
|
||||
transaction: transaction,
|
||||
);
|
||||
});
|
||||
return AgentMade(agent: await _infoOf(session, owner, agent), token: token);
|
||||
}
|
||||
|
||||
/// Refuses [person] where they are an agent: agents make none.
|
||||
Future<void> _refuseAgentOwner(Session session, UuidValue person) async {
|
||||
// SELECT count(*) FROM "agent_account" WHERE "agentId" = $1
|
||||
if (await AgentAccount.db.count(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(person),
|
||||
) >
|
||||
0) {
|
||||
_refused(AgentRefusal.notForAgents);
|
||||
}
|
||||
}
|
||||
|
||||
/// Refuses [owner] where they have [maxAgentsPerPerson] agents.
|
||||
Future<void> _refuseTooMany(
|
||||
Session session,
|
||||
UuidValue owner, {
|
||||
Transaction? transaction,
|
||||
}) async {
|
||||
// SELECT count(*) FROM "agent_account" WHERE "ownerId" = $1
|
||||
if (await AgentAccount.db.count(
|
||||
session,
|
||||
where: (t) => t.ownerId.equals(owner),
|
||||
transaction: transaction,
|
||||
) >=
|
||||
maxAgentsPerPerson) {
|
||||
_refused(AgentRefusal.tooMany);
|
||||
}
|
||||
}
|
||||
|
||||
/// [owner]'s agents, the first made first.
|
||||
Future<List<AgentInfo>> agentsOf(Session session, UuidValue owner) async {
|
||||
// SELECT … FROM "agent_account" WHERE "ownerId" = $1
|
||||
// ORDER BY "createdAt", "agentId"
|
||||
final rows = await AgentAccount.db.find(
|
||||
session,
|
||||
where: (t) => t.ownerId.equals(owner),
|
||||
orderBy: (t) => t.createdAt,
|
||||
);
|
||||
return await _infos(session, rows);
|
||||
}
|
||||
|
||||
Future<List<AgentInfo>> _infos(
|
||||
Session session,
|
||||
List<AgentAccount> rows,
|
||||
) async {
|
||||
if (rows.isEmpty) {
|
||||
return const [];
|
||||
}
|
||||
final ids = <UuidValue>{for (final row in rows) row.agentId};
|
||||
// SELECT … FROM "agent_token" WHERE "agentId" = ANY($1)
|
||||
final tokens = {
|
||||
for (final token in await AgentToken.db.find(
|
||||
session,
|
||||
where: (t) => t.agentId.inSet(ids),
|
||||
))
|
||||
token.agentId: token,
|
||||
};
|
||||
// SELECT … FROM "public_profile" WHERE "authUserId" = ANY($1)
|
||||
final handles = {
|
||||
for (final profile in await PublicProfile.db.find(
|
||||
session,
|
||||
where: (t) => t.authUserId.inSet(ids),
|
||||
))
|
||||
profile.authUserId: profile.handle,
|
||||
};
|
||||
return [
|
||||
for (final row in rows)
|
||||
AgentInfo(
|
||||
id: row.agentId,
|
||||
name: row.name,
|
||||
handle: handles[row.agentId] ?? '',
|
||||
createdAt: row.createdAt,
|
||||
lastUsedAt: tokens[row.agentId]?.lastUsedAt,
|
||||
hasToken: tokens.containsKey(row.agentId),
|
||||
hasKeys: row.encryptionPublicKey != null,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/// [owner]'s agent [agent]; refused as none where it is no agent of theirs.
|
||||
Future<AgentAccount> _ownedAgent(
|
||||
Session session,
|
||||
UuidValue owner,
|
||||
UuidValue agent, {
|
||||
Transaction? transaction,
|
||||
}) async =>
|
||||
// SELECT … FROM "agent_account" WHERE "agentId" = $1 AND "ownerId" = $2
|
||||
await AgentAccount.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(agent) & t.ownerId.equals(owner),
|
||||
transaction: transaction,
|
||||
) ??
|
||||
_refused(AgentRefusal.noSuchAgent);
|
||||
|
||||
Future<AgentInfo> _infoOf(
|
||||
Session session,
|
||||
UuidValue owner,
|
||||
UuidValue agent,
|
||||
) async =>
|
||||
(await _infos(session, [await _ownedAgent(session, owner, agent)])).single;
|
||||
|
||||
/// Gives [owner]'s agent [agent] a new token, which the answer alone holds:
|
||||
/// the one before ends, with every session of the agent's, here and on the
|
||||
/// members.
|
||||
Future<AgentMade> newAgentToken(
|
||||
Session session, {
|
||||
required UuidValue owner,
|
||||
required UuidValue agent,
|
||||
}) async {
|
||||
await _ownedAgent(session, owner, agent);
|
||||
final token = _newToken();
|
||||
await _endTokenAndSessions(
|
||||
session,
|
||||
agent,
|
||||
then: (transaction) async {
|
||||
// INSERT INTO "agent_token" (…) VALUES (…)
|
||||
await AgentToken.db.insertRow(
|
||||
session,
|
||||
AgentToken(
|
||||
agentId: agent,
|
||||
tokenHash: _hashOf(token),
|
||||
createdAt: clock.now().toUtc(),
|
||||
),
|
||||
transaction: transaction,
|
||||
);
|
||||
},
|
||||
);
|
||||
return AgentMade(agent: await _infoOf(session, owner, agent), token: token);
|
||||
}
|
||||
|
||||
/// Takes [owner]'s agent [agent]'s token back: it signs in no more until
|
||||
/// it gets a new one, and its sessions end, here and on the members.
|
||||
Future<void> revokeAgentToken(
|
||||
Session session, {
|
||||
required UuidValue owner,
|
||||
required UuidValue agent,
|
||||
}) async {
|
||||
await _ownedAgent(session, owner, agent);
|
||||
await _endTokenAndSessions(session, agent);
|
||||
}
|
||||
|
||||
Future<void> _endTokenAndSessions(
|
||||
Session session,
|
||||
UuidValue agent, {
|
||||
Future<void> Function(Transaction transaction)? then,
|
||||
}) async {
|
||||
await session.db.transaction((transaction) async {
|
||||
// DELETE FROM "agent_token" WHERE "agentId" = $1
|
||||
await AgentToken.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(agent),
|
||||
transaction: transaction,
|
||||
);
|
||||
await endSessionsEverywhere(session, agent, transaction: transaction);
|
||||
await then?.call(transaction);
|
||||
});
|
||||
await announceSessionsEnded(session, agent);
|
||||
}
|
||||
|
||||
/// Deletes [owner]'s agent [agent], as an account is deleted.
|
||||
Future<void> deleteAgent(
|
||||
Session session, {
|
||||
required UuidValue owner,
|
||||
required UuidValue agent,
|
||||
}) async {
|
||||
await _ownedAgent(session, owner, agent);
|
||||
await deletePerson(session, agent);
|
||||
}
|
||||
|
||||
/// Deletes [owner]'s agents, as accounts are deleted: before the owner.
|
||||
Future<void> deleteAgentsOf(Session session, UuidValue owner) async {
|
||||
// SELECT … FROM "agent_account" WHERE "ownerId" = $1
|
||||
for (final row in await AgentAccount.db.find(
|
||||
session,
|
||||
where: (t) => t.ownerId.equals(owner),
|
||||
)) {
|
||||
await deletePerson(session, row.agentId);
|
||||
}
|
||||
}
|
||||
|
||||
/// Forgets what the register keeps of [agent] as an agent, as its account
|
||||
/// goes, in [transaction].
|
||||
Future<void> forgetAgent(
|
||||
Session session,
|
||||
UuidValue agent, {
|
||||
required Transaction transaction,
|
||||
}) async {
|
||||
// DELETE FROM "agent_token" WHERE "agentId" = $1
|
||||
await AgentToken.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(agent),
|
||||
transaction: transaction,
|
||||
);
|
||||
// DELETE FROM "agent_account" WHERE "agentId" = $1
|
||||
await AgentAccount.db.deleteWhere(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(agent),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// The agent [token] signs in, its last use stamped now; refused for a
|
||||
/// token that is none or lapsed, where the operator switched agents off,
|
||||
/// and past [network]'s sign-ins this hour.
|
||||
Future<UuidValue> agentWithToken(
|
||||
Session session, {
|
||||
required String token,
|
||||
required String network,
|
||||
}) async {
|
||||
if (!agentsOfferedOn(session.serverpod)) {
|
||||
_refused(AgentRefusal.agentsOff);
|
||||
}
|
||||
if (!await _signInsPerNetwork.tryRecordAttempt(session, key: network)) {
|
||||
_refused(AgentRefusal.tooManyAttempts);
|
||||
}
|
||||
if (!token.startsWith(agentTokenPrefix)) {
|
||||
_refused(AgentRefusal.notValid);
|
||||
}
|
||||
final now = clock.now().toUtc();
|
||||
return await session.db.transaction((transaction) async {
|
||||
// SELECT … FROM "agent_token" WHERE "tokenHash" = $1 FOR UPDATE
|
||||
final row = await AgentToken.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.tokenHash.equals(_hashOf(token)),
|
||||
transaction: transaction,
|
||||
lockMode: LockMode.forUpdate,
|
||||
);
|
||||
if (row == null || _lapsed(row, now)) {
|
||||
_refused(AgentRefusal.notValid);
|
||||
}
|
||||
// UPDATE "agent_token" SET "lastUsedAt" = $1 WHERE "id" = $2
|
||||
await AgentToken.db.updateRow(
|
||||
session,
|
||||
row.copyWith(lastUsedAt: now),
|
||||
columns: (t) => [t.lastUsedAt],
|
||||
transaction: transaction,
|
||||
);
|
||||
return row.agentId;
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets [agent]'s public keys (D6), once; refused for a person, and for an
|
||||
/// agent whose keys are set. Throws an [ArgumentError] for a key that isn't
|
||||
/// 32 bytes.
|
||||
Future<void> setAgentKeys(
|
||||
Session session, {
|
||||
required UuidValue agent,
|
||||
required ByteData encryption,
|
||||
required ByteData signing,
|
||||
}) async {
|
||||
if (encryption.lengthInBytes != 32 || signing.lengthInBytes != 32) {
|
||||
throw ArgumentError("An agent's public keys are 32 bytes each.");
|
||||
}
|
||||
await session.db.transaction((transaction) async {
|
||||
// SELECT … FROM "agent_account" WHERE "agentId" = $1 FOR UPDATE
|
||||
final row =
|
||||
await AgentAccount.db.findFirstRow(
|
||||
session,
|
||||
where: (t) => t.agentId.equals(agent),
|
||||
transaction: transaction,
|
||||
lockMode: LockMode.forUpdate,
|
||||
) ??
|
||||
_refused(AgentRefusal.notForAgents);
|
||||
if (row.encryptionPublicKey != null) {
|
||||
_refused(AgentRefusal.keysSet);
|
||||
}
|
||||
// UPDATE "agent_account" SET "encryptionPublicKey" = $1,
|
||||
// "signingPublicKey" = $2 WHERE "id" = $3
|
||||
await AgentAccount.db.updateRow(
|
||||
session,
|
||||
row.copyWith(encryptionPublicKey: encryption, signingPublicKey: signing),
|
||||
columns: (t) => [t.encryptionPublicKey, t.signingPublicKey],
|
||||
transaction: transaction,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// The public keys of those of [people] that are agents with keys set.
|
||||
Future<Map<UuidValue, AccountPublicKeys>> agentPublicKeysOf(
|
||||
Session session,
|
||||
Set<UuidValue> people,
|
||||
) async {
|
||||
if (people.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
// SELECT … FROM "agent_account" WHERE "agentId" = ANY($1)
|
||||
return {
|
||||
for (final row in await AgentAccount.db.find(
|
||||
session,
|
||||
where: (t) => t.agentId.inSet(people),
|
||||
))
|
||||
if ((row.encryptionPublicKey, row.signingPublicKey) case (
|
||||
final encryption?,
|
||||
final signing?,
|
||||
))
|
||||
row.agentId: AccountPublicKeys(
|
||||
encryption: encryption,
|
||||
signing: signing,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/// Those of [people] that are agents, each with its owner.
|
||||
Future<Map<UuidValue, UuidValue>> agentOwnersOf(
|
||||
Session session,
|
||||
Set<UuidValue> people,
|
||||
) async {
|
||||
if (people.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
// SELECT … FROM "agent_account" WHERE "agentId" = ANY($1)
|
||||
return {
|
||||
for (final row in await AgentAccount.db.find(
|
||||
session,
|
||||
where: (t) => t.agentId.inSet(people),
|
||||
))
|
||||
row.agentId: row.ownerId,
|
||||
};
|
||||
}
|
||||
|
||||
/// Forgets the tokens unused for [agentTokenLapsesAfter]; how many. The
|
||||
/// hourly pruning runs it; their agents stay, for a new token.
|
||||
Future<int> forgetLapsedAgentTokens(Session session) async {
|
||||
final cutoff = clock.now().toUtc().subtract(agentTokenLapsesAfter);
|
||||
// DELETE FROM "agent_token" WHERE "lastUsedAt" <= $1
|
||||
// OR ("lastUsedAt" IS NULL AND "createdAt" <= $1)
|
||||
final gone = await AgentToken.db.deleteWhere(
|
||||
session,
|
||||
where: (t) =>
|
||||
(t.lastUsedAt <= cutoff) |
|
||||
(t.lastUsedAt.equals(null) & (t.createdAt <= cutoff)),
|
||||
);
|
||||
return gone.length;
|
||||
}
|
||||
|
||||
/// A session for the agent [agent] here (D43), made a person here first
|
||||
/// where it isn't one, with its scopes, lasting [agentSessionLifetime].
|
||||
Future<AuthSuccess> issueAgentSession(
|
||||
Session session,
|
||||
UuidValue agent,
|
||||
) async {
|
||||
final scopes = {identified, agentScope};
|
||||
await ensurePerson(session, agent, scopes: scopes);
|
||||
final managers = AuthServices.instance.tokenManager;
|
||||
final sessions = switch (managers) {
|
||||
MultiTokenManager(
|
||||
primaryTokenManager: final ServerSideSessionsTokenManager primary,
|
||||
) =>
|
||||
primary.serverSideSessions,
|
||||
_ => throw StateError('Agents sign in to server-side sessions alone.'),
|
||||
};
|
||||
return await sessions.createSession(
|
||||
session,
|
||||
authUserId: agent,
|
||||
method: agentSignInMethod,
|
||||
scopes: scopes,
|
||||
expiresAt: clock.now().toUtc().add(agentSessionLifetime),
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
|
||||
/// Whether people may make agents on [pod]'s instance (Halkyon's ADR 0044,
|
||||
/// D45): unless its operator switched them off (`AGENTS=off`). The
|
||||
/// register's alone counts: a member asks the register. Throws a
|
||||
/// [StateError] for a value it doesn't know ([agentsOfferedFrom]).
|
||||
bool agentsOfferedOn(Serverpod pod) => switch (_offeredOf[pod]) {
|
||||
(:final offered?) => offered,
|
||||
_ => agentsOfferedFrom(Platform.environment['AGENTS']),
|
||||
};
|
||||
|
||||
/// Whether [value] (`AGENTS`), whatever its case, offers agents: `on`, or
|
||||
/// none (unset or empty, as Compose passes an unset variable); `off`
|
||||
/// refuses them. Throws a [StateError] for anything else.
|
||||
bool agentsOfferedFrom(String? value) =>
|
||||
switch ((value ?? '').trim().toLowerCase()) {
|
||||
'' || 'on' => true,
|
||||
'off' => false,
|
||||
_ => throw StateError('AGENTS is "$value": it is on or off.'),
|
||||
};
|
||||
|
||||
/// Puts [offered] in place of the operator's `AGENTS` on [pod], null
|
||||
/// meaning what the environment says: tests do.
|
||||
void useAgents(Serverpod pod, bool? offered) =>
|
||||
_offeredOf[pod] = (offered: offered);
|
||||
|
||||
final _offeredOf = Expando<({bool? offered})>('Agents offered');
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:halkyon_base_server/src/auth/scopes.dart';
|
||||
import 'package:halkyon_base_server/src/common/network.dart';
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/register/account_register.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
||||
/// A person's agents (Halkyon's ADR 0044, D6, D12, D45), made and taken
|
||||
/// back from their Profile, and an agent's own keys. Everything goes
|
||||
/// through the register, which keeps agents.
|
||||
class AgentsEndpoint extends Endpoint {
|
||||
@override
|
||||
Set<Scope> get requiredScopes => {identified};
|
||||
|
||||
/// Makes an agent called [name] with [handle], with you its owner; its
|
||||
/// token is in the answer alone (D45). Refused where the operator
|
||||
/// switched agents off (`AgentRefusal.agentsOff`), past 5 agents
|
||||
/// (`tooMany`), to an agent (`notForAgents`), and for a handle or name a
|
||||
/// profile couldn't have (`ProfileRefusedException`); counts as an
|
||||
/// answer about a handle.
|
||||
Future<AgentMade> create(
|
||||
Session session, {
|
||||
required String name,
|
||||
required String handle,
|
||||
}) => _register(session).createAgent(
|
||||
session,
|
||||
owner: _person(session),
|
||||
name: name,
|
||||
handle: handle.trim(),
|
||||
network: networkOf(session),
|
||||
);
|
||||
|
||||
/// Your agents, the first made first.
|
||||
Future<List<AgentInfo>> mine(Session session) =>
|
||||
_register(session).agentsOf(session, _person(session));
|
||||
|
||||
/// A new token for your agent [agent], in the answer alone: the one
|
||||
/// before ends, and the agent's sessions with it. Refused for an agent
|
||||
/// not yours (`noSuchAgent`).
|
||||
Future<AgentMade> newToken(Session session, {required UuidValue agent}) =>
|
||||
_register(
|
||||
session,
|
||||
).newAgentToken(session, owner: _person(session), agent: agent);
|
||||
|
||||
/// Takes your agent [agent]'s token back: it signs in no more, and its
|
||||
/// sessions end. Refused for an agent not yours (`noSuchAgent`).
|
||||
Future<void> revoke(Session session, {required UuidValue agent}) => _register(
|
||||
session,
|
||||
).revokeAgentToken(session, owner: _person(session), agent: agent);
|
||||
|
||||
/// Deletes your agent [agent], as an account is deleted. Refused for an
|
||||
/// agent not yours (`noSuchAgent`).
|
||||
Future<void> delete(Session session, {required UuidValue agent}) => _register(
|
||||
session,
|
||||
).deleteAgent(session, owner: _person(session), agent: agent);
|
||||
|
||||
/// Sets your public keys, as an agent, once (D6): what members seal
|
||||
/// shared keys to, pinning them on first sight. Refused for a person
|
||||
/// (`notForAgents`) and once set (`keysSet`).
|
||||
Future<void> setKeys(
|
||||
Session session, {
|
||||
required ByteData encryption,
|
||||
required ByteData signing,
|
||||
}) async {
|
||||
final caller = session.authenticated!;
|
||||
if (!caller.scopes.contains(agentScope)) {
|
||||
throw AgentRefusedException(reason: AgentRefusal.notForAgents);
|
||||
}
|
||||
await _register(session).setAgentKeys(
|
||||
session,
|
||||
agent: caller.authUserId,
|
||||
encryption: encryption,
|
||||
signing: signing,
|
||||
);
|
||||
}
|
||||
|
||||
/// The person calling; refused for an agent, which makes no agents.
|
||||
UuidValue _person(Session session) {
|
||||
final caller = session.authenticated!;
|
||||
if (caller.scopes.contains(agentScope)) {
|
||||
throw AgentRefusedException(reason: AgentRefusal.notForAgents);
|
||||
}
|
||||
return caller.authUserId;
|
||||
}
|
||||
|
||||
AccountRegister _register(Session session) =>
|
||||
AccountRegister.of(session.serverpod);
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:clock/clock.dart';
|
|||
import 'package:halkyon_base_server/src/assistant/assistants_config.dart';
|
||||
import 'package:halkyon_base_server/src/auth/membership.dart';
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
import 'package:halkyon_base_server/src/register/account_register.dart';
|
||||
import 'package:halkyon_llm/halkyon_llm.dart';
|
||||
import 'package:serverpod/serverpod.dart';
|
||||
import 'package:serverpod_auth_idp_server/core.dart';
|
||||
|
|
@ -491,8 +492,9 @@ Future<void> grantAssistants(
|
|||
required List<String> assistants,
|
||||
required AssistantLimits budget,
|
||||
}) async {
|
||||
final granted = _checked(session, assistants, budget);
|
||||
final people = accounts.toSet();
|
||||
await _refuseAgents(session, people);
|
||||
final granted = _checked(session, assistants, budget);
|
||||
await session.db.transaction((transaction) async {
|
||||
await _knownOrRefused(session, people, transaction);
|
||||
for (final person in people) {
|
||||
|
|
@ -502,6 +504,16 @@ Future<void> grantAssistants(
|
|||
});
|
||||
}
|
||||
|
||||
/// Refuses where any of [people] is an agent (Halkyon's ADR 0044, D45),
|
||||
/// which uses none of the operator's assistants.
|
||||
Future<void> _refuseAgents(Session session, Set<UuidValue> people) async {
|
||||
if ((await AccountRegister.of(
|
||||
session.serverpod,
|
||||
).agentOwners(session, people)).isNotEmpty) {
|
||||
_refusedForOperator(OperatorRefusal.isAgent);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives [account] exactly [assistants], taking back the others, the
|
||||
/// agreements of those kept kept, and [budget]; with no assistants, the
|
||||
/// budget goes too. Refused as [grantAssistants] is.
|
||||
|
|
@ -511,6 +523,7 @@ Future<void> changeAssistants(
|
|||
required List<String> assistants,
|
||||
required AssistantLimits budget,
|
||||
}) async {
|
||||
await _refuseAgents(session, {account});
|
||||
final kept = _checked(session, assistants, budget);
|
||||
await session.db.transaction((transaction) async {
|
||||
await _knownOrRefused(session, {account}, transaction);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:halkyon_base_server/src/agent/agents.dart';
|
||||
import 'package:halkyon_base_server/src/assistant/assistant_grants.dart';
|
||||
import 'package:halkyon_base_server/src/auth/scopes.dart';
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart';
|
||||
|
|
@ -38,6 +39,11 @@ Future<void> deletePerson(
|
|||
HoldsRegister(:final members) => members,
|
||||
MemberOfRegister() => const <Uri>[],
|
||||
};
|
||||
// Their agents first, each as an account is deleted (Halkyon's ADR 0044,
|
||||
// D6): the register keeps them, and its members learn of each.
|
||||
if (base.accounts is HoldsRegister) {
|
||||
await deleteAgentsOf(session, authUserId);
|
||||
}
|
||||
var noticesKept = false;
|
||||
final afterwards = await session.db.transaction((transaction) async {
|
||||
// SELECT … FROM "serverpod_auth_core_user" WHERE "id" = $1
|
||||
|
|
@ -84,6 +90,8 @@ Future<void> deletePerson(
|
|||
await forgetAvatar(session, hash, transaction: transaction);
|
||||
}
|
||||
}
|
||||
// What the register keeps of them as an agent: its owner and its token.
|
||||
await forgetAgent(session, authUserId, transaction: transaction);
|
||||
// Their address's hash: the way they signed in.
|
||||
// DELETE FROM "email_identity" WHERE "authUserId" = $1
|
||||
await EmailIdentity.db.deleteWhere(
|
||||
|
|
|
|||
|
|
@ -12,3 +12,8 @@ const identified = Scope('identified');
|
|||
/// register: they approve, refuse and suspend accounts, and name other
|
||||
/// operators. The first is made by SQL; an operator never waits.
|
||||
const instanceOperator = Scope('operator');
|
||||
|
||||
/// Agents (Halkyon's ADR 0044, D6, D45): accounts a person made, signing in
|
||||
/// with a token alone. Their sessions carry it beside [identified], and the
|
||||
/// module refuses them what only people do.
|
||||
const agentScope = Scope('agent');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,731 @@
|
|||
/* 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;
|
||||
|
||||
/// An agent (Halkyon's ADR 0044, D6, D45): an account of the register that
|
||||
/// its owner made from their Profile, signing in with a token alone
|
||||
/// (`agent_token`), never a password. Its public keys are its own, set by
|
||||
/// the agent once where it runs; it has no others.
|
||||
abstract class AgentAccount
|
||||
implements _is.TableRow<int?>, _is.ProtocolSerialization {
|
||||
AgentAccount._({
|
||||
this.id,
|
||||
required this.agentId,
|
||||
required this.ownerId,
|
||||
required this.name,
|
||||
required this.createdAt,
|
||||
this.encryptionPublicKey,
|
||||
this.signingPublicKey,
|
||||
});
|
||||
|
||||
factory AgentAccount({
|
||||
int? id,
|
||||
required _is.UuidValue agentId,
|
||||
required _is.UuidValue ownerId,
|
||||
required String name,
|
||||
required DateTime createdAt,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
}) = _AgentAccountImpl;
|
||||
|
||||
factory AgentAccount.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return AgentAccount(
|
||||
id: jsonSerialization['id'] as int?,
|
||||
agentId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['agentId'],
|
||||
),
|
||||
ownerId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['ownerId'],
|
||||
),
|
||||
name: jsonSerialization['name'] as String,
|
||||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
encryptionPublicKey: jsonSerialization['encryptionPublicKey'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['encryptionPublicKey'],
|
||||
),
|
||||
signingPublicKey: jsonSerialization['signingPublicKey'] == null
|
||||
? null
|
||||
: _is.ByteDataJsonExtension.fromJson(
|
||||
jsonSerialization['signingPublicKey'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = AgentAccountTable();
|
||||
|
||||
static const db = AgentAccountRepository._();
|
||||
|
||||
@override
|
||||
int? id;
|
||||
|
||||
/// The agent's account: its AuthUser's id.
|
||||
_is.UuidValue agentId;
|
||||
|
||||
/// The person who made it, and whose it is.
|
||||
_is.UuidValue ownerId;
|
||||
|
||||
/// What its owner called it.
|
||||
String name;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
/// X25519's public key, which shared keys are sealed to; null until the
|
||||
/// agent set its keys.
|
||||
_idt.ByteData? encryptionPublicKey;
|
||||
|
||||
/// Ed25519's public key, which its signatures check against.
|
||||
_idt.ByteData? signingPublicKey;
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [AgentAccount]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
AgentAccount copyWith({
|
||||
int? id,
|
||||
_is.UuidValue? agentId,
|
||||
_is.UuidValue? ownerId,
|
||||
String? name,
|
||||
DateTime? createdAt,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentAccount',
|
||||
if (id != null) 'id': id,
|
||||
'agentId': agentId.toJson(),
|
||||
'ownerId': ownerId.toJson(),
|
||||
'name': name,
|
||||
'createdAt': createdAt.toJson(),
|
||||
if (encryptionPublicKey != null)
|
||||
'encryptionPublicKey': encryptionPublicKey?.toJson(),
|
||||
if (signingPublicKey != null)
|
||||
'signingPublicKey': signingPublicKey?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static AgentAccountInclude include() {
|
||||
return AgentAccountInclude._();
|
||||
}
|
||||
|
||||
static AgentAccountIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
AgentAccountInclude? include,
|
||||
}) {
|
||||
return AgentAccountIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _AgentAccountImpl extends AgentAccount {
|
||||
_AgentAccountImpl({
|
||||
int? id,
|
||||
required _is.UuidValue agentId,
|
||||
required _is.UuidValue ownerId,
|
||||
required String name,
|
||||
required DateTime createdAt,
|
||||
_idt.ByteData? encryptionPublicKey,
|
||||
_idt.ByteData? signingPublicKey,
|
||||
}) : super._(
|
||||
id: id,
|
||||
agentId: agentId,
|
||||
ownerId: ownerId,
|
||||
name: name,
|
||||
createdAt: createdAt,
|
||||
encryptionPublicKey: encryptionPublicKey,
|
||||
signingPublicKey: signingPublicKey,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AgentAccount]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
AgentAccount copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? agentId,
|
||||
_is.UuidValue? ownerId,
|
||||
String? name,
|
||||
DateTime? createdAt,
|
||||
Object? encryptionPublicKey = _Undefined,
|
||||
Object? signingPublicKey = _Undefined,
|
||||
}) {
|
||||
return AgentAccount(
|
||||
id: id is int? ? id : this.id,
|
||||
agentId: agentId ?? this.agentId,
|
||||
ownerId: ownerId ?? this.ownerId,
|
||||
name: name ?? this.name,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
encryptionPublicKey: encryptionPublicKey is _idt.ByteData?
|
||||
? encryptionPublicKey
|
||||
: this.encryptionPublicKey?.clone(),
|
||||
signingPublicKey: signingPublicKey is _idt.ByteData?
|
||||
? signingPublicKey
|
||||
: this.signingPublicKey?.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AgentAccountUpdateTable extends _is.UpdateTable<AgentAccountTable> {
|
||||
AgentAccountUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> agentId(_is.UuidValue value) =>
|
||||
_is.ColumnValue(
|
||||
table.agentId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> ownerId(_is.UuidValue value) =>
|
||||
_is.ColumnValue(
|
||||
table.ownerId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> name(String value) => _is.ColumnValue(
|
||||
table.name,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.createdAt,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> encryptionPublicKey(
|
||||
_idt.ByteData? value,
|
||||
) => _is.ColumnValue(
|
||||
table.encryptionPublicKey,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<_idt.ByteData, _idt.ByteData> signingPublicKey(
|
||||
_idt.ByteData? value,
|
||||
) => _is.ColumnValue(
|
||||
table.signingPublicKey,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class AgentAccountTable extends _is.Table<int?> {
|
||||
AgentAccountTable({super.tableRelation}) : super(tableName: 'agent_account') {
|
||||
updateTable = AgentAccountUpdateTable(this);
|
||||
agentId = _is.ColumnUuid(
|
||||
'agentId',
|
||||
this,
|
||||
);
|
||||
ownerId = _is.ColumnUuid(
|
||||
'ownerId',
|
||||
this,
|
||||
);
|
||||
name = _is.ColumnString(
|
||||
'name',
|
||||
this,
|
||||
);
|
||||
createdAt = _is.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
);
|
||||
encryptionPublicKey = _is.ColumnByteData(
|
||||
'encryptionPublicKey',
|
||||
this,
|
||||
);
|
||||
signingPublicKey = _is.ColumnByteData(
|
||||
'signingPublicKey',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final AgentAccountUpdateTable updateTable;
|
||||
|
||||
/// The agent's account: its AuthUser's id.
|
||||
late final _is.ColumnUuid agentId;
|
||||
|
||||
/// The person who made it, and whose it is.
|
||||
late final _is.ColumnUuid ownerId;
|
||||
|
||||
/// What its owner called it.
|
||||
late final _is.ColumnString name;
|
||||
|
||||
late final _is.ColumnDateTime createdAt;
|
||||
|
||||
/// X25519's public key, which shared keys are sealed to; null until the
|
||||
/// agent set its keys.
|
||||
late final _is.ColumnByteData encryptionPublicKey;
|
||||
|
||||
/// Ed25519's public key, which its signatures check against.
|
||||
late final _is.ColumnByteData signingPublicKey;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
agentId,
|
||||
ownerId,
|
||||
name,
|
||||
createdAt,
|
||||
encryptionPublicKey,
|
||||
signingPublicKey,
|
||||
];
|
||||
}
|
||||
|
||||
class AgentAccountInclude extends _is.IncludeObject {
|
||||
AgentAccountInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => AgentAccount.t;
|
||||
}
|
||||
|
||||
class AgentAccountIncludeList extends _is.IncludeList {
|
||||
AgentAccountIncludeList._({
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(AgentAccount.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => AgentAccount.t;
|
||||
}
|
||||
|
||||
class AgentAccountRepository {
|
||||
const AgentAccountRepository._();
|
||||
|
||||
/// Returns a list of [AgentAccount]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<AgentAccount>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<AgentAccount>(
|
||||
where: where?.call(AgentAccount.t),
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [AgentAccount] 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<AgentAccount?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<AgentAccount>(
|
||||
where: where?.call(AgentAccount.t),
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [AgentAccount] by its [id] or null if no such row exists.
|
||||
Future<AgentAccount?> findById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<AgentAccount>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [AgentAccount]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [AgentAccount]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<AgentAccount>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentAccount> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<AgentAccount>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [AgentAccount] and returns the inserted row.
|
||||
///
|
||||
/// The returned [AgentAccount] will have its `id` field set.
|
||||
Future<AgentAccount> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentAccount row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<AgentAccount>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [AgentAccount]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 [AgentAccount]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<AgentAccount>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentAccount> rows, {
|
||||
required _is.ColumnSelections<AgentAccountTable> conflictColumns,
|
||||
_is.ColumnSelections<AgentAccountTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<AgentAccount>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(AgentAccount.t),
|
||||
updateColumns: updateColumns?.call(AgentAccount.t),
|
||||
updateWhere: updateWhere?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [AgentAccount] 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 [AgentAccount] will have its `id` field set.
|
||||
Future<AgentAccount?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentAccount row, {
|
||||
required _is.ColumnSelections<AgentAccountTable> conflictColumns,
|
||||
_is.ColumnSelections<AgentAccountTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<AgentAccountTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<AgentAccount>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(AgentAccount.t),
|
||||
updateColumns: updateColumns?.call(AgentAccount.t),
|
||||
updateWhere: updateWhere?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [AgentAccount]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<AgentAccount>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentAccount> rows, {
|
||||
_is.ColumnSelections<AgentAccountTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<AgentAccount>(
|
||||
rows,
|
||||
columns: columns?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [AgentAccount]. 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<AgentAccount> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentAccount row, {
|
||||
_is.ColumnSelections<AgentAccountTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<AgentAccount>(
|
||||
row,
|
||||
columns: columns?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [AgentAccount] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<AgentAccount?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
required _is.ColumnValueListBuilder<AgentAccountUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<AgentAccount>(
|
||||
id,
|
||||
columnValues: columnValues(AgentAccount.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [AgentAccount]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<AgentAccount>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<AgentAccountUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<AgentAccountTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<AgentAccount>(
|
||||
columnValues: columnValues(AgentAccount.t.updateTable),
|
||||
where: where(AgentAccount.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [AgentAccount]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<AgentAccount>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentAccount> rows, {
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<AgentAccount>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [AgentAccount].
|
||||
Future<AgentAccount> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentAccount row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<AgentAccount>(
|
||||
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<AgentAccount>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<AgentAccountTable> where,
|
||||
_is.OrderByBuilder<AgentAccountTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentAccountTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<AgentAccount>(
|
||||
where: where(AgentAccount.t),
|
||||
orderBy: orderBy?.call(AgentAccount.t),
|
||||
orderByList: orderByList?.call(AgentAccount.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<AgentAccountTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<AgentAccount>(
|
||||
where: where?.call(AgentAccount.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [AgentAccount] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<AgentAccountTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<AgentAccount>(
|
||||
where: where(AgentAccount.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
/* 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;
|
||||
|
||||
/// An agent as its owner's Profile lists it (Halkyon's ADR 0044, D45).
|
||||
abstract class AgentInfo
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
AgentInfo._({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.handle,
|
||||
required this.createdAt,
|
||||
this.lastUsedAt,
|
||||
required this.hasToken,
|
||||
required this.hasKeys,
|
||||
});
|
||||
|
||||
factory AgentInfo({
|
||||
required _is.UuidValue id,
|
||||
required String name,
|
||||
required String handle,
|
||||
required DateTime createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
required bool hasToken,
|
||||
required bool hasKeys,
|
||||
}) = _AgentInfoImpl;
|
||||
|
||||
factory AgentInfo.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return AgentInfo(
|
||||
id: _is.UuidValueJsonExtension.fromJson(jsonSerialization['id']),
|
||||
name: jsonSerialization['name'] as String,
|
||||
handle: jsonSerialization['handle'] as String,
|
||||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
lastUsedAt: jsonSerialization['lastUsedAt'] == null
|
||||
? null
|
||||
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['lastUsedAt']),
|
||||
hasToken: _is.BoolJsonExtension.fromJson(jsonSerialization['hasToken']),
|
||||
hasKeys: _is.BoolJsonExtension.fromJson(jsonSerialization['hasKeys']),
|
||||
);
|
||||
}
|
||||
|
||||
/// The agent's account.
|
||||
_is.UuidValue id;
|
||||
|
||||
String name;
|
||||
|
||||
String handle;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
/// When it last signed in with its token; null while never.
|
||||
DateTime? lastUsedAt;
|
||||
|
||||
/// Whether it has a token: revoked, it has none until a new one is made.
|
||||
bool hasToken;
|
||||
|
||||
/// Whether it set its public keys.
|
||||
bool hasKeys;
|
||||
|
||||
/// Returns a shallow copy of this [AgentInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
AgentInfo copyWith({
|
||||
_is.UuidValue? id,
|
||||
String? name,
|
||||
String? handle,
|
||||
DateTime? createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
bool? hasToken,
|
||||
bool? hasKeys,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentInfo',
|
||||
'id': id.toJson(),
|
||||
'name': name,
|
||||
'handle': handle,
|
||||
'createdAt': createdAt.toJson(),
|
||||
if (lastUsedAt != null) 'lastUsedAt': lastUsedAt?.toJson(),
|
||||
'hasToken': hasToken,
|
||||
'hasKeys': hasKeys,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentInfo',
|
||||
'id': id.toJson(),
|
||||
'name': name,
|
||||
'handle': handle,
|
||||
'createdAt': createdAt.toJson(),
|
||||
if (lastUsedAt != null) 'lastUsedAt': lastUsedAt?.toJson(),
|
||||
'hasToken': hasToken,
|
||||
'hasKeys': hasKeys,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _AgentInfoImpl extends AgentInfo {
|
||||
_AgentInfoImpl({
|
||||
required _is.UuidValue id,
|
||||
required String name,
|
||||
required String handle,
|
||||
required DateTime createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
required bool hasToken,
|
||||
required bool hasKeys,
|
||||
}) : super._(
|
||||
id: id,
|
||||
name: name,
|
||||
handle: handle,
|
||||
createdAt: createdAt,
|
||||
lastUsedAt: lastUsedAt,
|
||||
hasToken: hasToken,
|
||||
hasKeys: hasKeys,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AgentInfo]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
AgentInfo copyWith({
|
||||
_is.UuidValue? id,
|
||||
String? name,
|
||||
String? handle,
|
||||
DateTime? createdAt,
|
||||
Object? lastUsedAt = _Undefined,
|
||||
bool? hasToken,
|
||||
bool? hasKeys,
|
||||
}) {
|
||||
return AgentInfo(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
handle: handle ?? this.handle,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
lastUsedAt: lastUsedAt is DateTime? ? lastUsedAt : this.lastUsedAt,
|
||||
hasToken: hasToken ?? this.hasToken,
|
||||
hasKeys: hasKeys ?? this.hasKeys,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/* AUTOMATICALLY GENERATED CODE DO NOT MODIFY */
|
||||
/* To generate run: "serverpod generate" */
|
||||
|
||||
// ignore_for_file: implementation_imports
|
||||
// ignore_for_file: library_private_types_in_public_api
|
||||
// ignore_for_file: non_constant_identifier_names
|
||||
// ignore_for_file: public_member_api_docs
|
||||
// ignore_for_file: type_literal_in_constant_pattern
|
||||
// ignore_for_file: use_super_parameters
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:halkyon_base_server/src/generated/protocol.dart' as _ibewtqgw;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import '../agent/agent_info.dart' as _idw6egxv;
|
||||
|
||||
/// An agent just made, or given a new token: the token, shown this once.
|
||||
abstract class AgentMade
|
||||
implements _is.SerializableModel, _is.ProtocolSerialization {
|
||||
AgentMade._({
|
||||
required this.agent,
|
||||
required this.token,
|
||||
});
|
||||
|
||||
factory AgentMade({
|
||||
required _idw6egxv.AgentInfo agent,
|
||||
required String token,
|
||||
}) = _AgentMadeImpl;
|
||||
|
||||
factory AgentMade.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return AgentMade(
|
||||
agent: _ibewtqgw.Protocol().deserialize<_idw6egxv.AgentInfo>(
|
||||
jsonSerialization['agent'],
|
||||
),
|
||||
token: jsonSerialization['token'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
_idw6egxv.AgentInfo agent;
|
||||
|
||||
String token;
|
||||
|
||||
/// Returns a shallow copy of this [AgentMade]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
AgentMade copyWith({
|
||||
_idw6egxv.AgentInfo? agent,
|
||||
String? token,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentMade',
|
||||
'agent': agent.toJson(),
|
||||
'token': token,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentMade',
|
||||
'agent': agent.toJsonForProtocol(),
|
||||
'token': token,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _AgentMadeImpl extends AgentMade {
|
||||
_AgentMadeImpl({
|
||||
required _idw6egxv.AgentInfo agent,
|
||||
required String token,
|
||||
}) : super._(
|
||||
agent: agent,
|
||||
token: token,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AgentMade]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
AgentMade copyWith({
|
||||
_idw6egxv.AgentInfo? agent,
|
||||
String? token,
|
||||
}) {
|
||||
return AgentMade(
|
||||
agent: agent ?? this.agent.copyWith(),
|
||||
token: token ?? this.token,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/* 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;
|
||||
|
||||
/// Why a call about agents was refused (Halkyon's ADR 0044, D45).
|
||||
enum AgentRefusal implements _is.SerializableModel {
|
||||
/// The operator switched agents off (`AGENTS=off`).
|
||||
agentsOff,
|
||||
|
||||
/// The person has as many agents as they may (5).
|
||||
tooMany,
|
||||
|
||||
/// An agent asked for what only people do, or a person for what only
|
||||
/// agents do.
|
||||
notForAgents,
|
||||
|
||||
/// No agent of yours is that: another person's answers alike.
|
||||
noSuchAgent,
|
||||
|
||||
/// No token is that, or it lapsed.
|
||||
notValid,
|
||||
|
||||
/// The agent set its keys already.
|
||||
keysSet,
|
||||
|
||||
/// Too many sign-ins from this network this hour.
|
||||
tooManyAttempts;
|
||||
|
||||
static AgentRefusal fromJson(String name) {
|
||||
switch (name) {
|
||||
case 'agentsOff':
|
||||
return AgentRefusal.agentsOff;
|
||||
case 'tooMany':
|
||||
return AgentRefusal.tooMany;
|
||||
case 'notForAgents':
|
||||
return AgentRefusal.notForAgents;
|
||||
case 'noSuchAgent':
|
||||
return AgentRefusal.noSuchAgent;
|
||||
case 'notValid':
|
||||
return AgentRefusal.notValid;
|
||||
case 'keysSet':
|
||||
return AgentRefusal.keysSet;
|
||||
case 'tooManyAttempts':
|
||||
return AgentRefusal.tooManyAttempts;
|
||||
default:
|
||||
throw ArgumentError(
|
||||
'Value "$name" cannot be converted to "AgentRefusal"',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toJson() => name;
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* 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;
|
||||
import '../agent/agent_refusal.dart' as _igo9c1fx;
|
||||
|
||||
/// A call about agents was refused; nothing changed.
|
||||
abstract class AgentRefusedException
|
||||
implements
|
||||
_is.SerializableException,
|
||||
_is.SerializableModel,
|
||||
_is.ProtocolSerialization {
|
||||
AgentRefusedException._({required this.reason});
|
||||
|
||||
factory AgentRefusedException({required _igo9c1fx.AgentRefusal reason}) =
|
||||
_AgentRefusedExceptionImpl;
|
||||
|
||||
factory AgentRefusedException.fromJson(
|
||||
Map<String, dynamic> jsonSerialization,
|
||||
) {
|
||||
return AgentRefusedException(
|
||||
reason: _igo9c1fx.AgentRefusal.fromJson(
|
||||
(jsonSerialization['reason'] as String),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_igo9c1fx.AgentRefusal reason;
|
||||
|
||||
/// Returns a shallow copy of this [AgentRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
AgentRefusedException copyWith({_igo9c1fx.AgentRefusal? reason});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentRefusedException',
|
||||
'reason': reason.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AgentRefusedException(reason: $reason)';
|
||||
}
|
||||
}
|
||||
|
||||
class _AgentRefusedExceptionImpl extends AgentRefusedException {
|
||||
_AgentRefusedExceptionImpl({required _igo9c1fx.AgentRefusal reason})
|
||||
: super._(reason: reason);
|
||||
|
||||
/// Returns a shallow copy of this [AgentRefusedException]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
AgentRefusedException copyWith({_igo9c1fx.AgentRefusal? reason}) {
|
||||
return AgentRefusedException(reason: reason ?? this.reason);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,659 @@
|
|||
/* 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;
|
||||
|
||||
/// The token an agent signs in with (Halkyon's ADR 0044, D12, D43, D45):
|
||||
/// one an agent, 32 random bytes shown to its owner once, kept as their
|
||||
/// SHA-256 alone. It lapses 90 days after it was last used.
|
||||
abstract class AgentToken
|
||||
implements _is.TableRow<int?>, _is.ProtocolSerialization {
|
||||
AgentToken._({
|
||||
this.id,
|
||||
required this.agentId,
|
||||
required this.tokenHash,
|
||||
required this.createdAt,
|
||||
this.lastUsedAt,
|
||||
});
|
||||
|
||||
factory AgentToken({
|
||||
int? id,
|
||||
required _is.UuidValue agentId,
|
||||
required String tokenHash,
|
||||
required DateTime createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
}) = _AgentTokenImpl;
|
||||
|
||||
factory AgentToken.fromJson(Map<String, dynamic> jsonSerialization) {
|
||||
return AgentToken(
|
||||
id: jsonSerialization['id'] as int?,
|
||||
agentId: _is.UuidValueJsonExtension.fromJson(
|
||||
jsonSerialization['agentId'],
|
||||
),
|
||||
tokenHash: jsonSerialization['tokenHash'] as String,
|
||||
createdAt: _is.DateTimeJsonExtension.fromJson(
|
||||
jsonSerialization['createdAt'],
|
||||
),
|
||||
lastUsedAt: jsonSerialization['lastUsedAt'] == null
|
||||
? null
|
||||
: _is.DateTimeJsonExtension.fromJson(jsonSerialization['lastUsedAt']),
|
||||
);
|
||||
}
|
||||
|
||||
static final t = AgentTokenTable();
|
||||
|
||||
static const db = AgentTokenRepository._();
|
||||
|
||||
@override
|
||||
int? id;
|
||||
|
||||
/// The agent's account.
|
||||
_is.UuidValue agentId;
|
||||
|
||||
/// SHA-256 of the token as the agent sends it, in hex.
|
||||
String tokenHash;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
/// When the agent last signed in with it; null while never.
|
||||
DateTime? lastUsedAt;
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => t;
|
||||
|
||||
/// Returns a shallow copy of this [AgentToken]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
AgentToken copyWith({
|
||||
int? id,
|
||||
_is.UuidValue? agentId,
|
||||
String? tokenHash,
|
||||
DateTime? createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
});
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'__className__': 'halkyon_base.AgentToken',
|
||||
if (id != null) 'id': id,
|
||||
'agentId': agentId.toJson(),
|
||||
'tokenHash': tokenHash,
|
||||
'createdAt': createdAt.toJson(),
|
||||
if (lastUsedAt != null) 'lastUsedAt': lastUsedAt?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJsonForProtocol() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static AgentTokenInclude include() {
|
||||
return AgentTokenInclude._();
|
||||
}
|
||||
|
||||
static AgentTokenIncludeList includeList({
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
AgentTokenInclude? include,
|
||||
}) {
|
||||
return AgentTokenIncludeList._(
|
||||
where: where,
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.t),
|
||||
include: include,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return _is.SerializationManager.encode(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _Undefined {}
|
||||
|
||||
class _AgentTokenImpl extends AgentToken {
|
||||
_AgentTokenImpl({
|
||||
int? id,
|
||||
required _is.UuidValue agentId,
|
||||
required String tokenHash,
|
||||
required DateTime createdAt,
|
||||
DateTime? lastUsedAt,
|
||||
}) : super._(
|
||||
id: id,
|
||||
agentId: agentId,
|
||||
tokenHash: tokenHash,
|
||||
createdAt: createdAt,
|
||||
lastUsedAt: lastUsedAt,
|
||||
);
|
||||
|
||||
/// Returns a shallow copy of this [AgentToken]
|
||||
/// with some or all fields replaced by the given arguments.
|
||||
@_is.useResult
|
||||
@override
|
||||
AgentToken copyWith({
|
||||
Object? id = _Undefined,
|
||||
_is.UuidValue? agentId,
|
||||
String? tokenHash,
|
||||
DateTime? createdAt,
|
||||
Object? lastUsedAt = _Undefined,
|
||||
}) {
|
||||
return AgentToken(
|
||||
id: id is int? ? id : this.id,
|
||||
agentId: agentId ?? this.agentId,
|
||||
tokenHash: tokenHash ?? this.tokenHash,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
lastUsedAt: lastUsedAt is DateTime? ? lastUsedAt : this.lastUsedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AgentTokenUpdateTable extends _is.UpdateTable<AgentTokenTable> {
|
||||
AgentTokenUpdateTable(super.table);
|
||||
|
||||
_is.ColumnValue<_is.UuidValue, _is.UuidValue> agentId(_is.UuidValue value) =>
|
||||
_is.ColumnValue(
|
||||
table.agentId,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<String, String> tokenHash(String value) => _is.ColumnValue(
|
||||
table.tokenHash,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> createdAt(DateTime value) =>
|
||||
_is.ColumnValue(
|
||||
table.createdAt,
|
||||
value,
|
||||
);
|
||||
|
||||
_is.ColumnValue<DateTime, DateTime> lastUsedAt(DateTime? value) =>
|
||||
_is.ColumnValue(
|
||||
table.lastUsedAt,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
class AgentTokenTable extends _is.Table<int?> {
|
||||
AgentTokenTable({super.tableRelation}) : super(tableName: 'agent_token') {
|
||||
updateTable = AgentTokenUpdateTable(this);
|
||||
agentId = _is.ColumnUuid(
|
||||
'agentId',
|
||||
this,
|
||||
);
|
||||
tokenHash = _is.ColumnString(
|
||||
'tokenHash',
|
||||
this,
|
||||
);
|
||||
createdAt = _is.ColumnDateTime(
|
||||
'createdAt',
|
||||
this,
|
||||
);
|
||||
lastUsedAt = _is.ColumnDateTime(
|
||||
'lastUsedAt',
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
late final AgentTokenUpdateTable updateTable;
|
||||
|
||||
/// The agent's account.
|
||||
late final _is.ColumnUuid agentId;
|
||||
|
||||
/// SHA-256 of the token as the agent sends it, in hex.
|
||||
late final _is.ColumnString tokenHash;
|
||||
|
||||
late final _is.ColumnDateTime createdAt;
|
||||
|
||||
/// When the agent last signed in with it; null while never.
|
||||
late final _is.ColumnDateTime lastUsedAt;
|
||||
|
||||
@override
|
||||
List<_is.Column> get columns => [
|
||||
id,
|
||||
agentId,
|
||||
tokenHash,
|
||||
createdAt,
|
||||
lastUsedAt,
|
||||
];
|
||||
}
|
||||
|
||||
class AgentTokenInclude extends _is.IncludeObject {
|
||||
AgentTokenInclude._();
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => AgentToken.t;
|
||||
}
|
||||
|
||||
class AgentTokenIncludeList extends _is.IncludeList {
|
||||
AgentTokenIncludeList._({
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? where,
|
||||
super.limit,
|
||||
super.offset,
|
||||
super.orderBy,
|
||||
super.orderByList,
|
||||
super.include,
|
||||
}) {
|
||||
super.where = where?.call(AgentToken.t);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, _is.Include?> get includes => include?.includes ?? {};
|
||||
|
||||
@override
|
||||
_is.Table<int?> get table => AgentToken.t;
|
||||
}
|
||||
|
||||
class AgentTokenRepository {
|
||||
const AgentTokenRepository._();
|
||||
|
||||
/// Returns a list of [AgentToken]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<AgentToken>> find(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.find<AgentToken>(
|
||||
where: where?.call(AgentToken.t),
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the first matching [AgentToken] 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<AgentToken?> findFirstRow(
|
||||
_is.DatabaseSession session, {
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? where,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findFirstRow<AgentToken>(
|
||||
where: where?.call(AgentToken.t),
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.t),
|
||||
offset: offset,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds a single [AgentToken] by its [id] or null if no such row exists.
|
||||
Future<AgentToken?> findById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
_is.Transaction? transaction,
|
||||
_is.LockMode? lockMode,
|
||||
_is.LockBehavior? lockBehavior,
|
||||
}) async {
|
||||
return session.db.findById<AgentToken>(
|
||||
id,
|
||||
transaction: transaction,
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts all [AgentToken]s in the list and returns the inserted rows.
|
||||
///
|
||||
/// The returned [AgentToken]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<AgentToken>> insert(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentToken> rows, {
|
||||
_is.Transaction? transaction,
|
||||
bool ignoreConflicts = false,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.insert<AgentToken>(
|
||||
rows,
|
||||
transaction: transaction,
|
||||
ignoreConflicts: ignoreConflicts,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Inserts a single [AgentToken] and returns the inserted row.
|
||||
///
|
||||
/// The returned [AgentToken] will have its `id` field set.
|
||||
Future<AgentToken> insertRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentToken row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.insertRow<AgentToken>(
|
||||
row,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts all [AgentToken]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 [AgentToken]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<AgentToken>> upsert(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentToken> rows, {
|
||||
required _is.ColumnSelections<AgentTokenTable> conflictColumns,
|
||||
_is.ColumnSelections<AgentTokenTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.upsert<AgentToken>(
|
||||
rows,
|
||||
conflictColumns: conflictColumns(AgentToken.t),
|
||||
updateColumns: updateColumns?.call(AgentToken.t),
|
||||
updateWhere: updateWhere?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Upserts a single [AgentToken] 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 [AgentToken] will have its `id` field set.
|
||||
Future<AgentToken?> upsertRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentToken row, {
|
||||
required _is.ColumnSelections<AgentTokenTable> conflictColumns,
|
||||
_is.ColumnSelections<AgentTokenTable>? updateColumns,
|
||||
_is.WhereExpressionBuilder<AgentTokenTable>? updateWhere,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.upsertRow<AgentToken>(
|
||||
row,
|
||||
conflictColumns: conflictColumns(AgentToken.t),
|
||||
updateColumns: updateColumns?.call(AgentToken.t),
|
||||
updateWhere: updateWhere?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [AgentToken]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<AgentToken>> update(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentToken> rows, {
|
||||
_is.ColumnSelections<AgentTokenTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.update<AgentToken>(
|
||||
rows,
|
||||
columns: columns?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [AgentToken]. 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<AgentToken> updateRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentToken row, {
|
||||
_is.ColumnSelections<AgentTokenTable>? columns,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateRow<AgentToken>(
|
||||
row,
|
||||
columns: columns?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates a single [AgentToken] by its [id] with the specified [columnValues].
|
||||
/// Returns the updated row or null if no row with the given id exists.
|
||||
Future<AgentToken?> updateById(
|
||||
_is.DatabaseSession session,
|
||||
int id, {
|
||||
required _is.ColumnValueListBuilder<AgentTokenUpdateTable> columnValues,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.updateById<AgentToken>(
|
||||
id,
|
||||
columnValues: columnValues(AgentToken.t.updateTable),
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Updates all [AgentToken]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<AgentToken>> updateWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.ColumnValueListBuilder<AgentTokenUpdateTable> columnValues,
|
||||
required _is.WhereExpressionBuilder<AgentTokenTable> where,
|
||||
int? limit,
|
||||
int? offset,
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.updateWhere<AgentToken>(
|
||||
columnValues: columnValues(AgentToken.t.updateTable),
|
||||
where: where(AgentToken.t),
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes all [AgentToken]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<AgentToken>> delete(
|
||||
_is.DatabaseSession session,
|
||||
List<AgentToken> rows, {
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.delete<AgentToken>(
|
||||
rows,
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.t),
|
||||
transaction: transaction,
|
||||
noReturn: noReturn,
|
||||
);
|
||||
}
|
||||
|
||||
/// Deletes a single [AgentToken].
|
||||
Future<AgentToken> deleteRow(
|
||||
_is.DatabaseSession session,
|
||||
AgentToken row, {
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.deleteRow<AgentToken>(
|
||||
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<AgentToken>> deleteWhere(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<AgentTokenTable> where,
|
||||
_is.OrderByBuilder<AgentTokenTable>? orderBy,
|
||||
_is.OrderByListBuilder<AgentTokenTable>? orderByList,
|
||||
_is.Transaction? transaction,
|
||||
bool noReturn = false,
|
||||
}) async {
|
||||
return session.db.deleteWhere<AgentToken>(
|
||||
where: where(AgentToken.t),
|
||||
orderBy: orderBy?.call(AgentToken.t),
|
||||
orderByList: orderByList?.call(AgentToken.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<AgentTokenTable>? where,
|
||||
int? limit,
|
||||
_is.Transaction? transaction,
|
||||
}) async {
|
||||
return session.db.count<AgentToken>(
|
||||
where: where?.call(AgentToken.t),
|
||||
limit: limit,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
|
||||
/// Acquires row-level locks on [AgentToken] rows matching the [where] expression.
|
||||
Future<void> lockRows(
|
||||
_is.DatabaseSession session, {
|
||||
required _is.WhereExpressionBuilder<AgentTokenTable> where,
|
||||
required _is.LockMode lockMode,
|
||||
required _is.Transaction transaction,
|
||||
_is.LockBehavior lockBehavior = _is.LockBehavior.wait,
|
||||
}) async {
|
||||
return session.db.lockRows<AgentToken>(
|
||||
where: where(AgentToken.t),
|
||||
lockMode: lockMode,
|
||||
lockBehavior: lockBehavior,
|
||||
transaction: transaction,
|
||||
);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue