Skip to content

Commit f7104ca

Browse files
author
serverpod_cloud
committed
feat(scloud): fad855be3668e6ed71189d859e4d628a5e2bec85
1 parent 4415167 commit f7104ca

11 files changed

Lines changed: 436 additions & 40 deletions

File tree

ground_control_client/lib/src/test_tools/mocks/client.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class EndpointAdminUsersMock extends Mock implements EndpointAdminUsers {}
3636

3737
class EndpointAdminProjectsMock extends Mock implements EndpointAdminProjects {}
3838

39+
class EndpointAdminProcurementMock extends Mock
40+
implements EndpointAdminProcurement {}
41+
3942
class EndpointBillingMock extends Mock implements EndpointBilling {}
4043

4144
class EndpointPlansMock extends Mock implements EndpointPlans {}
@@ -88,6 +91,9 @@ class ClientMock extends Mock implements Client {
8891
@override
8992
final AuthenticationKeyManager authenticationKeyManager;
9093

94+
@override
95+
final Modules modules = ModulesMock();
96+
9197
@override
9298
final EndpointCustomDomainName customDomainName =
9399
EndpointCustomDomainNameMock();
@@ -136,7 +142,8 @@ class ClientMock extends Mock implements Client {
136142
final EndpointAdminProjects adminProjects = EndpointAdminProjectsMock();
137143

138144
@override
139-
final Modules modules = ModulesMock();
145+
final EndpointAdminProcurement adminProcurement =
146+
EndpointAdminProcurementMock();
140147

141148
@override
142149
final EndpointBilling billing = EndpointBillingMock();

serverpod_cloud_cli/lib/command_runner/commands/admin/admin_command.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
22

3+
import 'admin_product_commands.dart';
34
import 'admin_projects_commands.dart';
45
import 'admin_users_commands.dart';
56

@@ -23,5 +24,6 @@ class CloudAdminCommand extends CloudCliCommand {
2324
addSubcommand(AdminListUsersCommand(logger: logger));
2425
addSubcommand(AdminInviteUserCommand(logger: logger));
2526
addSubcommand(AdminListProjectsCommand(logger: logger));
27+
addSubcommand(AdminProductCommand(logger: logger));
2628
}
2729
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import 'package:config/config.dart';
2+
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
3+
import 'package:serverpod_cloud_cli/commands/admin/product_admin.dart';
4+
5+
import '../../helpers/command_options.dart';
6+
7+
class AdminProductCommand extends CloudCliCommand {
8+
@override
9+
final name = 'product';
10+
11+
@override
12+
final description = 'Product procurement administration.';
13+
14+
AdminProductCommand({required super.logger}) {
15+
addSubcommand(AdminListProcuredCommand(logger: logger));
16+
addSubcommand(AdminProcureCommand(logger: logger));
17+
}
18+
}
19+
20+
enum AdminListProcuredOption<V> implements OptionDefinition<V> {
21+
user(
22+
UserEmailOption(argPos: 0, mandatory: true),
23+
);
24+
25+
const AdminListProcuredOption(this.option);
26+
27+
@override
28+
final ConfigOptionBase<V> option;
29+
}
30+
31+
class AdminListProcuredCommand
32+
extends CloudCliCommand<AdminListProcuredOption> {
33+
@override
34+
final name = 'list-procured';
35+
36+
@override
37+
final description = "List an owner's procured products.";
38+
39+
AdminListProcuredCommand({required super.logger})
40+
: super(options: AdminListProcuredOption.values);
41+
42+
@override
43+
Future<void> runWithConfig(
44+
final Configuration<AdminListProcuredOption> commandConfig,
45+
) async {
46+
final userEmail = commandConfig.value(AdminListProcuredOption.user);
47+
48+
await ProductAdminCommands.listProcuredProducts(
49+
runner.serviceProvider.cloudApiClient,
50+
logger: logger,
51+
userEmail: userEmail,
52+
);
53+
}
54+
}
55+
56+
enum AdminProcureOption<V> implements OptionDefinition<V> {
57+
user(
58+
UserEmailOption(argPos: 0, mandatory: true),
59+
),
60+
productName(
61+
StringOption(
62+
argName: 'name',
63+
argAbbrev: 'n',
64+
argPos: 1,
65+
helpText: 'The name of the product to procure.'
66+
' Can be passed as the second argument.',
67+
mandatory: true,
68+
),
69+
),
70+
productVersion(
71+
IntOption(
72+
argName: 'version',
73+
argAbbrev: 'v',
74+
argPos: 2,
75+
helpText: 'The product version (latest if unspecified).'
76+
' Can be passed as the third argument.',
77+
min: 0,
78+
),
79+
),
80+
overrideChecks(
81+
FlagOption(
82+
argName: 'override',
83+
helpText: 'Override product availability checks.',
84+
negatable: false,
85+
defaultsTo: false,
86+
),
87+
);
88+
89+
const AdminProcureOption(this.option);
90+
91+
@override
92+
final ConfigOptionBase<V> option;
93+
}
94+
95+
class AdminProcureCommand extends CloudCliCommand<AdminProcureOption> {
96+
@override
97+
final name = 'procure';
98+
99+
@override
100+
final description = 'Procure a product for an owner.\n'
101+
'By specifying the override flag, the product is procured '
102+
'even if locked or the owner lacks allowance.';
103+
104+
AdminProcureCommand({required super.logger})
105+
: super(options: AdminProcureOption.values);
106+
107+
@override
108+
Future<void> runWithConfig(
109+
final Configuration<AdminProcureOption> commandConfig,
110+
) async {
111+
final userEmail = commandConfig.value(AdminProcureOption.user);
112+
final productName = commandConfig.value(AdminProcureOption.productName);
113+
final ver = commandConfig.optionalValue(AdminProcureOption.productVersion);
114+
final override = commandConfig.value(AdminProcureOption.overrideChecks);
115+
116+
await ProductAdminCommands.procureProduct(
117+
runner.serviceProvider.cloudApiClient,
118+
logger: logger,
119+
userEmail: userEmail,
120+
productName: productName,
121+
productVersion: ver,
122+
overrideChecks: override,
123+
);
124+
}
125+
}

serverpod_cloud_cli/lib/command_runner/commands/admin/admin_users_commands.dart

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'package:config/config.dart';
2-
import 'package:email_validator/email_validator.dart';
32
import 'package:ground_control_client/ground_control_client.dart'
43
show UserAccountStatus;
54
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
@@ -65,15 +64,7 @@ class AdminListUsersCommand extends CloudCliCommand<AdminListUsersOption> {
6564

6665
enum AdminInviteUserOption<V> implements OptionDefinition<V> {
6766
user(
68-
StringOption(
69-
argName: 'user',
70-
argAbbrev: 'u',
71-
argPos: 0,
72-
helpText: 'The user email address. '
73-
'Can be specified as first argument.',
74-
mandatory: true,
75-
customValidator: _emailValidator,
76-
),
67+
UserEmailOption(argPos: 0, mandatory: true),
7768
);
7869

7970
const AdminInviteUserOption(this.option);
@@ -105,9 +96,3 @@ class AdminInviteUserCommand extends CloudCliCommand<AdminInviteUserOption> {
10596
);
10697
}
10798
}
108-
109-
void _emailValidator(final value) {
110-
if (!EmailValidator.validate(value)) {
111-
throw FormatException('Invalid email address: $value');
112-
}
113-
}

serverpod_cloud_cli/lib/command_runner/commands/project_command.dart

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'dart:io';
22

33
import 'package:config/config.dart';
4-
import 'package:email_validator/email_validator.dart';
54
import 'package:path/path.dart' as p;
65

76
import 'package:serverpod_cloud_cli/command_runner/cloud_cli_command.dart';
@@ -197,12 +196,6 @@ class CloudProjectLinkCommand
197196
}
198197
}
199198

