@fleetbase/registry-bridge-engine 0.0.1

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 (211) hide show
  1. package/.php-cs-fixer.php +29 -0
  2. package/LICENSE.md +651 -0
  3. package/README.md +122 -0
  4. package/addon/adapters/registry-bridge.js +5 -0
  5. package/addon/adapters/registry-extension-bundle.js +1 -0
  6. package/addon/adapters/registry-extension.js +1 -0
  7. package/addon/components/extension-card.hbs +12 -0
  8. package/addon/components/extension-card.js +235 -0
  9. package/addon/components/extension-form.hbs +237 -0
  10. package/addon/components/extension-form.js +123 -0
  11. package/addon/components/extension-modal-title.hbs +14 -0
  12. package/addon/components/extension-modal-title.js +20 -0
  13. package/addon/components/extension-monetize-form.hbs +56 -0
  14. package/addon/components/extension-monetize-form.js +7 -0
  15. package/addon/components/extension-pending-publish-viewer.hbs +52 -0
  16. package/addon/components/extension-pending-publish-viewer.js +37 -0
  17. package/addon/components/extension-reviewer-control.hbs +68 -0
  18. package/addon/components/extension-reviewer-control.js +68 -0
  19. package/addon/components/modals/confirm-extension-purchase.hbs +5 -0
  20. package/addon/components/modals/confirm-extension-purchase.js +3 -0
  21. package/addon/components/modals/extension-details.hbs +69 -0
  22. package/addon/components/modals/extension-details.js +33 -0
  23. package/addon/components/modals/extension-purchase-form.hbs +5 -0
  24. package/addon/components/modals/extension-purchase-form.js +3 -0
  25. package/addon/components/modals/extension-uninstall.hbs +25 -0
  26. package/addon/components/modals/extension-uninstall.js +11 -0
  27. package/addon/components/modals/select-extension-bundle.hbs +43 -0
  28. package/addon/components/modals/select-extension-bundle.js +31 -0
  29. package/addon/components/progress-bar.hbs +12 -0
  30. package/addon/components/progress-bar.js +8 -0
  31. package/addon/controllers/application.js +6 -0
  32. package/addon/controllers/developers/analytics.js +26 -0
  33. package/addon/controllers/developers/extensions/edit/bundles.js +70 -0
  34. package/addon/controllers/developers/extensions/edit/index.js +3 -0
  35. package/addon/controllers/developers/extensions/edit/monetize.js +7 -0
  36. package/addon/controllers/developers/extensions/edit.js +107 -0
  37. package/addon/controllers/developers/extensions/index.js +3 -0
  38. package/addon/controllers/developers/extensions/new.js +32 -0
  39. package/addon/controllers/developers/payments/index.js +39 -0
  40. package/addon/controllers/developers/payments/onboard.js +67 -0
  41. package/addon/controllers/explore/category.js +22 -0
  42. package/addon/controllers/explore/index.js +15 -0
  43. package/addon/controllers/installed.js +86 -0
  44. package/addon/controllers/purchased.js +18 -0
  45. package/addon/engine.js +44 -0
  46. package/addon/models/registry-extension-bundle.js +62 -0
  47. package/addon/models/registry-extension.js +215 -0
  48. package/addon/routes/application.js +12 -0
  49. package/addon/routes/developers/analytics.js +10 -0
  50. package/addon/routes/developers/credentials.js +3 -0
  51. package/addon/routes/developers/extensions/edit/bundles.js +21 -0
  52. package/addon/routes/developers/extensions/edit/details.js +3 -0
  53. package/addon/routes/developers/extensions/edit/index.js +3 -0
  54. package/addon/routes/developers/extensions/edit/monetize.js +3 -0
  55. package/addon/routes/developers/extensions/edit.js +18 -0
  56. package/addon/routes/developers/extensions/index.js +10 -0
  57. package/addon/routes/developers/extensions/new.js +3 -0
  58. package/addon/routes/developers/extensions.js +3 -0
  59. package/addon/routes/developers/payments/index.js +26 -0
  60. package/addon/routes/developers/payments/onboard.js +21 -0
  61. package/addon/routes/developers/payments.js +3 -0
  62. package/addon/routes/developers.js +3 -0
  63. package/addon/routes/explore/category.js +27 -0
  64. package/addon/routes/explore/index.js +17 -0
  65. package/addon/routes/explore.js +3 -0
  66. package/addon/routes/installed.js +10 -0
  67. package/addon/routes/purchased.js +10 -0
  68. package/addon/routes.js +28 -0
  69. package/addon/serializers/registry-extension-bundle.js +15 -0
  70. package/addon/serializers/registry-extension.js +21 -0
  71. package/addon/services/stripe.js +83 -0
  72. package/addon/styles/registry-bridge-engine.css +142 -0
  73. package/addon/templates/application.hbs +26 -0
  74. package/addon/templates/developers/analytics.hbs +83 -0
  75. package/addon/templates/developers/credentials.hbs +1 -0
  76. package/addon/templates/developers/extensions/edit/bundles.hbs +71 -0
  77. package/addon/templates/developers/extensions/edit/details.hbs +16 -0
  78. package/addon/templates/developers/extensions/edit/index.hbs +1 -0
  79. package/addon/templates/developers/extensions/edit/monetize.hbs +3 -0
  80. package/addon/templates/developers/extensions/edit.hbs +48 -0
  81. package/addon/templates/developers/extensions/index.hbs +27 -0
  82. package/addon/templates/developers/extensions/new.hbs +39 -0
  83. package/addon/templates/developers/extensions.hbs +1 -0
  84. package/addon/templates/developers/payments/index.hbs +33 -0
  85. package/addon/templates/developers/payments/onboard.hbs +48 -0
  86. package/addon/templates/developers/payments.hbs +1 -0
  87. package/addon/templates/developers.hbs +1 -0
  88. package/addon/templates/explore/category.hbs +12 -0
  89. package/addon/templates/explore/index.hbs +12 -0
  90. package/addon/templates/explore.hbs +1 -0
  91. package/addon/templates/installed.hbs +32 -0
  92. package/addon/templates/purchased.hbs +34 -0
  93. package/app/adapters/registry-bridge.js +1 -0
  94. package/app/adapters/registry-extension-bundle.js +1 -0
  95. package/app/adapters/registry-extension.js +1 -0
  96. package/app/components/extension-card.js +1 -0
  97. package/app/components/extension-form.js +1 -0
  98. package/app/components/extension-modal-title.js +1 -0
  99. package/app/components/extension-monetize-form.js +1 -0
  100. package/app/components/extension-pending-publish-viewer.js +1 -0
  101. package/app/components/extension-reviewer-control.js +1 -0
  102. package/app/components/modals/confirm-extension-purchase.js +1 -0
  103. package/app/components/modals/extension-details.js +1 -0
  104. package/app/components/modals/extension-purchase-form.js +1 -0
  105. package/app/components/modals/extension-uninstall.js +1 -0
  106. package/app/components/modals/select-extension-bundle.js +1 -0
  107. package/app/components/progress-bar.js +1 -0
  108. package/app/controllers/application.js +1 -0
  109. package/app/controllers/developers/analytics.js +1 -0
  110. package/app/controllers/developers/extensions/edit/bundles.js +1 -0
  111. package/app/controllers/developers/extensions/edit/index.js +1 -0
  112. package/app/controllers/developers/extensions/edit/monetize.js +1 -0
  113. package/app/controllers/developers/extensions/edit.js +1 -0
  114. package/app/controllers/developers/extensions/index.js +1 -0
  115. package/app/controllers/developers/extensions/new.js +1 -0
  116. package/app/controllers/developers/payments/index.js +1 -0
  117. package/app/controllers/developers/payments/onboard.js +1 -0
  118. package/app/controllers/explore/category.js +1 -0
  119. package/app/controllers/explore/index.js +1 -0
  120. package/app/controllers/installed.js +1 -0
  121. package/app/controllers/purchased.js +1 -0
  122. package/app/models/registry-extension-bundle.js +1 -0
  123. package/app/models/registry-extension.js +1 -0
  124. package/app/routes/developers/analytics.js +1 -0
  125. package/app/routes/developers/credentials.js +1 -0
  126. package/app/routes/developers/extensions/edit/bundles.js +1 -0
  127. package/app/routes/developers/extensions/edit/details.js +1 -0
  128. package/app/routes/developers/extensions/edit/index.js +1 -0
  129. package/app/routes/developers/extensions/edit/monetize.js +1 -0
  130. package/app/routes/developers/extensions/edit.js +1 -0
  131. package/app/routes/developers/extensions/index.js +1 -0
  132. package/app/routes/developers/extensions/new.js +1 -0
  133. package/app/routes/developers/extensions.js +1 -0
  134. package/app/routes/developers/payments/index.js +1 -0
  135. package/app/routes/developers/payments/onboard.js +1 -0
  136. package/app/routes/developers/payments.js +1 -0
  137. package/app/routes/developers.js +1 -0
  138. package/app/routes/explore/category.js +1 -0
  139. package/app/routes/explore/index.js +1 -0
  140. package/app/routes/explore.js +1 -0
  141. package/app/routes/installed.js +1 -0
  142. package/app/routes/purchased.js +1 -0
  143. package/app/serializers/registry-extension-bundle.js +1 -0
  144. package/app/serializers/registry-extension.js +1 -0
  145. package/app/services/stripe.js +1 -0
  146. package/app/templates/developers/analytics.js +1 -0
  147. package/app/templates/developers/credentials.js +1 -0
  148. package/app/templates/developers/extensions/edit/bundles.js +1 -0
  149. package/app/templates/developers/extensions/edit/details.js +1 -0
  150. package/app/templates/developers/extensions/edit/index.js +1 -0
  151. package/app/templates/developers/extensions/edit/monetize.js +1 -0
  152. package/app/templates/developers/extensions/edit.js +1 -0
  153. package/app/templates/developers/extensions/index.js +1 -0
  154. package/app/templates/developers/extensions/new.js +1 -0
  155. package/app/templates/developers/extensions.js +1 -0
  156. package/app/templates/developers/payments/index.js +1 -0
  157. package/app/templates/developers/payments/onboard.js +1 -0
  158. package/app/templates/developers/payments.js +1 -0
  159. package/app/templates/developers.js +1 -0
  160. package/app/templates/explore/category.js +1 -0
  161. package/app/templates/explore/index.js +1 -0
  162. package/app/templates/explore.js +1 -0
  163. package/app/templates/installed.js +1 -0
  164. package/app/templates/purchased.js +1 -0
  165. package/composer.json +95 -0
  166. package/config/environment.js +28 -0
  167. package/extension.json +10 -0
  168. package/index.js +26 -0
  169. package/package.json +129 -0
  170. package/phpstan.neon.dist +8 -0
  171. package/phpunit.xml.dist +16 -0
  172. package/server/.gitattributes +14 -0
  173. package/server/config/registry-bridge.php +32 -0
  174. package/server/migrations/2024_03_19_060627_create_registry_users_table.php +42 -0
  175. package/server/migrations/2024_03_21_051614_create_registry_extensions_table.php +76 -0
  176. package/server/migrations/2024_03_25_044537_create_registry_extension_bundles_table.php +54 -0
  177. package/server/migrations/2024_03_29_072101_registry_extension_installs.php +35 -0
  178. package/server/migrations/2024_07_16_155000_create_registry_extension_purchases.php +41 -0
  179. package/server/seeders/ExtensionsCategorySeeder.php +359 -0
  180. package/server/src/Console/Commands/Initialize.php +35 -0
  181. package/server/src/Console/Commands/PostInstallExtension.php +84 -0
  182. package/server/src/Exceptions/InstallFailedException.php +21 -0
  183. package/server/src/Expansions/CategoryExpansion.php +30 -0
  184. package/server/src/Http/Controllers/Internal/v1/ExtensionInstallerController.php +153 -0
  185. package/server/src/Http/Controllers/Internal/v1/RegistryAuthController.php +230 -0
  186. package/server/src/Http/Controllers/Internal/v1/RegistryController.php +54 -0
  187. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionBundleController.php +112 -0
  188. package/server/src/Http/Controllers/Internal/v1/RegistryExtensionController.php +257 -0
  189. package/server/src/Http/Controllers/Internal/v1/RegistryPaymentsController.php +227 -0
  190. package/server/src/Http/Controllers/RegistryBridgeController.php +13 -0
  191. package/server/src/Http/Filter/RegistryExtensionFilter.php +80 -0
  192. package/server/src/Http/Requests/AddRegistryUserRequest.php +47 -0
  193. package/server/src/Http/Requests/AuthenticateRegistryUserRequest.php +47 -0
  194. package/server/src/Http/Requests/CreateRegistryExtensionBundleRequest.php +42 -0
  195. package/server/src/Http/Requests/CreateRegistryExtensionRequest.php +31 -0
  196. package/server/src/Http/Requests/InstallExtensionRequest.php +30 -0
  197. package/server/src/Http/Requests/RegistryAuthRequest.php +46 -0
  198. package/server/src/Http/Requests/RegistryExtensionActionRequest.php +30 -0
  199. package/server/src/Http/Resources/RegistryUser.php +40 -0
  200. package/server/src/Models/RegistryExtension.php +656 -0
  201. package/server/src/Models/RegistryExtensionBundle.php +1015 -0
  202. package/server/src/Models/RegistryExtensionInstall.php +76 -0
  203. package/server/src/Models/RegistryExtensionPurchase.php +87 -0
  204. package/server/src/Models/RegistryUser.php +140 -0
  205. package/server/src/Providers/RegistryBridgeServiceProvider.php +117 -0
  206. package/server/src/Support/Bridge.php +53 -0
  207. package/server/src/Support/Utils.php +19 -0
  208. package/server/src/routes.php +58 -0
  209. package/server/tests/Feature.php +5 -0
  210. package/translations/en-us.yaml +119 -0
  211. package/tsconfig.declarations.json +10 -0
