@fleetbase/registry-bridge-engine 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/controllers/application.js +6 -1
- package/addon/controllers/developers/payments/settings.js +58 -0
- package/addon/routes/developers/payments/settings.js +23 -0
- package/addon/routes.js +1 -0
- package/addon/templates/application.hbs +28 -26
- package/addon/templates/developers/payments/index.hbs +9 -3
- package/addon/templates/developers/payments/onboard.hbs +1 -1
- package/addon/templates/developers/payments/settings.hbs +30 -0
- package/composer.json +1 -1
- package/config/environment.js +23 -0
- package/extension.json +1 -1
- package/package.json +3 -3
- package/server/migrations/2026_02_15_000001_create_registry_developer_accounts_table.php +44 -0
- package/server/migrations/2026_02_15_000002_add_account_type_to_registry_users_table.php +48 -0
- package/server/migrations/2026_02_15_000003_add_publisher_fields_to_registry_extensions_table.php +34 -0
- package/server/migrations/2026_02_15_100001_convert_purchases_to_polymorphic.php +45 -0
- package/server/migrations/2026_02_15_100002_add_purchaser_indexes.php +35 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +79 -29
- package/server/src/Http/Controllers/Internal/v1/RegistryDeveloperAccountController.php +355 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +28 -0
- package/server/src/Http/Controllers/Internal/v1/RegistryPaymentsController.php +74 -3
- package/server/src/Models/RegistryDeveloperAccount.php +147 -0
- package/server/src/Models/RegistryExtensionPurchase.php +17 -0
- package/server/src/Models/RegistryUser.php +85 -14
- package/server/src/routes.php +19 -5
- package/translations/en-us.yaml +7 -0
|
@@ -8,6 +8,7 @@ use Fleetbase\RegistryBridge\Http\Requests\AddRegistryUserRequest;
|
|
|
8
8
|
use Fleetbase\RegistryBridge\Http\Requests\AuthenticateRegistryUserRequest;
|
|
9
9
|
use Fleetbase\RegistryBridge\Http\Requests\RegistryAuthRequest;
|
|
10
10
|
use Fleetbase\RegistryBridge\Http\Resources\RegistryUser as RegistryUserResource;
|
|
11
|
+
use Fleetbase\RegistryBridge\Models\RegistryDeveloperAccount;
|
|
11
12
|
use Fleetbase\RegistryBridge\Models\RegistryExtension;
|
|
12
13
|
use Fleetbase\RegistryBridge\Models\RegistryUser;
|
|
13
14
|
use Fleetbase\RegistryBridge\Support\Bridge;
|
|
@@ -88,34 +89,59 @@ class RegistryAuthController extends Controller
|
|
|
88
89
|
$identity = $request->input('identity');
|
|
89
90
|
$password = $request->input('password');
|
|
90
91
|
|
|
91
|
-
//
|
|
92
|
+
// First, try to find a cloud user
|
|
92
93
|
$user = User::where(function ($query) use ($identity) {
|
|
93
94
|
$query->where('email', $identity)->orWhere('phone', $identity)->orWhere('username', $identity);
|
|
94
95
|
})->first();
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
if ($user && Auth::isValidPassword($password, $user->password)) {
|
|
98
|
+
// Cloud user authentication
|
|
99
|
+
$registryUser = RegistryUser::firstOrCreate(
|
|
100
|
+
[
|
|
101
|
+
'company_uuid' => $user->company_uuid,
|
|
102
|
+
'user_uuid' => $user->uuid,
|
|
103
|
+
],
|
|
104
|
+
[
|
|
105
|
+
'account_type' => 'cloud',
|
|
106
|
+
'scope' => '*',
|
|
107
|
+
'expires_at' => now()->addYear(),
|
|
108
|
+
'name' => $user->public_id . ' developer token',
|
|
109
|
+
]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return new RegistryUserResource($registryUser);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// If not a cloud user, try Registry Developer Account
|
|
116
|
+
$developerAccount = RegistryDeveloperAccount::where(function ($query) use ($identity) {
|
|
117
|
+
$query->where('email', $identity)->orWhere('username', $identity);
|
|
118
|
+
})->first();
|
|
119
|
+
|
|
120
|
+
if (!$developerAccount) {
|
|
98
121
|
return response()->error('Invalid credentials.', 401);
|
|
99
122
|
}
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!$registryUser) {
|
|
104
|
-
// Create registry user
|
|
105
|
-
$registryUser = RegistryUser::create([
|
|
106
|
-
'company_uuid' => $user->company_uuid,
|
|
107
|
-
'user_uuid' => $user->uuid,
|
|
108
|
-
'scope' => '*',
|
|
109
|
-
'expires_at' => now()->addYear(),
|
|
110
|
-
'name' => $user->public_id . ' developer token',
|
|
111
|
-
]);
|
|
124
|
+
if ($developerAccount->status !== 'active') {
|
|
125
|
+
return response()->error('Account is not active. Please verify your email.', 401);
|
|
112
126
|
}
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return response()->error('Unable to authenticate.');
|
|
128
|
+
if (Auth::isInvalidPassword($password, $developerAccount->password)) {
|
|
129
|
+
return response()->error('Invalid credentials.', 401);
|
|
117
130
|
}
|
|
118
131
|
|
|
132
|
+
// Developer account authentication
|
|
133
|
+
$registryUser = RegistryUser::firstOrCreate(
|
|
134
|
+
[
|
|
135
|
+
'developer_account_uuid' => $developerAccount->uuid,
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
'account_type' => 'developer',
|
|
139
|
+
'scope' => '*',
|
|
140
|
+
'expires_at' => now()->addYear(),
|
|
141
|
+
'name' => $developerAccount->username . ' developer token',
|
|
142
|
+
]
|
|
143
|
+
);
|
|
144
|
+
|
|
119
145
|
return new RegistryUserResource($registryUser);
|
|
120
146
|
}
|
|
121
147
|
|
|
@@ -266,29 +292,53 @@ class RegistryAuthController extends Controller
|
|
|
266
292
|
|
|
267
293
|
// Find package
|
|
268
294
|
$extension = RegistryExtension::findByPackageName($package);
|
|
295
|
+
|
|
269
296
|
if (!$extension) {
|
|
270
|
-
|
|
297
|
+
// First time publishing - create extension record
|
|
298
|
+
$publisherUuid = $registryUser->isDeveloperAccount()
|
|
299
|
+
? $registryUser->developer_account_uuid
|
|
300
|
+
: $registryUser->company_uuid;
|
|
301
|
+
|
|
302
|
+
$extension = RegistryExtension::create([
|
|
303
|
+
'uuid' => (string) Str::uuid(),
|
|
304
|
+
'package_name' => $package,
|
|
305
|
+
'publisher_type' => $registryUser->account_type,
|
|
306
|
+
'publisher_uuid' => $publisherUuid,
|
|
307
|
+
'company_uuid' => $registryUser->company_uuid, // Keep for backwards compatibility
|
|
308
|
+
'status' => 'published',
|
|
309
|
+
'payment_required' => false, // Default to free
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
return response()->json(['allowed' => true]);
|
|
271
313
|
}
|
|
272
314
|
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
315
|
+
// Check ownership
|
|
316
|
+
if ($registryUser->isCloudAccount()) {
|
|
317
|
+
if ($extension->publisher_type === 'cloud' && $extension->publisher_uuid !== $registryUser->company_uuid) {
|
|
318
|
+
return response()->error('You do not own this extension.', 403);
|
|
319
|
+
}
|
|
320
|
+
} elseif ($registryUser->isDeveloperAccount()) {
|
|
321
|
+
if ($extension->publisher_type === 'developer' && $extension->publisher_uuid !== $registryUser->developer_account_uuid) {
|
|
322
|
+
return response()->error('You do not own this extension.', 403);
|
|
323
|
+
}
|
|
278
324
|
}
|
|
279
325
|
|
|
280
|
-
//
|
|
281
|
-
if (
|
|
282
|
-
return response()->error('
|
|
326
|
+
// If publisher types don't match, deny access
|
|
327
|
+
if ($extension->publisher_type !== $registryUser->account_type) {
|
|
328
|
+
return response()->error('Account type mismatch for this extension.', 403);
|
|
283
329
|
}
|
|
284
330
|
|
|
285
|
-
//
|
|
331
|
+
// Update extension status
|
|
286
332
|
if ($action === 'publish') {
|
|
287
333
|
$extension->update(['status' => 'published']);
|
|
288
|
-
$extension->currentBundle
|
|
334
|
+
if ($extension->currentBundle) {
|
|
335
|
+
$extension->currentBundle->update(['status' => 'published']);
|
|
336
|
+
}
|
|
289
337
|
} elseif ($action === 'unpublish') {
|
|
290
338
|
$extension->update(['status' => 'unpublished']);
|
|
291
|
-
$extension->currentBundle
|
|
339
|
+
if ($extension->currentBundle) {
|
|
340
|
+
$extension->currentBundle->update(['status' => 'unpublished']);
|
|
341
|
+
}
|
|
292
342
|
}
|
|
293
343
|
|
|
294
344
|
// Passed all checks
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Fleetbase\RegistryBridge\Http\Controllers\Internal\v1;
|
|
4
|
+
|
|
5
|
+
use Fleetbase\Http\Controllers\Controller;
|
|
6
|
+
use Fleetbase\Models\VerificationCode;
|
|
7
|
+
use Fleetbase\RegistryBridge\Models\RegistryDeveloperAccount;
|
|
8
|
+
use Fleetbase\RegistryBridge\Models\RegistryUser;
|
|
9
|
+
use Illuminate\Http\Request;
|
|
10
|
+
use Illuminate\Support\Facades\Hash;
|
|
11
|
+
use Illuminate\Support\Facades\Validator;
|
|
12
|
+
use Illuminate\Support\Str;
|
|
13
|
+
|
|
14
|
+
class RegistryDeveloperAccountController extends Controller
|
|
15
|
+
{
|
|
16
|
+
/**
|
|
17
|
+
* Register a new Registry Developer Account.
|
|
18
|
+
*
|
|
19
|
+
* @return \Illuminate\Http\JsonResponse
|
|
20
|
+
*/
|
|
21
|
+
public function register(Request $request)
|
|
22
|
+
{
|
|
23
|
+
$validator = Validator::make($request->all(), [
|
|
24
|
+
'username' => 'required|string|min:3|max:255|unique:registry_developer_accounts,username|regex:/^[a-zA-Z0-9_-]+$/',
|
|
25
|
+
'email' => 'required|email|unique:registry_developer_accounts,email',
|
|
26
|
+
'password' => 'required|string|min:8',
|
|
27
|
+
'name' => 'nullable|string|max:255',
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
if ($validator->fails()) {
|
|
31
|
+
return response()->json([
|
|
32
|
+
'errors' => $validator->errors(),
|
|
33
|
+
], 422);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
$validated = $validator->validated();
|
|
37
|
+
|
|
38
|
+
$account = RegistryDeveloperAccount::create([
|
|
39
|
+
'uuid' => (string) Str::uuid(),
|
|
40
|
+
'username' => $validated['username'],
|
|
41
|
+
'email' => $validated['email'],
|
|
42
|
+
'password' => Hash::make($validated['password']),
|
|
43
|
+
'name' => $validated['name'] ?? $validated['username'],
|
|
44
|
+
'status' => 'pending_verification',
|
|
45
|
+
'verification_token' => Str::random(64),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
// Send verification email
|
|
49
|
+
$this->sendVerificationEmail($account);
|
|
50
|
+
|
|
51
|
+
return response()->json([
|
|
52
|
+
'status' => 'success',
|
|
53
|
+
'message' => 'Account created successfully. Please check your email to verify your account.',
|
|
54
|
+
'account' => [
|
|
55
|
+
'uuid' => $account->uuid,
|
|
56
|
+
'username' => $account->username,
|
|
57
|
+
'email' => $account->email,
|
|
58
|
+
],
|
|
59
|
+
], 201);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Verify email address using verification code.
|
|
64
|
+
*
|
|
65
|
+
* @return \Illuminate\Http\JsonResponse
|
|
66
|
+
*/
|
|
67
|
+
public function verifyEmail(Request $request)
|
|
68
|
+
{
|
|
69
|
+
$code = $request->input('code');
|
|
70
|
+
$email = $request->input('email');
|
|
71
|
+
|
|
72
|
+
if (!$code || !$email) {
|
|
73
|
+
return response()->error('Verification code and email are required.', 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Find the account
|
|
77
|
+
$account = RegistryDeveloperAccount::where('email', $email)->first();
|
|
78
|
+
|
|
79
|
+
if (!$account) {
|
|
80
|
+
return response()->error('Account not found.', 404);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if ($account->isActive()) {
|
|
84
|
+
return response()->json([
|
|
85
|
+
'status' => 'success',
|
|
86
|
+
'message' => 'Email already verified.',
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find the verification code
|
|
91
|
+
$verificationCode = VerificationCode::where('subject_uuid', $account->uuid)
|
|
92
|
+
->where('subject_type', RegistryDeveloperAccount::class)
|
|
93
|
+
->where('for', 'registry_developer_account_verification')
|
|
94
|
+
->where('code', $code)
|
|
95
|
+
->whereIn('status', ['pending', null]) // Support both pending and NULL status
|
|
96
|
+
->first();
|
|
97
|
+
|
|
98
|
+
if (!$verificationCode) {
|
|
99
|
+
return response()->error('Invalid or expired verification code.', 400);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if code is expired
|
|
103
|
+
if ($verificationCode->hasExpired()) {
|
|
104
|
+
return response()->error('Verification code has expired.', 400);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Mark as verified
|
|
108
|
+
$account->markEmailAsVerified();
|
|
109
|
+
$verificationCode->update(['status' => 'used']);
|
|
110
|
+
|
|
111
|
+
// Generate registry token for the developer account
|
|
112
|
+
$token = RegistryUser::generateToken();
|
|
113
|
+
|
|
114
|
+
$registryUser = RegistryUser::firstOrCreate(
|
|
115
|
+
[
|
|
116
|
+
'developer_account_uuid' => $account->uuid,
|
|
117
|
+
'account_type' => 'developer',
|
|
118
|
+
],
|
|
119
|
+
[
|
|
120
|
+
'token' => $token,
|
|
121
|
+
'name' => $account->name,
|
|
122
|
+
]
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// If registry user already exists, update the token
|
|
126
|
+
if (!$registryUser->wasRecentlyCreated) {
|
|
127
|
+
$registryUser->update(['token' => $token]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return response()->json([
|
|
131
|
+
'status' => 'success',
|
|
132
|
+
'message' => 'Email verified successfully. You can now log in.',
|
|
133
|
+
'token' => $token,
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resend verification email.
|
|
139
|
+
*
|
|
140
|
+
* @return \Illuminate\Http\JsonResponse
|
|
141
|
+
*/
|
|
142
|
+
public function resendVerification(Request $request)
|
|
143
|
+
{
|
|
144
|
+
$email = $request->input('email');
|
|
145
|
+
|
|
146
|
+
if (!$email) {
|
|
147
|
+
return response()->error('Email is required.', 400);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
$account = RegistryDeveloperAccount::where('email', $email)->first();
|
|
151
|
+
|
|
152
|
+
if (!$account) {
|
|
153
|
+
return response()->error('Account not found.', 404);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if ($account->isActive()) {
|
|
157
|
+
return response()->json([
|
|
158
|
+
'status' => 'success',
|
|
159
|
+
'message' => 'Email already verified.',
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Generate new token
|
|
164
|
+
$account->generateVerificationToken();
|
|
165
|
+
|
|
166
|
+
// Resend verification email
|
|
167
|
+
$this->sendVerificationEmail($account);
|
|
168
|
+
|
|
169
|
+
return response()->json([
|
|
170
|
+
'status' => 'success',
|
|
171
|
+
'message' => 'Verification email sent.',
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get account profile.
|
|
177
|
+
*
|
|
178
|
+
* @return \Illuminate\Http\JsonResponse
|
|
179
|
+
*/
|
|
180
|
+
public function profile(Request $request)
|
|
181
|
+
{
|
|
182
|
+
// This would require middleware to authenticate the user
|
|
183
|
+
// For now, we'll accept a token parameter
|
|
184
|
+
$token = $request->bearerToken() ?? $request->input('token');
|
|
185
|
+
|
|
186
|
+
if (!$token) {
|
|
187
|
+
return response()->error('Authentication required.', 401);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
$registryUser = RegistryUser::findFromToken($token);
|
|
191
|
+
|
|
192
|
+
if (!$registryUser || $registryUser->account_type !== 'developer') {
|
|
193
|
+
return response()->error('Invalid token or not a developer account.', 403);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
$account = $registryUser->developerAccount;
|
|
197
|
+
|
|
198
|
+
if (!$account) {
|
|
199
|
+
return response()->error('Developer account not found.', 404);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return response()->json([
|
|
203
|
+
'uuid' => $account->uuid,
|
|
204
|
+
'username' => $account->username,
|
|
205
|
+
'email' => $account->email,
|
|
206
|
+
'name' => $account->name,
|
|
207
|
+
'avatar_url' => $account->avatar_url,
|
|
208
|
+
'github_username' => $account->github_username,
|
|
209
|
+
'website' => $account->website,
|
|
210
|
+
'bio' => $account->bio,
|
|
211
|
+
'status' => $account->status,
|
|
212
|
+
'email_verified_at' => $account->email_verified_at,
|
|
213
|
+
'created_at' => $account->created_at,
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Update account profile.
|
|
219
|
+
*
|
|
220
|
+
* @return \Illuminate\Http\JsonResponse
|
|
221
|
+
*/
|
|
222
|
+
public function updateProfile(Request $request)
|
|
223
|
+
{
|
|
224
|
+
$token = $request->bearerToken() ?? $request->input('token');
|
|
225
|
+
|
|
226
|
+
if (!$token) {
|
|
227
|
+
return response()->error('Authentication required.', 401);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
$registryUser = RegistryUser::findFromToken($token);
|
|
231
|
+
|
|
232
|
+
if (!$registryUser || $registryUser->account_type !== 'developer') {
|
|
233
|
+
return response()->error('Invalid token or not a developer account.', 403);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
$account = $registryUser->developerAccount;
|
|
237
|
+
|
|
238
|
+
if (!$account) {
|
|
239
|
+
return response()->error('Developer account not found.', 404);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
$validator = Validator::make($request->all(), [
|
|
243
|
+
'name' => 'nullable|string|max:255',
|
|
244
|
+
'avatar_url' => 'nullable|url|max:500',
|
|
245
|
+
'github_username' => 'nullable|string|max:255',
|
|
246
|
+
'website' => 'nullable|url|max:500',
|
|
247
|
+
'bio' => 'nullable|string|max:1000',
|
|
248
|
+
]);
|
|
249
|
+
|
|
250
|
+
if ($validator->fails()) {
|
|
251
|
+
return response()->json([
|
|
252
|
+
'errors' => $validator->errors(),
|
|
253
|
+
], 422);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
$account->update($validator->validated());
|
|
257
|
+
|
|
258
|
+
return response()->json([
|
|
259
|
+
'status' => 'success',
|
|
260
|
+
'message' => 'Profile updated successfully.',
|
|
261
|
+
'account' => $account,
|
|
262
|
+
]);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Generate or regenerate registry token for authenticated developer account.
|
|
267
|
+
*
|
|
268
|
+
* @return \Illuminate\Http\JsonResponse
|
|
269
|
+
*/
|
|
270
|
+
public function generateToken(Request $request)
|
|
271
|
+
{
|
|
272
|
+
$email = $request->input('email');
|
|
273
|
+
$password = $request->input('password');
|
|
274
|
+
|
|
275
|
+
if (!$email || !$password) {
|
|
276
|
+
return response()->error('Email and password are required.', 400);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Find the account
|
|
280
|
+
$account = RegistryDeveloperAccount::where('email', $email)->first();
|
|
281
|
+
|
|
282
|
+
if (!$account) {
|
|
283
|
+
return response()->error('Account not found.', 404);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Verify password
|
|
287
|
+
if (!Hash::check($password, $account->password)) {
|
|
288
|
+
return response()->error('Invalid credentials.', 401);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check if account is active
|
|
292
|
+
if (!$account->isActive()) {
|
|
293
|
+
return response()->error('Account is not active. Please verify your email first.', 403);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Generate new token
|
|
297
|
+
$token = RegistryUser::generateToken();
|
|
298
|
+
|
|
299
|
+
// Find or create registry user
|
|
300
|
+
$registryUser = RegistryUser::firstOrCreate(
|
|
301
|
+
[
|
|
302
|
+
'developer_account_uuid' => $account->uuid,
|
|
303
|
+
'account_type' => 'developer',
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
'token' => $token,
|
|
307
|
+
'name' => $account->name,
|
|
308
|
+
]
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// If registry user already exists, regenerate the token
|
|
312
|
+
if (!$registryUser->wasRecentlyCreated) {
|
|
313
|
+
$registryUser->update(['token' => $token]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return response()->json([
|
|
317
|
+
'status' => 'success',
|
|
318
|
+
'message' => $registryUser->wasRecentlyCreated ? 'Token generated successfully.' : 'Token regenerated successfully.',
|
|
319
|
+
'token' => $token,
|
|
320
|
+
]);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Send verification email to the account.
|
|
325
|
+
*
|
|
326
|
+
* @return void
|
|
327
|
+
*/
|
|
328
|
+
private function sendVerificationEmail(RegistryDeveloperAccount $account)
|
|
329
|
+
{
|
|
330
|
+
try {
|
|
331
|
+
VerificationCode::generateEmailVerificationFor(
|
|
332
|
+
$account,
|
|
333
|
+
'registry_developer_account_verification',
|
|
334
|
+
[
|
|
335
|
+
'subject' => 'Verify your Registry Developer Account',
|
|
336
|
+
'content' => function ($verificationCode) use ($account) {
|
|
337
|
+
return "Hello {$account->name},\n\n" .
|
|
338
|
+
"Thank you for registering a Registry Developer Account!\n\n" .
|
|
339
|
+
"Your verification code is: {$verificationCode->code}\n\n" .
|
|
340
|
+
"To verify your account, copy and paste this command into your terminal:\n\n" .
|
|
341
|
+
"flb verify -e {$account->email} -c {$verificationCode->code}\n\n" .
|
|
342
|
+
"This code will expire in 1 hour.\n\n" .
|
|
343
|
+
'If you did not create this account, please ignore this email.';
|
|
344
|
+
},
|
|
345
|
+
]
|
|
346
|
+
);
|
|
347
|
+
} catch (\Exception $e) {
|
|
348
|
+
// Log the error but don't fail registration
|
|
349
|
+
logger()->error('Failed to send verification email', [
|
|
350
|
+
'email' => $account->email,
|
|
351
|
+
'error' => $e->getMessage(),
|
|
352
|
+
]);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -23,6 +23,34 @@ class RegistryExtensionController extends RegistryBridgeController
|
|
|
23
23
|
*/
|
|
24
24
|
public $resource = 'registry_extension';
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Display a public list of all published extensions.
|
|
28
|
+
*
|
|
29
|
+
* This endpoint is publicly accessible and returns all extensions with a status of 'published'.
|
|
30
|
+
* The results are cached for 15 minutes to improve performance and reduce database load.
|
|
31
|
+
* This endpoint is designed to be called by self-hosted instances to discover available extensions.
|
|
32
|
+
*
|
|
33
|
+
* @param Request $request the incoming HTTP request
|
|
34
|
+
*
|
|
35
|
+
* @return \Illuminate\Http\JsonResponse the collection of published extensions
|
|
36
|
+
*/
|
|
37
|
+
public function listPublicExtensions(Request $request)
|
|
38
|
+
{
|
|
39
|
+
$cacheKey = 'public-extensions-list';
|
|
40
|
+
$cacheTtl = now()->addMinutes(15);
|
|
41
|
+
|
|
42
|
+
$extensions = \Illuminate\Support\Facades\Cache::remember($cacheKey, $cacheTtl, function () {
|
|
43
|
+
return RegistryExtension::where('status', 'published')
|
|
44
|
+
->with(['author', 'category', 'currentBundle'])
|
|
45
|
+
->orderBy('install_count', 'desc')
|
|
46
|
+
->get();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
$this->resource::wrap('registryExtensions');
|
|
50
|
+
|
|
51
|
+
return $this->resource::collection($extensions);
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
/**
|
|
27
55
|
* Creates a record with request payload.
|
|
28
56
|
*
|
|
@@ -162,25 +162,56 @@ class RegistryPaymentsController extends Controller
|
|
|
162
162
|
$extension->flushCache();
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
// Determine purchaser (Company or RegistryDeveloperAccount)
|
|
166
|
+
$purchaser = null;
|
|
167
|
+
$purchaserType = null;
|
|
168
|
+
$purchaserUuid = null;
|
|
169
|
+
|
|
170
|
+
// Check if authenticated via session (cloud user)
|
|
171
|
+
if (session('company')) {
|
|
172
|
+
$purchaserUuid = session('company');
|
|
173
|
+
$purchaserType = 'Fleetbase\\Models\\Company';
|
|
174
|
+
}
|
|
175
|
+
// Check if authenticated via bearer token (developer account)
|
|
176
|
+
elseif ($request->bearerToken()) {
|
|
177
|
+
$registryUser = \Fleetbase\RegistryBridge\Models\RegistryUser::where('token', $request->bearerToken())->first();
|
|
178
|
+
if ($registryUser && $registryUser->developer_account_uuid) {
|
|
179
|
+
$purchaserUuid = $registryUser->developer_account_uuid;
|
|
180
|
+
$purchaserType = 'Fleetbase\\RegistryBridge\\Models\\RegistryDeveloperAccount';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!$purchaserUuid || !$purchaserType) {
|
|
185
|
+
return response()->error('Unable to identify purchaser. Please ensure you are logged in.');
|
|
186
|
+
}
|
|
187
|
+
|
|
165
188
|
// Check if already purchased
|
|
166
|
-
$purchaseRecordExists = RegistryExtensionPurchase::where([
|
|
189
|
+
$purchaseRecordExists = RegistryExtensionPurchase::where([
|
|
190
|
+
'purchaser_uuid' => $purchaserUuid,
|
|
191
|
+
'purchaser_type' => $purchaserType,
|
|
192
|
+
'extension_uuid' => $extension->uuid,
|
|
193
|
+
])->exists();
|
|
194
|
+
|
|
167
195
|
if ($purchaseRecordExists) {
|
|
168
196
|
return response()->json(['status' => 'purchase_complete', 'extension' => $extension]);
|
|
169
197
|
}
|
|
170
198
|
|
|
171
|
-
$stripe
|
|
199
|
+
$stripe = Utils::getStripeClient();
|
|
172
200
|
try {
|
|
173
201
|
$session = $stripe->checkout->sessions->retrieve($request->input('checkout_session_id'));
|
|
174
202
|
if (isset($session->status) && $session->status === 'complete') {
|
|
175
203
|
RegistryExtensionPurchase::firstOrCreate(
|
|
176
204
|
[
|
|
177
|
-
'
|
|
205
|
+
'purchaser_uuid' => $purchaserUuid,
|
|
206
|
+
'purchaser_type' => $purchaserType,
|
|
178
207
|
'extension_uuid' => $extension->uuid,
|
|
179
208
|
],
|
|
180
209
|
[
|
|
181
210
|
'stripe_checkout_session_id' => $session->id,
|
|
182
211
|
'stripe_payment_intent_id' => $session->payment_intent,
|
|
183
212
|
'locked_price' => $session->amount_total,
|
|
213
|
+
// Keep company_uuid for backward compatibility if it's a company
|
|
214
|
+
'company_uuid' => $purchaserType === 'Fleetbase\\Models\\Company' ? $purchaserUuid : null,
|
|
184
215
|
]
|
|
185
216
|
);
|
|
186
217
|
}
|
|
@@ -224,4 +255,44 @@ class RegistryPaymentsController extends Controller
|
|
|
224
255
|
|
|
225
256
|
return FleetbaseResource::collection($payments)->additional(['total_amount' => $totalPurchaseAmount]);
|
|
226
257
|
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Creates a Stripe account session for account management.
|
|
261
|
+
*
|
|
262
|
+
* This method creates a session that allows connected accounts to manage their account details,
|
|
263
|
+
* including updating bank account information, business details, and other settings.
|
|
264
|
+
*
|
|
265
|
+
* @param Request $request the incoming HTTP request
|
|
266
|
+
*
|
|
267
|
+
* @return \Illuminate\Http\JsonResponse returns a JSON response with the session's client secret or an error message
|
|
268
|
+
*/
|
|
269
|
+
public function createAccountManagementSession(Request $request)
|
|
270
|
+
{
|
|
271
|
+
$stripe = Utils::getStripeClient();
|
|
272
|
+
$company = Auth::getCompany();
|
|
273
|
+
|
|
274
|
+
if (!$company || !$company->stripe_connect_id) {
|
|
275
|
+
return response()->error('Stripe Connect account not found for this company.');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
$accountSession = $stripe->accountSessions->create([
|
|
280
|
+
'account' => $company->stripe_connect_id,
|
|
281
|
+
'components' => [
|
|
282
|
+
'account_management' => [
|
|
283
|
+
'enabled' => true,
|
|
284
|
+
'features' => [
|
|
285
|
+
'external_account_collection' => true,
|
|
286
|
+
],
|
|
287
|
+
],
|
|
288
|
+
],
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
return response()->json([
|
|
292
|
+
'clientSecret' => $accountSession->client_secret,
|
|
293
|
+
]);
|
|
294
|
+
} catch (\Exception $e) {
|
|
295
|
+
return response()->error($e->getMessage());
|
|
296
|
+
}
|
|
297
|
+
}
|
|
227
298
|
}
|