@fleetbase/registry-bridge-engine 0.1.5 → 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.
Files changed (26) hide show
  1. package/addon/controllers/application.js +6 -1
  2. package/addon/controllers/developers/payments/settings.js +58 -0
  3. package/addon/routes/developers/payments/settings.js +23 -0
  4. package/addon/routes.js +1 -0
  5. package/addon/templates/application.hbs +28 -26
  6. package/addon/templates/developers/payments/index.hbs +9 -3
  7. package/addon/templates/developers/payments/onboard.hbs +1 -1
  8. package/addon/templates/developers/payments/settings.hbs +30 -0
  9. package/composer.json +1 -1
  10. package/config/environment.js +23 -0
  11. package/extension.json +1 -1
  12. package/package.json +3 -3
  13. package/server/migrations/2026_02_15_000001_create_registry_developer_accounts_table.php +44 -0
  14. package/server/migrations/2026_02_15_000002_add_account_type_to_registry_users_table.php +48 -0
  15. package/server/migrations/2026_02_15_000003_add_publisher_fields_to_registry_extensions_table.php +34 -0
  16. package/server/migrations/2026_02_15_100001_convert_purchases_to_polymorphic.php +45 -0
  17. package/server/migrations/2026_02_15_100002_add_purchaser_indexes.php +35 -0
  18. package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +79 -29
  19. package/server/src/Http/Controllers/Internal/v1/RegistryDeveloperAccountController.php +355 -0
  20. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +28 -0
  21. package/server/src/Http/Controllers/Internal/v1/RegistryPaymentsController.php +74 -3
  22. package/server/src/Models/RegistryDeveloperAccount.php +147 -0
  23. package/server/src/Models/RegistryExtensionPurchase.php +17 -0
  24. package/server/src/Models/RegistryUser.php +85 -14
  25. package/server/src/routes.php +15 -1
  26. package/translations/en-us.yaml +7 -0