@@ -0,0 +1,112 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
+ use Fleetbase\Models\File;
7
+ use Fleetbase\RegistryBridge\Http\Controllers\RegistryBridgeController;
8
+ use Fleetbase\RegistryBridge\Http\Requests\CreateRegistryExtensionBundleRequest;
9
+ use Fleetbase\RegistryBridge\Http\Requests\RegistryExtensionActionRequest;
10
+ use Fleetbase\RegistryBridge\Models\RegistryExtensionBundle;
11
+ use Fleetbase\Support\Utils;
12
+ use Illuminate\Http\Request;
13
+ use Illuminate\Support\Facades\Storage;
14
+ use Illuminate\Support\Facades\Validator;
15
+
16
+ class RegistryExtensionBundleController extends RegistryBridgeController
17
+ {
18
+ /**
19
+ * The resource to query.
20
+ *
21
+ * @var string
22
+ */
23
+ public $resource = 'registry_extension_bundle';
24
+
25
+ /**
26
+ * Creates a record with request payload.
27
+ *
28
+ * @return \Illuminate\Http\Response
29
+ */
30
+ public function createRecord(Request $request)
31
+ {
32
+ $extensionId = $request->input('subject_uuid');
33
+
34
+ // Create validation request
35
+ $createRegistryExtensionBundleRequest = CreateRegistryExtensionBundleRequest::createFrom($request);
36
+ $rules = $createRegistryExtensionBundleRequest->rules();
37
+
38
+ // Manually validate request
39
+ $validator = Validator::make($request->input('registryExtensionBundle'), $rules);
40
+ if ($validator->fails()) {
41
+ return $createRegistryExtensionBundleRequest->responseWithErrors($validator);
42
+ }
43
+
44
+ // Extract bundle extension json for file validation
45
+ $bundleFile = File::where('uuid', $request->input('registryExtensionBundle.bundle_uuid'))->first();
46
+ if (!$bundleFile) {
47
+ return response()->error('Unable to find bundle file for validation.');
48
+ }
49
+
50
+ // Get extension.json contents
51
+ $bundleData = RegistryExtensionBundle::extractBundleData($bundleFile);
52
+ $extensionJson = Utils::getObjectKeyValue($bundleData, 'extension.json');
53
+ if (!$extensionJson) {
54
+ return response()->error('Unable to find `extension.json` file required in bundle.');
55
+ }
56
+
57
+ // Check if version is set
58
+ if (!isset($extensionJson->version)) {
59
+ return response()->error('No `version` set in the `extension.json`');
60
+ }
61
+
62
+ // Check if either api or engine property is set
63
+ if (!isset($extensionJson->engine) && !isset($extensionJson->api)) {
64
+ return response()->error('No `api` or `engine` property set in the `extension.json`');
65
+ }
66
+
67
+ // Set bundle number
68
+ $numberOfBundles = RegistryExtensionBundle::whereHas('extension', function ($query) use ($extensionId) {
69
+ $query->where('public_id', $extensionId);
70
+ })->count();
71
+ $extensionJson->bundle_number = ($numberOfBundles ?? 0) + 1;
72
+
73
+ try {
74
+ $record = $this->model->createRecordFromRequest($request);
75
+
76
+ // Update the record version from extension json
77
+ $record->update(['bundle_number' => $extensionJson->bundle_number, 'version' => $extensionJson->version]);
78
+ $record->updateMetaProperties((array) $bundleData);
79
+
80
+ return ['registryExtensionBundle' => new $this->resource($record)];
81
+ } catch (\Throwable $e) {
82
+ return response()->error($e->getMessage());
83
+ } catch (\Illuminate\Database\QueryException $e) {
84
+ return response()->error($e->getMessage());
85
+ } catch (FleetbaseRequestValidationException $e) {
86
+ return response()->error($e->getErrors());
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Handles the download request for an extension bundle.
92
+ *
93
+ * This function retrieves a specific `RegistryExtension` by its ID and attempts to download
94
+ * its latest bundle. If the extension exists and has an associated bundle, it returns a download response
95
+ * with the appropriate file. If the extension doesn't exist or doesn't have a bundle, it returns an error response.
96
+ *
97
+ * @param RegistryExtensionActionRequest $request the validated request object
98
+ *
99
+ * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Illuminate\Http\Response
100
+ * Returns a download response for the bundle if successful, or an error response if not
101
+ */
102
+ public function download(RegistryExtensionActionRequest $request)
103
+ {
104
+ $id = $request->input('id');
105
+ $extensionBundle = RegistryExtensionBundle::find($id);
106
+ if ($extensionBundle && $extensionBundle->bundle) {
107
+ return Storage::disk($extensionBundle->bundle->disk)->download($extensionBundle->bundle->path, $extensionBundle->bundle->name);
108
+ }
109
+
110
+ return response()->error('Failed to download extension bundle');
111
+ }
112
+ }
@@ -0,0 +1,257 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\Exceptions\FleetbaseRequestValidationException;
6
+ use Fleetbase\RegistryBridge\Http\Controllers\RegistryBridgeController;
7
+ use Fleetbase\RegistryBridge\Http\Requests\CreateRegistryExtensionRequest;
8
+ use Fleetbase\RegistryBridge\Http\Requests\RegistryExtensionActionRequest;
9
+ use Fleetbase\RegistryBridge\Models\RegistryExtension;
10
+ use Fleetbase\Support\Utils;
11
+ use Illuminate\Http\Request;
12
+ use Illuminate\Support\Facades\Storage;
13
+ use Illuminate\Support\Facades\Validator;
14
+
15
+ class RegistryExtensionController extends RegistryBridgeController
16
+ {
17
+ /**
18
+ * The resource to query.
19
+ *
20
+ * @var string
21
+ */
22
+ public $resource = 'registry_extension';
23
+
24
+ /**
25
+ * Creates a record with request payload.
26
+ *
27
+ * @return \Illuminate\Http\Response
28
+ */
29
+ public function createRecord(Request $request)
30
+ {
31
+ // Create validation request
32
+ $createRegistryExtensionRequest = CreateRegistryExtensionRequest::createFrom($request);
33
+ $rules = $createRegistryExtensionRequest->rules();
34
+
35
+ // Manually validate request
36
+ $validator = Validator::make($request->input('registryExtension'), $rules);
37
+ if ($validator->fails()) {
38
+ return $createRegistryExtensionRequest->responseWithErrors($validator);
39
+ }
40
+
41
+ try {
42
+ $record = $this->model->createRecordFromRequest($request);
43
+
44
+ return ['registryExtension' => new $this->resource($record)];
45
+ } catch (\Throwable $e) {
46
+ return response()->error($e->getMessage());
47
+ } catch (\Illuminate\Database\QueryException $e) {
48
+ return response()->error($e->getMessage());
49
+ } catch (FleetbaseRequestValidationException $e) {
50
+ return response()->error($e->getErrors());
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Display a list of installed extensions for the current company.
56
+ *
57
+ * This function retrieves all extensions that are installed for the company
58
+ * identified by the `company_uuid` stored in the session. It disables the cache
59
+ * for the `RegistryExtension` model to ensure fresh data is fetched from the database.
60
+ *
61
+ * The extensions are filtered based on their association with any installation
62
+ * record that matches the `company_uuid` from the session. The resulting collection
63
+ * of installed extensions is then wrapped and returned as a resource collection.
64
+ *
65
+ * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection the collection of installed extensions wrapped as a resource
66
+ */
67
+ public function installed()
68
+ {
69
+ $installedExtensions = RegistryExtension::disableCache()->whereHas('installs', function ($query) {
70
+ $query->where('company_uuid', session('company'));
71
+ })->get();
72
+
73
+ $this->resource::wrap('registryExtensions');
74
+
75
+ return $this->resource::collection($installedExtensions);
76
+ }
77
+
78
+ /**
79
+ * Display a list of purchased extensions for the current company.
80
+ *
81
+ * This function retrieves all extensions that are purchased for the company
82
+ * identified by the `company_uuid` stored in the session. It disables the cache
83
+ * for the `RegistryExtension` model to ensure fresh data is fetched from the database.
84
+ *
85
+ * The extensions are filtered based on their association with any purchase
86
+ * record that matches the `company_uuid` from the session. The resulting collection
87
+ * of purchased extensions is then wrapped and returned as a resource collection.
88
+ *
89
+ * @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection the collection of purchased extensions wrapped as a resource
90
+ */
91
+ public function purchased()
92
+ {
93
+ $purchasedExtensions = RegistryExtension::disableCache()->whereHas('purchases', function ($query) {
94
+ $query->where('company_uuid', session('company'));
95
+ })->get();
96
+
97
+ $this->resource::wrap('registryExtensions');
98
+
99
+ return $this->resource::collection($purchasedExtensions);
100
+ }
101
+
102
+ /**
103
+ * Retrieve analytics for a specific registry extension.
104
+ *
105
+ * This method fetches analytics for a registry extension identified by
106
+ * the provided ID. It gathers various metrics, including the number of
107
+ * installs, uninstalls, purchases, and the total purchase amount. If the
108
+ * extension cannot be found, it returns an error response. Otherwise, it
109
+ * returns the collected metrics as a JSON response.
110
+ *
111
+ * @param Request $request
112
+ * The incoming request instance containing the extension ID
113
+ *
114
+ * @return \Illuminate\Http\JsonResponse
115
+ * A JSON response containing the collected analytics metrics or an error message
116
+ */
117
+ public function analytics(Request $request)
118
+ {
119
+ $id = $request->input('id');
120
+ $extension = RegistryExtension::find($id);
121
+ if (!$extension) {
122
+ return response()->error('Unable to find extension to fetch analytics.');
123
+ }
124
+
125
+ $metrics = [];
126
+
127
+ // Get number of installs
128
+ $metrics['install_count'] = $extension->installs()->count();
129
+
130
+ // Get number of uninstalls
131
+ $metrics['uninstall_count'] = $extension->installs()->withoutGlobalScopes()->whereNotNull('deleted_at')->count();
132
+
133
+ // Get number of purchases
134
+ $metrics['purchase_count'] = $extension->purchases()->count();
135
+
136
+ // Get total amount in purchases
137
+ $totalPurchaseAmount = $extension->purchases()->get()->sum('locked_price');
138
+ $metrics['purchase_amount'] = Utils::moneyFormat($totalPurchaseAmount, $extension->currency);
139
+
140
+ // Respond with metrics
141
+ return response()->json($metrics);
142
+ }
143
+
144
+ /**
145
+ * Approves a specific extension by its ID.
146
+ *
147
+ * This function locates a `RegistryExtension` using the provided ID and sets its status to 'approved'.
148
+ * If the extension is successfully found and updated, it returns the extension resource. If the extension
149
+ * cannot be found, it returns an error response indicating the inability to locate the extension.
150
+ *
151
+ * @param RegistryExtensionActionRequest $request the validated request object
152
+ *
153
+ * @return \Illuminate\Http\Response|array returns an array containing the extension resource if successful,
154
+ * or an error response if the extension cannot be found
155
+ */
156
+ public function approve(RegistryExtensionActionRequest $request)
157
+ {
158
+ $id = $request->input('id');
159
+ $extension = RegistryExtension::find($id);
160
+ if ($extension) {
161
+ $extension->update(['status' => 'approved', 'current_bundle_uuid' => $extension->next_bundle_uuid]);
162
+ $extension->nextBundle()->update(['status' => 'approved']);
163
+ } else {
164
+ return response()->error('Unable to find extension for approval.');
165
+ }
166
+
167
+ return ['registryExtension' => new $this->resource($extension)];
168
+ }
169
+
170
+ /**
171
+ * Rejects a specific extension by its ID.
172
+ *
173
+ * Locates a `RegistryExtension` using the provided ID and updates its status to 'rejected'. It also
174
+ * intends to send a rejection reason via email to the extension's author (as indicated by commented code).
175
+ * If the extension is found and updated, it returns the extension resource. If not found, it returns an
176
+ * error response indicating the extension could not be located.
177
+ *
178
+ * Note: This method assumes the rejection reason is handled separately (possibly by another request).
179
+ *
180
+ * @param RegistryExtensionActionRequest $request the validated request object
181
+ *
182
+ * @return \Illuminate\Http\Response|array returns an array containing the extension resource if successful,
183
+ * or an error response if the extension cannot be found
184
+ */
185
+ public function reject(RegistryExtensionActionRequest $request)
186
+ {
187
+ $id = $request->input('id');
188
+ $extension = RegistryExtension::find($id);
189
+ if ($extension) {
190
+ $extension->update(['status' => 'rejected']);
191
+ $extension->nextBundle()->update(['status' => 'rejected']);
192
+ } else {
193
+ return response()->error('Unable to find extension for rejection.');
194
+ }
195
+
196
+ // send rejection reason via email to extension author
197
+ // $reason = $request->input('reason');
198
+
199
+ return ['registryExtension' => new $this->resource($extension)];
200
+ }
201
+
202
+ /**
203
+ * Submits an extension for review based on its ID.
204
+ *
205
+ * This method attempts to submit a `RegistryExtension` for review. It first checks
206
+ * if the extension is ready for submission by calling the static method
207
+ * `isExtensionReadyForSubmission`. If the extension is not ready, it returns an error response.
208
+ * If the extension is ready, it updates the extension's status to 'awaiting_review' and returns
209
+ * a JSON response indicating success.
210
+ *
211
+ * @param string $id the unique identifier of the extension to be submitted
212
+ *
213
+ * @return \Illuminate\Http\JsonResponse returns a JSON response indicating the outcome of the operation
214
+ *
215
+ * @throws \Illuminate\Database\Eloquent\ModelNotFoundException if no extension with the given ID is found
216
+ */
217
+ public function submit(string $id)
218
+ {
219
+ $isReady = RegistryExtension::isExtensionReadyForSubmission($id);
220
+ if (!$isReady) {
221
+ return response()->error('Unable to submit extension for review.');
222
+ }
223
+
224
+ $extension = RegistryExtension::find($id);
225
+ if ($extension) {
226
+ $extension->update(['status' => 'awaiting_review']);
227
+ }
228
+
229
+ return ['registryExtension' => new $this->resource($extension)];
230
+ }
231
+
232
+ /**
233
+ * Handles the download request for an extension bundle.
234
+ *
235
+ * This function retrieves a specific `RegistryExtension` by its ID and attempts to download
236
+ * its latest bundle. If the extension exists and has an associated bundle, it returns a download response
237
+ * with the appropriate file. If the extension doesn't exist or doesn't have a bundle, it returns an error response.
238
+ *
239
+ * @param RegistryExtensionActionRequest $request the validated request object
240
+ *
241
+ * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Illuminate\Http\Response
242
+ * Returns a download response for the bundle if successful, or an error response if not
243
+ */
244
+ public function downloadBundle(RegistryExtensionActionRequest $request)
245
+ {
246
+ $id = $request->input('id');
247
+ $extension = RegistryExtension::find($id);
248
+ if ($extension && $extension->nextBundle) {
249
+ $bundleFile = data_get($extension, 'nextBundle.bundle');
250
+ if ($bundleFile) {
251
+ return Storage::disk($bundleFile->disk)->download($bundleFile->path, $bundleFile->name);
252
+ }
253
+ }
254
+
255
+ return response()->error('Failed to download extension bundle');
256
+ }
257
+ }
@@ -0,0 +1,227 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Http\Controllers\Internal\v1;
4
+
5
+ use Fleetbase\Http\Controllers\Controller;
6
+ use Fleetbase\Http\Resources\FleetbaseResource;
7
+ use Fleetbase\RegistryBridge\Models\RegistryExtension;
8
+ use Fleetbase\RegistryBridge\Models\RegistryExtensionPurchase;
9
+ use Fleetbase\RegistryBridge\Support\Utils;
10
+ use Fleetbase\Support\Auth;
11
+ use Illuminate\Http\Request;
12
+ use Illuminate\Support\Str;
13
+
14
+ /**
15
+ * Handles payment processing and Stripe account management for registry extensions.
16
+ *
17
+ * This controller provides functionalities such as checking if a company has a Stripe Connect account,
18
+ * creating Stripe accounts, managing Stripe account sessions, and handling Stripe Checkout sessions.
19
+ */
20
+ class RegistryPaymentsController extends Controller
21
+ {
22
+ /**
23
+ * Checks if the currently authenticated company has an associated Stripe Connect account.
24
+ *
25
+ * This method verifies if the authenticated company has a Stripe Connect ID that starts with 'acct_'.
26
+ *
27
+ * @return \Illuminate\Http\JsonResponse returns a JSON response indicating the presence of a Stripe Connect account
28
+ */
29
+ public function hasStripeConnectAccount()
30
+ {
31
+ $company = Auth::getCompany();
32
+ if ($company) {
33
+ return response()->json([
34
+ 'hasStripeConnectAccount' => !empty($company->stripe_connect_id) && Str::startsWith($company->stripe_connect_id, 'acct_'),
35
+ ]);
36
+ }
37
+
38
+ return response()->json([
39
+ 'hasStripeConnectAccount' => false,
40
+ ]);
41
+ }
42
+
43
+ /**
44
+ * Creates a new Stripe account for the currently authenticated company and stores the account ID.
45
+ *
46
+ * This method utilizes the Fleetbase utility class to create a Stripe Express account and saves the
47
+ * Stripe account ID to the current company's profile. In case of failure, it returns an error.
48
+ *
49
+ * @return \Illuminate\Http\JsonResponse returns the Stripe account ID or an error message in JSON format
50
+ */
51
+ public function getStripeAccount()
52
+ {
53
+ $stripe = Utils::getStripeClient();
54
+
55
+ try {
56
+ $account = $stripe->accounts->create([
57
+ 'controller' => [
58
+ 'stripe_dashboard' => [
59
+ 'type' => 'express',
60
+ ],
61
+ 'fees' => [
62
+ 'payer' => 'application',
63
+ ],
64
+ 'losses' => [
65
+ 'payments' => 'application',
66
+ ],
67
+ ],
68
+ ]);
69
+
70
+ // Save account ID to current company session
71
+ $company = Auth::getCompany();
72
+ if ($company) {
73
+ $company->update(['stripe_connect_id' => $account->id]);
74
+ }
75
+
76
+ return response()->json(['account' => $account->id]);
77
+ } catch (\Exception $e) {
78
+ return response()->error($e->getMessage());
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Creates a Stripe account session for account onboarding or management.
84
+ *
85
+ * This method creates a session for the current company's Stripe account, allowing for onboarding or management activities.
86
+ * It accepts an 'account' parameter from the request, defaulting to the company's stored Stripe Connect ID if not provided.
87
+ *
88
+ * @param Request $request the incoming HTTP request containing optional 'account' parameter
89
+ *
90
+ * @return \Illuminate\Http\JsonResponse returns a JSON response with the session's client secret or an error message
91
+ */
92
+ public function getStripeAccountSession(Request $request)
93
+ {
94
+ $stripe = Utils::getStripeClient();
95
+ $company = Auth::getCompany();
96
+
97
+ try {
98
+ $accountSession = $stripe->accountSessions->create([
99
+ 'account' => $request->input('account', $company->stripe_connect_id),
100
+ 'components' => [
101
+ 'account_onboarding' => [
102
+ 'enabled' => true,
103
+ ],
104
+ ],
105
+ ]);
106
+
107
+ return response()->json([
108
+ 'clientSecret' => $accountSession->client_secret,
109
+ ]);
110
+ } catch (\Exception $e) {
111
+ return response()->error($e->getMessage());
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Creates a Stripe Checkout session for a specified registry extension.
117
+ *
118
+ * This method initializes a checkout session for purchasing a registry extension identified by a UUID.
119
+ * It requires a 'uri' for redirection after the checkout and an 'extension' UUID to identify the product.
120
+ *
121
+ * @param Request $request the incoming HTTP request with 'uri' and 'extension' parameters
122
+ *
123
+ * @return \Illuminate\Http\JsonResponse returns the checkout session's client secret or an error message
124
+ */
125
+ public function createStripeCheckoutSession(Request $request)
126
+ {
127
+ $redirectUri = $request->input('uri');
128
+ $extension = RegistryExtension::where('uuid', $request->input('extension'))->first();
129
+ if (!$extension) {
130
+ return response()->error('The extension you attempted to purchase does not exist.');
131
+ }
132
+
133
+ try {
134
+ $checkoutSession = $extension->createStripeCheckoutSession($redirectUri);
135
+ } catch (\Throwable $e) {
136
+ return response()->error($e->getMessage());
137
+ }
138
+
139
+ return response()->json(['clientSecret' => $checkoutSession->client_secret]);
140
+ }
141
+
142
+ /**
143
+ * Retrieves the status of an ongoing Stripe Checkout session.
144
+ *
145
+ * This method checks the status of a Stripe Checkout session associated with a registry extension purchase.
146
+ * It ensures the extension exists and checks if it has already been purchased. It then retrieves and returns
147
+ * the checkout session status or creates a purchase record if the session is complete.
148
+ *
149
+ * @param Request $request the incoming HTTP request containing 'extension' and 'checkout_session_id'
150
+ *
151
+ * @return \Illuminate\Http\JsonResponse returns the checkout session status or an error message
152
+ */
153
+ public function getStripeCheckoutSessionStatus(Request $request)
154
+ {
155
+ $extension = RegistryExtension::where('uuid', $request->input('extension'))->first();
156
+ if (!$extension) {
157
+ return response()->error('The extension you attempted to purchase does not exist.');
158
+ }
159
+
160
+ // Flush cache for extension
161
+ if (method_exists($extension, 'flushCache')) {
162
+ $extension->flushCache();
163
+ }
164
+
165
+ // Check if already purchased
166
+ $purchaseRecordExists = RegistryExtensionPurchase::where(['company_uuid' => session('company'), 'extension_uuid' => $extension->uuid])->exists();
167
+ if ($purchaseRecordExists) {
168
+ return response()->json(['status' => 'purchase_complete', 'extension' => $extension]);
169
+ }
170
+
171
+ $stripe = Utils::getStripeClient();
172
+ try {
173
+ $session = $stripe->checkout->sessions->retrieve($request->input('checkout_session_id'));
174
+ if (isset($session->status) && $session->status === 'complete') {
175
+ RegistryExtensionPurchase::firstOrCreate(
176
+ [
177
+ 'company_uuid' => session('company'),
178
+ 'extension_uuid' => $extension->uuid,
179
+ ],
180
+ [
181
+ 'stripe_checkout_session_id' => $session->id,
182
+ 'stripe_payment_intent_id' => $session->payment_intent,
183
+ 'locked_price' => $session->amount_total,
184
+ ]
185
+ );
186
+ }
187
+
188
+ // Flush cache for extension
189
+ if (method_exists($extension, 'flushCache')) {
190
+ $extension->flushCache();
191
+ }
192
+
193
+ return response()->json(['status' => $session->status, 'extension' => $extension]);
194
+ } catch (\Error $e) {
195
+ return response()->error($e->getMessage());
196
+ }
197
+ }
198
+
199
+ public function getAuthorReceivedPayments(Request $request)
200
+ {
201
+ $limit = $request->input('limit', 30);
202
+ $query = RegistryExtensionPurchase::whereHas(
203
+ 'extension',
204
+ function ($query) {
205
+ $query->where('company_uuid', session('company'));
206
+ }
207
+ )->with(
208
+ [
209
+ 'extension' => function ($query) {
210
+ $query->select(['uuid', 'public_id', 'name', 'category_uuid']);
211
+ $query->with(['category']);
212
+ },
213
+ 'company' => function ($query) {
214
+ $query->select(['uuid', 'name'])->withoutGlobalScopes();
215
+ },
216
+ ]
217
+ );
218
+
219
+ // Handle sorting
220
+ app(RegistryExtensionPurchase::class)->applySorts($request, $query);
221
+
222
+ $payments = $query->fastPaginate($limit);
223
+ $totalPurchaseAmount = $query->get()->sum('locked_price');
224
+
225
+ return FleetbaseResource::collection($payments)->additional(['total_amount' => $totalPurchaseAmount]);
226
+ }
227
+ }
@@ -0,0 +1,13 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Http\Controllers;
4
+
5
+ use Fleetbase\Http\Controllers\FleetbaseController;
6
+
7
+ class RegistryBridgeController extends FleetbaseController
8
+ {
9
+ /**
10
+ * The package namespace used to resolve from.
11
+ */
12
+ public string $namespace = '\\Fleetbase\\RegistryBridge';
13
+ }
@@ -0,0 +1,80 @@
1
+ <?php
2
+
3
+ namespace Fleetbase\RegistryBridge\Http\Filter;
4
+
5
+ use Fleetbase\Http\Filter\Filter;
6
+ use Fleetbase\Support\Utils;
7
+ use Illuminate\Support\Str;
8
+
9
+ class RegistryExtensionFilter extends Filter
10
+ {
11
+ public function queryForInternal()
12
+ {
13
+ if ($this->request->boolean('explore')) {
14
+ return;
15
+ }
16
+ $this->builder->where('company_uuid', $this->session->get('company'));
17
+ }
18
+
19
+ public function queryForPublic()
20
+ {
21
+ $this->builder->where('company_uuid', $this->session->get('company'));
22
+ }
23
+
24
+ public function query(?string $searchQuery)
25
+ {
26
+ $this->builder->search($searchQuery);
27
+ }
28
+
29
+ public function isAuthor()
30
+ {
31
+ $this->builder->where('company_uuid', session('company'));
32
+ }
33
+
34
+ public function explore()
35
+ {
36
+ $this->builder->where('status', 'published');
37
+ $this->builder->without(['current_bundle', 'next_bundle', 'category']);
38
+ $this->builder->with(['screenshots']);
39
+ }
40
+
41
+ public function category($category)
42
+ {
43
+ if (Str::isUuid($category)) {
44
+ return $this->builder->where('category_uuid', $category);
45
+ }
46
+
47
+ if (Utils::isPublicId($category)) {
48
+ return $this->builder->whereHas('category', function ($query) use ($category) {
49
+ $query->where('public_id', $category)->where('for', 'extension_category')->where('core_category', 1);
50
+ });
51
+ }
52
+
53
+ // assume slug
54
+ return $this->builder->whereHas('category', function ($query) use ($category) {
55
+ $query->where('slug', $category)->where('for', 'extension_category')->where('core_category', 1);
56
+ });
57
+ }
58
+
59
+ public function createdAt($createdAt)
60
+ {
61
+ $createdAt = Utils::dateRange($createdAt);
62
+
63
+ if (is_array($createdAt)) {
64
+ $this->builder->whereBetween('created_at', $createdAt);
65
+ } else {
66
+ $this->builder->whereDate('created_at', $createdAt);
67
+ }
68
+ }
69
+
70
+ public function updatedAt($updatedAt)
71
+ {
72
+ $updatedAt = Utils::dateRange($updatedAt);
73
+
74
+ if (is_array($updatedAt)) {
75
+ $this->builder->whereBetween('updated_at', $updatedAt);
76
+ } else {
77
+ $this->builder->whereDate('updated_at', $updatedAt);
78
+ }
79
+ }
80
+ }