diff --git a/app/Http/ApiControllers/Controller.php b/app/Http/ApiControllers/Controller.php
new file mode 100644
index 0000000..bc0e11d
--- /dev/null
+++ b/app/Http/ApiControllers/Controller.php
@@ -0,0 +1,8 @@
+user());
+ }
+}
\ No newline at end of file
diff --git a/app/Http/ApiControllers/PortfolioController.php b/app/Http/ApiControllers/PortfolioController.php
new file mode 100644
index 0000000..72299a4
--- /dev/null
+++ b/app/Http/ApiControllers/PortfolioController.php
@@ -0,0 +1,24 @@
+setScopes(['myPortfolios']);
+ $filterRequest->setEagerRelations(['users', 'transactions', 'holdings']);
+ $filterRequest->setFilterableRelations(['holdings' => 'symbol', 'transactions' => 'symbol']);
+ $filterRequest->setSearchableColumns(['title', 'notes']);
+
+ return PortfolioResource::collection($filterRequest->get());
+ }
+}
\ No newline at end of file
diff --git a/app/Http/ApiControllers/TransactionController.php b/app/Http/ApiControllers/TransactionController.php
new file mode 100644
index 0000000..b482720
--- /dev/null
+++ b/app/Http/ApiControllers/TransactionController.php
@@ -0,0 +1,15 @@
+user());
+ }
+}
\ No newline at end of file
diff --git a/app/Http/ApiControllers/UserController.php b/app/Http/ApiControllers/UserController.php
new file mode 100644
index 0000000..468ca52
--- /dev/null
+++ b/app/Http/ApiControllers/UserController.php
@@ -0,0 +1,15 @@
+user());
+ }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php
index 8333b01..0551b89 100644
--- a/app/Http/Controllers/ConnectedAccountController.php
+++ b/app/Http/Controllers/ConnectedAccountController.php
@@ -6,7 +6,6 @@ use Exception;
use App\Models\User;
use App\Models\ConnectedAccount;
use Illuminate\Support\MessageBag;
-use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Blade;
use Laravel\Socialite\Facades\Socialite;
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 8677cd5..71116b2 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -1,8 +1,8 @@
+ */
+ public function toArray(Request $request): array
+ {
+ return parent::toArray($request);
+ }
+}
diff --git a/app/Http/Resources/PortfolioResource.php b/app/Http/Resources/PortfolioResource.php
new file mode 100644
index 0000000..8a71071
--- /dev/null
+++ b/app/Http/Resources/PortfolioResource.php
@@ -0,0 +1,28 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ return [
+ 'id' => $this->id,
+ 'title' => $this->title,
+ 'wishlist' => $this->wishlist,
+ 'owner' => UserResource::make($this->owner),
+ 'transactions' => TransactionResource::collection($this->whenLoaded('transactions')),
+ 'holdings' => HoldingResource::collection($this->whenLoaded('holdings')),
+ 'created_at' => $this->created_at,
+ 'updated_at' => $this->updated_at,
+ ];
+ }
+}
diff --git a/app/Http/Resources/TransactionResource.php b/app/Http/Resources/TransactionResource.php
new file mode 100644
index 0000000..358bfe5
--- /dev/null
+++ b/app/Http/Resources/TransactionResource.php
@@ -0,0 +1,19 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+ return parent::toArray($request);
+ }
+}
diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php
new file mode 100644
index 0000000..b68e3fa
--- /dev/null
+++ b/app/Http/Resources/UserResource.php
@@ -0,0 +1,27 @@
+
+ */
+ public function toArray(Request $request): array
+ {
+
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'email' => $this->email,
+ 'profile_photo_url' => $this->profile_photo_url,
+ 'created_at' => $this->created_at,
+ 'updated_at' => $this->updated_at,
+ ];
+ }
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 0450ca0..39f79b0 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -3,6 +3,7 @@
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
+use Illuminate\Http\Resources\Json\JsonResource;
class AppServiceProvider extends ServiceProvider
{
@@ -22,6 +23,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
- //
+ JsonResource::withoutWrapping();
}
}
diff --git a/app/Providers/JetstreamServiceProvider.php b/app/Providers/JetstreamServiceProvider.php
index f931bf3..f7a2ed0 100644
--- a/app/Providers/JetstreamServiceProvider.php
+++ b/app/Providers/JetstreamServiceProvider.php
@@ -43,13 +43,8 @@ class JetstreamServiceProvider extends ServiceProvider
*/
protected function configurePermissions(): void
{
- Jetstream::defaultApiTokenPermissions(['read']);
+ Jetstream::defaultApiTokenPermissions([]);
- Jetstream::permissions([
- 'create',
- 'read',
- 'update',
- 'delete',
- ]);
+ Jetstream::permissions([]);
}
}
diff --git a/app/Support/FilterRequest.php b/app/Support/FilterRequest.php
new file mode 100644
index 0000000..07b64ef
--- /dev/null
+++ b/app/Support/FilterRequest.php
@@ -0,0 +1,205 @@
+query = (new $modelClass)->query();
+ }
+
+ /**
+ * Sets eager loads on the underlying query
+ *
+ * @param array $scopes
+ * @return void
+ */
+ public function setEagerRelations(string|array $relations)
+ {
+ $this->query = $this->query->with($relations);
+ }
+
+ /**
+ * Sets scopes on the underlying query
+ *
+ * @param array $scopes
+ * @return void
+ */
+ public function setScopes(string|array $scopes): void
+ {
+ $this->query = $this->query->scopes($scopes);
+ }
+
+ /**
+ * Allows nested json data to be aliased into a virtual column for the query
+ *
+ * @param array $columns can be an array of keys (e.g. `body.total_amount`)
+ * @return void
+ */
+ public function setVirtualColumns(array $columns)
+ {
+ foreach($columns as $column) {
+ $column = Str::replace('.', '->', $column);
+ $alias = Str::snake(Str::afterLast($column, '->'));
+
+ array_push($this->select, $column . ' as ' . $alias);
+ }
+ }
+
+ /**
+ * Set columns that should be searched
+ *
+ * @param array $columns can be an array of keys (e.g. `user.name` or a nested array with
+ * `relation`, `table`, and `column` attributes)
+ * @return void
+ */
+ public function setSearchableColumns(array $columns)
+ {
+ $this->searchableColumns = $columns;
+ }
+
+ /**
+ * Set relations that can be filtered
+ *
+ * @param array $relations is an array of keys (relations) and values (columns)
+ * @return void
+ */
+ public function setFilterableRelations(array $relations)
+ {
+ $this->filterableRelations = $relations;
+ }
+
+ /**
+ * Set related columns using aggregate function (e.g. `package.label` would become `package_label`)
+ * which enables filtering, searching, and sorting on the front end
+ *
+ * @param array $columns can be an array of keys (e.g. `user.name` or a nested array with
+ * `relation`, `table`, and `column` attributes)
+ * @return void
+ */
+ public function setRelationshipColumns(array $columns)
+ {
+ foreach($columns as $column) {
+
+ // advanced
+ if (is_array($column)) {
+
+ $this->query->withAggregate($column['relation'], $column['table'].'.'.$column['column']);
+
+ continue;
+ }
+
+ // not a relationship
+ if(!Str::contains($column, '.')) {
+ continue;
+ }
+
+ // normal rx
+ $relationship = Str::before($column, '.');
+ $key = Str::after($column, '.');
+
+ $this->query->withAggregate($relationship, $key);
+ }
+ }
+
+ /**
+ * Get the resulting paginated collection
+ *
+ * @return LengthAwarePaginator
+ */
+ public function get(): LengthAwarePaginator
+ {
+ // handle sort
+ if (!empty(request()->query('sortBy'))) {
+ if (Str::contains(request()->query('sortBy'), '.')) {
+ $this->query->joinRelation(Str::before(request()->query('sortBy'), '.'));
+ }
+ $this->query->orderBy(
+ request()->query('sortBy'),
+ request()->query('sortDesc', false) == "true" ? 'DESC' : 'ASC'
+ );
+ }
+
+ // handle filter
+ if (request()->has('filter')) {
+
+ foreach(request()->query('filter') as $filter => $params) {
+
+ if (array_key_exists($filter, $this->filterableRelations)) {
+
+ // filtered rx
+ foreach(explode(',', $params) as $param) {
+ $this->query->whereHas($filter, function ($query) use ($filter, $param) {
+ $query->where($this->filterableRelations[$filter], $param);
+ });
+ }
+
+ } else {
+ // traditional filter
+ foreach(explode(',', $params) as $param) {
+ $this->query->having($filter, $param);
+ }
+
+ }
+ }
+ }
+
+ // handle search
+ if (request()->has('search') && !empty($this->searchableColumns)) {
+ // make searchable relationships aggregate columns
+ $this->setRelationshipColumns($this->searchableColumns);
+
+ $this->query->where(function($query) {
+
+ foreach($this->searchableColumns as $column) {
+
+ // advanced
+ if (is_array($column)) {
+ $query->orWhereHas($column['relation'], function($query) use ($column) {
+ $query->where($column['table'].'.'.$column['column'], "like", '%' . request()->query('search') . '%');
+ });
+
+ continue;
+ }
+
+ // normal RX
+ if(Str::contains($column, '.')) {
+
+ $query->orWhereHas(Str::before($column, '.'), function($query) use ($column) {
+ $query->where(Str::after($column, '.'), "like", '%' . request()->query('search') . '%');
+ });
+
+ continue;
+ }
+
+ // not rx
+ $query->orWhere($column, "like", '%' . request()->query('search') . '%');
+ }
+ });
+ }
+
+ // handle per page
+ if (request()->query('itemsPerPage') == "-1") {
+ $perPage = $this->query->count();
+ } else {
+ $perPage = request()->query('itemsPerPage', 15);
+ }
+
+ // run
+ return $this->query->addSelect($this->select)->paginate($perPage);
+ }
+}
diff --git a/config/jetstream.php b/config/jetstream.php
index 7b9d0f6..b1f2c73 100644
--- a/config/jetstream.php
+++ b/config/jetstream.php
@@ -60,7 +60,7 @@ return [
'features' => [
Features::termsAndPrivacyPolicy(),
Features::profilePhotos(),
- // Features::api(),
+ Features::api(),
// Features::teams(['invitations' => true]),
Features::accountDeletion(),
],
diff --git a/lang/en.json b/lang/en.json
index f40abc4..c2dead1 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -70,7 +70,7 @@
"API Token": "API Token",
"Please copy your new API token. For your security, it won\\'t be shown again.": "Please copy your new API token. For your security, it won\\'t be shown again.",
"API Token Permissions": "API Token Permissions",
- "API tokens allow third-party services to authenticate with our application on your behalf.": "API tokens allow third-party services to authenticate with our application on your behalf.",
+ "API tokens allow third-party services to authenticate with Investbrain on your behalf.": "API tokens allow third-party services to authenticate with Investbrain on your behalf.",
"Delete API Token": "Delete API Token",
"Are you sure you would like to delete this API token?": "Are you sure you would like to delete this API token?",
"This is a secure area of the application. Please confirm your password before continuing.": "This is a secure area of the application. Please confirm your password before continuing.",
diff --git a/lang/es.json b/lang/es.json
index 13a81b2..04ef548 100644
--- a/lang/es.json
+++ b/lang/es.json
@@ -70,7 +70,7 @@
"API Token": "Token API",
"Please copy your new API token. For your security, it won't be shown again.": "Por favor, copia tu nuevo token API. Por seguridad, no se mostrará nuevamente.",
"API Token Permissions": "Permisos del Token API",
- "API tokens allow third-party services to authenticate with our application on your behalf.": "Los tokens API permiten que servicios de terceros se autentiquen con nuestra aplicación en tu nombre.",
+ "API tokens allow third-party services to authenticate with Investbrain on your behalf.": "Los tokens API permiten que servicios de terceros se autentiquen con Investbrain en tu nombre.",
"Delete API Token": "Eliminar Token API",
"Are you sure you would like to delete this API token?": "¿Estás seguro de que deseas eliminar este token API?",
"This is a secure area of the application. Please confirm your password before continuing.": "Esta es un área segura de la aplicación. Por favor, confirma tu contraseña antes de continuar.",
diff --git a/resources/views/api/api-token-manager.blade.php b/resources/views/api/api-token-manager.blade.php
index d161ca0..aad3ece 100644
--- a/resources/views/api/api-token-manager.blade.php
+++ b/resources/views/api/api-token-manager.blade.php
@@ -6,7 +6,7 @@
- {{ __('API tokens allow third-party services to authenticate with our application on your behalf.') }}
+ {{ __('API tokens allow third-party services to authenticate with Investbrain on your behalf.') }}
diff --git a/routes/api.php b/routes/api.php
index ccc387f..d19bba6 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -1,8 +1,18 @@
user();
-})->middleware('auth:sanctum');
+Route::middleware(['auth:sanctum'])->group(function () {
+
+ // user
+ Route::get('/me', [UserController::class, 'me']);
+
+ // portfolio
+ Route::get('/portfolio', [PortfolioController::class, 'index']);
+
+ // transaction
+
+ // holding
+});
\ No newline at end of file
diff --git a/routes/web.php b/routes/web.php
index d961542..0c60cd7 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -5,8 +5,8 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HoldingController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\PortfolioController;
-use App\Http\Controllers\ConnectedAccountController;
use App\Http\Controllers\TransactionController;
+use App\Http\Controllers\ConnectedAccountController;
use App\Http\Controllers\InvitedOnboardingController;
use Laravel\Jetstream\Http\Controllers\Livewire\PrivacyPolicyController;
use Laravel\Jetstream\Http\Controllers\Livewire\TermsOfServiceController;