create method
Two calls in a fixed order — whoami before create — so the account's
type/organizationId decide ownerType the way
lib/ui/screens/create_app/_state.dart does, rather than the caller
having to know it. FakeAdapter.calls in apps_service_test.dart
proves the order.
A member of an organization is refused locally, before the POST —
the backend's own FORBIDDEN: 403 (appCreateService, #30) is the
backstop, not the only gate, the way an already-stale whoami read
could otherwise cost a wasted round trip. Individuals are never
refused here — there is no "member" concept on an individual account.
Implementation
Future<Map<String, dynamic>> create({
required Dio apiDio,
required String name,
required String packageName,
}) async {
try {
final whoamiResp = await apiDio.get(Endpoints.whoami);
final whoami = whoamiResp.data['data'] as Map<String, dynamic>;
final organizationId = whoami['organizationId'] as String?;
final ownerType =
whoami['type'] == 'organization' && organizationId != null
? 'organization'
: 'individual';
if (ownerType == 'organization' && whoami['role'] == 'member') {
throw const AuthCliFault(
ExitCodes.forbidden,
'Only admins and developers can create organization apps.',
errorCode: 'forbidden',
);
}
final resp = await apiDio.post(
Endpoints.appCreate,
data: {
'name': name,
'packageName': packageName,
'ownerType': ownerType,
if (organizationId != null) 'organizationId': organizationId,
},
);
return resp.data['data'] as Map<String, dynamic>;
} on DioException catch (e) {
throw _mapError(e, action: 'create app');
}
}