refactor(profile): handles and profiles move into halkyon_base

People's public profiles are the instance's register's (ADR 0016), so
the module now holds them: the public_profile table (unchanged, so
Learn's migration records versions only; devices get the table empty,
as they hold every module's tables), the profile and profiles
endpoints under `base`, the handle rules, the link checks and the page
at /@handle, whose foot names the app as the host's configuration says
(profilePageNote). Deleting a person deletes their profile in the
module's core.

Hosts read profiles through the module (profileOf, profilesOf,
personWithHandle, fixHandle; saveProfile for their tests), behind an
AccountRegister that is this server's own database for now. Learn's
feedback, discussions and publishing ask it; publishing fixes the
handle just before it writes the release, outside its transaction.
Explore still joins the table in its search, so Learn holds the
register wherever it runs. Learn's app calls the module's endpoints;
a profile not found is ProfileNotFoundException. Todo's database gets
the table too (it was never deployed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-19 22:20:27 +02:00
commit 4107cc3087
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
8 changed files with 38851 additions and 1197 deletions

View file

@ -61,7 +61,7 @@ void main() {
)).id;
people.add(id);
if (handle != null) {
await endpoints.profile.save(signedIn(id), handle, '', '', []);
await saveProfile(session, id, handle: handle);
}
return id;
}

View file

@ -34,7 +34,12 @@ void main() {
);
Future<void> profile(UuidValue person, String handle, String name) =>
endpoints.profile.save(signedIn(person), handle, name, '', []);
saveProfile(
sessionBuilder.build(),
person,
handle: handle,
displayName: name,
);
setUp(() async {
Serverpod.instance.initializeOfflineSync(syncTables: syncTables);

View file

@ -44,7 +44,7 @@ void main() {
)).id;
people.add(id);
if (handle != null) {
await endpoints.profile.save(signedIn(id), handle, '', '', []);
await saveProfile(session, id, handle: handle);
}
return id;
}

View file