200-
void _emailValidator(final value) {
201-
if (!EmailValidator.validate(value)) {
202-
throw FormatException('Invalid email address: $value');
203-
}
204-
}
205-
206199
const _projectRoleNames = ['admin'];
207200
const _projectRoleHelp = {'admin': 'Admins have full access to the project.'};
208201

@@ -211,13 +204,7 @@ enum ProjectInviteUserOption<V> implements OptionDefinition<V> {
211204
ProjectIdOption(asFirstArg: true),
212205
),
213206
user(
214-
StringOption(
215-
argName: 'user',
216-
argAbbrev: 'u',
217-
helpText: 'The email address of the user.',
218-
mandatory: true,
219-
customValidator: _emailValidator,
220-
),
207+
UserEmailOption(mandatory: true),
221208
),
222209
roles(
223210
MultiStringOption(
@@ -273,14 +260,7 @@ enum ProjectRevokeUserOption<V> implements OptionDefinition<V> {
273260
ProjectIdOption(asFirstArg: true),
274261
),
275262
user(
276-
StringOption(
277-
argName: 'user',
278-
argAbbrev: 'u',
279-
argPos: 1,
280-
helpText: 'The email address of the user.',
281-
mandatory: true,
282-
customValidator: _emailValidator,
283-
),
263+
UserEmailOption(argPos: 1, mandatory: true),
284264
),
285265
roles(
286266
MultiStringOption(

serverpod_cloud_cli/lib/command_runner/helpers/command_options.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import 'package:config/config.dart';
22
import 'package:serverpod_cloud_cli/util/scloud_config/scloud_config.dart';
33

4+
import 'email_validator.dart';
5+
46
abstract final class CommandConfigConstants {
57
static const listOptionAbbrev = 'l';
68
}
@@ -103,3 +105,17 @@ class UtcOption extends FlagOption {
103105
envName: 'SERVERPOD_CLOUD_DISPLAY_UTC',
104106
);
105107
}
108+
109+
class UserEmailOption extends StringOption {
110+
const UserEmailOption({
111+
super.argPos,
112+
super.mandatory,
113+
}) : super(
114+
argName: 'user',
115+
argAbbrev: 'u',
116+
customValidator: emailValidator,
117+
// a bit convoluted due to Dart's const requirements:
118+
helpText: 'The user email address.'
119+
'${argPos == 0 ? ' Can be passed as the first argument.' : argPos == 1 ? ' Can be passed as the second argument.' : ''}',
120+
);
121+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:email_validator/email_validator.dart';
2+
3+
/// Validates an email address.
4+
///
5+
/// Throws a [FormatException] if the email address is invalid.
6+
void emailValidator(final String value) {
7+
if (!EmailValidator.validate(value)) {
8+
throw FormatException('Invalid email address: $value');
9+
}
10+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'package:ground_control_client/ground_control_client.dart';
2+
import 'package:serverpod_cloud_cli/command_logger/command_logger.dart';
3+
import 'package:serverpod_cloud_cli/util/printers/table_printer.dart';
4+
5+
abstract class ProductAdminCommands {
6+
static Future<void> listProcuredProducts(
7+
final Client cloudApiClient, {
8+
required final CommandLogger logger,
9+
required final String userEmail,
10+
}) async {
11+
final productRecords =
12+
await cloudApiClient.adminProcurement.listProcuredProducts(
13+
userEmail: userEmail,
14+
);
15+
16+
final table = TablePrinter(
17+
headers: [
18+
'Product',
19+
'Type',
20+
],
21+
rows: productRecords.map((final product) => [
22+
product.$1,
23+
product.$2,
24+
]),
25+
);
26+
table.writeLines(logger.line);
27+
}
28+
29+
static Future<void> procureProduct(
30+
final Client cloudApiClient, {
31+
required final CommandLogger logger,
32+
required final String userEmail,
33+
required final String productName,
34+
final int? productVersion,
35+
final bool? overrideChecks,
36+
}) async {
37+
await cloudApiClient.adminProcurement.procureProduct(
38+
userEmail: userEmail,
39+
productName: productName,
40+
productVersion: productVersion,
41+
overrideChecks: overrideChecks,
42+
);
43+
44+
logger.success(
45+
'The product has been procured for the user.',
46+
newParagraph: true,
47+
);
48+
}
49+
}

0 commit comments

Comments
 (0)