@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
@@ -1,3 +1,8 @@
1
1
  import Controller from '@ember/controller';
2
+ import config from '../config/environment';
2
3
 
3
- export default class ApplicationController extends Controller {}
4
+ export default class ApplicationController extends Controller {
5
+ get selfHostedRegistry() {
6
+ return config.registry.selfHosted === true;
7
+ }
8
+ }
@@ -0,0 +1,58 @@
1
+ import Controller from '@ember/controller';
2
+ import { inject as service } from '@ember/service';
3
+ import { tracked } from '@glimmer/tracking';
4
+ import { action } from '@ember/object';
5
+ import { loadConnectAndInitialize } from '@stripe/connect-js';
6
+ import config from '../../../config/environment';
7
+
8
+ export default class DevelopersPaymentsSettingsController extends Controller {
9
+ @service fetch;
10
+ @service notifications;
11
+
12
+ @tracked connectInstance;
13
+ @tracked accountManagementComponent;
14
+ @tracked isLoading = true;
15
+
16
+ @action
17
+ async setupStripe(element) {
18
+ try {
19
+ await this.initializeStripe();
20
+ if (this.accountManagementComponent) {
21
+ element.appendChild(this.accountManagementComponent);
22
+ }
23
+ this.isLoading = false;
24
+ } catch (error) {
25
+ this.notifications.serverError(error);
26
+ this.isLoading = false;
27
+ }
28
+ }
29
+
30
+ async fetchClientSecret() {
31
+ try {
32
+ const { clientSecret } = await this.fetch.post('payments/account-management-session', {}, { namespace: '~registry/v1' });
33
+ return clientSecret;
34
+ } catch (error) {
35
+ this.notifications.serverError(error);
36
+ return null;
37
+ }
38
+ }
39
+
40
+ async initializeStripe() {
41
+ if (this.connectInstance) {
42
+ return;
43
+ }
44
+
45
+ this.connectInstance = loadConnectAndInitialize({
46
+ publishableKey: config.stripe.publishableKey,
47
+ fetchClientSecret: this.fetchClientSecret.bind(this),
48
+ appearance: {
49
+ overlays: 'dialog',
50
+ variables: {
51
+ colorPrimary: '#635BFF',
52
+ },
53
+ },
54
+ });
55
+
56
+ this.accountManagementComponent = this.connectInstance.create('account-management');
57
+ }
58
+ }
@@ -0,0 +1,23 @@
1
+ import Route from '@ember/routing/route';
2
+ import { inject as service } from '@ember/service';
3
+
4
+ export default class DevelopersPaymentsSettingsRoute extends Route {
5
+ @service notifications;
6
+ @service hostRouter;
7
+ @service fetch;
8
+ @service intl;
9
+
10
+ async beforeModel() {
11
+ // Check if user has a Stripe Connect account before allowing access
12
+ try {
13
+ const { hasStripeConnectAccount } = await this.fetch.get('payments/has-stripe-connect-account', {}, { namespace: '~registry/v1' });
14
+ if (!hasStripeConnectAccount) {
15
+ this.notifications.warning(this.intl.t('registry-bridge.developers.payments.no-account-warning'));
16
+ return this.hostRouter.transitionTo('console.extensions.payments.onboard');
17
+ }
18
+ } catch (error) {
19
+ this.notifications.serverError(error);
20
+ return this.hostRouter.transitionTo('console.extensions.payments');
21
+ }
22
+ }
23
+ }
package/addon/routes.js CHANGED
@@ -18,6 +18,7 @@ export default buildRoutes(function () {
18
18
  this.route('payments', function () {
19
19
  this.route('index', { path: '/' });
20
20
  this.route('onboard');
21
+ this.route('settings');
21
22
  });
22
23
  this.route('credentials');
23
24
  });
@@ -23,32 +23,34 @@
23
23
  </LinkTo>
24
24
  {{/each}}
25
25
  </Layout::Sidebar::Panel>