@ -21,6 +21,16 @@ void main() {
expect(info.sourceCodeUrl, upstreamSourceCodeUrl);
});
test('says which domain handles are on', () async {
final info = await endpoints.instance.info(
sessionBuilder.copyWith(
authentication: AuthenticationOverride.unauthenticated(),
),
);
expect(info.accountDomain, isNotEmpty);
});
test('names the oldest app build it still works with, to anyone, so an '
'older app can say it needs an update', () async {
final info = await endpoints.instance.info(

View file

@ -1,365 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:clock/clock.dart';
import 'package:halkyon_base_server/base.dart';
import 'package:halkyon_learn_server/src/generated/protocol.dart';
import 'package:serverpod/protocol.dart' show CloudStorageEntry;
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:test/test.dart';
import '../support/auth_fixtures.dart';
import '../test_tools/serverpod_test_tools.dart';
void main() {
withServerpod(
'Public profiles',
rollbackDatabase: RollbackDatabase.disabled,
(sessionBuilder, endpoints) {
late Session session;
setUp(() {
useLearnBase(sessionBuilder);
session = sessionBuilder.build();
});
tearDown(
() => PublicProfile.db.deleteWhere(session, where: (t) => t.id > 0),
);
/// A person with an identity, signed in.
TestSessionBuilder identifiedPerson() => sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
const Uuid().v7(),
{identified},
),
);
final alice = identifiedPerson();
final bob = identifiedPerson();
Future<Profile> save(
TestSessionBuilder person,
String handle, {
String displayName = '',
String bio = '',
List<String> links = const [],
}) => endpoints.profile.save(person, handle, displayName, bio, links);
Matcher refused(ProfileRefusal reason) => throwsA(
isA<ProfileRefusedException>().having(
(error) => error.reason,
'reason',
reason,
),
);
test('a person with an identity makes a profile, found by its handle '
'whatever its case, at the account domain', () async {
final saved = await save(
alice,
'Raisondetredev',
displayName: ' Tizian ',
bio: 'Learning Japanese.',
links: [' https://halkyon.app ', ''],
);
expect(saved.handle, 'Raisondetredev');
expect(saved.address, startsWith('Raisondetredev@'));
expect(saved.displayName, 'Tizian');
expect(saved.links, ['https://halkyon.app']);
expect(saved.handleFixed, isFalse);
// Generated classes compare by identity: their JSON by value.
expect((await endpoints.profile.mine(alice))!.toJson(), saved.toJson());
expect(
(await endpoints.profiles.byHandle(bob, 'raisondetredev')).toJson(),
saved.toJson(),
);
});
test('an anonymous person has no profile to make or read', () async {
final anonymous = sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
const Uuid().v7(),
{},
),
);
await expectLater(
save(anonymous, 'nobody'),
throwsA(isA<ServerpodInsufficientAccessException>()),
);
await expectLater(
endpoints.profile.mine(anonymous),
throwsA(isA<ServerpodInsufficientAccessException>()),
);
await expectLater(
endpoints.profile.checkHandle(anonymous, 'nobody'),
throwsA(isA<ServerpodInsufficientAccessException>()),
);
expect(await PublicProfile.db.count(session), 0);
});
test('a handle is checked as saving would check it: free, or your '
'own, whatever its case; else why not', () async {
await save(alice, 'alice');
Future<ProfileRefusal?> check(String handle) =>
endpoints.profile.checkHandle(bob, handle);
expect(await check('bob'), isNull);
expect(await check('ALICE'), ProfileRefusal.handleTaken);
expect(await check('a-b'), ProfileRefusal.handleInvalid);
expect(await check('Admin'), ProfileRefusal.handleReserved);
expect(
await endpoints.profile.checkHandle(alice, 'Alice'),
isNull,
);
// Checking makes nothing.
expect(await endpoints.profile.mine(bob), isNull);
});
test('nobody signed in reads profiles', () async {
await save(alice, 'alice');
await expectLater(
endpoints.profiles.byHandle(sessionBuilder, 'alice'),
throwsA(isA<ServerpodUnauthenticatedException>()),
);
});
test('a handle someone else has is refused, whatever its case; their '
'profile stays theirs', () async {
await save(alice, 'alice', displayName: 'Alice');
await expectLater(
save(bob, 'ALICE'),
refused(ProfileRefusal.handleTaken),
);
expect((await endpoints.profile.mine(alice))!.displayName, 'Alice');
expect(await endpoints.profile.mine(bob), isNull);
});
test('handles are 1 to 30 letters, digits or underscores, not one '
'kept for the instance', () async {
for (final handle in ['', 'with space', 'é', 'a-b', 'a' * 31]) {
await expectLater(
save(alice, handle),
refused(ProfileRefusal.handleInvalid),
reason: handle,
);
}
await expectLater(
save(alice, 'Admin'),
refused(ProfileRefusal.handleReserved),
);
expect((await save(alice, 'a_1' * 10)).handle, 'a_1' * 10);
});
test('the name, bio and links stay within their bounds', () async {
await expectLater(
save(alice, 'alice', displayName: 'x' * 31),
refused(ProfileRefusal.displayNameTooLong),
);
await expectLater(
save(alice, 'alice', bio: 'x' * 501),
refused(ProfileRefusal.bioTooLong),
);
await expectLater(
save(alice, 'alice', links: List.filled(5, 'https://a.example')),
refused(ProfileRefusal.tooManyLinks),
);
for (final link in ['halkyon.app', 'ftp://a.example', 'https://']) {
await expectLater(
save(alice, 'alice', links: [link]),
refused(ProfileRefusal.linkInvalid),
reason: link,
);
}
});
test('the handle changes until the first publish; then it is fixed, '
'and the profile stays, though the rest still changes', () async {
await save(alice, 'alice');
expect((await save(alice, 'alice_jp')).handle, 'alice_jp');
final row = (await PublicProfile.db.findFirstRow(
session,
where: (t) => t.handleKey.equals('alice_jp'),
))!;
await PublicProfile.db.updateRow(
session,
row.copyWith(firstPublishedAt: clock.now().toUtc()),
);
await expectLater(
save(alice, 'alice'),
refused(ProfileRefusal.published),
);
expect(
await endpoints.profile.checkHandle(alice, 'alice'),
ProfileRefusal.published,
);
expect(
await endpoints.profile.checkHandle(alice, 'ALICE_jp'),
isNull,
);
await expectLater(
endpoints.profile.remove(alice),
refused(ProfileRefusal.published),
);
final renamed = await save(alice, 'Alice_JP', displayName: 'Alice');
expect((renamed.handle, renamed.handleFixed), ('Alice_JP', true));
});
test('removing a profile frees its handle', () async {
await save(alice, 'alice');
await endpoints.profile.remove(alice);
expect(await endpoints.profile.mine(alice), isNull);
expect((await save(bob, 'alice')).handle, 'alice');
});
test("a handle nobody has isn't found", () async {
await expectLater(
endpoints.profiles.byHandle(alice, 'nobody'),
throwsA(isA<NotFoundException>()),
);
});
test('deleting a person deletes their profile', () async {
useLearnBase(sessionBuilder);
final carol = (await AuthServices.instance.authUsers.create(session))
.id;
await endpoints.profile.save(
sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
carol.uuid,
{identified},
),
),
'carol',
'',
'',
[],
);
await deletePerson(session, carol);
expect(await PublicProfile.db.count(session), 0);
});
group('An avatar', () {
/// A PNG of one pixel, [shade] of grey.
ByteData png(int shade) {
final bytes = base64Decode(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
);
// A byte past the signature, so each shade is another file.
bytes[bytes.length - 1] = shade;
return ByteData.sublistView(bytes);
}
Future<int> stored() => CloudStorageEntry.db.count(
session,
where: (t) => t.storageId.equals('public'),
);
tearDown(
() => CloudStorageEntry.db.deleteWhere(
session,
where: (t) => t.storageId.equals('public'),
),
);
test('is shown from a public URL that names the picture, not the '
'person', () async {
await save(alice, 'alice');
final profile = await endpoints.profile.setAvatar(alice, png(1));
final url = profile.avatarUrl!;
expect(url, contains('avatars'));
final row = (await PublicProfile.db.findFirstRow(
session,
where: (t) => t.handleKey.equals('alice'),
))!;
expect(url, isNot(contains(row.authUserId.toString())));
expect(await stored(), 1);
});
test('is a PNG of at most 1 MB, nothing else', () async {
await save(alice, 'alice');
await expectLater(
endpoints.profile.setAvatar(
alice,
ByteData.sublistView(Uint8List.fromList(utf8.encode('GIF89a…'))),
),
refused(ProfileRefusal.avatarInvalid),
);
final huge = Uint8List(1024 * 1024 + 1)
..setRange(0, 8, png(1).buffer.asUint8List(0, 8));
await expectLater(
endpoints.profile.setAvatar(alice, ByteData.sublistView(huge)),
refused(ProfileRefusal.avatarInvalid),
);
expect(await stored(), 0);
});
test('needs a profile to show on', () async {
await expectLater(
endpoints.profile.setAvatar(alice, png(1)),
throwsA(isA<NotFoundException>()),
);
});
test('a new one replaces the old, whose file goes; one another '
'profile shows stays', () async {
await save(alice, 'alice');
await save(bob, 'bob');
await endpoints.profile.setAvatar(alice, png(1));
await endpoints.profile.setAvatar(bob, png(1));
await endpoints.profile.setAvatar(alice, png(2));
expect(await stored(), 2);
await endpoints.profile.setAvatar(bob, png(3));
expect(await stored(), 2);
final removed = await endpoints.profile.removeAvatar(alice);
expect(removed.avatarUrl, isNull);
expect(await stored(), 1);
});
test('goes with its person, and with the profile', () async {
useLearnBase(sessionBuilder);
final carol = (await AuthServices.instance.authUsers.create(
session,
)).id;
final asCarol = sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
carol.uuid,
{identified},
),
);
await save(asCarol, 'carol');
await endpoints.profile.setAvatar(asCarol, png(4));
await save(alice, 'alice');
await endpoints.profile.setAvatar(alice, png(5));
await deletePerson(session, carol);
expect(await stored(), 1);
await endpoints.profile.remove(alice);
expect(await stored(), 0);
});
});
test('the instance says which domain handles are on', () async {
expect(
(await endpoints.instance.info(sessionBuilder)).accountDomain,
isNotEmpty,
);
});
},
);
}