@@ -0,0 +1,147 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Models;
4
+
5
+ use Fleetbase\Models\Model;
6
+ use Fleetbase\Traits\HasUuid;
7
+ use Illuminate\Support\Str;
8
+
9
+ class RegistryDeveloperAccount extends Model
10
+ {
11
+ use HasUuid;
12
+
13
+ /**
14
+ * The database table used by the model.
15
+ *
16
+ * @var string
17
+ */
18
+ protected $table = 'registry_developer_accounts';
19
+
20
+ /**
21
+ * The attributes that are mass assignable.
22
+ *
23
+ * @var array
24
+ */
25
+ protected $fillable = [
26
+ 'username',
27
+ 'email',
28
+ 'password',
29
+ 'name',
30
+ 'avatar_url',
31
+ 'github_username',
32
+ 'website',
33
+ 'bio',
34
+ 'email_verified_at',
35
+ 'verification_token',
36
+ 'status',
37
+ ];
38
+
39
+ /**
40
+ * The attributes that should be cast to native types.
41
+ *
42
+ * @var array
43
+ */
44
+ protected $casts = [
45
+ 'email_verified_at' => 'datetime',
46
+ ];
47
+
48
+ /**
49
+ * The attributes excluded from the model's JSON form.
50
+ *
51
+ * @var array
52
+ */
53
+ protected $hidden = [
54
+ 'password',
55
+ 'verification_token',
56
+ ];
57
+
58
+ /**
59
+ * The "booting" method of the model.
60
+ */
61
+ protected static function boot()
62
+ {
63
+ parent::boot();
64
+
65
+ static::creating(function ($model) {
66
+ if (empty($model->verification_token)) {
67
+ $model->verification_token = Str::random(64);
68
+ }
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Get the registry users associated with this developer account.
74
+ *
75
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
76
+ */
77
+ public function registryUsers()
78
+ {
79
+ return $this->hasMany(RegistryUser::class, 'developer_account_uuid', 'uuid');
80
+ }
81
+
82
+ /**
83
+ * Get the extensions published by this developer account.
84
+ *
85
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
86
+ */
87
+ public function extensions()
88
+ {
89
+ return $this->hasMany(RegistryExtension::class, 'publisher_uuid', 'uuid')
90
+ ->where('publisher_type', 'developer');
91
+ }
92
+
93
+ /**
94
+ * Check if the account is active.
95
+ */
96
+ public function isActive(): bool
97
+ {
98
+ return $this->status === 'active';
99
+ }
100
+
101
+ /**
102
+ * Check if the account is suspended.
103
+ */
104
+ public function isSuspended(): bool
105
+ {
106
+ return $this->status === 'suspended';
107
+ }
108
+
109
+ /**
110
+ * Check if the account is pending verification.
111
+ */
112
+ public function isPendingVerification(): bool
113
+ {
114
+ return $this->status === 'pending_verification';
115
+ }
116
+
117
+ /**
118
+ * Check if the email is verified.
119
+ */
120
+ public function isEmailVerified(): bool
121
+ {
122
+ return $this->email_verified_at !== null;
123
+ }
124
+
125
+ /**
126
+ * Mark the email as verified.
127
+ */
128
+ public function markEmailAsVerified(): bool
129
+ {
130
+ return $this->update([
131
+ 'email_verified_at' => now(),
132
+ 'status' => 'active',
133
+ 'verification_token' => null,
134
+ ]);
135
+ }
136
+
137
+ /**
138
+ * Generate a new verification token.
139
+ */
140
+ public function generateVerificationToken(): string
141
+ {
142
+ $token = Str::random(64);
143
+ $this->update(['verification_token' => $token]);
144
+
145
+ return $token;
146
+ }
147
+ }
@@ -29,6 +29,8 @@ class RegistryExtensionPurchase extends Model
29
29
  protected $fillable = [
30
30
  'uuid',
31
31
  'company_uuid',
32
+ 'purchaser_uuid',
33
+ 'purchaser_type',
32
34
  'extension_uuid',
33
35
  'stripe_checkout_session_id',
34
36
  'stripe_payment_intent_id',
@@ -70,7 +72,22 @@ class RegistryExtensionPurchase extends Model
70
72
  protected $without = ['company', 'extension'];
71
73
 
72
74
  /**
75
+ * Get the purchaser (polymorphic relationship).
76
+ * Can be either a Company or RegistryDeveloperAccount.
77
+ *
78
+ * @return \Illuminate\Database\Eloquent\Relations\MorphTo
79
+ */
80
+ public function purchaser()
81
+ {
82
+ return $this->morphTo('purchaser', 'purchaser_type', 'purchaser_uuid', 'uuid');
83
+ }
84
+
85
+ /**
86
+ * Legacy relationship for backward compatibility.
87
+ *
73
88
  * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
89
+ *
90
+ * @deprecated Use purchaser() instead
74
91
  */
75
92
  public function company()
76
93
  {
@@ -40,6 +40,8 @@ class RegistryUser extends Model
40
40
  protected $fillable = [
41
41
  'company_uuid',
42
42
  'user_uuid',
43
+ 'account_type',
44
+ 'developer_account_uuid',
43
45
  'token',
44
46
  'registry_token',
45
47
  'scope',
@@ -115,6 +117,14 @@ class RegistryUser extends Model
115
117
  return $this->belongsTo(User::class);
116
118
  }
117
119
 
120
+ /**
121
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
122
+ */
123
+ public function developerAccount()
124
+ {
125
+ return $this->belongsTo(RegistryDeveloperAccount::class, 'developer_account_uuid', 'uuid');
126
+ }
127
+
118
128
  /**
119
129
  * @return \Illuminate\Database\Eloquent\Relations\HasMany
120
130
  */
@@ -132,11 +142,15 @@ class RegistryUser extends Model
132
142
  }
133
143
 
134
144
  /**
135
- * Undocumented function.
145
+ * Get the is_admin attribute.
136
146
  */
137
147
  public function getIsAdminAttribute(): bool
138
148
  {
139
- return $this->user->is_admin === true;
149
+ if ($this->account_type === 'developer') {
150
+ return false;
151
+ }
152
+
153
+ return $this->user && $this->user->is_admin === true;
140
154
  }
141
155
 
142
156
  /**
@@ -190,9 +204,8 @@ class RegistryUser extends Model
190
204
  /**
191
205
  * Find a registry user by their username.
192
206
  *
193
- * This method joins the users table with the registry users table to find a
194
- * registry user by their email or username. If a matching user is found, the
195
- * corresponding registry user is returned.
207
+ * This method searches for a registry user by email or username,
208
+ * supporting both cloud and developer account types.
196
209
  *
197
210
  * @param string $username the username to search for
198
211
  *
@@ -200,16 +213,32 @@ class RegistryUser extends Model
200
213
  */
201
214
  public static function findFromUsername(string $username): ?RegistryUser
202
215
  {
216
+ // First try to find a cloud user
217
+ $cloudUser = static::select('registry_users.*')
218
+ ->join('users', function ($join) use ($username) {
219
+ $join->on('users.uuid', '=', 'registry_users.user_uuid')
220
+ ->on('users.company_uuid', '=', 'registry_users.company_uuid')
221
+ ->where(function ($query) use ($username) {
222
+ $query->where('users.email', $username)
223
+ ->orWhere('users.username', $username);
224
+ });
225
+ })
226
+ ->where('registry_users.account_type', 'cloud')
227
+ ->first();
228
+
229
+ if ($cloudUser) {
230
+ return $cloudUser;
231
+ }
232
+
233
+ // If not found, try to find a developer account
203
234
  return static::select('registry_users.*')
204
- ->join('users', function ($join) use ($username) {
205
- $join->on('users.uuid', '=', 'registry_users.user_uuid')
206
- ->on('users.company_uuid', '=', 'registry_users.company_uuid')
207
- ->where(function ($query) use ($username) {
208
- $query->where('users.email', $username)
209
- ->orWhere('users.username', $username);
210
- });
211
- })
212
- ->first();
235
+ ->join('registry_developer_accounts', 'registry_developer_accounts.uuid', '=', 'registry_users.developer_account_uuid')
236
+ ->where('registry_users.account_type', 'developer')
237
+ ->where(function ($query) use ($username) {
238
+ $query->where('registry_developer_accounts.email', $username)
239
+ ->orWhere('registry_developer_accounts.username', $username);
240
+ })
241
+ ->first();
213
242
  }
214
243
 
215
244
  public static function findFromToken(string $token): ?RegistryUser
@@ -252,4 +281,46 @@ class RegistryUser extends Model
252
281
  {
253
282
  return [$this->public_id];
254
283
  }
284
+
285
+ /**
286
+ * Check if this is a developer account.
287
+ */
288
+ public function isDeveloperAccount(): bool
289
+ {
290
+ return $this->account_type === 'developer';
291
+ }
292
+
293
+ /**
294
+ * Check if this is a cloud account.
295
+ */
296
+ public function isCloudAccount(): bool
297
+ {
298
+ return $this->account_type === 'cloud';
299
+ }
300
+
301
+ /**
302
+ * Get permissions for this registry user.
303
+ */
304
+ public function getPermissions(): array
305
+ {
306
+ if ($this->isDeveloperAccount()) {
307
+ return [
308
+ 'can_install_free_extensions' => true,
309
+ 'can_install_paid_extensions' => false,
310
+ 'can_publish_extensions' => true,
311
+ 'can_purchase_extensions' => false,
312
+ 'can_access_console' => false,
313
+ 'can_manage_company' => false,
314
+ ];
315
+ }
316
+
317
+ return [
318
+ 'can_install_free_extensions' => true,
319
+ 'can_install_paid_extensions' => true,
320
+ 'can_publish_extensions' => true,
321
+ 'can_purchase_extensions' => true,
322
+ 'can_access_console' => true,
323
+ 'can_manage_company' => true,
324
+ ];
325
+ }
255
326
  }
@@ -12,9 +12,16 @@ use Illuminate\Support\Facades\Route;
12
12
  | is assigned the "api" middleware group. Enjoy building your API!
13
13
  |
14
14
  */
15
- // Lookup package endpoint
15
+ // Public endpoints (no authentication required)
16
+ Route::get(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/extensions', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryExtensionController@listPublicExtensions');
16
17
  Route::get(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/lookup', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryController@lookupPackage');
17
18
  Route::post(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/bundle-upload', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryController@bundleUpload');
19
+
20
+ // Developer account registration (public, no auth required)
21
+ Route::post(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/developer-account/register', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryDeveloperAccountController@register');
22
+ Route::post(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/developer-account/verify', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryDeveloperAccountController@verifyEmail');
23
+ Route::post(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/developer-account/resend-verification', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryDeveloperAccountController@resendVerification');
24
+ Route::post(config('registry-bridge.api.routing.prefix', '~registry') . '/v1/developer-account/generate-token', 'Fleetbase\RegistryBridge\Http\Controllers\Internal\v1\RegistryDeveloperAccountController@generateToken');
18
25
  Route::prefix(config('registry-bridge.api.routing.prefix', '~registry'))->middleware(['fleetbase.registry'])->namespace('Fleetbase\RegistryBridge\Http\Controllers')->group(
19
26
  function ($router) {
20
27
  /*
@@ -35,6 +42,12 @@ Route::prefix(config('registry-bridge.api.routing.prefix', '~registry'))->middle
35
42
  $router->post('check-publish', 'RegistryAuthController@checkPublishAllowed');
36
43
  });
37
44
 
45
+ // Developer account profile routes (require authentication)
46
+ $router->group(['prefix' => 'developer-account'], function ($router) {
47
+ $router->get('profile', 'RegistryDeveloperAccountController@profile');
48
+ $router->post('profile', 'RegistryDeveloperAccountController@updateProfile');
49
+ });
50
+
38
51
  $router->group(['middleware' => ['fleetbase.protected']], function ($router) {
39
52
  $router->get('categories', 'RegistryController@categories');
40
53
  $router->get('engines', 'RegistryController@getInstalledEngines');
@@ -48,6 +61,7 @@ Route::prefix(config('registry-bridge.api.routing.prefix', '~registry'))->middle
48
61
  $router->group(['prefix' => 'payments'], function ($router) {
49
62
  $router->post('account', 'RegistryPaymentsController@getStripeAccount');
50
63
  $router->post('account-session', 'RegistryPaymentsController@getStripeAccountSession');
64
+ $router->post('account-management-session', 'RegistryPaymentsController@createAccountManagementSession');
51
65
  $router->get('has-stripe-connect-account', 'RegistryPaymentsController@hasStripeConnectAccount');
52
66
  $router->post('create-checkout-session', 'RegistryPaymentsController@createStripeCheckoutSession');
53
67
  $router->post('get-checkout-session', 'RegistryPaymentsController@getStripeCheckoutSessionStatus');
@@ -49,6 +49,13 @@ registry-bridge:
49
49
  purchased:
50
50
  title: Purchased Extensions
51
51
  developers:
52
+ payments:
53
+ manage-account: Manage Payout Account
54
+ no-account-warning: You must complete the Stripe onboarding process before accessing account settings.
55
+ settings:
56
+ title: Manage Payout Account
57
+ description: Manage your Stripe account details, including your business information and bank accounts for receiving payouts.
58
+ loading: Loading account management...
52
59
  extensions:
53
60
  extensions: Extensions
54
61
  create-new-extension: Create new Extension