26
- <Layout::Sidebar::Panel @open={{true}} @title="Developers">
27
- <Layout::Sidebar::Item
28
- @route="console.extensions.developers.extensions"
29
- @icon="box-archive"
30
- @permission="registry-bridge list extension-bundle"
31
- @visible={{can "registry-bridge see extension-bundle"}}
32
- >Extensions</Layout::Sidebar::Item>
33
- <Layout::Sidebar::Item
34
- @route="console.extensions.developers.analytics"
35
- @icon="chart-simple"
36
- @permission="registry-bridge list extension-analytic"
37
- @visible={{can "registry-bridge see extension-analytic"}}
38
- >Analytics</Layout::Sidebar::Item>
39
- <Layout::Sidebar::Item
40
- @route="console.extensions.developers.payments"
41
- @icon="cash-register"
42
- @permission="registry-bridge list extension-payment"
43
- @visible={{can "registry-bridge see extension-payment"}}
44
- >Payments</Layout::Sidebar::Item>
45
- <Layout::Sidebar::Item
46
- @route="console.extensions.developers.credentials"
47
- @icon="key"
48
- @permission="registry-bridge list registry-token"
49
- @visible={{can "registry-bridge see registry-token"}}
50
- >Credentials</Layout::Sidebar::Item>
51
- </Layout::Sidebar::Panel>
26
+ {{#if this.selfHostedRegistry}}
27
+ <Layout::Sidebar::Panel @open={{true}} @title="Developers">
28
+ <Layout::Sidebar::Item
29
+ @route="console.extensions.developers.extensions"
30
+ @icon="box-archive"
31
+ @permission="registry-bridge list extension-bundle"
32
+ @visible={{can "registry-bridge see extension-bundle"}}
33
+ >Extensions</Layout::Sidebar::Item>
34
+ <Layout::Sidebar::Item
35
+ @route="console.extensions.developers.analytics"
36
+ @icon="chart-simple"
37
+ @permission="registry-bridge list extension-analytic"
38
+ @visible={{can "registry-bridge see extension-analytic"}}
39
+ >Analytics</Layout::Sidebar::Item>
40
+ <Layout::Sidebar::Item
41
+ @route="console.extensions.developers.payments"
42
+ @icon="cash-register"
43
+ @permission="registry-bridge list extension-payment"
44
+ @visible={{can "registry-bridge see extension-payment"}}
45
+ >Payments</Layout::Sidebar::Item>
46
+ <Layout::Sidebar::Item
47
+ @route="console.extensions.developers.credentials"
48
+ @icon="key"
49
+ @permission="registry-bridge list registry-token"
50
+ @visible={{can "registry-bridge see registry-token"}}
51
+ >Credentials</Layout::Sidebar::Item>
52
+ </Layout::Sidebar::Panel>
53
+ {{/if}}
52
54
  <Spacer @height="200px" />
53
55
  </EmberWormhole>
54
56
 
@@ -1,8 +1,14 @@
1
1
  <Layout::Section::Header @title="Payments">
2
2
  {{#if this.hasStripeConnectAccount}}
3
- <div class="flex flex-row space-x-1">
4
- <span class="text-sm text-black dark:text-white font-bold">Total Amount:</span>
5
- <span class="text-sm text-black dark:text-white">{{format-currency @model.total_amount "USD"}}</span>
3
+ <div class="flex items-center space-x-4">
4
+ <LinkTo @route="developers.payments.settings" class="btn btn-primary">
5
+ <FaIcon @icon="cog" class="mr-1" />
6
+ {{t "registry-bridge.developers.payments.manage-account"}}
7
+ </LinkTo>
8
+ <div class="flex flex-row space-x-1">
9
+ <span class="text-sm text-black dark:text-white font-bold">Total Amount:</span>
10
+ <span class="text-sm text-black dark:text-white">{{format-currency @model.total_amount "USD"}}</span>
11
+ </div>
6
12
  </div>
7
13
  {{/if}}
8
14
  </Layout::Section::Header>
@@ -38,7 +38,7 @@
38
38
  </div>
39
39
  <div
40
40
  id="embedded-onboarding-container"
41
- class="min-h-20 bg-gray-50 dark:bg-gray-100 shadow-md rounded-lg border border-gray-300 dark:border-gray-900 {{unless this.onboardInProgress 'hidden'}}"
41
+ class="p-4 min-h-20 bg-gray-50 dark:bg-gray-100 shadow-md rounded-lg border border-gray-300 dark:border-gray-900 {{unless this.onboardInProgress 'hidden'}}"
42
42
  {{did-insert (fn this.createTrackedElement "embeddedOnboardingContainer")}}
43
43
  >
44
44
  </div>
@@ -0,0 +1,30 @@
1
+ <Layout::Section::Header @title={{t "registry-bridge.developers.payments.settings.title"}} />
2
+
3
+ <Layout::Section::Body class="overflow-y-scroll h-full">
4
+ <div class="container">
5
+ <div class="max-w-5xl mx-auto mt-4">
6
+ <div class="content">
7
+ <div class="mb-4">
8
+ <p class="text-sm text-gray-700 dark:text-gray-300">
9
+ {{t "registry-bridge.developers.payments.settings.description"}}
10
+ </p>
11
+ </div>
12
+ {{#if this.isLoading}}
13
+ <div class="flex items-center justify-center p-8">
14
+ <Spinner
15
+ @loadingMessage={{t "registry-bridge.developers.payments.settings.loading"}}
16
+ @loadingMessageClass="ml-2 text-black dark:text-white"
17
+ @wrapperClass="flex flex-row items-center"
18
+ />
19
+ </div>
20
+ {{/if}}
21
+ <div
22
+ id="account-management-container"
23
+ {{did-insert this.setupStripe}}
24
+ class="p-4 min-h-20 bg-gray-50 dark:bg-gray-100 shadow-md rounded-lg border border-gray-300 dark:border-gray-900"
25
+ >
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </Layout::Section::Body>
package/composer.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetbase/registry-bridge",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "keywords": [
6
6
  "fleetbase-extension",
@@ -13,6 +13,9 @@ module.exports = function (environment) {
13
13
  stripe: {
14
14
  publishableKey: getenv('STRIPE_KEY'),
15
15
  },
16
+ registry: {
17
+ selfHosted: toBoolean(getenv('SELF_HOSTED_REGISTRY', false)),
18
+ },
16
19
  };
17
20
 
18
21
  return ENV;
@@ -26,3 +29,23 @@ function getMountedEngineRoutePrefix() {
26
29
 
27
30
  return `console.${mountedEngineRoutePrefix}.`;
28
31
  }
32
+
33
+ function toBoolean(value) {
34
+ switch (value) {
35
+ case 'true':
36
+ case '1':
37
+ case 1:
38
+ case true:
39
+ return true;
40
+ case 'false':
41
+ case '0':
42
+ case 0:
43
+ case false:
44
+ case null:
45
+ case undefined:
46
+ case '':
47
+ return false;
48
+ default:
49
+ return false;
50
+ }
51
+ }
package/extension.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "Registry Bridge",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "repository": "https://github.com/fleetbase/registry-bridge",
6
6
  "license": "AGPL-3.0-or-later",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetbase/registry-bridge-engine",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Internal Bridge between Fleetbase API and Extensions Registry",
5
5
  "fleetbase": {
6
6
  "route": "extensions"
@@ -39,8 +39,8 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@babel/core": "^7.23.2",
42
- "@fleetbase/ember-core": "^0.3.10",
43
- "@fleetbase/ember-ui": "^0.3.18",
42
+ "@fleetbase/ember-core": "^0.3.12",
43
+ "@fleetbase/ember-ui": "^0.3.21",
44
44
  "@fortawesome/ember-fontawesome": "^2.0.0",
45
45
  "@fortawesome/fontawesome-svg-core": "6.4.0",
46
46
  "@fortawesome/free-brands-svg-icons": "6.4.0",
@@ -0,0 +1,44 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ */
12
+ public function up(): void
13
+ {
14
+ Schema::create('registry_developer_accounts', function (Blueprint $table) {
15
+ $table->increments('id');
16
+ $table->uuid('uuid')->unique()->index();
17
+ $table->string('username')->unique();
18
+ $table->string('email')->unique();
19
+ $table->string('password');
20
+ $table->string('name')->nullable();
21
+ $table->string('avatar_url')->nullable();
22
+ $table->string('github_username')->nullable();
23
+ $table->string('website')->nullable();
24
+ $table->text('bio')->nullable();
25
+ $table->timestamp('email_verified_at')->nullable();
26
+ $table->string('verification_token')->nullable();
27
+ $table->enum('status', ['active', 'suspended', 'pending_verification'])->default('pending_verification');
28
+ $table->timestamps();
29
+ $table->softDeletes();
30
+
31
+ $table->index('email');
32
+ $table->index('username');
33
+ $table->index('status');
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Reverse the migrations.
39
+ */
40
+ public function down(): void
41
+ {
42
+ Schema::dropIfExists('registry_developer_accounts');
43
+ }
44
+ };
@@ -0,0 +1,48 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ */
12
+ public function up(): void
13
+ {
14
+ Schema::table('registry_users', function (Blueprint $table) {
15
+ $table->enum('account_type', ['cloud', 'developer'])->default('cloud')->after('uuid');
16
+ $table->uuid('developer_account_uuid')->nullable()->after('account_type');
17
+
18
+ $table->foreign('developer_account_uuid')
19
+ ->references('uuid')
20
+ ->on('registry_developer_accounts')
21
+ ->onDelete('cascade');
22
+
23
+ $table->index(['account_type', 'developer_account_uuid']);
24
+ });
25
+
26
+ // Make company_uuid and user_uuid nullable since developer accounts won't have them
27
+ Schema::table('registry_users', function (Blueprint $table) {
28
+ $table->uuid('company_uuid')->nullable()->change();
29
+ $table->uuid('user_uuid')->nullable()->change();
30
+ });
31
+ }
32
+
33
+ /**
34
+ * Reverse the migrations.
35
+ */
36
+ public function down(): void
37
+ {
38
+ Schema::table('registry_users', function (Blueprint $table) {
39
+ $table->dropForeign(['developer_account_uuid']);
40
+ $table->dropColumn(['account_type', 'developer_account_uuid']);
41
+ });
42
+
43
+ Schema::table('registry_users', function (Blueprint $table) {
44
+ $table->uuid('company_uuid')->nullable(false)->change();
45
+ $table->uuid('user_uuid')->nullable(false)->change();
46
+ });
47
+ }
48
+ };
@@ -0,0 +1,34 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ */
12
+ public function up(): void
13
+ {
14
+ Schema::table('registry_extensions', function (Blueprint $table) {
15
+ $table->enum('publisher_type', ['cloud', 'developer'])->default('cloud')->after('company_uuid');
16
+ $table->uuid('publisher_uuid')->nullable()->after('publisher_type');
17
+
18
+ $table->index(['publisher_type', 'publisher_uuid']);
19
+ });
20
+
21
+ // Backfill existing extensions with publisher data from company_uuid
22
+ DB::statement('UPDATE registry_extensions SET publisher_type = "cloud", publisher_uuid = company_uuid WHERE publisher_uuid IS NULL');
23
+ }
24
+
25
+ /**
26
+ * Reverse the migrations.
27
+ */
28
+ public function down(): void
29
+ {
30
+ Schema::table('registry_extensions', function (Blueprint $table) {
31
+ $table->dropColumn(['publisher_type', 'publisher_uuid']);
32
+ });
33
+ }
34
+ };
@@ -0,0 +1,45 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ *
12
+ * @return void
13
+ */
14
+ public function up()
15
+ {
16
+ Schema::table('registry_extension_purchases', function (Blueprint $table) {
17
+ // Add polymorphic columns using Fleetbase convention (subject)
18
+ $table->string('purchaser_uuid')->nullable()->after('uuid');
19
+ $table->string('purchaser_type')->nullable()->after('purchaser_uuid');
20
+
21
+ // Keep company_uuid for backward compatibility during migration
22
+ // Will be removed in a future migration after data migration
23
+ });
24
+
25
+ // Migrate existing data: convert company_uuid to polymorphic relationship
26
+ DB::table('registry_extension_purchases')
27
+ ->whereNotNull('company_uuid')
28
+ ->update([
29
+ 'purchaser_uuid' => DB::raw('company_uuid'),
30
+ 'purchaser_type' => 'Fleetbase\\Models\\Company'
31
+ ]);
32
+ }
33
+
34
+ /**
35
+ * Reverse the migrations.
36
+ *
37
+ * @return void
38
+ */
39
+ public function down()
40
+ {
41
+ Schema::table('registry_extension_purchases', function (Blueprint $table) {
42
+ $table->dropColumn(['purchaser_uuid', 'purchaser_type']);
43
+ });
44
+ }
45
+ };
@@ -0,0 +1,35 @@
1
+ <?php
2
+
3
+ use Illuminate\Database\Migrations\Migration;
4
+ use Illuminate\Database\Schema\Blueprint;
5
+ use Illuminate\Support\Facades\Schema;
6
+
7
+ return new class extends Migration
8
+ {
9
+ /**
10
+ * Run the migrations.
11
+ *
12
+ * @return void
13
+ */
14
+ public function up()
15
+ {
16
+ Schema::table('registry_extension_purchases', function (Blueprint $table) {
17
+ // Add indexes for polymorphic relationship queries
18
+ $table->index(['purchaser_uuid', 'purchaser_type'], 'purchases_purchaser_index');
19
+ $table->index(['extension_uuid', 'purchaser_uuid', 'purchaser_type'], 'purchases_extension_purchaser_index');
20
+ });
21
+ }
22
+
23
+ /**
24
+ * Reverse the migrations.
25
+ *
26
+ * @return void
27
+ */
28
+ public function down()
29
+ {
30
+ Schema::table('registry_extension_purchases', function (Blueprint $table) {
31
+ $table->dropIndex('purchases_purchaser_index');
32
+ $table->dropIndex('purchases_extension_purchaser_index');
33
+ });
34
+ }
35
+ };