View file

@ -1,232 +0,0 @@
import 'package:halkyon_base_server/base.dart';
import 'package:halkyon_learn_server/src/generated/protocol.dart';
import 'package:halkyon_learn_server/src/profile/link_verification.dart';
import 'package:halkyon_learn_server/src/profile/profile_page.dart';
import 'package:halkyon_learn_server/src/profile/profile_rules.dart';
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:test/test.dart';
import '../test_tools/serverpod_test_tools.dart';
/// Pages by their address, in place of the web; a link without one can't
/// be had.
final class _Web implements LinkFetcher {
final pages = <String, String>{};
@override
Future<FetchedPage?> fetch(Uri link) async => switch (pages['$link']) {
final html? => (url: link, html: html),
null => null,
};
}
void main() {
withServerpod(
'Verified profile links',
// Checks are counted by Serverpod's rate limiter, in transactions of
// their own, which the test's rollback can't wrap.
rollbackDatabase: RollbackDatabase.disabled,
(sessionBuilder, endpoints) {
late Session session;
late _Web web;
TestSessionBuilder identifiedPerson() => sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
const Uuid().v7(),
{identified},
),
);
final alice = identifiedPerson();
final bob = identifiedPerson();
setUp(() {
session = sessionBuilder.build();
web = _Web();
LinkChecks.of(session.serverpod).fetcher = web;
});
tearDown(() async {
await PublicProfile.db.deleteWhere(session, where: (t) => t.id > 0);
await RateLimitedRequestAttempt.db.deleteWhere(
session,
where: (t) => t.id.notEquals(null),
);
});
/// A page linking back to [handle]'s profile page with `rel="me"`.
String linkingBack(String handle) =>
'<a rel="me" href="${profilePageUrl(session.serverpod, handle)}">'
'My profile</a>';
Future<Profile> save(
TestSessionBuilder person,
String handle,
List<String> links,
) => endpoints.profile.save(person, handle, 'Alice', '', links);
Matcher refused(ProfileRefusal reason) => throwsA(
isA<ProfileRefusedException>().having(
(error) => error.reason,
'reason',
reason,
),
);
test('checking is refused when not signed in, and to an anonymous '
'person, who has no profile', () async {
await expectLater(
endpoints.profile.checkLinks(
sessionBuilder.copyWith(
authentication: AuthenticationOverride.unauthenticated(),
),
),
throwsA(isA<ServerpodUnauthenticatedException>()),
);
await expectLater(
endpoints.profile.checkLinks(
sessionBuilder.copyWith(
authentication: AuthenticationOverride.authenticationInfo(
const Uuid().v7(),
{},
),
),
),
throwsA(isA<ServerpodInsufficientAccessException>()),
);
});
test('checking marks the links whose pages link back to the profile '
"page, which others see too, beside the page's address", () async {
await save(alice, 'Alice', [
'https://alice.example/',
'https://social.example/@alice',
'https://elsewhere.example/',
]);
web.pages['https://alice.example/'] = linkingBack('alice');
web.pages['https://social.example/@alice'] =
'<a href="${profilePageUrl(session.serverpod, 'Alice')}">me</a>';
final checked = await endpoints.profile.checkLinks(alice);
expect(checked.verifiedLinks, ['https://alice.example/']);
expect(
checked.pageUrl,
'${profilePageUrl(session.serverpod, 'Alice')}',
);
expect(checked.pageUrl, endsWith('/@Alice'));
expect(
(await endpoints.profiles.byHandle(bob, 'alice')).verifiedLinks,
['https://alice.example/'],
);
});
test('saving keeps the checks of the links left as they were, and drops '
'those of links changed or removed; a new handle drops them all, as '
'the page moved', () async {
await save(alice, 'alice', [
'https://alice.example/',
'https://blog.example/',
]);
web.pages['https://alice.example/'] = linkingBack('alice');
web.pages['https://blog.example/'] = linkingBack('alice');
await endpoints.profile.checkLinks(alice);
final saved = await save(alice, 'alice', [
'https://alice.example/',
'https://blog.example/new',
]);
expect(saved.verifiedLinks, ['https://alice.example/']);
final renamed = await save(alice, 'alice2', [
'https://alice.example/',
]);
expect(renamed.verifiedLinks, isEmpty);
});
test('an eleventh check within the hour is refused', () async {
await save(alice, 'alice', ['https://alice.example/']);
for (var i = 0; i < 10; i++) {
await endpoints.profile.checkLinks(alice);
}
await expectLater(
endpoints.profile.checkLinks(alice),
refused(ProfileRefusal.tooManyLinkChecks),
);
});
group('the profile page', () {
test("shows the profile in the visitor's language, its links marked "
'rel="me", what anybody wrote escaped', () async {
await endpoints.profile.save(
alice,
'alice',
'<script>Alice</script>',
'Learns Japanese & German.\nLikes tea.',
['https://alice.example/', 'https://blog.example/?a=1&b=2'],
);
web.pages['https://alice.example/'] = linkingBack('alice');
await endpoints.profile.checkLinks(alice);
final page = await profilePage(
session,
'@ALICE',
host: profilePageUrl(session.serverpod, 'alice').host,
acceptLanguage: 'de-CH,de;q=0.9,en;q=0.5',
);
expect(page.status, 200);
expect(page.html, contains('<html lang="de">'));
expect(page.html, contains('&lt;script&gt;Alice&lt;/script&gt;'));
expect(page.html, isNot(contains('<script>')));
expect(page.html, contains('Learns Japanese &amp; German.<br>'));
expect(
page.html,
contains('<link rel="me" href="https://alice.example/">'),
);
expect(
page.html,
contains(
'<a rel="me noopener" href="https://blog.example/?a=1&amp;b=2">',
),
);
expect(page.html, contains('geprüft'));
expect(page.html, contains('alice@'));
});
test(
'is not found for a handle nobody has, or anything else',
() async {
for (final segment in ['@nobody', 'alice', '@', '@a/b']) {
final page = await profilePage(
session,
segment,
host: profilePageUrl(session.serverpod, 'x').host,
);
expect(page.status, 404, reason: segment);
}
},
);
test(
'asked for on another host, sends the visitor to its own address',
() async {
await save(alice, 'alice', const []);
final page = await profilePage(
session,
'@alice',
host: 'elsewhere.example',
);
expect(page.status, 308);
expect(
page.location,
'${profilePageUrl(session.serverpod, 'alice')}',
);
},
);
});
},
);
}

View file

@ -74,9 +74,12 @@ void main() {
),
);
Future<Profile> profile(UuidValue person, String handle) => endpoints
.profile
.save(signedIn(person), handle, handle.toUpperCase(), '', []);
Future<Profile> profile(UuidValue person, String handle) => saveProfile(
sessionBuilder.build(),
person,
handle: handle,
displayName: handle.toUpperCase(),
);
Future<Publication> publish(
UuidValue person,
@ -416,7 +419,7 @@ void main() {
);
// The handle is fixed from the first publish on.
expect(
(await endpoints.profile.mine(signedIn(alice)))!.handleFixed,
(await profileOf(sessionBuilder.build(), alice))!.handleFixed,
isTrue,
);
});

View file

@ -52,10 +52,6 @@ import 'package:halkyon_learn_server/src/generated/future_calls.dart'
as _iip9210p;
import 'package:halkyon_learn_server/src/generated/instance/instance_info.dart'
as _iof1vv19;
import 'package:halkyon_learn_server/src/generated/profile/profile.dart'
as _i1bgmvyh;
import 'package:halkyon_learn_server/src/generated/profile/profile_refusal.dart'
as _igp2arco;
import 'package:halkyon_learn_server/src/generated/publish/publication.dart'
as _ieaj397n;
import 'package:serverpod/serverpod.dart' as _is;
@ -215,10 +211,6 @@ class TestEndpoints {
late final _InstanceEndpoint instance;
late final _ProfileEndpoint profile;
late final _ProfilesEndpoint profiles;
late final _PublishEndpoint publish;
}
@ -269,14 +261,6 @@ class _InternalTestEndpoints extends TestEndpoints
endpoints,
serializationManager,
);
profile = _ProfileEndpoint(
endpoints,
serializationManager,
);
profiles = _ProfilesEndpoint(
endpoints,
serializationManager,
);
publish = _PublishEndpoint(
endpoints,
serializationManager,
@ -1861,278 +1845,6 @@ class _InstanceEndpoint {
}
}
class _ProfileEndpoint {
_ProfileEndpoint(
this._endpointDispatch,
this._serializationManager,
);
final _is.EndpointDispatch _endpointDispatch;
final _is.SerializationManager _serializationManager;
_ida.Future<_i1bgmvyh.Profile?> mine(
_ist.TestSessionBuilder sessionBuilder,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'mine',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'mine',
parameters: _ist.testObjectToJson({}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile?>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<_i1bgmvyh.Profile> save(
_ist.TestSessionBuilder sessionBuilder,
String handle,
String displayName,
String bio,
List<String> links,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'save',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'save',
parameters: _ist.testObjectToJson({
'handle': handle,
'displayName': displayName,
'bio': bio,
'links': links,
}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<_i1bgmvyh.Profile> checkLinks(
_ist.TestSessionBuilder sessionBuilder,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'checkLinks',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'checkLinks',
parameters: _ist.testObjectToJson({}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<_igp2arco.ProfileRefusal?> checkHandle(
_ist.TestSessionBuilder sessionBuilder,
String handle,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'checkHandle',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'checkHandle',
parameters: _ist.testObjectToJson({'handle': handle}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_igp2arco.ProfileRefusal?>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<_i1bgmvyh.Profile> setAvatar(
_ist.TestSessionBuilder sessionBuilder,
_idt.ByteData png,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'setAvatar',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'setAvatar',
parameters: _ist.testObjectToJson({'png': png}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<_i1bgmvyh.Profile> removeAvatar(
_ist.TestSessionBuilder sessionBuilder,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'removeAvatar',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'removeAvatar',
parameters: _ist.testObjectToJson({}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
_ida.Future<void> remove(_ist.TestSessionBuilder sessionBuilder) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profile',
method: 'remove',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profile',
methodName: 'remove',
parameters: _ist.testObjectToJson({}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<void>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
}
class _ProfilesEndpoint {
_ProfilesEndpoint(
this._endpointDispatch,
this._serializationManager,
);
final _is.EndpointDispatch _endpointDispatch;
final _is.SerializationManager _serializationManager;
_ida.Future<_i1bgmvyh.Profile> byHandle(
_ist.TestSessionBuilder sessionBuilder,
String handle,
) async {
return _ist.callAwaitableFunctionAndHandleExceptions(() async {
var _localUniqueSession =
(sessionBuilder as _ist.InternalTestSessionBuilder).internalBuild(
endpoint: 'profiles',
method: 'byHandle',
);
try {
var _localCallContext = await _endpointDispatch.getMethodCallContext(
createSessionCallback: (_) => _localUniqueSession,
endpointPath: 'profiles',
methodName: 'byHandle',
parameters: _ist.testObjectToJson({'handle': handle}),
serializationManager: _serializationManager,
);
var _localReturnValue =
await (_localCallContext.method.call(
_localUniqueSession,
_localCallContext.arguments,
)
as _ida.Future<_i1bgmvyh.Profile>);
return _localReturnValue;
} finally {
await _localUniqueSession.close();
}
});
}
}
class _PublishEndpoint {
_PublishEndpoint(
this._endpointDispatch,