diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php
index 1396fec..90e961e 100644
--- a/app/Actions/Fortify/CreateNewUser.php
+++ b/app/Actions/Fortify/CreateNewUser.php
@@ -4,10 +4,10 @@ namespace App\Actions\Fortify;
use App\Models\User;
use App\Traits\WithTrimStrings;
-use Laravel\Jetstream\Jetstream;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\CreatesNewUsers;
+use Laravel\Jetstream\Jetstream;
class CreateNewUser implements CreatesNewUsers
{
diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php
index 195ff57..4162e5f 100644
--- a/app/Actions/Fortify/UpdateUserProfileInformation.php
+++ b/app/Actions/Fortify/UpdateUserProfileInformation.php
@@ -4,15 +4,15 @@ namespace App\Actions\Fortify;
use App\Models\User;
use App\Traits\WithTrimStrings;
-use Illuminate\Validation\Rule;
-use Illuminate\Support\Facades\Validator;
use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
use WithTrimStrings;
-
+
/**
* Validate and update the given user's profile information.
*
diff --git a/app/Console/Commands/CaptureDailyChange.php b/app/Console/Commands/CaptureDailyChange.php
index a4cca40..c148bbf 100644
--- a/app/Console/Commands/CaptureDailyChange.php
+++ b/app/Console/Commands/CaptureDailyChange.php
@@ -38,9 +38,9 @@ class CaptureDailyChange extends Command
*/
public function handle()
{
- Portfolio::with('holdings.market_data')->get()->each(function($portfolio){
+ Portfolio::with('holdings.market_data')->get()->each(function ($portfolio) {
- $this->line('Capturing daily change for ' . $portfolio->title);
+ $this->line('Capturing daily change for '.$portfolio->title);
$total_cost_basis = $portfolio->holdings->sum('total_cost_basis');
@@ -48,7 +48,7 @@ class CaptureDailyChange extends Command
$realized_gains = $portfolio->holdings->sum('realized_gain_dollars');
- $total_market_value = $portfolio->holdings->sum(function($holding) {
+ $total_market_value = $portfolio->holdings->sum(function ($holding) {
return $holding->market_data->market_value * $holding->quantity;
});
@@ -58,7 +58,7 @@ class CaptureDailyChange extends Command
'total_cost_basis' => $total_cost_basis,
'total_gain' => $total_market_value - $total_cost_basis,
'total_dividends_earned' => $total_dividends,
- 'realized_gains' => $realized_gains
+ 'realized_gains' => $realized_gains,
]);
});
}
diff --git a/app/Console/Commands/RefreshDividendData.php b/app/Console/Commands/RefreshDividendData.php
index fa97c9b..a501614 100644
--- a/app/Console/Commands/RefreshDividendData.php
+++ b/app/Console/Commands/RefreshDividendData.php
@@ -2,8 +2,8 @@
namespace App\Console\Commands;
-use App\Models\Holding;
use App\Models\Dividend;
+use App\Models\Holding;
use Illuminate\Console\Command;
class RefreshDividendData extends Command
@@ -43,17 +43,17 @@ class RefreshDividendData extends Command
{
$holdings = Holding::distinct();
- if (!($this->option('force') ?? false)) {
+ if (! ($this->option('force') ?? false)) {
$holdings->where('quantity', '>', 0);
- }
+ }
if ($this->option('user')) {
$holdings->myHoldings($this->option('user'));
}
foreach ($holdings->get(['symbol']) as $holding) {
- $this->line('Refreshing ' . $holding->symbol);
-
+ $this->line('Refreshing '.$holding->symbol);
+
Dividend::refreshDividendData($holding->symbol);
}
}
diff --git a/app/Console/Commands/RefreshMarketData.php b/app/Console/Commands/RefreshMarketData.php
index f36a090..4e3fd0c 100644
--- a/app/Console/Commands/RefreshMarketData.php
+++ b/app/Console/Commands/RefreshMarketData.php
@@ -42,18 +42,18 @@ class RefreshMarketData extends Command
public function handle()
{
$force = $this->option('force') ?? false;
-
+
// get all symbols from market data
$holdings = Holding::where('quantity', '>', 0)
- ->select(['symbol'])
- ->distinct();
-
+ ->select(['symbol'])
+ ->distinct();
+
if ($this->option('user')) {
$holdings->myHoldings($this->option('user'));
}
foreach ($holdings->get() as $holding) {
- $this->line('Refreshing ' . $holding->symbol);
+ $this->line('Refreshing '.$holding->symbol);
MarketData::getMarketData($holding->symbol, $force);
}
diff --git a/app/Console/Commands/RefreshSplitData.php b/app/Console/Commands/RefreshSplitData.php
index a358630..fa8dcee 100644
--- a/app/Console/Commands/RefreshSplitData.php
+++ b/app/Console/Commands/RefreshSplitData.php
@@ -2,8 +2,8 @@
namespace App\Console\Commands;
-use App\Models\Split;
use App\Models\Holding;
+use App\Models\Split;
use Illuminate\Console\Command;
class RefreshSplitData extends Command
@@ -42,14 +42,14 @@ class RefreshSplitData extends Command
{
$holdings = Holding::distinct();
- if (!($this->option('force') ?? false)) {
+ if (! ($this->option('force') ?? false)) {
$holdings->where('quantity', '>', 0);
- }
+ }
foreach ($holdings->get(['symbol']) as $holding) {
- $this->line('Refreshing ' . $holding->symbol);
-
+ $this->line('Refreshing '.$holding->symbol);
+
Split::refreshSplitData($holding->symbol);
- }
+ }
}
}
diff --git a/app/Console/Commands/SyncDailyChange.php b/app/Console/Commands/SyncDailyChange.php
index 3171aab..fb77a1a 100644
--- a/app/Console/Commands/SyncDailyChange.php
+++ b/app/Console/Commands/SyncDailyChange.php
@@ -5,6 +5,7 @@ namespace App\Console\Commands;
use App\Models\Portfolio;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
+
use function Laravel\Prompts\search;
class SyncDailyChange extends Command implements PromptsForMissingInput
@@ -61,14 +62,14 @@ class SyncDailyChange extends Command implements PromptsForMissingInput
public function handle()
{
try {
-
+
$portfolio = Portfolio::findOrFail($this->argument('portfolio_id'));
$this->line('Syncing daily change history... This may take a moment.');
$portfolio->syncDailyChanges();
- $this->line('Awesome! Daily change history for '. $portfolio->title .' has been completed.');
+ $this->line('Awesome! Daily change history for '.$portfolio->title.' has been completed.');
} catch (\Throwable $e) {
diff --git a/app/Console/Commands/SyncHoldingData.php b/app/Console/Commands/SyncHoldingData.php
index fe82398..0ffb6bb 100644
--- a/app/Console/Commands/SyncHoldingData.php
+++ b/app/Console/Commands/SyncHoldingData.php
@@ -47,7 +47,7 @@ class SyncHoldingData extends Command
}
foreach ($holdings->get() as $holding) {
- $this->line('Refreshing ' . $holding->symbol);
+ $this->line('Refreshing '.$holding->symbol);
$holding->syncTransactionsAndDividends();
}
diff --git a/app/Exports/BackupExport.php b/app/Exports/BackupExport.php
index e2e8242..d4dcb23 100644
--- a/app/Exports/BackupExport.php
+++ b/app/Exports/BackupExport.php
@@ -14,18 +14,14 @@ class BackupExport implements WithMultipleSheets
public function __construct(
public bool $empty = false
- )
- { }
+ ) {}
- /**
- * @return array
- */
public function sheets(): array
{
- return [
- new PortfoliosSheet($this->empty),
- new TransactionsSheet($this->empty),
- new DailyChangesSheet($this->empty)
- ];
+ return [
+ new PortfoliosSheet($this->empty),
+ new TransactionsSheet($this->empty),
+ new DailyChangesSheet($this->empty),
+ ];
}
}
diff --git a/app/Exports/Sheets/DailyChangesSheet.php b/app/Exports/Sheets/DailyChangesSheet.php
index dd05530..9e6868c 100644
--- a/app/Exports/Sheets/DailyChangesSheet.php
+++ b/app/Exports/Sheets/DailyChangesSheet.php
@@ -11,7 +11,7 @@ class DailyChangesSheet implements FromCollection, WithHeadings, WithTitle
{
public function __construct(
public bool $empty = false
- ) { }
+ ) {}
public function headings(): array
{
@@ -23,21 +23,18 @@ class DailyChangesSheet implements FromCollection, WithHeadings, WithTitle
'Total Gain',
'Total Dividends Earned',
'Realized Gains',
- 'Annotation'
+ 'Annotation',
];
}
/**
- * @return \Illuminate\Support\Collection
- */
+ * @return \Illuminate\Support\Collection
+ */
public function collection()
{
return $this->empty ? collect() : DailyChange::myDailyChanges()->get();
}
- /**
- * @return string
- */
public function title(): string
{
return 'Daily Changes';
diff --git a/app/Exports/Sheets/PortfoliosSheet.php b/app/Exports/Sheets/PortfoliosSheet.php
index f2bd293..b9ece9a 100644
--- a/app/Exports/Sheets/PortfoliosSheet.php
+++ b/app/Exports/Sheets/PortfoliosSheet.php
@@ -11,8 +11,8 @@ class PortfoliosSheet implements FromCollection, WithHeadings, WithTitle
{
public function __construct(
public bool $empty = false
- ) { }
-
+ ) {}
+
public function headings(): array
{
return [
@@ -21,21 +21,18 @@ class PortfoliosSheet implements FromCollection, WithHeadings, WithTitle
'Notes',
'Wishlist',
'Created',
- 'Updated'
+ 'Updated',
];
}
/**
- * @return \Illuminate\Support\Collection
- */
+ * @return \Illuminate\Support\Collection
+ */
public function collection()
{
return $this->empty ? collect() : Portfolio::myPortfolios()->get();
}
- /**
- * @return string
- */
public function title(): string
{
return 'Portfolios';
diff --git a/app/Exports/Sheets/TransactionsSheet.php b/app/Exports/Sheets/TransactionsSheet.php
index 9abd820..e135bce 100644
--- a/app/Exports/Sheets/TransactionsSheet.php
+++ b/app/Exports/Sheets/TransactionsSheet.php
@@ -3,15 +3,15 @@
namespace App\Exports\Sheets;
use App\Models\Transaction;
-use Maatwebsite\Excel\Concerns\WithTitle;
-use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\FromCollection;
+use Maatwebsite\Excel\Concerns\WithHeadings;
+use Maatwebsite\Excel\Concerns\WithTitle;
class TransactionsSheet implements FromCollection, WithHeadings, WithTitle
{
public function __construct(
public bool $empty = false
- ) { }
+ ) {}
public function headings(): array
{
@@ -27,21 +27,18 @@ class TransactionsSheet implements FromCollection, WithHeadings, WithTitle
'Reinvested Dividend',
'Date',
'Created',
- 'Updated'
+ 'Updated',
];
}
/**
- * @return \Illuminate\Support\Collection
- */
+ * @return \Illuminate\Support\Collection
+ */
public function collection()
{
return $this->empty ? collect() : Transaction::myTransactions()->get();
}
- /**
- * @return string
- */
public function title(): string
{
return 'Transactions';
diff --git a/app/Http/ApiControllers/Controller.php b/app/Http/ApiControllers/Controller.php
index bc0e11d..01da444 100644
--- a/app/Http/ApiControllers/Controller.php
+++ b/app/Http/ApiControllers/Controller.php
@@ -1,8 +1,8 @@
validated());
-
+
return PortfolioResource::make($portfolio);
}
@@ -55,4 +55,4 @@ class PortfolioController extends ApiController
return response()->noContent();
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/ApiControllers/TransactionController.php b/app/Http/ApiControllers/TransactionController.php
index 64924bb..5c24eba 100644
--- a/app/Http/ApiControllers/TransactionController.php
+++ b/app/Http/ApiControllers/TransactionController.php
@@ -2,13 +2,12 @@
namespace App\Http\ApiControllers;
-use App\Models\Transaction;
-use Illuminate\Http\Request;
-use Illuminate\Support\Facades\Gate;
-use HackerEsq\FilterModels\FilterModels;
+use App\Http\ApiControllers\Controller as ApiController;
use App\Http\Requests\TransactionRequest;
use App\Http\Resources\TransactionResource;
-use App\Http\ApiControllers\Controller as ApiController;
+use App\Models\Transaction;
+use HackerEsq\FilterModels\FilterModels;
+use Illuminate\Support\Facades\Gate;
class TransactionController extends ApiController
{
@@ -23,11 +22,11 @@ class TransactionController extends ApiController
}
public function store(TransactionRequest $request)
- {
+ {
Gate::authorize('fullAccess', $request->portfolio);
$transaction = Transaction::create($request->validated());
-
+
return TransactionResource::make($transaction);
}
@@ -55,4 +54,4 @@ class TransactionController extends ApiController
return response()->noContent();
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/ApiControllers/UserController.php b/app/Http/ApiControllers/UserController.php
index 468ca52..4472e42 100644
--- a/app/Http/ApiControllers/UserController.php
+++ b/app/Http/ApiControllers/UserController.php
@@ -2,9 +2,9 @@
namespace App\Http\ApiControllers;
-use Illuminate\Http\Request;
-use App\Http\Resources\UserResource;
use App\Http\ApiControllers\Controller as ApiController;
+use App\Http\Resources\UserResource;
+use Illuminate\Http\Request;
class UserController extends ApiController
{
@@ -12,4 +12,4 @@ class UserController extends ApiController
{
return UserResource::make($request->user());
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/ConnectedAccountController.php b/app/Http/Controllers/ConnectedAccountController.php
index 0551b89..d242e0d 100644
--- a/app/Http/Controllers/ConnectedAccountController.php
+++ b/app/Http/Controllers/ConnectedAccountController.php
@@ -2,21 +2,19 @@
namespace App\Http\Controllers;
-use Exception;
-use App\Models\User;
use App\Models\ConnectedAccount;
-use Illuminate\Support\MessageBag;
+use App\Models\User;
+use App\Notifications\VerifyConnectedAccountNotification;
+use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Blade;
+use Illuminate\Support\MessageBag;
use Laravel\Socialite\Facades\Socialite;
-use App\Notifications\VerifyConnectedAccountNotification;
class ConnectedAccountController extends Controller
{
-
/**
* Redirect the user to the GitHub authentication page.
- *
*/
public function redirectToProvider(string $provider)
{
@@ -27,7 +25,6 @@ class ConnectedAccountController extends Controller
/**
* Obtain the user information from GitHub.
- *
*/
public function handleProviderCallback(string $provider)
{
@@ -44,21 +41,21 @@ class ConnectedAccountController extends Controller
}
// check if this account is already linked
- $connected_account = ConnectedAccount::firstOrNew([
+ $connected_account = ConnectedAccount::firstOrNew([
'provider' => $provider,
- 'provider_id' => $providerUser->id
+ 'provider_id' => $providerUser->id,
], [
'token' => $providerUser->token,
'secret' => $providerUser->tokenSecret,
'refresh_token' => $providerUser->refreshToken,
'expires_at' => $providerUser->expiresIn,
- 'verified_at' => false
+ 'verified_at' => false,
]);
// already linked and verified, let's go login!
if (
- $connected_account->exists
- && !is_null($connected_account->verified_at)
+ $connected_account->exists
+ && ! is_null($connected_account->verified_at)
) {
Auth::login($connected_account->user, true);
@@ -67,20 +64,20 @@ class ConnectedAccountController extends Controller
}
// new user, let's create one
- if (!$user = User::where('email', $providerUser->email)->first()) {
+ if (! $user = User::where('email', $providerUser->email)->first()) {
$user = User::create([
'name' => $providerUser->name,
'email' => $providerUser->email,
- 'email_verified_at' => now()
+ 'email_verified_at' => now(),
]);
-
+
$connected_account->user_id = $user->id;
$connected_account->verified_at = now();
$connected_account->save();
-
+
Auth::login($user, true);
-
+
return redirect(route('dashboard'));
}
@@ -91,23 +88,23 @@ class ConnectedAccountController extends Controller
$user->notify(new VerifyConnectedAccountNotification($connected_account->id));
return redirect(route('login'))
- ->with('status', __(
- 'Account already exists. Check your email to connect your :provider account.',
- ['provider' => config("services.$provider.name")]
- ));
+ ->with('status', __(
+ 'Account already exists. Check your email to connect your :provider account.',
+ ['provider' => config("services.$provider.name")]
+ ));
}
protected function validateProvider($provider): void
{
- if (!in_array($provider, explode(',', config('services.enabled_login_providers')))) {
-
+ if (! in_array($provider, explode(',', config('services.enabled_login_providers')))) {
+
throw new Exception('Please provide a valid social provider.');
}
}
public function verify(ConnectedAccount $connected_account)
{
- if (!$connected_account->verified_at) {
+ if (! $connected_account->verified_at) {
// mark request as verified
$connected_account->verified_at = now();
@@ -127,8 +124,8 @@ class ConnectedAccountController extends Controller
'css' => 'alert-success',
'icon' => Blade::render(""),
'position' => 'toast-top toast-end',
- 'timeout' => '5000'
- ]
+ 'timeout' => '5000',
+ ],
]));
}
}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
index 71116b2..8677cd5 100644
--- a/app/Http/Controllers/Controller.php
+++ b/app/Http/Controllers/Controller.php
@@ -1,8 +1,8 @@
remember(
- 'dashboard-metrics-' . $user->id,
- 10,
+ 'dashboard-metrics-'.$user->id,
+ 10,
function () {
return
Holding::query()
- ->myHoldings()
- ->withoutWishlists()
- ->withPortfolioMetrics()
- ->first();
+ ->myHoldings()
+ ->withoutWishlists()
+ ->withPortfolioMetrics()
+ ->first();
}
);
diff --git a/app/Http/Controllers/HoldingController.php b/app/Http/Controllers/HoldingController.php
index 95762d5..8a59399 100644
--- a/app/Http/Controllers/HoldingController.php
+++ b/app/Http/Controllers/HoldingController.php
@@ -8,21 +8,20 @@ use Illuminate\Http\Request;
class HoldingController extends Controller
{
-
/**
* Display the specified resource.
*/
- public function show(Request $request, Portfolio $portfolio, String $symbol)
+ public function show(Request $request, Portfolio $portfolio, string $symbol)
{
$holding = Holding::with([
- 'market_data',
- 'transactions' => function ($query) use ($symbol) {
- $query->where('transactions.symbol', $symbol);
- }
- ])
- ->symbol($symbol)
- ->portfolio($portfolio->id)
- ->firstOrFail();
+ 'market_data',
+ 'transactions' => function ($query) use ($symbol) {
+ $query->where('transactions.symbol', $symbol);
+ },
+ ])
+ ->symbol($symbol)
+ ->portfolio($portfolio->id)
+ ->firstOrFail();
$formattedTransactions = $holding->getFormattedTransactions();
diff --git a/app/Http/Controllers/InvitedOnboardingController.php b/app/Http/Controllers/InvitedOnboardingController.php
index f28aead..6a8386a 100644
--- a/app/Http/Controllers/InvitedOnboardingController.php
+++ b/app/Http/Controllers/InvitedOnboardingController.php
@@ -2,21 +2,19 @@
namespace App\Http\Controllers;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
use Illuminate\Http\Request;
class InvitedOnboardingController extends Controller
{
-
/**
* Check if the invited user needs a password?
- *
*/
public function __invoke(Request $request, Portfolio $portfolio, User $user)
{
- if (!$request->hasValidSignature()) {
+ if (! $request->hasValidSignature()) {
abort(401, 'Invalid signature');
}
@@ -26,7 +24,7 @@ class InvitedOnboardingController extends Controller
// route to create password form
return view('auth.invited-onboarding', [
'portfolio' => $portfolio,
- 'user' => $user
+ 'user' => $user,
]);
}
diff --git a/app/Http/Controllers/PortfolioController.php b/app/Http/Controllers/PortfolioController.php
index efa9980..24ddb80 100644
--- a/app/Http/Controllers/PortfolioController.php
+++ b/app/Http/Controllers/PortfolioController.php
@@ -9,7 +9,6 @@ use Illuminate\Support\Facades\Gate;
class PortfolioController extends Controller
{
-
/**
* Show the form for creating a new resource.
*/
@@ -26,21 +25,21 @@ class PortfolioController extends Controller
Gate::authorize('readOnly', $portfolio);
$portfolio->load(['transactions', 'holdings']);
-
+
// get portfolio metrics
$metrics = cache()->remember(
- 'portfolio-metrics-' . $portfolio->id,
- 60,
+ 'portfolio-metrics-'.$portfolio->id,
+ 60,
function () use ($portfolio) {
return Holding::query()
- ->portfolio($portfolio->id)
- ->withPortfolioMetrics()
- ->first();
+ ->portfolio($portfolio->id)
+ ->withPortfolioMetrics()
+ ->first();
}
);
$formattedHoldings = $portfolio->getFormattedHoldings();
-
+
return view('portfolio.show', compact(['portfolio', 'metrics', 'formattedHoldings']));
}
}
diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php
index 900544e..def59a0 100644
--- a/app/Http/Controllers/TransactionController.php
+++ b/app/Http/Controllers/TransactionController.php
@@ -4,7 +4,6 @@ namespace App\Http\Controllers;
class TransactionController extends Controller
{
-
/**
* Display the specified resource.
*/
diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php
index 8a8539d..4ebdf94 100644
--- a/app/Http/Middleware/SetLocale.php
+++ b/app/Http/Middleware/SetLocale.php
@@ -14,7 +14,7 @@ class SetLocale
*/
public function handle(Request $request, Closure $next)
{
- if (!session()->has('locale')) {
+ if (! session()->has('locale')) {
session()->put('locale', $request->getPreferredLanguage(
config('app.available_locales')
));
@@ -24,4 +24,4 @@ class SetLocale
return $next($request);
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Requests/FormRequest.php b/app/Http/Requests/FormRequest.php
index d0f8334..87eb516 100644
--- a/app/Http/Requests/FormRequest.php
+++ b/app/Http/Requests/FormRequest.php
@@ -6,9 +6,8 @@ use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extends BaseFormRequest
{
-
public function requestOrModelValue($key, $model): mixed
{
return $this->request->get($key) ?? $this->{$model}?->{$key};
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/app/Http/Requests/HoldingRequest.php b/app/Http/Requests/HoldingRequest.php
index a75d82d..c34b3c2 100644
--- a/app/Http/Requests/HoldingRequest.php
+++ b/app/Http/Requests/HoldingRequest.php
@@ -2,11 +2,8 @@
namespace App\Http\Requests;
-use App\Http\Requests\FormRequest;
-
class HoldingRequest extends FormRequest
{
-
/**
* Get the validation rules that apply to the request.
*
@@ -16,7 +13,7 @@ class HoldingRequest extends FormRequest
{
$rules = [
- 'reinvest_dividends' => ['sometimes', 'boolean']
+ 'reinvest_dividends' => ['sometimes', 'boolean'],
];
return $rules;
diff --git a/app/Http/Requests/PortfolioRequest.php b/app/Http/Requests/PortfolioRequest.php
index ab94472..57252f4 100644
--- a/app/Http/Requests/PortfolioRequest.php
+++ b/app/Http/Requests/PortfolioRequest.php
@@ -2,11 +2,8 @@
namespace App\Http\Requests;
-use App\Http\Requests\FormRequest;
-
class PortfolioRequest extends FormRequest
{
-
/**
* Get the validation rules that apply to the request.
*
@@ -21,9 +18,9 @@ class PortfolioRequest extends FormRequest
'wishlist' => ['sometimes', 'nullable', 'boolean'],
];
- if (!is_null($this->portfolio)) {
+ if (! is_null($this->portfolio)) {
$rules['title'][0] = 'sometimes';
- }
+ }
return $rules;
}
diff --git a/app/Http/Requests/TransactionRequest.php b/app/Http/Requests/TransactionRequest.php
index 01907f1..3943ab9 100644
--- a/app/Http/Requests/TransactionRequest.php
+++ b/app/Http/Requests/TransactionRequest.php
@@ -3,18 +3,16 @@
namespace App\Http\Requests;
use App\Models\Portfolio;
-use App\Http\Requests\FormRequest;
-use App\Rules\SymbolValidationRule;
use App\Rules\QuantityValidationRule;
+use App\Rules\SymbolValidationRule;
class TransactionRequest extends FormRequest
{
-
protected function prepareForValidation(): void
{
$this->merge([
- 'portfolio' => Portfolio::find($this->requestOrModelValue('portfolio_id', 'transaction'))
+ 'portfolio' => Portfolio::find($this->requestOrModelValue('portfolio_id', 'transaction')),
]);
}
@@ -25,28 +23,28 @@ class TransactionRequest extends FormRequest
*/
public function rules(): array
{
-
+
$rules = [
'portfolio_id' => ['required', 'exists:portfolios,id'],
'symbol' => ['required', 'string', new SymbolValidationRule],
'transaction_type' => ['required', 'string', 'in:BUY,SELL'],
- 'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:' . now()->format('Y-m-d')],
+ 'date' => ['required', 'date_format:Y-m-d', 'before_or_equal:'.now()->format('Y-m-d')],
'quantity' => [
- 'required',
- 'numeric',
- 'min:0',
+ 'required',
+ 'numeric',
+ 'min:0',
new QuantityValidationRule(
$this->input('portfolio'),
$this->requestOrModelValue('symbol', 'transaction'),
$this->requestOrModelValue('transaction_type', 'transaction'),
$this->requestOrModelValue('date', 'transaction')
- )
+ ),
],
'cost_basis' => ['exclude_if:transaction_type,SELL', 'min:0', 'numeric'],
'sale_price' => ['exclude_if:transaction_type,BUY', 'min:0', 'numeric'],
];
- if (!is_null($this->transaction)) {
+ if (! is_null($this->transaction)) {
$rules['portfolio_id'][0] = 'sometimes';
$rules['symbol'][0] = 'sometimes';
$rules['transaction_type'][0] = 'sometimes';
@@ -64,7 +62,7 @@ class TransactionRequest extends FormRequest
) {
$rules['cost_basis'][0] = 'required';
}
- }
+ }
return $rules;
}
diff --git a/app/Http/Resources/HoldingResource.php b/app/Http/Resources/HoldingResource.php
index 57cd293..a11b0cc 100644
--- a/app/Http/Resources/HoldingResource.php
+++ b/app/Http/Resources/HoldingResource.php
@@ -31,7 +31,7 @@ class HoldingResource extends JsonResource
'market_gain_dollars' => $this->market_gain_dollars,
'market_gain_percent' => $this->market_gain_percent,
'created_at' => $this->created_at,
- 'updated_at' => $this->updated_at
+ 'updated_at' => $this->updated_at,
];
}
}
diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php
index 41eeff5..4896aa5 100644
--- a/app/Http/Resources/UserResource.php
+++ b/app/Http/Resources/UserResource.php
@@ -8,7 +8,7 @@ use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
-{
+{
/**
* Transform the resource into an array.
*
diff --git a/app/Imports/BackupImport.php b/app/Imports/BackupImport.php
index aae4f7d..50e1a88 100644
--- a/app/Imports/BackupImport.php
+++ b/app/Imports/BackupImport.php
@@ -2,39 +2,35 @@
namespace App\Imports;
-use App\Models\User;
-use App\Imports\Sheets\PortfoliosSheet;
-use Illuminate\Support\Facades\Artisan;
+use App\Console\Commands\RefreshDividendData;
+use App\Console\Commands\RefreshMarketData;
use App\Console\Commands\SyncDailyChange;
use App\Console\Commands\SyncHoldingData;
use App\Imports\Sheets\DailyChangesSheet;
+use App\Imports\Sheets\PortfoliosSheet;
use App\Imports\Sheets\TransactionsSheet;
-use Maatwebsite\Excel\Events\AfterImport;
+use App\Models\BackupImport as BackupImportModel;
+use App\Models\User;
+use Illuminate\Support\Facades\Artisan;
use Maatwebsite\Excel\Concerns\Importable;
use Maatwebsite\Excel\Concerns\WithEvents;
+use Maatwebsite\Excel\Concerns\WithMultipleSheets;
+use Maatwebsite\Excel\Events\AfterImport;
use Maatwebsite\Excel\Events\BeforeImport;
use Maatwebsite\Excel\Events\ImportFailed;
-use App\Console\Commands\RefreshMarketData;
-use App\Console\Commands\RefreshDividendData;
-use App\Models\BackupImport as BackupImportModel;
-use Maatwebsite\Excel\Concerns\WithMultipleSheets;
-class BackupImport implements WithMultipleSheets, WithEvents
+class BackupImport implements WithEvents, WithMultipleSheets
{
-
use Importable;
public function __construct(
public BackupImportModel $backupImportModel
- ) { }
+ ) {}
- /**
- * @return array
- */
public function registerEvents(): array
{
return [
- BeforeImport::class => fn() => $this->backupImportModel->update([
+ BeforeImport::class => fn () => $this->backupImportModel->update([
'status' => 'in_progress',
'message' => __('Import is in progress...'),
]),
@@ -43,24 +39,24 @@ class BackupImport implements WithMultipleSheets, WithEvents
$this->backupImportModel->update([
'status' => 'success',
'message' => 'Import completed successfully!',
- 'completed_at' => now()
+ 'completed_at' => now(),
]);
-
+
Artisan::queue(RefreshMarketData::class, ['--user' => $this->backupImportModel->user_id, '--force' => true])
->chain([
- fn() => Artisan::call(RefreshDividendData::class, ['--user' => $this->backupImportModel->user_id, '--force' => true]),
- fn() => Artisan::call(SyncHoldingData::class, ['--user' => $this->backupImportModel->user_id]),
- fn() => User::find($this->backupImportModel->user_id)->portfolios->each(function($portfolio) {
+ fn () => Artisan::call(RefreshDividendData::class, ['--user' => $this->backupImportModel->user_id, '--force' => true]),
+ fn () => Artisan::call(SyncHoldingData::class, ['--user' => $this->backupImportModel->user_id]),
+ fn () => User::find($this->backupImportModel->user_id)->portfolios->each(function ($portfolio) {
Artisan::queue(SyncDailyChange::class, ['portfolio_id' => $portfolio->id]);
- })
+ }),
]);
},
- ImportFailed::class => fn(ImportFailed $event) => $this->backupImportModel->update([
+ ImportFailed::class => fn (ImportFailed $event) => $this->backupImportModel->update([
'status' => 'failed',
- 'message' => 'Error: '. substr($event->getException()->getMessage(), 0, 220),
+ 'message' => 'Error: '.substr($event->getException()->getMessage(), 0, 220),
'has_errors' => true,
- 'completed_at' => now()
+ 'completed_at' => now(),
]),
];
}
diff --git a/app/Imports/Sheets/DailyChangesSheet.php b/app/Imports/Sheets/DailyChangesSheet.php
index d03e382..298719a 100644
--- a/app/Imports/Sheets/DailyChangesSheet.php
+++ b/app/Imports/Sheets/DailyChangesSheet.php
@@ -3,42 +3,39 @@
namespace App\Imports\Sheets;
use App\Imports\ValidatesPortfolioAccess;
-use App\Models\DailyChange;
use App\Models\BackupImport;
+use App\Models\DailyChange;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
-use Maatwebsite\Excel\Events\BeforeSheet;
-use Maatwebsite\Excel\Concerns\WithEvents;
-use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
+use Maatwebsite\Excel\Concerns\ToCollection;
+use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
+use Maatwebsite\Excel\Events\BeforeSheet;
-class DailyChangesSheet implements ToCollection, WithHeadingRow, WithValidation, SkipsEmptyRows, WithEvents
+class DailyChangesSheet implements SkipsEmptyRows, ToCollection, WithEvents, WithHeadingRow, WithValidation
{
use ValidatesPortfolioAccess;
public function __construct(
public BackupImport $backupImport
- ) { }
+ ) {}
- /**
- * @return array
- */
public function registerEvents(): array
{
return [
- BeforeSheet::class => function(BeforeSheet $event) {
+ BeforeSheet::class => function (BeforeSheet $event) {
DB::commit();
$this->backupImport->update([
'message' => __('Importing daily changes...'),
]);
DB::beginTransaction();
- }
+ },
];
}
-
+
public function collection(Collection $dailyChanges)
{
$dailyChanges->chunk($this->batchSize())->each(function ($chunk) {
@@ -56,7 +53,7 @@ class DailyChangesSheet implements ToCollection, WithHeadingRow, WithValidation,
'realized_gains' => $dailyChange['realized_gains'],
'annotation' => $dailyChange['annotation'],
'portfolio_id' => $dailyChange['portfolio_id'],
- 'date' => Carbon::parse($dailyChange['date'])->format('Y-m-d')
+ 'date' => Carbon::parse($dailyChange['date'])->format('Y-m-d'),
];
});
@@ -71,7 +68,7 @@ class DailyChangesSheet implements ToCollection, WithHeadingRow, WithValidation,
'realized_gains',
'annotation',
'portfolio_id',
- 'date'
+ 'date',
]
);
});
@@ -85,7 +82,7 @@ class DailyChangesSheet implements ToCollection, WithHeadingRow, WithValidation,
public function rules(): array
{
return [
- 'portfolio_id' => ['required', 'uuid'],
+ 'portfolio_id' => ['required', 'uuid'],
'date' => ['required', 'date'],
'total_market_value' => ['sometimes', 'nullable', 'numeric'],
'total_cost_basis' => ['sometimes', 'nullable', 'min:0', 'numeric'],
diff --git a/app/Imports/Sheets/PortfoliosSheet.php b/app/Imports/Sheets/PortfoliosSheet.php
index 551e940..9e7dc8e 100644
--- a/app/Imports/Sheets/PortfoliosSheet.php
+++ b/app/Imports/Sheets/PortfoliosSheet.php
@@ -2,36 +2,33 @@
namespace App\Imports\Sheets;
-use App\Models\Portfolio;
use App\Models\BackupImport;
+use App\Models\Portfolio;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
-use Maatwebsite\Excel\Events\BeforeSheet;
-use Maatwebsite\Excel\Concerns\WithEvents;
-use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
+use Maatwebsite\Excel\Concerns\ToCollection;
+use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
+use Maatwebsite\Excel\Events\BeforeSheet;
-class PortfoliosSheet implements ToCollection, WithValidation, WithHeadingRow, SkipsEmptyRows, WithEvents
+class PortfoliosSheet implements SkipsEmptyRows, ToCollection, WithEvents, WithHeadingRow, WithValidation
{
public function __construct(
public BackupImport $backupImport
- ) { }
-
- /**
- * @return array
- */
+ ) {}
+
public function registerEvents(): array
{
return [
- BeforeSheet::class => function(BeforeSheet $event) {
+ BeforeSheet::class => function (BeforeSheet $event) {
DB::commit();
$this->backupImport->update([
'message' => __('Importing portfolios...'),
]);
DB::beginTransaction();
- }
+ },
];
}
@@ -42,7 +39,7 @@ class PortfoliosSheet implements ToCollection, WithValidation, WithHeadingRow, S
Portfolio::unguard(); // ensures we can set an owner for the portfolio
$portfolio = Portfolio::fullAccess($this->backupImport->user_id)->updateOrCreate([
- 'id' => $portfolio['portfolio_id']
+ 'id' => $portfolio['portfolio_id'],
], [
'id' => $portfolio['portfolio_id'] ?? null,
'title' => $portfolio['title'],
diff --git a/app/Imports/Sheets/TransactionsSheet.php b/app/Imports/Sheets/TransactionsSheet.php
index 544257d..5599e9e 100644
--- a/app/Imports/Sheets/TransactionsSheet.php
+++ b/app/Imports/Sheets/TransactionsSheet.php
@@ -3,42 +3,38 @@
namespace App\Imports\Sheets;
use App\Imports\ValidatesPortfolioAccess;
+use App\Models\BackupImport;
use App\Models\Holding;
use App\Models\Transaction;
-use Illuminate\Support\Str;
-use App\Models\BackupImport;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
-use Maatwebsite\Excel\Events\BeforeSheet;
-use Maatwebsite\Excel\Concerns\WithEvents;
-use Maatwebsite\Excel\Concerns\ToCollection;
+use Illuminate\Support\Str;
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
+use Maatwebsite\Excel\Concerns\ToCollection;
+use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithValidation;
+use Maatwebsite\Excel\Events\BeforeSheet;
-class TransactionsSheet implements ToCollection, WithHeadingRow, WithValidation, SkipsEmptyRows, WithEvents
+class TransactionsSheet implements SkipsEmptyRows, ToCollection, WithEvents, WithHeadingRow, WithValidation
{
-
use ValidatesPortfolioAccess;
public function __construct(
public BackupImport $backupImport
- ) { }
+ ) {}
- /**
- * @return array
- */
public function registerEvents(): array
{
return [
- BeforeSheet::class => function(BeforeSheet $event) {
+ BeforeSheet::class => function (BeforeSheet $event) {
DB::commit();
$this->backupImport->update([
'message' => __('Importing transactions...'),
]);
DB::beginTransaction();
- }
+ },
];
}
@@ -62,7 +58,7 @@ class TransactionsSheet implements ToCollection, WithHeadingRow, WithValidation,
'sale_price' => $transaction['sale_price'],
'split' => boolval($transaction['split']) ? 1 : 0,
'reinvested_dividend' => boolval($transaction['reinvested_dividend']) ? 1 : 0,
- 'date' => Carbon::parse($transaction['date'])->format('Y-m-d')
+ 'date' => Carbon::parse($transaction['date'])->format('Y-m-d'),
];
});
@@ -79,23 +75,23 @@ class TransactionsSheet implements ToCollection, WithHeadingRow, WithValidation,
'sale_price',
'split',
'reinvested_dividend',
- 'date'
+ 'date',
]
);
// stub out related holdings
- $chunk->unique(fn($item) => $item['symbol'] . $item['portfolio_id'])
- ->each(function($holding) {
-
- Holding::firstOrCreate([
- 'symbol' => $holding['symbol'],
- 'portfolio_id' => $holding['portfolio_id']
- ], [
- 'quantity' => 0,
- 'average_cost_basis' => 0,
- 'splits_synced_at' => now(),
- ]);
- });
+ $chunk->unique(fn ($item) => $item['symbol'].$item['portfolio_id'])
+ ->each(function ($holding) {
+
+ Holding::firstOrCreate([
+ 'symbol' => $holding['symbol'],
+ 'portfolio_id' => $holding['portfolio_id'],
+ ], [
+ 'quantity' => 0,
+ 'average_cost_basis' => 0,
+ 'splits_synced_at' => now(),
+ ]);
+ });
});
}
diff --git a/app/Imports/ValidatesPortfolioAccess.php b/app/Imports/ValidatesPortfolioAccess.php
index 74a6185..a684114 100644
--- a/app/Imports/ValidatesPortfolioAccess.php
+++ b/app/Imports/ValidatesPortfolioAccess.php
@@ -6,19 +6,18 @@ use App\Models\Portfolio;
trait ValidatesPortfolioAccess
{
-
public function validatePortfolioAccess($collection)
{
$uniquePortfolios = $collection->unique('portfolio_id')->pluck('portfolio_id');
$countPortfoliosWithAccess = Portfolio::fullAccess($this->backupImport->user_id)
- ->whereIn('id', $uniquePortfolios)
- ->count();
+ ->whereIn('id', $uniquePortfolios)
+ ->count();
if (
$countPortfoliosWithAccess < $uniquePortfolios->count()
) {
- throw new \Exception(__("You do not have access to that portfolio."));
+ throw new \Exception(__('You do not have access to that portfolio.'));
}
}
}
diff --git a/app/Interfaces/MarketData/AlphaVantageMarketData.php b/app/Interfaces/MarketData/AlphaVantageMarketData.php
index 31391e5..ab8da8d 100644
--- a/app/Interfaces/MarketData/AlphaVantageMarketData.php
+++ b/app/Interfaces/MarketData/AlphaVantageMarketData.php
@@ -2,33 +2,35 @@
namespace App\Interfaces\MarketData;
+use App\Interfaces\MarketData\Types\Dividend;
+use App\Interfaces\MarketData\Types\Ohlc;
+use App\Interfaces\MarketData\Types\Quote;
+use App\Interfaces\MarketData\Types\Split;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
-use App\Interfaces\MarketData\Types\Quote;
-use App\Interfaces\MarketData\Types\Split;
-use App\Interfaces\MarketData\Types\Dividend;
-use App\Interfaces\MarketData\Types\Ohlc;
use Tschucki\Alphavantage\Facades\Alphavantage;
class AlphaVantageMarketData implements MarketDataInterface
{
- public function exists(String $symbol): Bool
+ public function exists(string $symbol): bool
{
return $this->quote($symbol)->isNotEmpty();
}
- public function quote(String $symbol): Quote
+ public function quote(string $symbol): Quote
{
$quote = Alphavantage::core()->quoteEndpoint($symbol);
$quote = Arr::get($quote, 'Global Quote', []);
- if (empty($quote)) return new Quote();
+ if (empty($quote)) {
+ return new Quote;
+ }
$fundamental = cache()->remember(
- 'av-symbol-'.$symbol,
- 1440,
+ 'av-symbol-'.$symbol,
+ 1440,
function () use ($symbol) {
return Alphavantage::fundamentals()->overview($symbol);
}
@@ -49,71 +51,71 @@ class AlphaVantageMarketData implements MarketDataInterface
: null,
'dividend_yield' => Arr::get($fundamental, 'DividendYield') != 'None'
? Arr::get($fundamental, 'DividendYield')
- : null
- ]);
+ : null,
+ ]);
}
- public function dividends(String $symbol, $startDate, $endDate): Collection
+ public function dividends(string $symbol, $startDate, $endDate): Collection
{
$dividends = Alphavantage::fundamentals()->dividends($symbol);
$dividends = Arr::get($dividends, 'data', []);
return collect($dividends)
- ->filter(function($dividend) use ($startDate, $endDate) {
-
- return Carbon::parse(Arr::get($dividend, 'ex_dividend_date'))->between($startDate, $endDate);
- })
- ->map(function($dividend) use ($symbol) {
-
- return new Dividend([
- 'symbol' => $symbol,
- 'date' => Carbon::parse(Arr::get($dividend, 'ex_dividend_date')),
- 'dividend_amount' => Arr::get($dividend, 'amount'),
- ]);
- });
+ ->filter(function ($dividend) use ($startDate, $endDate) {
+
+ return Carbon::parse(Arr::get($dividend, 'ex_dividend_date'))->between($startDate, $endDate);
+ })
+ ->map(function ($dividend) use ($symbol) {
+
+ return new Dividend([
+ 'symbol' => $symbol,
+ 'date' => Carbon::parse(Arr::get($dividend, 'ex_dividend_date')),
+ 'dividend_amount' => Arr::get($dividend, 'amount'),
+ ]);
+ });
}
- public function splits(String $symbol, $startDate, $endDate): Collection
- {
+ public function splits(string $symbol, $startDate, $endDate): Collection
+ {
$splits = Alphavantage::fundamentals()->splits($symbol);
$splits = Arr::get($splits, 'data', []);
return collect($splits)
- ->filter(function($split) use ($startDate, $endDate) {
-
- return Carbon::parse(Arr::get($split, 'effective_date'))->between($startDate, $endDate);
- })
- ->map(function($split) use ($symbol) {
-
- return new Split([
- 'symbol' => $symbol,
- 'date' => Carbon::parse(Arr::get($split, 'effective_date')),
- 'split_amount' => Arr::get($split, 'split_factor'),
- ]);
- });
+ ->filter(function ($split) use ($startDate, $endDate) {
+
+ return Carbon::parse(Arr::get($split, 'effective_date'))->between($startDate, $endDate);
+ })
+ ->map(function ($split) use ($symbol) {
+
+ return new Split([
+ 'symbol' => $symbol,
+ 'date' => Carbon::parse(Arr::get($split, 'effective_date')),
+ 'split_amount' => Arr::get($split, 'split_factor'),
+ ]);
+ });
}
- public function history(String $symbol, $startDate, $endDate): Collection
+ public function history(string $symbol, $startDate, $endDate): Collection
{
$history = Alphavantage::timeSeries()->daily($symbol, 'full');
$history = Arr::get($history, 'Time Series (Daily)', []);
-
+
return collect($history)
- ->filter(function ($history, $date) use ($startDate, $endDate) {
+ ->filter(function ($history, $date) use ($startDate, $endDate) {
- return Carbon::parse($date)->between($startDate, $endDate);
- })
- ->mapWithKeys(function($history, $date) use ($symbol) {
+ return Carbon::parse($date)->between($startDate, $endDate);
+ })
+ ->mapWithKeys(function ($history, $date) use ($symbol) {
- $date = Carbon::parse($date)->format('Y-m-d');
-
- return [ $date => new Ohlc([
- 'symbol' => $symbol,
- 'date' => $date,
- 'close' => Arr::get($history, '4. close')
- ]) ];
- });
+ $date = Carbon::parse($date)->format('Y-m-d');
+
+ return [$date => new Ohlc([
+ 'symbol' => $symbol,
+ 'date' => $date,
+ 'close' => Arr::get($history, '4. close'),
+ ])];
+ });
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/FakeMarketData.php b/app/Interfaces/MarketData/FakeMarketData.php
index dfbdb27..5f32550 100644
--- a/app/Interfaces/MarketData/FakeMarketData.php
+++ b/app/Interfaces/MarketData/FakeMarketData.php
@@ -2,22 +2,22 @@
namespace App\Interfaces\MarketData;
-use Illuminate\Support\Carbon;
-use Illuminate\Support\Collection;
-use App\Interfaces\MarketData\Types\Quote;
use App\Interfaces\MarketData\Types\Dividend;
use App\Interfaces\MarketData\Types\Ohlc;
+use App\Interfaces\MarketData\Types\Quote;
use App\Interfaces\MarketData\Types\Split;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Collection;
class FakeMarketData implements MarketDataInterface
{
- public function exists(String $symbol): Bool
+ public function exists(string $symbol): bool
{
return true;
}
- public function quote(String $symbol): Quote
+ public function quote(string $symbol): Quote
{
return new Quote([
@@ -31,11 +31,11 @@ class FakeMarketData implements MarketDataInterface
'market_cap' => 9800700600,
'book_value' => 4.7,
'last_dividend_date' => now()->subDays(45),
- 'dividend_yield' => 0.033
+ 'dividend_yield' => 0.033,
]);
}
- public function dividends(String $symbol, $startDate, $endDate): Collection
+ public function dividends(string $symbol, $startDate, $endDate): Collection
{
return collect([
@@ -57,23 +57,23 @@ class FakeMarketData implements MarketDataInterface
]);
}
- public function splits(String $symbol, $startDate, $endDate): Collection
- {
+ public function splits(string $symbol, $startDate, $endDate): Collection
+ {
return collect([
new Split([
'symbol' => $symbol,
'date' => now()->subMonths(36),
'split_amount' => 10,
- ])
+ ]),
]);
}
- public function history(String $symbol, $startDate, $endDate): Collection
+ public function history(string $symbol, $startDate, $endDate): Collection
{
$numDays = Carbon::parse($startDate)->diffInDays($endDate, true);
- for ($i = 0; $i < $numDays; $i++) {
+ for ($i = 0; $i < $numDays; $i++) {
$date = now()->subDays($i)->format('Y-m-d');
@@ -83,7 +83,7 @@ class FakeMarketData implements MarketDataInterface
'close' => rand(150, 400),
]);
}
-
+
return collect($series);
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/FallbackInterface.php b/app/Interfaces/MarketData/FallbackInterface.php
index 8deb605..6a1bad5 100644
--- a/app/Interfaces/MarketData/FallbackInterface.php
+++ b/app/Interfaces/MarketData/FallbackInterface.php
@@ -6,21 +6,20 @@ use Illuminate\Support\Facades\Log;
class FallbackInterface
{
-
protected string $latest_error;
public function __call($method, $arguments)
{
$providers = explode(',', config('investbrain.provider', 'yahoo'));
-
+
foreach ($providers as $provider) {
$provider = trim($provider);
try {
- if (!in_array($provider, array_keys(config('investbrain.interfaces', [])))) {
+ if (! in_array($provider, array_keys(config('investbrain.interfaces', [])))) {
throw new \Exception("Provider [{$provider}] is not a valid market data interface.");
}
@@ -30,7 +29,7 @@ class FallbackInterface
return app()->make($provider_class_name)->$method(...$arguments);
} catch (\Throwable $e) {
-
+
$this->latest_error = $e->getMessage();
Log::warning("Failed calling method {$method} ({$provider}): {$this->latest_error}");
diff --git a/app/Interfaces/MarketData/FinnhubMarketData.php b/app/Interfaces/MarketData/FinnhubMarketData.php
index d55e966..6dd28df 100644
--- a/app/Interfaces/MarketData/FinnhubMarketData.php
+++ b/app/Interfaces/MarketData/FinnhubMarketData.php
@@ -2,13 +2,13 @@
namespace App\Interfaces\MarketData;
-use Illuminate\Support\Arr;
-use Illuminate\Support\Carbon;
-use Illuminate\Support\Collection;
+use App\Interfaces\MarketData\Types\Dividend;
use App\Interfaces\MarketData\Types\Ohlc;
use App\Interfaces\MarketData\Types\Quote;
use App\Interfaces\MarketData\Types\Split;
-use App\Interfaces\MarketData\Types\Dividend;
+use Illuminate\Support\Arr;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Collection;
class FinnhubMarketData implements MarketDataInterface
{
@@ -16,13 +16,14 @@ class FinnhubMarketData implements MarketDataInterface
public function __construct()
{
-
+
$this->client = new \Finnhub\Api\DefaultApi(
- new \GuzzleHttp\Client(),
+ new \GuzzleHttp\Client,
\Finnhub\Configuration::getDefaultConfiguration()->setApiKey('token', config('finnhub.key'))
);
}
- public function exists(String $symbol): Bool
+
+ public function exists(string $symbol): bool
{
return $this->quote($symbol)->isNotEmpty();
@@ -32,20 +33,22 @@ class FinnhubMarketData implements MarketDataInterface
{
$quote = $this->client->quote($symbol);
- if (empty($quote)) return new Quote();
-
+ if (empty($quote)) {
+ return new Quote;
+ }
+
$fundamental = cache()->remember(
- 'fh-symbol-'.$symbol,
- 1440,
+ 'fh-symbol-'.$symbol,
+ 1440,
function () use ($symbol) {
- return $this->client->companyBasicFinancials($symbol, "all");
+ return $this->client->companyBasicFinancials($symbol, 'all');
}
);
-
+
return new Quote([
'name' => Arr::get($fundamental, 'metric.name'),
'symbol' => $symbol,
- 'market_value' => Arr::get($quote, 'c'),
+ 'market_value' => Arr::get($quote, 'c'),
'fifty_two_week_high' => Arr::get($fundamental, 'metric.52WeekHigh'),
'fifty_two_week_low' => Arr::get($fundamental, 'metric.52WeekLow'),
'forward_pe' => Arr::get($fundamental, 'metric.forwardPE'), // confirm
@@ -54,15 +57,15 @@ class FinnhubMarketData implements MarketDataInterface
'book_value' => Arr::get($fundamental, 'metric.bookValuePerShare'), // confirm
'last_dividend_date' => Arr::get($fundamental, 'metric.lastDivDate'), // confirm
'dividend_yield' => Arr::get($fundamental, 'metric.dividendYield'), // confirm
- ]);
+ ]);
}
public function dividends($symbol, $startDate, $endDate): Collection
{
$dividends = $this->client->stockDividends($symbol, $startDate->format('Y-m-d'), $endDate->format('Y-m-d'));
-
- return collect($dividends)->map(function($dividend) use ($symbol) {
-
+
+ return collect($dividends)->map(function ($dividend) use ($symbol) {
+
return new Dividend([
'symbol' => $symbol,
'date' => Carbon::parse(Arr::get($dividend, 'date')),
@@ -72,12 +75,12 @@ class FinnhubMarketData implements MarketDataInterface
}
public function splits($symbol, $startDate, $endDate): Collection
- {
+ {
$splits = $this->client->stockSplits($symbol, $startDate->format('Y-m-d'), $endDate->format('Y-m-d'));
- return collect($splits)->map(function($split) use ($symbol) {
-
+ return collect($splits)->map(function ($split) use ($symbol) {
+
return new Split([
'symbol' => $symbol,
'date' => Carbon::parse(Arr::get($split, 'date')),
@@ -89,18 +92,19 @@ class FinnhubMarketData implements MarketDataInterface
public function history($symbol, $startDate, $endDate): Collection
{
- $history = $this->client->stockCandles($symbol, "D", $startDate->timestamp, $endDate->timestamp);
+ $history = $this->client->stockCandles($symbol, 'D', $startDate->timestamp, $endDate->timestamp);
$timestamps = Arr::get($history, 't', []);
$closes = Arr::get($history, 'c', []);
return collect($timestamps)->mapWithKeys(function ($timestamp, $index) use ($symbol, $closes) {
$date = Carbon::createFromTimestamp($timestamp)->format('Y-m-d');
- return [ $date => new Ohlc([
+
+ return [$date => new Ohlc([
'symbol' => $symbol,
'date' => $date,
'close' => $closes[$index],
- ]) ];
+ ])];
});
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/MarketDataInterface.php b/app/Interfaces/MarketData/MarketDataInterface.php
index 20cf8c6..4dcfaab 100644
--- a/app/Interfaces/MarketData/MarketDataInterface.php
+++ b/app/Interfaces/MarketData/MarketDataInterface.php
@@ -2,59 +2,33 @@
namespace App\Interfaces\MarketData;
-use Illuminate\Support\Collection;
use App\Interfaces\MarketData\Types\Quote;
+use Illuminate\Support\Collection;
interface MarketDataInterface
{
/**
* Does this symbol actually exist?
- *
- * @param String $symbol
- *
- * @return Bool
*/
- public function exists(String $symbol): Bool;
+ public function exists(string $symbol): bool;
/**
* Get quote data
- *
- * @param String $symbol
- *
- * @return Quote
*/
- public function quote(String $symbol): Quote;
+ public function quote(string $symbol): Quote;
/**
* Get dividend data
- *
- * @param String $symbol
- * @param \DateTimeInterface $startDate
- * @param \DateTimeInterface $endDate
- *
- * @return Collection
*/
- public function dividends(String $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
+ public function dividends(string $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
/**
* Get split data
- *
- * @param String $symbol
- * @param \DateTimeInterface $startDate
- * @param \DateTimeInterface $endDate
- *
- * @return Collection
*/
- public function splits(String $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
+ public function splits(string $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
/**
* Get historical close data
- *
- * @param String $symbol
- * @param \DateTimeInterface $startDate
- * @param \DateTimeInterface $endDate
- *
- * @return Collection
*/
- public function history(String $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
+ public function history(string $symbol, \DateTimeInterface $startDate, \DateTimeInterface $endDate): Collection;
}
diff --git a/app/Interfaces/MarketData/Types/Dividend.php b/app/Interfaces/MarketData/Types/Dividend.php
index eb0d61f..19c928e 100644
--- a/app/Interfaces/MarketData/Types/Dividend.php
+++ b/app/Interfaces/MarketData/Types/Dividend.php
@@ -4,13 +4,13 @@ namespace App\Interfaces\MarketData\Types;
use DateTime;
use Illuminate\Support\Carbon;
-use App\Interfaces\MarketData\Types\MarketDataType;
class Dividend extends MarketDataType
{
public function setSymbol(string $symbol): self
{
$this->items['symbol'] = $symbol;
+
return $this;
}
@@ -22,6 +22,7 @@ class Dividend extends MarketDataType
public function setDividendAmount($dividendAmount): self
{
$this->items['dividend_amount'] = (float) $dividendAmount;
+
return $this;
}
@@ -30,9 +31,10 @@ class Dividend extends MarketDataType
return $this->items['dividend_amount'] ?? 0.0;
}
- public function setDate(String|DateTime $date): self
+ public function setDate(string|DateTime $date): self
{
$this->items['date'] = Carbon::parse($date)->format('Y-m-d H:i:s');
+
return $this;
}
@@ -40,4 +42,4 @@ class Dividend extends MarketDataType
{
return $this->items['date'] ?? null;
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/Types/MarketDataType.php b/app/Interfaces/MarketData/Types/MarketDataType.php
index 1b01ac5..9b0ffef 100644
--- a/app/Interfaces/MarketData/Types/MarketDataType.php
+++ b/app/Interfaces/MarketData/Types/MarketDataType.php
@@ -2,18 +2,15 @@
namespace App\Interfaces\MarketData\Types;
-use Illuminate\Support\Str;
use Illuminate\Support\Collection;
+use Illuminate\Support\Str;
class MarketDataType extends Collection
{
- /**
- *
- */
public function __construct($items = [])
{
- foreach($this->getArrayableItems($items) as $key => $value) {
+ foreach ($this->getArrayableItems($items) as $key => $value) {
$this->{$key} = $value;
}
@@ -33,4 +30,4 @@ class MarketDataType extends Collection
{
return $this->items[$key] ?? null;
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/Types/Ohlc.php b/app/Interfaces/MarketData/Types/Ohlc.php
index 2f7bf09..b96ad4a 100644
--- a/app/Interfaces/MarketData/Types/Ohlc.php
+++ b/app/Interfaces/MarketData/Types/Ohlc.php
@@ -4,13 +4,13 @@ namespace App\Interfaces\MarketData\Types;
use DateTime;
use Illuminate\Support\Carbon;
-use App\Interfaces\MarketData\Types\MarketDataType;
class Ohlc extends MarketDataType
{
public function setSymbol(string $symbol): self
{
$this->items['symbol'] = $symbol;
+
return $this;
}
@@ -22,6 +22,7 @@ class Ohlc extends MarketDataType
public function setOpen($open): self
{
$this->items['open'] = (float) $open;
+
return $this;
}
@@ -33,6 +34,7 @@ class Ohlc extends MarketDataType
public function setHigh($high): self
{
$this->items['high'] = (float) $high;
+
return $this;
}
@@ -44,6 +46,7 @@ class Ohlc extends MarketDataType
public function setLow($low): self
{
$this->items['low'] = (float) $low;
+
return $this;
}
@@ -55,6 +58,7 @@ class Ohlc extends MarketDataType
public function setClose($close): self
{
$this->items['close'] = (float) $close;
+
return $this;
}
@@ -63,9 +67,10 @@ class Ohlc extends MarketDataType
return $this->items['close'] ?? 0.0;
}
- public function setDate(String|DateTime $date): self
+ public function setDate(string|DateTime $date): self
{
$this->items['date'] = Carbon::parse($date)->format('Y-m-d H:i:s');
+
return $this;
}
@@ -73,4 +78,4 @@ class Ohlc extends MarketDataType
{
return $this->items['date'] ?? null;
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/Types/Quote.php b/app/Interfaces/MarketData/Types/Quote.php
index 24109ca..0fcbb23 100644
--- a/app/Interfaces/MarketData/Types/Quote.php
+++ b/app/Interfaces/MarketData/Types/Quote.php
@@ -4,13 +4,13 @@ namespace App\Interfaces\MarketData\Types;
use DateTime;
use Illuminate\Support\Carbon;
-use App\Interfaces\MarketData\Types\MarketDataType;
class Quote extends MarketDataType
-{
+{
public function setName($name): self
{
$this->items['name'] = (string) $name;
+
return $this;
}
@@ -22,6 +22,7 @@ class Quote extends MarketDataType
public function setSymbol($symbol): self
{
$this->items['symbol'] = (string) $symbol;
+
return $this;
}
@@ -30,9 +31,10 @@ class Quote extends MarketDataType
return $this->items['symbol'] ?? '';
}
- public function setMarketValue($marketValue): self
+ public function setMarketValue($marketValue): self
{
$this->items['market_value'] = (float) $marketValue;
+
return $this;
}
@@ -41,9 +43,10 @@ class Quote extends MarketDataType
return $this->items['market_value'] ?? 0.0;
}
- public function setFiftyTwoWeekHigh($high): self
+ public function setFiftyTwoWeekHigh($high): self
{
$this->items['fifty_two_week_high'] = (float) $high;
+
return $this;
}
@@ -52,9 +55,10 @@ class Quote extends MarketDataType
return $this->items['fifty_two_week_high'] ?? 0.0;
}
- public function setFiftyTwoWeekLow($low): self
+ public function setFiftyTwoWeekLow($low): self
{
$this->items['fifty_two_week_low'] = (float) $low;
+
return $this;
}
@@ -63,9 +67,10 @@ class Quote extends MarketDataType
return $this->items['fifty_two_week_low'] ?? 0.0;
}
- public function setForwardPE($pe): self
+ public function setForwardPE($pe): self
{
$this->items['forward_pe'] = (float) $pe;
+
return $this;
}
@@ -74,9 +79,10 @@ class Quote extends MarketDataType
return $this->items['forward_pe'] ?? 0.0;
}
- public function setTrailingPE($pe): self
+ public function setTrailingPE($pe): self
{
$this->items['trailing_pe'] = (float) $pe;
+
return $this;
}
@@ -88,6 +94,7 @@ class Quote extends MarketDataType
public function setMarketCap($cap): self
{
$this->items['market_cap'] = (int) $cap;
+
return $this;
}
@@ -96,9 +103,10 @@ class Quote extends MarketDataType
return $this->items['market_cap'] ?? 0;
}
- public function setBookValue($value): self
+ public function setBookValue($value): self
{
$this->items['book_value'] = (float) $value;
+
return $this;
}
@@ -110,6 +118,7 @@ class Quote extends MarketDataType
public function setLastDividendDate(mixed $date): self
{
$this->items['last_dividend_date'] = is_null($date) ? null : Carbon::parse($date)->format('Y-m-d H:i:s');
+
return $this;
}
@@ -118,9 +127,10 @@ class Quote extends MarketDataType
return $this->items['last_dividend_date'] ?? null;
}
- public function setDividendYield($yield): self
+ public function setDividendYield($yield): self
{
$this->items['dividend_yield'] = (float) $yield;
+
return $this;
}
@@ -128,4 +138,4 @@ class Quote extends MarketDataType
{
return $this->items['dividend_yield'] ?? 0.0;
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/Types/Split.php b/app/Interfaces/MarketData/Types/Split.php
index 748bac2..595c811 100644
--- a/app/Interfaces/MarketData/Types/Split.php
+++ b/app/Interfaces/MarketData/Types/Split.php
@@ -4,13 +4,13 @@ namespace App\Interfaces\MarketData\Types;
use DateTime;
use Illuminate\Support\Carbon;
-use App\Interfaces\MarketData\Types\MarketDataType;
class Split extends MarketDataType
{
public function setSymbol(string $symbol): self
{
$this->items['symbol'] = $symbol;
+
return $this;
}
@@ -22,6 +22,7 @@ class Split extends MarketDataType
public function setSplitAmount($splitAmount): self
{
$this->items['split_amount'] = (float) $splitAmount;
+
return $this;
}
@@ -30,9 +31,10 @@ class Split extends MarketDataType
return $this->items['split_amount'] ?? 0.0;
}
- public function setDate(String|DateTime $date): self
+ public function setDate(string|DateTime $date): self
{
$this->items['date'] = Carbon::parse($date)->format('Y-m-d H:i:s');
+
return $this;
}
@@ -40,4 +42,4 @@ class Split extends MarketDataType
{
return $this->items['date'] ?? null;
}
-}
\ No newline at end of file
+}
diff --git a/app/Interfaces/MarketData/YahooMarketData.php b/app/Interfaces/MarketData/YahooMarketData.php
index 1c7f77a..6bc31bd 100644
--- a/app/Interfaces/MarketData/YahooMarketData.php
+++ b/app/Interfaces/MarketData/YahooMarketData.php
@@ -2,36 +2,39 @@
namespace App\Interfaces\MarketData;
-use Illuminate\Support\Collection;
-use Scheb\YahooFinanceApi\ApiClient;
+use App\Interfaces\MarketData\Types\Dividend;
use App\Interfaces\MarketData\Types\Ohlc;
use App\Interfaces\MarketData\Types\Quote;
use App\Interfaces\MarketData\Types\Split;
-use App\Interfaces\MarketData\Types\Dividend;
+use Illuminate\Support\Collection;
+use Scheb\YahooFinanceApi\ApiClient;
use Scheb\YahooFinanceApi\ApiClientFactory as YahooFinance;
class YahooMarketData implements MarketDataInterface
{
public ApiClient $client;
- public function __construct() {
+ public function __construct()
+ {
// create yahoo finance client factory
$this->client = YahooFinance::createApiClient();
}
- public function exists(String $symbol): Bool
+ public function exists(string $symbol): bool
{
return $this->quote($symbol)->isNotEmpty();
}
- public function quote(String $symbol): Quote
+ public function quote(string $symbol): Quote
{
$quote = $this->client->getQuote($symbol);
- if (empty($quote)) return collect();
+ if (empty($quote)) {
+ return collect();
+ }
return new Quote([
'name' => $quote->getLongName() ?? $quote->getShortName(),
@@ -44,52 +47,52 @@ class YahooMarketData implements MarketDataInterface
'market_cap' => $quote->getMarketCap(),
'book_value' => $quote->getBookValue(),
'last_dividend_date' => $quote->getDividendDate(),
- 'dividend_yield' => $quote->getTrailingAnnualDividendYield() * 100
+ 'dividend_yield' => $quote->getTrailingAnnualDividendYield() * 100,
]);
}
- public function dividends(String $symbol, $startDate, $endDate): Collection
+ public function dividends(string $symbol, $startDate, $endDate): Collection
{
return collect($this->client->getHistoricalDividendData($symbol, $startDate, $endDate))
- ->map(function($dividend) use ($symbol) {
-
- return new Dividend([
- 'symbol' => $symbol,
- 'date' => $dividend->getDate(),
- 'dividend_amount' => $dividend->getDividends(),
- ]);
- });
+ ->map(function ($dividend) use ($symbol) {
+
+ return new Dividend([
+ 'symbol' => $symbol,
+ 'date' => $dividend->getDate(),
+ 'dividend_amount' => $dividend->getDividends(),
+ ]);
+ });
}
- public function splits(String $symbol, $startDate, $endDate): Collection
- {
+ public function splits(string $symbol, $startDate, $endDate): Collection
+ {
return collect($this->client->getHistoricalSplitData($symbol, $startDate, $endDate))
- ->map(function($split) use ($symbol) {
- $split_amount = explode(':', $split->getStockSplits());
+ ->map(function ($split) use ($symbol) {
+ $split_amount = explode(':', $split->getStockSplits());
- return new Split([
- 'symbol' => $symbol,
- 'date' => $split->getDate(),
- 'split_amount' => $split_amount[0] / $split_amount[1],
- ]);
- });
+ return new Split([
+ 'symbol' => $symbol,
+ 'date' => $split->getDate(),
+ 'split_amount' => $split_amount[0] / $split_amount[1],
+ ]);
+ });
}
- public function history(String $symbol, $startDate, $endDate): Collection
+ public function history(string $symbol, $startDate, $endDate): Collection
{
return collect($this->client->getHistoricalQuoteData($symbol, ApiClient::INTERVAL_1_DAY, $startDate, $endDate))
- ->mapWithKeys(function($history) use ($symbol) {
+ ->mapWithKeys(function ($history) use ($symbol) {
$date = $history->getDate()->format('Y-m-d');
- return [ $date => new Ohlc([
- 'symbol' => $symbol,
- 'date' => $date,
- 'close' => $history->getClose(),
- ]) ];
+ return [$date => new Ohlc([
+ 'symbol' => $symbol,
+ 'date' => $date,
+ 'close' => $history->getClose(),
+ ])];
});
}
-}
\ No newline at end of file
+}
diff --git a/app/Jobs/BackupImportJob.php b/app/Jobs/BackupImportJob.php
index 5d536da..e3c7c44 100644
--- a/app/Jobs/BackupImportJob.php
+++ b/app/Jobs/BackupImportJob.php
@@ -2,15 +2,15 @@
namespace App\Jobs;
-use Throwable;
-use App\Models\User;
-use App\Models\BackupImport;
-use Maatwebsite\Excel\Facades\Excel;
-use Illuminate\Foundation\Queue\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use App\Notifications\ImportSucceededNotification;
-use App\Notifications\ImportFailedNotification;
use App\Imports\BackupImport as BackupImportExcel;
+use App\Models\BackupImport;
+use App\Models\User;
+use App\Notifications\ImportFailedNotification;
+use App\Notifications\ImportSucceededNotification;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Queue\Queueable;
+use Maatwebsite\Excel\Facades\Excel;
+use Throwable;
class BackupImportJob implements ShouldQueue
{
@@ -19,7 +19,7 @@ class BackupImportJob implements ShouldQueue
/**
* The number of times the job may be attempted.
*/
- public $tries = 1;
+ public $tries = 1;
/**
* The number of seconds the job can run before timing out.
@@ -42,7 +42,7 @@ class BackupImportJob implements ShouldQueue
*/
public function __construct(
public BackupImport $backupImport
- ) {
+ ) {
$this->user = User::find($this->backupImport->user_id);
}
@@ -50,7 +50,7 @@ class BackupImportJob implements ShouldQueue
* Execute the job.
*/
public function handle(): void
- {
+ {
Excel::import(new BackupImportExcel($this->backupImport), $this->backupImport->path, config('livewire.temporary_file_upload.disk', null));
$this->user->notify(new ImportSucceededNotification);
@@ -63,9 +63,9 @@ class BackupImportJob implements ShouldQueue
{
$this->backupImport->update([
'status' => 'failed',
- 'message' => 'Error: '. substr($e->getMessage(), 0, 220),
+ 'message' => 'Error: '.substr($e->getMessage(), 0, 220),
'has_errors' => true,
- 'completed_at' => now()
+ 'completed_at' => now(),
]);
$this->user->notify(new ImportFailedNotification($e->getMessage()));
diff --git a/app/Models/AiChat.php b/app/Models/AiChat.php
index 3b4e386..c8e1c5b 100644
--- a/app/Models/AiChat.php
+++ b/app/Models/AiChat.php
@@ -2,8 +2,8 @@
namespace App\Models;
-use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
+use Illuminate\Database\Eloquent\Model;
class AiChat extends Model
{
@@ -11,7 +11,7 @@ class AiChat extends Model
protected $fillable = [
'role',
- 'content'
+ 'content',
];
protected $hidden = [];
@@ -26,7 +26,8 @@ class AiChat extends Model
});
}
- public function user() {
+ public function user()
+ {
return $this->belongsTo(User::class);
}
diff --git a/app/Models/BackupImport.php b/app/Models/BackupImport.php
index 1ddd7fe..a747880 100644
--- a/app/Models/BackupImport.php
+++ b/app/Models/BackupImport.php
@@ -2,11 +2,9 @@
namespace App\Models;
-use Maatwebsite\Excel\Facades\Excel;
-use Illuminate\Database\Eloquent\Model;
-use App\Imports\BackupImport as BackupImportExcel;
use App\Jobs\BackupImportJob;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
+use Illuminate\Database\Eloquent\Model;
class BackupImport extends Model
{
@@ -20,7 +18,7 @@ class BackupImport extends Model
'status', // pending, in_progress, success, failed
'message', // Import starting, Import is in progress, Importing portfolios, Importing transactions, Importing daily changes, Import completed successfully
'has_errors',
- 'completed_at'
+ 'completed_at',
];
protected static function boot()
@@ -32,9 +30,9 @@ class BackupImport extends Model
$import->status = 'pending';
$import->message = __('Import starting...');
});
-
+
static::created(function ($import) {
-
+
BackupImportJob::dispatch($import);
});
}
@@ -47,7 +45,7 @@ class BackupImport extends Model
{
return [
'has_errors' => 'boolean',
- 'completed_at' => 'datetime'
+ 'completed_at' => 'datetime',
];
}
}
diff --git a/app/Models/ConnectedAccount.php b/app/Models/ConnectedAccount.php
index 372544f..e3911a4 100644
--- a/app/Models/ConnectedAccount.php
+++ b/app/Models/ConnectedAccount.php
@@ -29,7 +29,7 @@ class ConnectedAccount extends Model
];
protected $with = [
- 'user'
+ 'user',
];
/**
@@ -52,4 +52,4 @@ class ConnectedAccount extends Model
{
return $this->belongsTo(User::class);
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/DailyChange.php b/app/Models/DailyChange.php
index 59df828..21ad0ef 100644
--- a/app/Models/DailyChange.php
+++ b/app/Models/DailyChange.php
@@ -3,12 +3,12 @@
namespace App\Models;
use App\Traits\HasCompositePrimaryKey;
-use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
class DailyChange extends Model
{
- use HasFactory, HasCompositePrimaryKey;
+ use HasCompositePrimaryKey, HasFactory;
public $timestamps = false;
@@ -32,13 +32,13 @@ class DailyChange extends Model
protected $casts = [
'date' => 'datetime',
];
-
+
public function scopePortfolio($query, $portfolio)
{
return $query->where('portfolio_id', $portfolio);
}
- public function scopeMyDailyChanges()
+ public function scopeMyDailyChanges()
{
return $this->whereHas('portfolio', function ($query) {
$query->whereHas('users', function ($query) {
@@ -47,12 +47,13 @@ class DailyChange extends Model
});
}
- public function scopeWithoutWishlists($query) {
+ public function scopeWithoutWishlists($query)
+ {
return $query->whereHas('portfolio', function ($query) {
$query->where('portfolios.wishlist', 0);
});
}
-
+
public function portfolio()
{
return $this->belongsTo(Portfolio::class);
diff --git a/app/Models/Dividend.php b/app/Models/Dividend.php
index b53b7d9..7755c9c 100644
--- a/app/Models/Dividend.php
+++ b/app/Models/Dividend.php
@@ -2,15 +2,12 @@
namespace App\Models;
-use App\Models\Holding;
-use App\Models\MarketData;
-use App\Models\Transaction;
-use Illuminate\Support\Str;
-use Illuminate\Support\Carbon;
-use Illuminate\Database\Eloquent\Model;
use App\Interfaces\MarketData\MarketDataInterface;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Str;
class Dividend extends Model
{
@@ -30,15 +27,18 @@ class Dividend extends Model
'last_dividend_update' => 'datetime',
];
- public function marketData() {
+ public function marketData()
+ {
return $this->belongsTo(MarketData::class, 'symbol', 'symbol');
}
- public function holdings() {
+ public function holdings()
+ {
return $this->hasMany(Holding::class, 'symbol', 'symbol');
}
- public function transactions() {
+ public function transactions()
+ {
return $this->hasMany(Transaction::class, 'symbol', 'symbol');
}
@@ -49,7 +49,6 @@ class Dividend extends Model
/**
* Grab new dividend data
- *
*/
public static function refreshDividendData(string $symbol): void
{
@@ -64,11 +63,11 @@ class Dividend extends Model
$end_date = now();
// nope, refresh forward looking only
- if ( $dividends_meta->total_dividends ) {
-
+ if ($dividends_meta->total_dividends) {
+
$start_date = $dividends_meta->last_dividend_update->addHours(24);
}
-
+
// skip refresh if there's already recent data
if ($start_date->greaterThan($end_date)) {
@@ -83,7 +82,7 @@ class Dividend extends Model
// ah, we found some dividends...
if ($dividend_data->isNotEmpty()) {
// create mass insert
- foreach ($dividend_data as $index => $dividend){
+ foreach ($dividend_data as $index => $dividend) {
$dividend_data[$index] = [...$dividend, ...['id' => Str::uuid()->toString(), 'updated_at' => now(), 'created_at' => now()]];
}
@@ -109,7 +108,7 @@ class Dividend extends Model
{
// group by holdings
$dividends = self::select(['holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount'])
- ->selectRaw('
+ ->selectRaw('
(COALESCE(CASE WHEN transactions.transaction_type = "BUY"
AND date(transactions.date) <= date(dividends.date)
THEN transactions.quantity ELSE 0 END, 0)
@@ -119,22 +118,22 @@ class Dividend extends Model
* dividends.dividend_amount
AS total_received
')
- ->join('transactions', 'transactions.symbol', '=', 'dividends.symbol')
- ->join('holdings', 'transactions.portfolio_id', '=', 'holdings.portfolio_id')
- ->where('dividends.symbol', $symbol)
- ->groupBy('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount', 'total_received')
- ->havingRaw('total_received > 0')
- ->get();
+ ->join('transactions', 'transactions.symbol', '=', 'dividends.symbol')
+ ->join('holdings', 'transactions.portfolio_id', '=', 'holdings.portfolio_id')
+ ->where('dividends.symbol', $symbol)
+ ->groupBy('holdings.portfolio_id', 'dividends.date', 'dividends.symbol', 'dividends.dividend_amount', 'total_received')
+ ->havingRaw('total_received > 0')
+ ->get();
- // iterate through holdings and update
+ // iterate through holdings and update
Holding::where(['symbol' => $symbol])
- ->get()
- ->each(function ($holding) use ($dividends) {
- $holding->update([
- 'dividends_earned' => $dividends->where('portfolio_id', $holding->portfolio_id)
- ->sum('total_received')
- ]);
- });
+ ->get()
+ ->each(function ($holding) use ($dividends) {
+ $holding->update([
+ 'dividends_earned' => $dividends->where('portfolio_id', $holding->portfolio_id)
+ ->sum('total_received'),
+ ]);
+ });
}
public static function reinvestDividends(iterable $dividend_data, MarketData $market_data): void
@@ -144,21 +143,21 @@ class Dividend extends Model
'symbol' => $market_data->symbol,
'reinvest_dividends' => true,
])
- ->get()
- ->each(function($holding) use ($dividend_data, $market_data) {
+ ->get()
+ ->each(function ($holding) use ($dividend_data, $market_data) {
- foreach($dividend_data as $dividend) {
+ foreach ($dividend_data as $dividend) {
- Transaction::create([
- 'date' => $dividend['date'],
- 'portfolio_id' => $holding->portfolio_id,
- 'symbol' => $holding->symbol,
- 'transaction_type' => "BUY",
- 'reinvested_dividend' => true,
- 'cost_basis' => 0,
- 'quantity' => ($dividend['dividend_amount'] * $holding->qtyOwned(Carbon::parse($dividend['date']))) / $market_data->market_value,
- ]);
- }
- });
+ Transaction::create([
+ 'date' => $dividend['date'],
+ 'portfolio_id' => $holding->portfolio_id,
+ 'symbol' => $holding->symbol,
+ 'transaction_type' => 'BUY',
+ 'reinvested_dividend' => true,
+ 'cost_basis' => 0,
+ 'quantity' => ($dividend['dividend_amount'] * $holding->qtyOwned(Carbon::parse($dividend['date']))) / $market_data->market_value,
+ ]);
+ }
+ });
}
}
diff --git a/app/Models/Holding.php b/app/Models/Holding.php
index 6cb4d43..122042d 100644
--- a/app/Models/Holding.php
+++ b/app/Models/Holding.php
@@ -2,16 +2,10 @@
namespace App\Models;
-use App\Models\Split;
-use App\Models\AiChat;
-use App\Models\Dividend;
-use App\Models\Portfolio;
-use App\Models\MarketData;
-use App\Models\Transaction;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
class Holding extends Model
{
@@ -27,13 +21,13 @@ class Holding extends Model
'realized_gain_dollars',
'dividends_earned',
'splits_synced_at',
- 'reinvest_dividends'
+ 'reinvest_dividends',
];
protected $casts = [
'splits_synced_at' => 'datetime',
'first_transaction_date' => 'datetime',
- 'reinvest_dividends' => 'boolean'
+ 'reinvest_dividends' => 'boolean',
];
/**
@@ -41,7 +35,7 @@ class Holding extends Model
*
* @return void
*/
- public function market_data()
+ public function market_data()
{
return $this->hasOne(MarketData::class, 'symbol', 'symbol');
}
@@ -51,7 +45,7 @@ class Holding extends Model
*
* @return void
*/
- public function transactions()
+ public function transactions()
{
return $this->hasManyThrough(Transaction::class, Portfolio::class, 'id', 'portfolio_id', 'portfolio_id', 'id')->orderBy('date', 'DESC');
}
@@ -61,11 +55,11 @@ class Holding extends Model
*
* @return void
*/
- public function dividends()
+ public function dividends()
{
return $this->hasMany(Dividend::class, 'symbol', 'symbol')
- ->select(['dividends.symbol','dividends.date','dividends.dividend_amount'])
- ->selectRaw("SUM(
+ ->select(['dividends.symbol', 'dividends.date', 'dividends.dividend_amount'])
+ ->selectRaw("SUM(
CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = '$this->portfolio_id'
@@ -73,7 +67,7 @@ class Holding extends Model
THEN transactions.quantity
ELSE 0 END
) AS purchased")
- ->selectRaw("SUM(
+ ->selectRaw("SUM(
CASE WHEN transaction_type = 'SELL'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = '$this->portfolio_id'
@@ -81,7 +75,7 @@ class Holding extends Model
THEN transactions.quantity
ELSE 0 END
) AS sold")
- ->selectRaw("SUM(
+ ->selectRaw("SUM(
(CASE WHEN transaction_type = 'BUY'
AND transactions.symbol = dividends.symbol
AND transactions.portfolio_id = '$this->portfolio_id'
@@ -94,16 +88,16 @@ class Holding extends Model
THEN transactions.quantity ELSE 0 END)
* dividends.dividend_amount
) AS total_received")
- ->join('transactions', 'transactions.symbol', 'dividends.symbol')
- ->groupBy(['dividends.symbol','dividends.date','dividends.dividend_amount'])
- ->orderBy('dividends.date', 'DESC')
- ->where('dividends.date', '>=', function ($query) {
- $query->selectRaw('min(transactions.date)')
- ->from('transactions')
- ->whereRaw("transactions.portfolio_id = '$this->portfolio_id'")
- ->whereRaw("transactions.symbol = '$this->symbol'");
- })
- ->having('total_received', '>', 0);
+ ->join('transactions', 'transactions.symbol', 'dividends.symbol')
+ ->groupBy(['dividends.symbol', 'dividends.date', 'dividends.dividend_amount'])
+ ->orderBy('dividends.date', 'DESC')
+ ->where('dividends.date', '>=', function ($query) {
+ $query->selectRaw('min(transactions.date)')
+ ->from('transactions')
+ ->whereRaw("transactions.portfolio_id = '$this->portfolio_id'")
+ ->whereRaw("transactions.symbol = '$this->symbol'");
+ })
+ ->having('total_received', '>', 0);
}
/**
@@ -111,7 +105,7 @@ class Holding extends Model
*
* @return void
*/
- public function portfolio()
+ public function portfolio()
{
return $this->belongsTo(Portfolio::class);
}
@@ -121,7 +115,7 @@ class Holding extends Model
*
* @return void
*/
- public function splits()
+ public function splits()
{
return $this->hasMany(Split::class, 'symbol', 'symbol')
->orderBy('date', 'DESC');
@@ -140,11 +134,11 @@ class Holding extends Model
public function scopeWithMarketData($query)
{
return $query->withAggregate('market_data', 'name')
- ->withAggregate('market_data', 'market_value')
- ->withAggregate('market_data', 'fifty_two_week_low')
- ->withAggregate('market_data', 'fifty_two_week_high')
- ->withAggregate('market_data', 'updated_at')
- ->join('market_data', 'holdings.symbol', 'market_data.symbol');
+ ->withAggregate('market_data', 'market_value')
+ ->withAggregate('market_data', 'fifty_two_week_low')
+ ->withAggregate('market_data', 'fifty_two_week_high')
+ ->withAggregate('market_data', 'updated_at')
+ ->join('market_data', 'holdings.symbol', 'market_data.symbol');
}
public function scopeWithPerformance($query)
@@ -164,49 +158,50 @@ class Holding extends Model
return $query->where('holdings.symbol', $symbol);
}
- public function scopeWithoutWishlists($query) {
+ public function scopeWithoutWishlists($query)
+ {
return $query->whereHas('portfolio', function ($query) {
- $query->where('portfolios.wishlist', 0);
- });
+ $query->where('portfolios.wishlist', 0);
+ });
}
public function scopeMyHoldings($query, $userId = null)
{
- return $query->whereHas('portfolio', function($query) use ($userId) {
+ return $query->whereHas('portfolio', function ($query) use ($userId) {
$query->whereRelation('users', 'id', $userId ?? auth()->user()->id);
});
}
- public function scopeWithPortfolioMetrics($query)
+ public function scopeWithPortfolioMetrics($query)
{
return $query->selectRaw('COALESCE(SUM(holdings.dividends_earned), 0) AS total_dividends_earned')
- ->selectRaw('COALESCE(SUM(holdings.realized_gain_dollars), 0) AS realized_gain_dollars')
- ->selectRaw('COALESCE(SUM(holdings.quantity * market_data.market_value), 0) AS total_market_value')
- ->selectRaw('COALESCE(SUM(holdings.total_cost_basis), 0) AS total_cost_basis')
- ->selectRaw('COALESCE(SUM(holdings.quantity * market_data.market_value), 0) - COALESCE(SUM(holdings.total_cost_basis), 0) AS total_gain_dollars')
+ ->selectRaw('COALESCE(SUM(holdings.realized_gain_dollars), 0) AS realized_gain_dollars')
+ ->selectRaw('COALESCE(SUM(holdings.quantity * market_data.market_value), 0) AS total_market_value')
+ ->selectRaw('COALESCE(SUM(holdings.total_cost_basis), 0) AS total_cost_basis')
+ ->selectRaw('COALESCE(SUM(holdings.quantity * market_data.market_value), 0) - COALESCE(SUM(holdings.total_cost_basis), 0) AS total_gain_dollars')
// ->selectRaw('COALESCE((@total_gain_dollars / @sum_total_cost_basis) * 100,0) AS total_gain_percent')
- ->join('market_data', 'market_data.symbol', '=', 'holdings.symbol');
+ ->join('market_data', 'market_data.symbol', '=', 'holdings.symbol');
}
public function syncTransactionsAndDividends()
{
// pull existing transaction data
$query = Transaction::where([
- 'portfolio_id' => $this->portfolio_id,
- 'symbol' => $this->symbol,
- ])->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) AS `qty_purchases`')
- ->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS `qty_sales`')
- ->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN (quantity * cost_basis) ELSE 0 END) AS `total_cost_basis`')
- ->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN (quantity * sale_price) ELSE 0 END) AS `total_sale_price`')
- ->first();
+ 'portfolio_id' => $this->portfolio_id,
+ 'symbol' => $this->symbol,
+ ])->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) AS `qty_purchases`')
+ ->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS `qty_sales`')
+ ->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN (quantity * cost_basis) ELSE 0 END) AS `total_cost_basis`')
+ ->selectRaw('SUM(CASE WHEN transaction_type = "SELL" THEN (quantity * sale_price) ELSE 0 END) AS `total_sale_price`')
+ ->first();
$total_quantity = round($query->qty_purchases - $query->qty_sales, 3);
$average_cost_basis = (
- $query->qty_purchases > 0
- && $total_quantity > 0
- )
- ? $query->total_cost_basis / $query->qty_purchases
+ $query->qty_purchases > 0
+ && $total_quantity > 0
+ )
+ ? $query->total_cost_basis / $query->qty_purchases
: 0;
// update holding
@@ -214,18 +209,20 @@ class Holding extends Model
'quantity' => $total_quantity,
'average_cost_basis' => $average_cost_basis,
'total_cost_basis' => $total_quantity * $average_cost_basis,
- 'realized_gain_dollars' => $query->qty_purchases > 0 && $query->total_sale_price > 0
- ? $query->total_sale_price - ($query->qty_sales * ($query->total_cost_basis / $query->qty_purchases))
+ 'realized_gain_dollars' => $query->qty_purchases > 0 && $query->total_sale_price > 0
+ ? $query->total_sale_price - ($query->qty_sales * ($query->total_cost_basis / $query->qty_purchases))
: 0,
- 'dividends_earned' => $this->dividends->sum('total_received')
+ 'dividends_earned' => $this->dividends->sum('total_received'),
]);
$this->save();
}
- public function qtyOwned(\Illuminate\Support\Carbon $date = null)
+ public function qtyOwned(?\Illuminate\Support\Carbon $date = null)
{
- if ($date == null) $date = now();
+ if ($date == null) {
+ $date = now();
+ }
$transactions = $this->transactions->where('date', '<=', $date);
@@ -237,16 +234,20 @@ class Holding extends Model
}
public function dailyPerformance(
- \Illuminate\Support\Carbon $start_date = null,
- \Illuminate\Support\Carbon $end_date = null,
+ ?\Illuminate\Support\Carbon $start_date = null,
+ ?\Illuminate\Support\Carbon $end_date = null,
) {
- if ($start_date == null) $start_date = now();
- if ($end_date == null) $end_date = now();
+ if ($start_date == null) {
+ $start_date = now();
+ }
+ if ($end_date == null) {
+ $end_date = now();
+ }
- $date_interval = "DATE_ADD(date, INTERVAL 1 DAY)";
+ $date_interval = 'DATE_ADD(date, INTERVAL 1 DAY)';
if (config('database.default') === 'sqlite') {
-
+
$date_interval = "date(date, '+1 day')";
} else {
@@ -265,14 +266,14 @@ class Holding extends Model
FROM date_series
) as date_series")
)
- ->select([
- 'date_series.date',
- DB::raw("
+ ->select([
+ 'date_series.date',
+ DB::raw("
ROUND(
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'BUY' THEN transactions.quantity ELSE 0 END), 0) -
COALESCE(SUM(CASE WHEN transactions.transaction_type = 'SELL' THEN transactions.quantity ELSE 0 END), 0), 3) AS `owned`
"),
- DB::raw("
+ DB::raw("
COALESCE(CASE
WHEN (
ROUND(
@@ -285,29 +286,30 @@ class Holding extends Model
END)
END, 0) AS cost_basis
"),
- DB::raw("COALESCE(SUM(CASE WHEN transaction_type = 'SELL' THEN ((sale_price - cost_basis) * quantity) ELSE 0 END), 0) AS `realized_gains`")
- ])
- ->leftJoin('transactions', function ($join) {
- $join->on(DB::raw('DATE(transactions.date)'), '<=', 'date_series.date')
- ->where('transactions.symbol', '=', $this->symbol)
- ->where('transactions.portfolio_id', '=', $this->portfolio_id);
- })
- ->groupBy('date_series.date')
- ->orderBy('date_series.date')
- ->get()
- ->keyBy('date');
+ DB::raw("COALESCE(SUM(CASE WHEN transaction_type = 'SELL' THEN ((sale_price - cost_basis) * quantity) ELSE 0 END), 0) AS `realized_gains`"),
+ ])
+ ->leftJoin('transactions', function ($join) {
+ $join->on(DB::raw('DATE(transactions.date)'), '<=', 'date_series.date')
+ ->where('transactions.symbol', '=', $this->symbol)
+ ->where('transactions.portfolio_id', '=', $this->portfolio_id);
+ })
+ ->groupBy('date_series.date')
+ ->orderBy('date_series.date')
+ ->get()
+ ->keyBy('date');
}
public function getFormattedTransactions()
{
$formattedTransactions = '';
- foreach($this->transactions->sortByDesc('date') as $transaction) {
- $formattedTransactions .= " * ".$transaction->date->format('Y-m-d')
- ." ". $transaction->transaction_type
- ." ". $transaction->quantity
- ." @ ". $transaction->cost_basis
+ foreach ($this->transactions->sortByDesc('date') as $transaction) {
+ $formattedTransactions .= ' * '.$transaction->date->format('Y-m-d')
+ .' '.$transaction->transaction_type
+ .' '.$transaction->quantity
+ .' @ '.$transaction->cost_basis
." each \n\n";
}
+
return $formattedTransactions;
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/MarketData.php b/app/Models/MarketData.php
index d7ce420..69d137b 100644
--- a/app/Models/MarketData.php
+++ b/app/Models/MarketData.php
@@ -2,16 +2,18 @@
namespace App\Models;
-use Illuminate\Database\Eloquent\Model;
use App\Interfaces\MarketData\MarketDataInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
class MarketData extends Model
{
use HasFactory;
protected $primaryKey = 'symbol';
+
protected $keyType = 'string';
+
public $incrementing = false;
protected $fillable = [
@@ -25,7 +27,7 @@ class MarketData extends Model
'market_cap',
'book_value',
'last_dividend_date',
- 'dividend_yield'
+ 'dividend_yield',
];
protected $casts = [
@@ -37,10 +39,10 @@ class MarketData extends Model
'trailing_pe' => 'float',
'market_cap' => 'float',
'book_value' => 'float',
- 'dividend_yield' => 'float'
+ 'dividend_yield' => 'float',
];
- public function holdings()
+ public function holdings()
{
return $this->hasMany(Holding::class, 'symbol', 'symbol');
}
@@ -50,20 +52,20 @@ class MarketData extends Model
return $query->where('symbol', $symbol);
}
- public static function getMarketData($symbol, $force = false)
+ public static function getMarketData($symbol, $force = false)
{
$market_data = self::firstOrNew([
- 'symbol' => $symbol
+ 'symbol' => $symbol,
]);
// check if new or stale
if (
$force
- || !$market_data->exists
+ || ! $market_data->exists
|| is_null($market_data->updated_at)
|| $market_data->updated_at->diffInMinutes(now()) >= config('investbrain.refresh')
) {
-
+
// get quote
$quote = app(MarketDataInterface::class)->quote($symbol);
@@ -76,4 +78,4 @@ class MarketData extends Model
return $market_data;
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/Portfolio.php b/app/Models/Portfolio.php
index 2195fdf..12054ab 100644
--- a/app/Models/Portfolio.php
+++ b/app/Models/Portfolio.php
@@ -2,17 +2,16 @@
namespace App\Models;
-use App\Models\AiChat;
+use App\Interfaces\MarketData\MarketDataInterface;
+use App\Notifications\InvitedOnboardingNotification;
use Carbon\CarbonPeriod;
+use Illuminate\Database\Eloquent\Concerns\HasUuids;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
-use Illuminate\Support\Str;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
-use Illuminate\Database\Eloquent\Model;
-use App\Interfaces\MarketData\MarketDataInterface;
-use Illuminate\Database\Eloquent\Concerns\HasUuids;
-use App\Notifications\InvitedOnboardingNotification;
-use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Support\Str;
class Portfolio extends Model
{
@@ -30,7 +29,7 @@ class Portfolio extends Model
protected static function boot()
{
parent::boot();
-
+
static::saved(function ($portfolio) {
self::ensurePortfolioHasOwner($portfolio);
@@ -40,7 +39,7 @@ class Portfolio extends Model
protected $hidden = [];
protected $casts = [
- 'wishlist' => 'boolean'
+ 'wishlist' => 'boolean',
];
protected $with = ['users', 'transactions'];
@@ -53,8 +52,8 @@ class Portfolio extends Model
public function holdings()
{
return $this->hasMany(Holding::class, 'portfolio_id')
- ->withMarketData()
- ->withPerformance();
+ ->withMarketData()
+ ->withPerformance();
}
public function transactions()
@@ -77,25 +76,25 @@ class Portfolio extends Model
return $this->morphMany(AiChat::class, 'chatable')->where('user_id', auth()->user()->id);
}
- public function scopeMyPortfolios()
+ public function scopeMyPortfolios()
{
return $this->whereHas('users', function ($query) {
$query->where('user_id', auth()->user()->id);
});
}
- public function scopeFullAccess($query, $user_id = null)
+ public function scopeFullAccess($query, $user_id = null)
{
return $query->whereHas('users', function ($query) use ($user_id) {
$query->where('user_id', $user_id ?? auth()->user()->id)
- ->where(function ($query) {
- $query->where('full_access', true)
- ->orWhere('owner', true);
- });
+ ->where(function ($query) {
+ $query->where('full_access', true)
+ ->orWhere('owner', true);
+ });
});
}
- public function scopeWithoutWishlists()
+ public function scopeWithoutWishlists()
{
return $this->where(['wishlist' => false]);
}
@@ -103,7 +102,7 @@ class Portfolio extends Model
public function setOwnerIdAttribute($value)
{
// enable queued jobs to create portfolios with owners
- if (!auth()->user()?->id && !$this->owner_id) {
+ if (! auth()->user()?->id && ! $this->owner_id) {
static::$owner_id = $value;
}
}
@@ -115,18 +114,18 @@ class Portfolio extends Model
public function getOwnerAttribute()
{
- if (!$this->relationLoaded('user')) {
-
+ if (! $this->relationLoaded('user')) {
+
$this->load('users');
}
return $this->users->where('pivot.owner', true)->first();
}
- public static function ensurePortfolioHasOwner(self $portfolio)
+ public static function ensurePortfolioHasOwner(self $portfolio)
{
// make sure we don't remove owner access
- if (!$portfolio->owner_id) {
+ if (! $portfolio->owner_id) {
$owner[static::$owner_id ?? auth()->user()->id] = ['owner' => true];
// save
@@ -138,24 +137,24 @@ class Portfolio extends Model
public function syncDailyChanges(): void
{
$holdings = $this->holdings()
- ->join('transactions', function($join) {
- $join->on('transactions.symbol', '=', 'holdings.symbol')
- ->where('transactions.portfolio_id', '=', $this->id);
- })
- ->select('holdings.symbol', 'holdings.portfolio_id', DB::raw('min(transactions.date) as first_transaction_date')) // get first transaction date
- ->groupBy(['holdings.symbol', 'holdings.portfolio_id'])
- ->get();
+ ->join('transactions', function ($join) {
+ $join->on('transactions.symbol', '=', 'holdings.symbol')
+ ->where('transactions.portfolio_id', '=', $this->id);
+ })
+ ->select('holdings.symbol', 'holdings.portfolio_id', DB::raw('min(transactions.date) as first_transaction_date')) // get first transaction date
+ ->groupBy(['holdings.symbol', 'holdings.portfolio_id'])
+ ->get();
$dividends = Dividend::whereIn('symbol', $holdings->pluck('symbol'))->get();
-
+
$total_performance = [];
- $holdings->each(function($holding) use (&$total_performance, $dividends) {
+ $holdings->each(function ($holding) use (&$total_performance, $dividends) {
$period = CarbonPeriod::create(
- $holding->first_transaction_date,
- now()->isBefore(Carbon::parse(config('investbrain.daily_change_time_of_day')))
- ? now()->subDay()
+ $holding->first_transaction_date,
+ now()->isBefore(Carbon::parse(config('investbrain.daily_change_time_of_day')))
+ ? now()->subDay()
: now()
);
@@ -170,11 +169,11 @@ class Portfolio extends Model
$dividends_earned = 0;
$holding_performance = [];
- foreach($period as $date) {
+ foreach ($period as $date) {
$date = $date->format('Y-m-d');
$close = $this->getMostRecentCloseData($all_history, $date);
-
+
$total_market_value = $daily_performance->get($date)->owned * $close;
$dividends_earned += $daily_performance->get($date)->owned * ($dividends->get($date)?->dividend_amount ?? 0);
@@ -182,18 +181,18 @@ class Portfolio extends Model
$holding_performance[$date] = [
'date' => $date,
'portfolio_id' => $this->id,
- 'total_market_value' => $total_market_value,
+ 'total_market_value' => $total_market_value,
'total_cost_basis' => $daily_performance->get($date)->cost_basis,
'total_gain' => $total_market_value - $daily_performance->get($date)->cost_basis,
'realized_gains' => $daily_performance->get($date)->realized_gains,
- 'total_dividends_earned' => $dividends_earned
+ 'total_dividends_earned' => $dividends_earned,
];
}
}
foreach ($holding_performance as $date => $performance) {
if (Arr::get($total_performance, $date) == null) {
-
+
$total_performance[$date] = $performance;
} else {
@@ -207,9 +206,9 @@ class Portfolio extends Model
}
});
- if (!empty($total_performance)) {
+ if (! empty($total_performance)) {
DB::transaction(function () use ($total_performance) {
-
+
$this->daily_change()->upsert(
$total_performance,
['date', 'portfolio_id'],
@@ -218,7 +217,7 @@ class Portfolio extends Model
'total_cost_basis',
'total_gain',
'realized_gains',
- 'total_dividends_earned'
+ 'total_dividends_earned',
]
);
});
@@ -229,10 +228,10 @@ class Portfolio extends Model
{
$close = Arr::get($history, "$date.close", 0);
- if (!$close && $i < $max_attempts) {
+ if (! $close && $i < $max_attempts) {
$i++;
-
+
$date = Carbon::parse($date)->subDay()->format('Y-m-d');
return $this->getMostRecentCloseData($history, $date, $i);
@@ -244,53 +243,47 @@ class Portfolio extends Model
public function getFormattedHoldings()
{
$formattedHoldings = '';
- foreach($this->holdings as $holding) {
- $formattedHoldings .= " * Holding of ".$holding->market_data->name." (".$holding->symbol.")"
- ."; with ". ($holding->quantity > 0 ? $holding->quantity : 'ZERO') . " shares"
- ."; avg cost basis ". $holding->average_cost_basis
- ."; curr market value ". $holding->market_data->market_value
- ."; unrealized gains ". $holding->market_gain_dollars
- ."; realized gains ". $holding->realized_gain_dollars
- ."; dividends earned ". $holding->dividends_earned
+ foreach ($this->holdings as $holding) {
+ $formattedHoldings .= ' * Holding of '.$holding->market_data->name.' ('.$holding->symbol.')'
+ .'; with '.($holding->quantity > 0 ? $holding->quantity : 'ZERO').' shares'
+ .'; avg cost basis '.$holding->average_cost_basis
+ .'; curr market value '.$holding->market_data->market_value
+ .'; unrealized gains '.$holding->market_gain_dollars
+ .'; realized gains '.$holding->realized_gain_dollars
+ .'; dividends earned '.$holding->dividends_earned
."\n\n";
}
+
return $formattedHoldings;
}
/**
* Share a portfolio with a user
- *
- * @param string $email
- * @param boolean $fullAccess
- * @return void
*/
public function share(string $email, bool $fullAccess = false): void
{
$user = User::firstOrCreate([
- 'email' => $email
+ 'email' => $email,
], [
- 'name' => Str::title(Str::before($email, '@'))
+ 'name' => Str::title(Str::before($email, '@')),
]);
$permissions[$user->id] = [
- 'full_access' => $fullAccess
+ 'full_access' => $fullAccess,
];
$sync = $this->users()->syncWithoutDetaching($permissions);
- if (!empty($sync['attached'])) {
+ if (! empty($sync['attached'])) {
- foreach($sync['attached'] as $newUserId) {
+ foreach ($sync['attached'] as $newUserId) {
User::find($newUserId)->notify(new InvitedOnboardingNotification($this, auth()->user()));
- };
+ }
}
}
/**
* Un-share a portfolio
- *
- * @param string $userId
- * @return void
*/
public function unShare(string $userId): void
{
diff --git a/app/Models/Split.php b/app/Models/Split.php
index 092cf07..8429a5f 100644
--- a/app/Models/Split.php
+++ b/app/Models/Split.php
@@ -2,13 +2,12 @@
namespace App\Models;
-use App\Models\Transaction;
-use Illuminate\Support\Str;
-use Illuminate\Support\Facades\DB;
-use Illuminate\Database\Eloquent\Model;
use App\Interfaces\MarketData\MarketDataInterface;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Str;
class Split extends Model
{
@@ -28,22 +27,23 @@ class Split extends Model
'last_date' => 'datetime',
];
- public function holdings() {
+ public function holdings()
+ {
return $this->hasMany(Holding::class, 'symbol', 'symbol');
}
- public function transactions() {
+ public function transactions()
+ {
return $this->hasMany(Transaction::class, 'symbol', 'symbol');
}
/**
* Grab new split data
*
- * @param string $symbol
- * @param \DateTimeInterface|null $start_date
+ * @param \DateTimeInterface|null $start_date
* @return void
*/
- public static function refreshSplitData(string $symbol)
+ public static function refreshSplitData(string $symbol)
{
// dates for split data
$splits_meta = self::where(['symbol' => $symbol])
@@ -58,9 +58,9 @@ class Split extends Model
// nope, need to populate newer split data
if ($splits_meta->total_splits) {
-
+
$start_date = $splits_meta->last_date->addHours(48);
- $end_date = now();
+ $end_date = now();
}
// get some data
@@ -71,10 +71,10 @@ class Split extends Model
if ($split_data->isNotEmpty()) {
// insert records
- (new self)->insert($split_data->map(function($split) {
+ (new self)->insert($split_data->map(function ($split) {
return [...$split, ...['id' => Str::uuid()->toString()]];
- })->toArray());
+ })->toArray());
}
// sync to transactions
@@ -84,39 +84,39 @@ class Split extends Model
/**
* Syncs all transactions of symbol with split data
*
- * @param string $symbol
+ * @param string $symbol
* @return void
*/
- public static function syncToTransactions($symbol)
+ public static function syncToTransactions($symbol)
{
// get splits joined with matching holdings
$splits = self::select([
- 'splits.date',
- 'splits.symbol',
- 'splits.split_amount',
- 'holdings.portfolio_id'
- ])
- ->where([
- 'splits.symbol' => $symbol,
- ])
- ->whereDate('splits.date', '>', DB::raw('IFNULL(holdings.splits_synced_at, "0000-00-00")'))
- ->where('holdings.quantity', '>', 0)
- ->join('holdings', 'splits.symbol', 'holdings.symbol')
- ->orderBy('splits.date', 'ASC')
- ->get();
+ 'splits.date',
+ 'splits.symbol',
+ 'splits.split_amount',
+ 'holdings.portfolio_id',
+ ])
+ ->where([
+ 'splits.symbol' => $symbol,
+ ])
+ ->whereDate('splits.date', '>', DB::raw('IFNULL(holdings.splits_synced_at, "0000-00-00")'))
+ ->where('holdings.quantity', '>', 0)
+ ->join('holdings', 'splits.symbol', 'holdings.symbol')
+ ->orderBy('splits.date', 'ASC')
+ ->get();
- foreach($splits as $split) {
+ foreach ($splits as $split) {
// get qty owned when split was issued
$qty_owned = Transaction::where([
- 'symbol' => $split->symbol,
- 'portfolio_id' => $split->portfolio_id
- ])
+ 'symbol' => $split->symbol,
+ 'portfolio_id' => $split->portfolio_id,
+ ])
->whereDate('transactions.date', '<', $split->date->format('Y-m-d'))
->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) -
SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS qty_owned')
->value('qty_owned');
-
+
if ($qty_owned > 0) {
Transaction::create([
@@ -128,14 +128,14 @@ class Split extends Model
'cost_basis' => 0,
'split' => true,
'created_at' => now(),
- 'updated_at' => now()
+ 'updated_at' => now(),
]);
Holding::where([
'symbol' => $split->symbol,
- 'portfolio_id' => $split->portfolio_id
+ 'portfolio_id' => $split->portfolio_id,
])->update([
- 'splits_synced_at' => now()
+ 'splits_synced_at' => now(),
]);
}
}
diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php
index a4793d0..dc9cc99 100644
--- a/app/Models/Transaction.php
+++ b/app/Models/Transaction.php
@@ -2,12 +2,11 @@
namespace App\Models;
-use App\Models\MarketData;
-use Illuminate\Support\Arr;
-use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Arr;
class Transaction extends Model
{
@@ -23,7 +22,7 @@ class Transaction extends Model
'cost_basis',
'sale_price',
'split',
- 'reinvested_dividend'
+ 'reinvested_dividend',
];
protected $hidden = [];
@@ -31,7 +30,7 @@ class Transaction extends Model
protected $casts = [
'date' => 'datetime',
'split' => 'boolean',
- 'reinvested_dividend' => 'boolean'
+ 'reinvested_dividend' => 'boolean',
];
protected static function boot()
@@ -52,14 +51,14 @@ class Transaction extends Model
$transaction->refreshMarketData();
- cache()->forget('portfolio-metrics-' . $transaction->portfolio_id);
+ cache()->forget('portfolio-metrics-'.$transaction->portfolio_id);
});
static::deleted(function ($transaction) {
$transaction->syncToHolding();
- cache()->forget('portfolio-metrics-' . $transaction->portfolio_id);
+ cache()->forget('portfolio-metrics-'.$transaction->portfolio_id);
});
}
@@ -96,13 +95,13 @@ class Transaction extends Model
public function scopeWithMarketData($query)
{
return $query->withAggregate('market_data', 'name')
- ->withAggregate('market_data', 'market_value')
- ->withAggregate('market_data', 'fifty_two_week_low')
- ->withAggregate('market_data', 'fifty_two_week_high')
- ->withAggregate('market_data', 'updated_at')
- ->join('market_data', 'transactions.symbol', 'market_data.symbol');
+ ->withAggregate('market_data', 'market_value')
+ ->withAggregate('market_data', 'fifty_two_week_low')
+ ->withAggregate('market_data', 'fifty_two_week_high')
+ ->withAggregate('market_data', 'updated_at')
+ ->join('market_data', 'transactions.symbol', 'market_data.symbol');
}
-
+
public function scopePortfolio($query, $portfolio)
{
return $query->where('portfolio_id', $portfolio);
@@ -128,7 +127,7 @@ class Transaction extends Model
return $query->whereDate('date', '<=', $date);
}
- public function scopeMyTransactions()
+ public function scopeMyTransactions()
{
return $this->whereHas('portfolio', function ($query) {
$query->whereHas('users', function ($query) {
@@ -137,7 +136,7 @@ class Transaction extends Model
});
}
- public function refreshMarketData()
+ public function refreshMarketData()
{
return MarketData::getMarketData($this->attributes['symbol']);
}
@@ -154,7 +153,7 @@ class Transaction extends Model
'symbol' => $this->symbol,
'transaction_type' => 'BUY',
])->whereDate('date', '<=', $this->date)
- ->average('cost_basis');
+ ->average('cost_basis');
$this->cost_basis = $average_cost_basis ?? 0;
@@ -166,7 +165,8 @@ class Transaction extends Model
*
* @return void
*/
- public function syncToHolding() {
+ public function syncToHolding()
+ {
// if symbol name changed, sync previous symbol too
if (Arr::has($this->changes, 'symbol')) {
@@ -181,7 +181,7 @@ class Transaction extends Model
// get the holding for a symbol and portfolio (or create one)
Holding::firstOrNew([
'portfolio_id' => $this->portfolio_id,
- 'symbol' => $this->symbol
+ 'symbol' => $this->symbol,
], [
'portfolio_id' => $this->portfolio_id,
'symbol' => $this->symbol,
@@ -191,4 +191,4 @@ class Transaction extends Model
'splits_synced_at' => now(),
])->syncTransactionsAndDividends();
}
-}
\ No newline at end of file
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index be58b82..a51b7a6 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -3,27 +3,27 @@
namespace App\Models;
use App\Traits\HasConnectedAccounts;
-use Laravel\Sanctum\HasApiTokens;
-use Laravel\Jetstream\HasProfilePhoto;
-use Illuminate\Notifications\Notifiable;
-use Laravel\Fortify\TwoFactorAuthenticatable;
-use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
+use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
-use Staudenmeir\EloquentHasManyDeep\HasRelationships;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
-use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Notifications\Notifiable;
+use Laravel\Fortify\TwoFactorAuthenticatable;
+use Laravel\Jetstream\HasProfilePhoto;
+use Laravel\Sanctum\HasApiTokens;
+use Staudenmeir\EloquentHasManyDeep\HasManyDeep;
+use Staudenmeir\EloquentHasManyDeep\HasRelationships;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens;
+ use HasConnectedAccounts;
use HasFactory;
use HasProfilePhoto;
+ use HasRelationships;
+ use HasUuids;
use Notifiable;
use TwoFactorAuthenticatable;
- use HasUuids;
- use HasRelationships;
- use HasConnectedAccounts;
protected $fillable = [
'name',
@@ -65,7 +65,7 @@ class User extends Authenticatable implements MustVerifyEmail
{
return $this->hasManyDeep(Holding::class, ['portfolio_user', Portfolio::class])
->withMarketData()
- ->withPerformance();
+ ->withPerformance();
}
public function transactions(): HasManyDeep
@@ -78,6 +78,6 @@ class User extends Authenticatable implements MustVerifyEmail
WHEN transaction_type = \'SELL\'
THEN COALESCE(transactions.sale_price - transactions.cost_basis, 0)
ELSE COALESCE(market_data.market_value - transactions.cost_basis, 0)
- END AS gain_dollars');
+ END AS gain_dollars');
}
}
diff --git a/app/Notifications/ImportFailedNotification.php b/app/Notifications/ImportFailedNotification.php
index 3d615e2..be01d94 100644
--- a/app/Notifications/ImportFailedNotification.php
+++ b/app/Notifications/ImportFailedNotification.php
@@ -3,9 +3,9 @@
namespace App\Notifications;
use Illuminate\Bus\Queueable;
-use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
class ImportFailedNotification extends Notification implements ShouldQueue
{
@@ -16,7 +16,7 @@ class ImportFailedNotification extends Notification implements ShouldQueue
*/
public function __construct(
public string $errorMessage
- ) { }
+ ) {}
/**
* Get the notification's delivery channels.
@@ -34,12 +34,12 @@ class ImportFailedNotification extends Notification implements ShouldQueue
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
- ->greeting('Oh no!')
- ->subject("Your Investbrain import failed!")
- ->line("Heads up, your Investbrain import was unable to successfully complete. There were errors which caused the import to fail.")
- ->action("Try again?", route('import-export'))
- ->line("**Technical details:**")
- ->line($this->errorMessage);
+ ->greeting('Oh no!')
+ ->subject('Your Investbrain import failed!')
+ ->line('Heads up, your Investbrain import was unable to successfully complete. There were errors which caused the import to fail.')
+ ->action('Try again?', route('import-export'))
+ ->line('**Technical details:**')
+ ->line($this->errorMessage);
}
/**
diff --git a/app/Notifications/ImportSucceededNotification.php b/app/Notifications/ImportSucceededNotification.php
index 12ec924..1ff2773 100644
--- a/app/Notifications/ImportSucceededNotification.php
+++ b/app/Notifications/ImportSucceededNotification.php
@@ -2,12 +2,10 @@
namespace App\Notifications;
-use App\Models\User;
-use App\Models\Portfolio;
use Illuminate\Bus\Queueable;
-use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
class ImportSucceededNotification extends Notification implements ShouldQueue
{
@@ -16,7 +14,7 @@ class ImportSucceededNotification extends Notification implements ShouldQueue
/**
* Create a new notification instance.
*/
- public function __construct() { }
+ public function __construct() {}
/**
* Get the notification's delivery channels.
@@ -34,10 +32,10 @@ class ImportSucceededNotification extends Notification implements ShouldQueue
public function toMail(object $notifiable): MailMessage
{
return (new MailMessage)
- ->greeting('Woot! 🎉')
- ->subject("Your Investbrain import was successful!")
- ->line("Just a heads up that your Investbrain import succeeded! Your portfolios, transactions, and daily changes are now available in your account.")
- ->action("Get Started", route('dashboard'));
+ ->greeting('Woot! 🎉')
+ ->subject('Your Investbrain import was successful!')
+ ->line('Just a heads up that your Investbrain import succeeded! Your portfolios, transactions, and daily changes are now available in your account.')
+ ->action('Get Started', route('dashboard'));
}
/**
diff --git a/app/Notifications/InvitedOnboardingNotification.php b/app/Notifications/InvitedOnboardingNotification.php
index eea0738..d605111 100644
--- a/app/Notifications/InvitedOnboardingNotification.php
+++ b/app/Notifications/InvitedOnboardingNotification.php
@@ -2,12 +2,12 @@
namespace App\Notifications;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
use Illuminate\Bus\Queueable;
-use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
class InvitedOnboardingNotification extends Notification implements ShouldQueue
{
@@ -19,7 +19,7 @@ class InvitedOnboardingNotification extends Notification implements ShouldQueue
public function __construct(
public Portfolio $portfolio,
public User $sender,
- ) { }
+ ) {}
/**
* Get the notification's delivery channels.
@@ -40,14 +40,14 @@ class InvitedOnboardingNotification extends Notification implements ShouldQueue
$url = url()->signedRoute('invited_onboarding', ['portfolio' => $this->portfolio->id, 'user' => $notifiable->id], now()->addDays(90));
return (new MailMessage)
- ->replyTo($this->sender->email, $this->sender->name)
- ->greeting('Hey there! 👋')
- ->subject("You've been invited to {$this->portfolio->title} on Investbrain!")
- ->line("{$this->sender->name} has invited you to **{$this->portfolio->title}** on Investbrain, a smart open-source investment tracker that consolidates and monitors market performance across your different brokerages.")
- ->line("Once you're in, you'll be able to see all the holdings, dividends, market performance and more for {$this->portfolio->title}!")
- ->action("Get Started", $url)
- ->line("If you have any questions, you can reply to this email.")
- ->salutation("See you there,\n". e($this->sender->name));
+ ->replyTo($this->sender->email, $this->sender->name)
+ ->greeting('Hey there! 👋')
+ ->subject("You've been invited to {$this->portfolio->title} on Investbrain!")
+ ->line("{$this->sender->name} has invited you to **{$this->portfolio->title}** on Investbrain, a smart open-source investment tracker that consolidates and monitors market performance across your different brokerages.")
+ ->line("Once you're in, you'll be able to see all the holdings, dividends, market performance and more for {$this->portfolio->title}!")
+ ->action('Get Started', $url)
+ ->line('If you have any questions, you can reply to this email.')
+ ->salutation("See you there,\n".e($this->sender->name));
}
/**
diff --git a/app/Notifications/VerifyConnectedAccountNotification.php b/app/Notifications/VerifyConnectedAccountNotification.php
index e2cb48b..a4e034d 100644
--- a/app/Notifications/VerifyConnectedAccountNotification.php
+++ b/app/Notifications/VerifyConnectedAccountNotification.php
@@ -2,11 +2,11 @@
namespace App\Notifications;
-use Illuminate\Bus\Queueable;
use App\Models\ConnectedAccount;
-use Illuminate\Notifications\Notification;
+use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
class VerifyConnectedAccountNotification extends Notification implements ShouldQueue
{
@@ -17,7 +17,7 @@ class VerifyConnectedAccountNotification extends Notification implements ShouldQ
*/
public function __construct(
public string $connected_account_id
- ) { }
+ ) {}
/**
* Get the notification's delivery channels.
@@ -40,11 +40,11 @@ class VerifyConnectedAccountNotification extends Notification implements ShouldQ
$url = url()->signedRoute('oauth.verify_connected_account', ['connected_account' => $this->connected_account_id], now()->days($days = 7));
return (new MailMessage)
- ->greeting('Welcome back!')
- ->subject("Connect your $provider account with Investbrain")
- ->line("You recently attempted to log into an existing Investbrain account using $provider. To safeguard your Investbrain account, please confirm this was you by pressing the 'Connect $provider' button below:")
- ->action("Connect $provider", $url)
- ->line("If you do not recognize this activity, we recommend [changing your password](".route('profile.show').") as soon as possible. Otherwise, you can disregard this message. This link will expire in {$days} days.");
+ ->greeting('Welcome back!')
+ ->subject("Connect your $provider account with Investbrain")
+ ->line("You recently attempted to log into an existing Investbrain account using $provider. To safeguard your Investbrain account, please confirm this was you by pressing the 'Connect $provider' button below:")
+ ->action("Connect $provider", $url)
+ ->line('If you do not recognize this activity, we recommend [changing your password]('.route('profile.show').") as soon as possible. Otherwise, you can disregard this message. This link will expire in {$days} days.");
}
/**
diff --git a/app/Policies/PortfolioPolicy.php b/app/Policies/PortfolioPolicy.php
index a3228e9..e060100 100644
--- a/app/Policies/PortfolioPolicy.php
+++ b/app/Policies/PortfolioPolicy.php
@@ -2,25 +2,18 @@
namespace App\Policies;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
class PortfolioPolicy
{
-
- /**
- *
- */
public function readOnly(User $user, Portfolio $portfolio)
{
$pivot = $portfolio->users()->where('user_id', $user->id)->first();
- return !!$pivot;
+ return (bool) $pivot;
}
- /**
- *
- */
public function fullAccess(User $user, Portfolio $portfolio)
{
$pivot = $portfolio->users()->where('user_id', $user->id)->first();
@@ -28,9 +21,6 @@ class PortfolioPolicy
return $pivot && ($pivot->pivot->full_access || $pivot->pivot->owner);
}
- /**
- *
- */
public function owner(User $user, Portfolio $portfolio)
{
$pivot = $portfolio->users()->where('user_id', $user->id)->first();
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 39f79b0..9350699 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,8 +2,8 @@
namespace App\Providers;
-use Illuminate\Support\ServiceProvider;
use Illuminate\Http\Resources\Json\JsonResource;
+use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
diff --git a/app/Providers/JetstreamServiceProvider.php b/app/Providers/JetstreamServiceProvider.php
index f7a2ed0..c8e2abc 100644
--- a/app/Providers/JetstreamServiceProvider.php
+++ b/app/Providers/JetstreamServiceProvider.php
@@ -2,11 +2,11 @@
namespace App\Providers;
-use Illuminate\Support\Arr;
-use Laravel\Jetstream\Features;
use App\Actions\Jetstream\DeleteUser;
+use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
+use Laravel\Jetstream\Features;
use Laravel\Jetstream\Jetstream;
class JetstreamServiceProvider extends ServiceProvider
@@ -29,7 +29,7 @@ class JetstreamServiceProvider extends ServiceProvider
Jetstream::deleteUsersUsing(DeleteUser::class);
- if ( config('investbrain.self_hosted', false) ) {
+ if (config('investbrain.self_hosted', false)) {
Config::set(
'jetstream.features',
diff --git a/app/Rules/QuantityValidationRule.php b/app/Rules/QuantityValidationRule.php
index 74027bc..818b893 100644
--- a/app/Rules/QuantityValidationRule.php
+++ b/app/Rules/QuantityValidationRule.php
@@ -13,24 +13,19 @@ class QuantityValidationRule implements ValidationRule
* @return void
*/
public function __construct(
- protected ?Portfolio $portfolio,
- protected ?string $symbol,
+ protected ?Portfolio $portfolio,
+ protected ?string $symbol,
protected ?string $transactionType,
protected ?string $date
) {
$this->portfolio = $portfolio;
- $this->symbol = $symbol;
+ $this->symbol = $symbol;
$this->transactionType = $transactionType;
$this->date = $date;
}
/**
* Validate the attribute.
- *
- * @param string $attribute
- * @param mixed $value
- * @param \Closure $fail
- * @return void
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
@@ -42,17 +37,17 @@ class QuantityValidationRule implements ValidationRule
if ($this->transactionType == 'SELL') {
$purchase_qty = $this->portfolio->transactions()
- ->symbol($this->symbol)
- ->buy()
- ->beforeDate($this->date)
- ->sum('quantity');
+ ->symbol($this->symbol)
+ ->buy()
+ ->beforeDate($this->date)
+ ->sum('quantity');
$sales_qty = $this->portfolio->transactions()
- ->symbol($this->symbol)
- ->sell()
- ->beforeDate($this->date)
- ->sum('quantity');
-
+ ->symbol($this->symbol)
+ ->sell()
+ ->beforeDate($this->date)
+ ->sum('quantity');
+
$maxQuantity = $purchase_qty - $sales_qty;
if (round($value, 3) > round($maxQuantity, 3)) {
diff --git a/app/Rules/SymbolValidationRule.php b/app/Rules/SymbolValidationRule.php
index 428aefd..ae544e1 100644
--- a/app/Rules/SymbolValidationRule.php
+++ b/app/Rules/SymbolValidationRule.php
@@ -22,11 +22,6 @@ class SymbolValidationRule implements ValidationRule
/**
* Validate the attribute.
- *
- * @param string $attribute
- * @param mixed $value
- * @param \Closure $fail
- * @return void
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
@@ -38,8 +33,8 @@ class SymbolValidationRule implements ValidationRule
}
// Check if the symbol exists in the Market Data table first (avoid API call)
- if (!app(MarketDataInterface::class)->exists($value)) {
- $fail('The symbol provided (' . $this->symbol . ') is not valid');
+ if (! app(MarketDataInterface::class)->exists($value)) {
+ $fail('The symbol provided ('.$this->symbol.') is not valid');
}
}
-}
\ No newline at end of file
+}
diff --git a/app/Support/Helpers.php b/app/Support/Helpers.php
index 77f2b79..36ea8bb 100644
--- a/app/Support/Helpers.php
+++ b/app/Support/Helpers.php
@@ -1,5 +1,5 @@
user()) {
+ if (! $request->user()) {
return $results;
}
@@ -22,13 +20,13 @@ class Spotlight
->where('title', 'LIKE', '%'.$request->input('search').'%')
->limit(5)
->get();
- $portfolios->each(function($portfolio) use ($results) {
+ $portfolios->each(function ($portfolio) use ($results) {
$results->push([
- 'name' => 'Portfolio: '. $portfolio->title,
+ 'name' => 'Portfolio: '.$portfolio->title,
'description' => null,
'link' => route('portfolio.show', ['portfolio' => $portfolio->id]),
- 'avatar' => null
+ 'avatar' => null,
]);
});
@@ -36,20 +34,20 @@ class Spotlight
->where('holdings.quantity', '>', 0)
->where(function ($query) use ($request) {
return $query->where('holdings.symbol', 'LIKE', '%'.$request->input('search').'%')
- ->orWhere('market_data.name', 'LIKE', '%'.$request->input('search').'%');
+ ->orWhere('market_data.name', 'LIKE', '%'.$request->input('search').'%');
})
->limit(5)
->get();
- $holdings->each(function($holding) use ($results) {
+ $holdings->each(function ($holding) use ($results) {
$results->push([
'name' => 'Holding: '.$holding->market_data->name.' ('.$holding->symbol.')',
'description' => $holding->portfolio->title,
'link' => route('holding.show', ['portfolio' => $holding->portfolio->id, 'symbol' => $holding->symbol]),
- 'avatar' => null
+ 'avatar' => null,
]);
});
return $results;
}
-}
\ No newline at end of file
+}
diff --git a/app/Traits/HasCompositePrimaryKey.php b/app/Traits/HasCompositePrimaryKey.php
index f2af335..31d4306 100644
--- a/app/Traits/HasCompositePrimaryKey.php
+++ b/app/Traits/HasCompositePrimaryKey.php
@@ -1,6 +1,6 @@
getKeyName() as $key) {
// UPDATE: Added isset() per devflow's comment.
- if (isset($this->$key))
+ if (isset($this->$key)) {
$query->where($key, '=', $this->$key);
- else
- throw new \Exception(__METHOD__ . 'Missing part of the primary key: ' . $key);
+ } else {
+ throw new \Exception(__METHOD__.'Missing part of the primary key: '.$key);
+ }
}
return $query;
@@ -37,7 +38,7 @@ trait HasCompositePrimaryKey
/**
* Execute a query for a single record by ID.
*
- * @param array $ids Array of keys, like [column => value].
+ * @param array $ids Array of keys, like [column => value].
* @param array $columns
* @return mixed|static
*/
@@ -48,6 +49,7 @@ trait HasCompositePrimaryKey
foreach ($me->getKeyName() as $key) {
$query->where($key, '=', $ids[$key]);
}
+
return $query->first($columns);
}
-}
\ No newline at end of file
+}
diff --git a/app/Traits/HasConnectedAccounts.php b/app/Traits/HasConnectedAccounts.php
index 4926d4e..3f5215d 100644
--- a/app/Traits/HasConnectedAccounts.php
+++ b/app/Traits/HasConnectedAccounts.php
@@ -4,9 +4,9 @@ namespace App\Traits;
use App\Models\ConnectedAccount;
use App\Models\User;
-use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Support\Str;
/**
* @property Collection $connectedAccounts
@@ -63,4 +63,4 @@ trait HasConnectedAccounts
{
return $this->hasMany(ConnectedAccount::class);
}
-}
\ No newline at end of file
+}
diff --git a/app/Traits/WithTrimStrings.php b/app/Traits/WithTrimStrings.php
index 1fc10c4..0795312 100644
--- a/app/Traits/WithTrimStrings.php
+++ b/app/Traits/WithTrimStrings.php
@@ -13,10 +13,10 @@ trait WithTrimStrings
public function updatedWithTrimStrings(string $property, mixed $value): void
{
- if (is_string($value) && !in_array($property, $this->trimExceptions())) {
+ if (is_string($value) && ! in_array($property, $this->trimExceptions())) {
$this->fill([
$property => Str::trim($value),
]);
}
}
-}
\ No newline at end of file
+}
diff --git a/app/View/Components/MainLayout.php b/app/View/Components/MainLayout.php
index a145423..8378f86 100644
--- a/app/View/Components/MainLayout.php
+++ b/app/View/Components/MainLayout.php
@@ -11,7 +11,7 @@ class MainLayout extends Component
// Slots
public mixed $body = null,
- ) { }
+ ) {}
/**
* Get the view / contents that represents the component.
diff --git a/config/excel.php b/config/excel.php
index 5a8a7d1..0ddb85c 100644
--- a/config/excel.php
+++ b/config/excel.php
@@ -15,7 +15,7 @@ return [
| Here you can specify how big the chunk should be.
|
*/
- 'chunk_size' => 1000,
+ 'chunk_size' => 1000,
/*
|--------------------------------------------------------------------------
@@ -42,15 +42,15 @@ return [
| Configure e.g. delimiter, enclosure and line ending for CSV exports.
|
*/
- 'csv' => [
- 'delimiter' => ',',
- 'enclosure' => '"',
- 'line_ending' => PHP_EOL,
- 'use_bom' => false,
+ 'csv' => [
+ 'delimiter' => ',',
+ 'enclosure' => '"',
+ 'line_ending' => PHP_EOL,
+ 'use_bom' => false,
'include_separator_line' => false,
- 'excel_compatibility' => false,
- 'output_encoding' => '',
- 'test_auto_detect' => true,
+ 'excel_compatibility' => false,
+ 'output_encoding' => '',
+ 'test_auto_detect' => true,
],
/*
@@ -61,20 +61,20 @@ return [
| Configure e.g. default title, creator, subject,...
|
*/
- 'properties' => [
- 'creator' => '',
+ 'properties' => [
+ 'creator' => '',
'lastModifiedBy' => '',
- 'title' => '',
- 'description' => '',
- 'subject' => '',
- 'keywords' => '',
- 'category' => '',
- 'manager' => '',
- 'company' => '',
+ 'title' => '',
+ 'description' => '',
+ 'subject' => '',
+ 'keywords' => '',
+ 'category' => '',
+ 'manager' => '',
+ 'company' => '',
],
],
- 'imports' => [
+ 'imports' => [
/*
|--------------------------------------------------------------------------
@@ -87,7 +87,7 @@ return [
| you can enable it by setting read_only to false.
|
*/
- 'read_only' => true,
+ 'read_only' => true,
/*
|--------------------------------------------------------------------------
@@ -111,7 +111,7 @@ return [
| Available options: none|slug|custom
|
*/
- 'heading_row' => [
+ 'heading_row' => [
'formatter' => 'slug',
],
@@ -123,12 +123,12 @@ return [
| Configure e.g. delimiter, enclosure and line ending for CSV imports.
|
*/
- 'csv' => [
- 'delimiter' => null,
- 'enclosure' => '"',
+ 'csv' => [
+ 'delimiter' => null,
+ 'enclosure' => '"',
'escape_character' => '\\',
- 'contiguous' => false,
- 'input_encoding' => Csv::GUESS_ENCODING,
+ 'contiguous' => false,
+ 'input_encoding' => Csv::GUESS_ENCODING,
],
/*
@@ -139,16 +139,16 @@ return [
| Configure e.g. default title, creator, subject,...
|
*/
- 'properties' => [
- 'creator' => '',
+ 'properties' => [
+ 'creator' => '',
'lastModifiedBy' => '',
- 'title' => '',
- 'description' => '',
- 'subject' => '',
- 'keywords' => '',
- 'category' => '',
- 'manager' => '',
- 'company' => '',
+ 'title' => '',
+ 'description' => '',
+ 'subject' => '',
+ 'keywords' => '',
+ 'category' => '',
+ 'manager' => '',
+ 'company' => '',
],
/*
@@ -159,10 +159,10 @@ return [
| Configure middleware that is executed on getting a cell value
|
*/
- 'cells' => [
+ 'cells' => [
'middleware' => [
- //\Maatwebsite\Excel\Middleware\TrimCellValue::class,
- //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
+ // \Maatwebsite\Excel\Middleware\TrimCellValue::class,
+ // \Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class,
],
],
@@ -178,21 +178,21 @@ return [
|
*/
'extension_detector' => [
- 'xlsx' => Excel::XLSX,
- 'xlsm' => Excel::XLSX,
- 'xltx' => Excel::XLSX,
- 'xltm' => Excel::XLSX,
- 'xls' => Excel::XLS,
- 'xlt' => Excel::XLS,
- 'ods' => Excel::ODS,
- 'ots' => Excel::ODS,
- 'slk' => Excel::SLK,
- 'xml' => Excel::XML,
+ 'xlsx' => Excel::XLSX,
+ 'xlsm' => Excel::XLSX,
+ 'xltx' => Excel::XLSX,
+ 'xltm' => Excel::XLSX,
+ 'xls' => Excel::XLS,
+ 'xlt' => Excel::XLS,
+ 'ods' => Excel::ODS,
+ 'ots' => Excel::ODS,
+ 'slk' => Excel::SLK,
+ 'xml' => Excel::XML,
'gnumeric' => Excel::GNUMERIC,
- 'htm' => Excel::HTML,
- 'html' => Excel::HTML,
- 'csv' => Excel::CSV,
- 'tsv' => Excel::TSV,
+ 'htm' => Excel::HTML,
+ 'html' => Excel::HTML,
+ 'csv' => Excel::CSV,
+ 'tsv' => Excel::TSV,
/*
|--------------------------------------------------------------------------
@@ -203,7 +203,7 @@ return [
| Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF
|
*/
- 'pdf' => Excel::DOMPDF,
+ 'pdf' => Excel::DOMPDF,
],
/*
@@ -223,11 +223,11 @@ return [
| [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class
|
*/
- 'value_binder' => [
+ 'value_binder' => [
'default' => Maatwebsite\Excel\DefaultValueBinder::class,
],
- 'cache' => [
+ 'cache' => [
/*
|--------------------------------------------------------------------------
| Default cell caching driver
@@ -244,7 +244,7 @@ return [
| Drivers: memory|illuminate|batch
|
*/
- 'driver' => 'memory',
+ 'driver' => 'memory',
/*
|--------------------------------------------------------------------------
@@ -256,7 +256,7 @@ return [
| Here you can tweak the memory limit to your liking.
|
*/
- 'batch' => [
+ 'batch' => [
'memory_limit' => 60000,
],
@@ -272,7 +272,7 @@ return [
| at "null" it will use the default store.
|
*/
- 'illuminate' => [
+ 'illuminate' => [
'store' => null,
],
@@ -308,7 +308,7 @@ return [
*/
'transactions' => [
'handler' => 'db',
- 'db' => [
+ 'db' => [
'connection' => null,
],
],
@@ -326,7 +326,7 @@ return [
| and the create file (file).
|
*/
- 'local_path' => storage_path('framework/cache/laravel-excel'),
+ 'local_path' => storage_path('framework/cache/laravel-excel'),
/*
|--------------------------------------------------------------------------
@@ -338,7 +338,7 @@ return [
| If omitted the default permissions of the filesystem will be used.
|
*/
- 'local_permissions' => [
+ 'local_permissions' => [
// 'dir' => 0755,
// 'file' => 0644,
],
@@ -357,8 +357,8 @@ return [
| in conjunction with queued imports and exports.
|
*/
- 'remote_disk' => env('TEMP_UPLOAD_DISK', null),
- 'remote_prefix' => 'excel-tmp',
+ 'remote_disk' => env('TEMP_UPLOAD_DISK', null),
+ 'remote_prefix' => 'excel-tmp',
/*
|--------------------------------------------------------------------------
diff --git a/config/investbrain.php b/config/investbrain.php
index cb4ad56..0889aa5 100644
--- a/config/investbrain.php
+++ b/config/investbrain.php
@@ -15,5 +15,5 @@ return [
'self_hosted' => env('SELF_HOSTED', true),
- 'daily_change_time_of_day' => env('DAILY_CHANGE_TIME', '23:00')
-];
\ No newline at end of file
+ 'daily_change_time_of_day' => env('DAILY_CHANGE_TIME', '23:00'),
+];
diff --git a/config/mary.php b/config/mary.php
index f8e7de7..cd3a390 100644
--- a/config/mary.php
+++ b/config/mary.php
@@ -13,7 +13,6 @@ return [
* prefix => 'mary-'
*
*
- *
*/
'prefix' => '',
@@ -40,6 +39,6 @@ return [
'components' => [
'spotlight' => [
'class' => 'App\Support\Spotlight',
- ]
- ]
+ ],
+ ],
];
diff --git a/config/services.php b/config/services.php
index d1faef0..3eeb4f7 100644
--- a/config/services.php
+++ b/config/services.php
@@ -41,7 +41,7 @@ return [
'redirect' => '/auth/github/callback',
'logo' => 'github-icon',
'color' => '#393939',
- 'name' => 'GitHub'
+ 'name' => 'GitHub',
],
'google' => [
@@ -49,7 +49,7 @@ return [
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => '/auth/google/callback',
'color' => '#4285F4',
- 'name' => 'Google'
+ 'name' => 'Google',
],
'facebook' => [
@@ -57,7 +57,7 @@ return [
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => '/auth/facebook/callback',
'color' => '#0165E1',
- 'name' => 'Facebook'
+ 'name' => 'Facebook',
],
'linkedin-openid' => [
@@ -65,10 +65,10 @@ return [
'client_secret' => env('LINKEDIN_CLIENT_SECRET'),
'redirect' => '/auth/linkedin-openid/callback',
'color' => '#0a66c2',
- 'name' => 'Linkedin'
+ 'name' => 'Linkedin',
],
//
'enabled_login_providers' => env('ENABLED_LOGIN_PROVIDERS', ''),
- 'ai_chat_enabled' => env('AI_CHAT_ENABLED', false)
+ 'ai_chat_enabled' => env('AI_CHAT_ENABLED', false),
];
diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php
index 1cf8c49..78c05f1 100644
--- a/database/factories/TransactionFactory.php
+++ b/database/factories/TransactionFactory.php
@@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\Factories\Factory;
*/
class TransactionFactory extends Factory
{
- protected static ?string $transaction_type;
+ protected static ?string $transaction_type;
/**
* Define the model's default state.
@@ -24,15 +24,15 @@ class TransactionFactory extends Factory
return [
'symbol' => $this->faker->randomElement(['AAPL', 'GOOG', 'AMZN']),
'transaction_type' => $transaction_type,
- 'portfolio_id' => Portfolio::factory()->create()->id,
+ 'portfolio_id' => Portfolio::factory()->create()->id,
'date' => $this->faker->date('Y-m-d'),
'quantity' => 1,
- 'cost_basis' => $transaction_type == 'BUY'
+ 'cost_basis' => $transaction_type == 'BUY'
? $this->faker->randomFloat(2, 10, 500)
- : null,
- 'sale_price' => $transaction_type == 'SELL'
+ : null,
+ 'sale_price' => $transaction_type == 'SELL'
? $this->faker->randomFloat(2, 10, 500)
- : null,
+ : null,
];
}
@@ -83,7 +83,7 @@ class TransactionFactory extends Factory
return $this->state(fn (array $attributes) => [
'transaction_type' => 'BUY',
'cost_basis' => $this->faker->randomFloat(2, 10, 500),
- 'sale_price' => null
+ 'sale_price' => null,
]);
}
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
index 76c211a..13a4102 100644
--- a/database/factories/UserFactory.php
+++ b/database/factories/UserFactory.php
@@ -31,7 +31,7 @@ class UserFactory extends Factory
'two_factor_secret' => null,
'two_factor_recovery_codes' => null,
'remember_token' => Str::random(10),
- 'profile_photo_path' => null
+ 'profile_photo_path' => null,
];
}
diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php
index f60b77d..2c296af 100644
--- a/database/migrations/0001_01_01_000000_create_users_table.php
+++ b/database/migrations/0001_01_01_000000_create_users_table.php
@@ -12,7 +12,7 @@ return new class extends Migration
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
- $table->uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
diff --git a/database/migrations/2021_01_30_102537_create_portfolios_table.php b/database/migrations/2021_01_30_102537_create_portfolios_table.php
index 3ee0a89..186d92f 100644
--- a/database/migrations/2021_01_30_102537_create_portfolios_table.php
+++ b/database/migrations/2021_01_30_102537_create_portfolios_table.php
@@ -14,7 +14,7 @@ return new class extends Migration
public function up()
{
Schema::create('portfolios', function (Blueprint $table) {
- $table->uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->string('title');
$table->text('notes')->nullable();
$table->boolean('wishlist')->default(false);
@@ -31,4 +31,4 @@ return new class extends Migration
{
Schema::dropIfExists('portfolios');
}
-};
\ No newline at end of file
+};
diff --git a/database/migrations/2021_01_30_112537_create_portfolio_users_table.php b/database/migrations/2021_01_30_112537_create_portfolio_users_table.php
index 6205c04..000dd1d 100644
--- a/database/migrations/2021_01_30_112537_create_portfolio_users_table.php
+++ b/database/migrations/2021_01_30_112537_create_portfolio_users_table.php
@@ -1,10 +1,10 @@
MarketDataSeeder::class,
- '--force' => true
+ '--force' => true,
]);
}
diff --git a/database/migrations/2021_02_25_041227_create_daily_change_table.php b/database/migrations/2021_02_25_041227_create_daily_change_table.php
index 5e0e413..6259975 100644
--- a/database/migrations/2021_02_25_041227_create_daily_change_table.php
+++ b/database/migrations/2021_02_25_041227_create_daily_change_table.php
@@ -1,9 +1,9 @@
uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->date('date');
$table->foreignIdFor(MarketData::class, 'symbol');
$table->float('dividend_amount', 12, 4);
diff --git a/database/migrations/2021_02_25_041246_create_splits_table.php b/database/migrations/2021_02_25_041246_create_splits_table.php
index ffcf8b0..774c422 100644
--- a/database/migrations/2021_02_25_041246_create_splits_table.php
+++ b/database/migrations/2021_02_25_041246_create_splits_table.php
@@ -1,9 +1,9 @@
uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->date('date');
$table->foreignIdFor(MarketData::class, 'symbol');
$table->float('split_amount', 12, 4);
diff --git a/database/migrations/2021_02_25_041257_create_transactions_table.php b/database/migrations/2021_02_25_041257_create_transactions_table.php
index f5a7497..b7640eb 100644
--- a/database/migrations/2021_02_25_041257_create_transactions_table.php
+++ b/database/migrations/2021_02_25_041257_create_transactions_table.php
@@ -1,10 +1,10 @@
uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->foreignIdFor(MarketData::class, 'symbol');
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
$table->string('transaction_type', 15);
diff --git a/database/migrations/2021_09_06_014744_create_holdings_table.php b/database/migrations/2021_09_06_014744_create_holdings_table.php
index e43f690..450624e 100644
--- a/database/migrations/2021_09_06_014744_create_holdings_table.php
+++ b/database/migrations/2021_09_06_014744_create_holdings_table.php
@@ -1,10 +1,10 @@
uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->foreignIdFor(Portfolio::class, 'portfolio_id')->constrained()->onDelete('cascade');
$table->foreignIdFor(MarketData::class, 'symbol');
$table->float('quantity', 12, 4);
diff --git a/database/migrations/2024_10_19_155635_create_connected_accounts_table.php b/database/migrations/2024_10_19_155635_create_connected_accounts_table.php
index 6e7269b..bd9378c 100644
--- a/database/migrations/2024_10_19_155635_create_connected_accounts_table.php
+++ b/database/migrations/2024_10_19_155635_create_connected_accounts_table.php
@@ -1,9 +1,9 @@
uuid('id')->primary();
+ $table->uuid('id')->primary();
$table->foreignIdFor(User::class, 'user_id')->constrained()->onDelete('cascade');
$table->morphs('chatable');
$table->string('role');
diff --git a/database/seeders/MarketDataSeeder.php b/database/seeders/MarketDataSeeder.php
index 90d81b9..8956fca 100644
--- a/database/seeders/MarketDataSeeder.php
+++ b/database/seeders/MarketDataSeeder.php
@@ -2,9 +2,9 @@
namespace Database\Seeders;
+use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
-use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class MarketDataSeeder extends Seeder
{
@@ -14,7 +14,7 @@ class MarketDataSeeder extends Seeder
* Run the database seeds.
*/
public function run(): void
- {
+ {
$chunkSize = 500;
// Path to the CSV file
@@ -29,7 +29,7 @@ class MarketDataSeeder extends Seeder
while (($row = fgetcsv($handle, 0, ',')) !== false) {
- if (!$header) {
+ if (! $header) {
// header must be the first row
$header = $row;
@@ -40,31 +40,31 @@ class MarketDataSeeder extends Seeder
$data = array_combine($header, $row);
$rows[] = [
- 'symbol' => $data['symbol'],
- 'name' => $data['name'],
+ 'symbol' => $data['symbol'],
+ 'name' => $data['name'],
'meta_data' => json_encode([
'country' => $data['country'],
'first_trade_year' => $data['first_trade_year'],
'sector' => $data['sector'],
'industry' => $data['industry'],
- ]),
+ ]),
];
$rowCount++;
if ($rowCount % $chunkSize == 0) {
DB::table('market_data')->insertOrIgnore($rows);
- $rows = [];
+ $rows = [];
}
} catch (\Throwable $e) {
-
- throw new \Exception('Error: '. $e->getMessage());
+
+ throw new \Exception('Error: '.$e->getMessage());
}
}
}
// final clean up
- if (!empty($rows)) {
+ if (! empty($rows)) {
DB::table('market_data')->insertOrIgnore($rows);
}
diff --git a/routes/api.php b/routes/api.php
index 32395b5..a00de46 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -1,11 +1,11 @@
name('api.')->group(function () {
@@ -25,4 +25,4 @@ Route::middleware(['auth:sanctum'])->name('api.')->group(function () {
// market data
Route::get('/market-data/{symbol}', [MarketDataController::class, 'show'])->name('market-data.show');
-});
\ No newline at end of file
+});
diff --git a/routes/console.php b/routes/console.php
index 2382db8..b002734 100644
--- a/routes/console.php
+++ b/routes/console.php
@@ -1,35 +1,34 @@
weekdays()->everyMinute();
/**
- *
* This scheduled job records daily changes to your portfolios every weekday
*/
Schedule::command(CaptureDailyChange::class)->dailyAt(config('investbrain.daily_change_time_of_day'))->weekdays();
/**
- *
* Refreshes dividend data for your holdings (and syncs new dividends to holdings)
*/
Schedule::command(RefreshDividendData::class)->daily()->days([1, 3, 5]);
/**
- *
* Refreshes split data for your holdings (and creates new transactions for new splits)
*/
Schedule::command(RefreshSplitData::class)->weekly();
/**
- *
* Periodically reconciles your holdings with transactions and dividends
*/
Schedule::command(SyncHoldingData::class)->yearly();
diff --git a/routes/web.php b/routes/web.php
index 0c60cd7..492a4b3 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,19 +1,19 @@
actingAs($this->user);
Transaction::factory(10)->create();
-
+
$this->actingAs($this->user)
->getJson(route('api.holding.index', ['page' => 1, 'itemsPerPage' => 5]))
->assertOk()
->assertJsonStructure([
'data' => [['id', 'symbol', 'portfolio_id', 'total_market_value', 'dividends_earned']],
'meta' => ['current_page', 'last_page', 'total'],
- 'links' => ['first', 'last', 'prev', 'next']
+ 'links' => ['first', 'last', 'prev', 'next'],
]);
}
@@ -45,7 +46,7 @@ class HoldingsTest extends TestCase
// create transactions with existing user
$this->actingAs($this->user);
Transaction::factory(10)->create();
-
+
// Create a new user
$this->actingAs($user = User::factory()->create());
Transaction::factory(1)->create();
@@ -88,14 +89,14 @@ class HoldingsTest extends TestCase
$transaction = Transaction::factory()->create();
$data = [
- 'reinvest_dividends' => true
+ 'reinvest_dividends' => true,
];
$this->actingAs($this->user)
->putJson(route('api.holding.update', ['portfolio' => $transaction->portfolio_id, 'symbol' => $transaction->symbol]), $data)
->assertOk()
->assertJsonFragment([
- 'reinvest_dividends' => true
+ 'reinvest_dividends' => true,
]);
}
@@ -105,7 +106,7 @@ class HoldingsTest extends TestCase
$transaction = Transaction::factory()->create();
$data = [
- 'reinvest_dividends' => true
+ 'reinvest_dividends' => true,
];
$otherUser = User::factory()->create();
@@ -113,5 +114,4 @@ class HoldingsTest extends TestCase
->putJson(route('api.holding.update', ['portfolio' => $transaction->portfolio_id, 'symbol' => $transaction->symbol]), $data)
->assertForbidden();
}
-
-}
\ No newline at end of file
+}
diff --git a/tests/Api/PortfoliosTest.php b/tests/Api/PortfoliosTest.php
index 44dfd5b..1339a2f 100644
--- a/tests/Api/PortfoliosTest.php
+++ b/tests/Api/PortfoliosTest.php
@@ -2,16 +2,17 @@
namespace Tests\Api;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Tests\TestCase;
class PortfoliosTest extends TestCase
{
use RefreshDatabase;
protected User $user;
+
protected Portfolio $portfolio;
protected function setUp(): void
@@ -27,14 +28,14 @@ class PortfoliosTest extends TestCase
$this->actingAs($this->user);
Portfolio::factory(10)->create();
-
+
$this->actingAs($this->user)
->getJson(route('api.portfolio.index', ['page' => 1, 'itemsPerPage' => 5]))
->assertOk()
->assertJsonStructure([
'data' => [['id', 'title', 'owner', 'holdings', 'transactions']],
'meta' => ['current_page', 'last_page', 'total'],
- 'links' => ['first', 'last', 'prev', 'next']
+ 'links' => ['first', 'last', 'prev', 'next'],
]);
}
@@ -43,7 +44,7 @@ class PortfoliosTest extends TestCase
// create portfolios with existing user
$this->actingAs($this->user);
Portfolio::factory(10)->create();
-
+
// Create a new user
$this->actingAs($user = User::factory()->create());
Portfolio::factory(1)->create();
@@ -61,12 +62,12 @@ class PortfoliosTest extends TestCase
public function test_can_create_a_portfolio()
{
$data = Portfolio::factory()->make()->toArray();
-
+
$this->actingAs($this->user)
->postJson(route('api.portfolio.store'), $data)
->assertCreated()
->assertJsonStructure(['id', 'title', 'owner']);
-
+
$this->assertDatabaseHas('portfolios', ['title' => $data['title']]);
}
@@ -102,12 +103,12 @@ class PortfoliosTest extends TestCase
$this->actingAs($this->user);
$portfolio = Portfolio::factory()->create();
-
+
$this->actingAs($this->user)
->putJson(route('api.portfolio.update', $portfolio), $updatedData)
->assertOk()
->assertJson($updatedData);
-
+
$this->assertDatabaseHas('portfolios', $updatedData);
}
@@ -126,7 +127,7 @@ class PortfoliosTest extends TestCase
->putJson(route('api.portfolio.update', $portfolio), ['title' => 'A brand new updated title'])
->assertOk()
->assertJsonFragment([
- 'title' => 'A brand new updated title'
+ 'title' => 'A brand new updated title',
]);
}
@@ -185,7 +186,7 @@ class PortfoliosTest extends TestCase
$this->actingAs($this->user)
->deleteJson(route('api.portfolio.destroy', $portfolio))
->assertNoContent();
-
+
$this->assertDatabaseMissing('portfolios', ['id' => $portfolio->id]);
}
@@ -199,4 +200,4 @@ class PortfoliosTest extends TestCase
->deleteJson(route('api.portfolio.destroy', $portfolio))
->assertForbidden();
}
-}
\ No newline at end of file
+}
diff --git a/tests/Api/TransactionsTest.php b/tests/Api/TransactionsTest.php
index 321e101..19435e3 100644
--- a/tests/Api/TransactionsTest.php
+++ b/tests/Api/TransactionsTest.php
@@ -2,17 +2,18 @@
namespace Tests\Api;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Portfolio;
use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Tests\TestCase;
class TransactionsTest extends TestCase
{
use RefreshDatabase;
protected User $user;
+
protected Portfolio $portfolio;
protected function setUp(): void
@@ -21,7 +22,7 @@ class TransactionsTest extends TestCase
// make user
$this->user = User::factory()->create();
-
+
// make portfolio
$this->portfolio = Portfolio::factory()->makeOne();
$this->portfolio->setOwnerIdAttribute($this->user->id);
@@ -33,14 +34,14 @@ class TransactionsTest extends TestCase
$this->actingAs($this->user);
Transaction::factory(10)->create();
-
+
$this->actingAs($this->user)
->getJson(route('api.transaction.index', ['page' => 1, 'itemsPerPage' => 5]))
->assertOk()
->assertJsonStructure([
'data' => [['id', 'symbol', 'transaction_type', 'portfolio_id', 'date']],
'meta' => ['current_page', 'last_page', 'total'],
- 'links' => ['first', 'last', 'prev', 'next']
+ 'links' => ['first', 'last', 'prev', 'next'],
]);
}
@@ -49,7 +50,7 @@ class TransactionsTest extends TestCase
// create transactions with existing user
$this->actingAs($this->user);
Transaction::factory(10)->create();
-
+
// Create a new user
$this->actingAs($user = User::factory()->create());
Transaction::factory(1)->create();
@@ -88,7 +89,7 @@ class TransactionsTest extends TestCase
'quantity',
'date',
'cost_basis',
- 'sale_price'
+ 'sale_price',
]);
}
@@ -97,7 +98,7 @@ class TransactionsTest extends TestCase
$this->actingAs($this->user)
->postJson(route('api.transaction.store'), [
'portfolio_id' => $this->portfolio->id,
- 'symbol' => null
+ 'symbol' => null,
])
->assertUnprocessable()
->assertJsonValidationErrors(['symbol']);
@@ -133,7 +134,7 @@ class TransactionsTest extends TestCase
'symbol' => 'ZZZ',
'transaction_type' => 'BUY',
'cost_basis' => 200.19,
- 'quantity' => 5
+ 'quantity' => 5,
];
$this->actingAs($this->user)
@@ -162,7 +163,7 @@ class TransactionsTest extends TestCase
->putJson(route('api.transaction.update', $transaction), ['symbol' => 'ZZZ'])
->assertOk()
->assertJsonFragment([
- 'symbol' => 'ZZZ'
+ 'symbol' => 'ZZZ',
]);
}
@@ -198,4 +199,4 @@ class TransactionsTest extends TestCase
->deleteJson(route('api.transaction.destroy', $transaction))
->assertForbidden();
}
-}
\ No newline at end of file
+}
diff --git a/tests/ApiTokenPermissionsTest.php b/tests/ApiTokenPermissionsTest.php
index 9afd10d..e268ef5 100644
--- a/tests/ApiTokenPermissionsTest.php
+++ b/tests/ApiTokenPermissionsTest.php
@@ -8,7 +8,6 @@ use Illuminate\Support\Str;
use Laravel\Jetstream\Features;
use Laravel\Jetstream\Http\Livewire\ApiTokenManager;
use Livewire\Livewire;
-use Tests\TestCase;
class ApiTokenPermissionsTest extends TestCase
{
diff --git a/tests/AuthenticationTest.php b/tests/AuthenticationTest.php
index 97f35d1..b54c9c7 100644
--- a/tests/AuthenticationTest.php
+++ b/tests/AuthenticationTest.php
@@ -4,7 +4,6 @@ namespace Tests;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
-use Tests\TestCase;
class AuthenticationTest extends TestCase
{
diff --git a/tests/BrowserSessionsTest.php b/tests/BrowserSessionsTest.php
index 2b540a4..af975f6 100644
--- a/tests/BrowserSessionsTest.php
+++ b/tests/BrowserSessionsTest.php
@@ -6,7 +6,6 @@ use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Jetstream\Http\Livewire\LogoutOtherBrowserSessionsForm;
use Livewire\Livewire;
-use Tests\TestCase;
class BrowserSessionsTest extends TestCase
{
diff --git a/tests/CaptureDailyChangeTest.php b/tests/CaptureDailyChangeTest.php
index 41d6596..1b9db54 100644
--- a/tests/CaptureDailyChangeTest.php
+++ b/tests/CaptureDailyChangeTest.php
@@ -2,19 +2,18 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
-use App\Models\Portfolio;
use App\Models\DailyChange;
+use App\Models\Portfolio;
use App\Models\Transaction;
-use Illuminate\Support\Facades\Artisan;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Facades\Artisan;
class CaptureDailyChangeTest extends TestCase
{
use RefreshDatabase;
- public function setUp(): void
+ protected function setUp(): void
{
parent::setUp();
@@ -25,8 +24,6 @@ class CaptureDailyChangeTest extends TestCase
$this->transaction = Transaction::factory()->sell()->lastMonth()->portfolio($this->portfolio->id)->symbol('AAPL')->create();
}
- /**
- */
public function test_daily_change_for_portfolios()
{
// Run the command
@@ -47,10 +44,10 @@ class CaptureDailyChangeTest extends TestCase
$this->assertCount(1, $daily_change);
$this->assertEqualsWithDelta(
- $this->transaction->sale_price - $this->transaction->cost_basis,
+ $this->transaction->sale_price - $this->transaction->cost_basis,
$daily_change->first()->realized_gains,
0.01
);
-
+
}
}
diff --git a/tests/ConnectedAccountTest.php b/tests/ConnectedAccountTest.php
index f97f600..af5cbde 100644
--- a/tests/ConnectedAccountTest.php
+++ b/tests/ConnectedAccountTest.php
@@ -2,13 +2,12 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
+use App\Http\Controllers\ConnectedAccountController;
use App\Models\ConnectedAccount;
+use App\Models\User;
+use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Auth;
use Laravel\Socialite\Facades\Socialite;
-use Illuminate\Foundation\Testing\RefreshDatabase;
-use App\Http\Controllers\ConnectedAccountController;
class ConnectedAccountTest extends TestCase
{
@@ -19,7 +18,7 @@ class ConnectedAccountTest extends TestCase
protected function setUp(): void
{
parent::setUp();
- $this->controller = new ConnectedAccountController();
+ $this->controller = new ConnectedAccountController;
}
public function test_handle_provider_callback_with_already_connected_account()
@@ -33,7 +32,7 @@ class ConnectedAccountTest extends TestCase
'email' => 'alice@example.com',
'email_verified_at' => now(),
]);
- $providerUser = (object)[
+ $providerUser = (object) [
'id' => '67890',
'name' => 'Alice Smith',
'email' => 'alice@example.com',
@@ -47,9 +46,9 @@ class ConnectedAccountTest extends TestCase
'provider_id' => $providerUser->id,
'user_id' => $user->id,
'token' => $providerUser->token,
- 'verified_at' => now()
+ 'verified_at' => now(),
]);
-
+
Socialite::shouldReceive('driver')
->with($provider)
->andReturnSelf()
@@ -68,7 +67,7 @@ class ConnectedAccountTest extends TestCase
{
$provider = 'github';
config(['services.enabled_login_providers' => 'github,google']);
- $providerUser = (object)[
+ $providerUser = (object) [
'id' => '12345',
'name' => 'John Doe',
'email' => 'john@example.com',
@@ -109,7 +108,7 @@ class ConnectedAccountTest extends TestCase
'email' => 'jane@example.com',
'email_verified_at' => now(),
]);
- $providerUser = (object)[
+ $providerUser = (object) [
'id' => '54321',
'name' => 'Jane Doe',
'email' => 'jane@example.com',
@@ -164,5 +163,4 @@ class ConnectedAccountTest extends TestCase
$response->assertRedirect(route('dashboard'));
$response->assertSessionHas('toast');
}
-
}
diff --git a/tests/DashboardTest.php b/tests/DashboardTest.php
index 8a73069..c098ec0 100644
--- a/tests/DashboardTest.php
+++ b/tests/DashboardTest.php
@@ -2,19 +2,16 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Holding;
use App\Models\Portfolio;
use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class DashboardTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_user_has_portfolios(): void
{
$this->actingAs($user = User::factory()->create());
@@ -24,8 +21,6 @@ class DashboardTest extends TestCase
$this->assertCount(5, $user->portfolios);
}
- /**
- */
public function test_user_has_transactions(): void
{
$this->actingAs($user = User::factory()->create());
@@ -35,8 +30,6 @@ class DashboardTest extends TestCase
$this->assertCount(10, $user->transactions);
}
- /**
- */
public function test_user_has_holdings(): void
{
$this->actingAs($user = User::factory()->create());
@@ -48,22 +41,20 @@ class DashboardTest extends TestCase
$this->assertCount(1, $user->holdings);
}
- /**
- */
public function test_user_has_dashboard_metrics(): void
{
$this->actingAs($user = User::factory()->create());
$portfolio = Portfolio::factory()->create();
-
+
Transaction::factory(5)->buy()->lastYear()->portfolio($portfolio->id)->symbol('AAPL')->create();
$transaction = Transaction::factory()->sell()->lastMonth()->portfolio($portfolio->id)->symbol('AAPL')->create();
$metrics = Holding::query()
- ->myHoldings()
- ->withPortfolioMetrics()
- ->first();
-
+ ->myHoldings()
+ ->withPortfolioMetrics()
+ ->first();
+
$this->assertEqualsWithDelta(
$transaction->sale_price - $transaction->cost_basis,
$metrics->realized_gain_dollars,
diff --git a/tests/DeleteAccountTest.php b/tests/DeleteAccountTest.php
index 0551820..a75aac8 100644
--- a/tests/DeleteAccountTest.php
+++ b/tests/DeleteAccountTest.php
@@ -7,7 +7,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Jetstream\Features;
use Laravel\Jetstream\Http\Livewire\DeleteUserForm;
use Livewire\Livewire;
-use Tests\TestCase;
class DeleteAccountTest extends TestCase
{
diff --git a/tests/DividendsTest.php b/tests/DividendsTest.php
index 7478869..e116f5a 100644
--- a/tests/DividendsTest.php
+++ b/tests/DividendsTest.php
@@ -2,31 +2,27 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
-use App\Models\Split;
-use App\Models\Holding;
use App\Models\Dividend;
-use App\Models\Portfolio;
+use App\Models\Holding;
use App\Models\MarketData;
+use App\Models\Portfolio;
use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class DividendsTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_new_dividends_update_holding(): void
{
$this->actingAs($user = User::factory()->create());
-
+
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
$holding = Holding::query()->portfolio($portfolio->id)->symbol('ACME')->first();
-
+
$this->assertEquals(0, $holding->dividends_earned);
Dividend::refreshDividendData('ACME');
@@ -36,19 +32,17 @@ class DividendsTest extends TestCase
$this->assertEquals(4.95, $holding->dividends_earned);
}
- /**
- */
public function test_new_dividends_are_reinvested(): void
{
$this->actingAs($user = User::factory()->create());
-
+
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
$holding = Holding::query()->portfolio($portfolio->id)->symbol('ACME')->first();
$holding->reinvest_dividends = true;
$holding->save();
-
+
$this->assertEquals(0, $holding->dividends_earned);
Dividend::refreshDividendData('ACME');
@@ -56,18 +50,16 @@ class DividendsTest extends TestCase
$transactions = Transaction::where(['reinvested_dividend' => true])->symbol('ACME')->portfolio($portfolio->id)->get();
$market_data = MarketData::symbol('ACME')->first();
- $dividendsReinvested = $transactions->sum('quantity');
+ $dividendsReinvested = $transactions->sum('quantity');
$this->assertCount(3, $transactions);
$this->assertEqualsWithDelta(4.95, $dividendsReinvested * $market_data->market_value, 0.01);
}
- /**
- */
public function test_do_not_duplicate_recent_dividends(): void
{
$this->actingAs($user = User::factory()->create());
-
+
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
@@ -76,9 +68,9 @@ class DividendsTest extends TestCase
Dividend::create([
'symbol' => 'ACME',
'date' => now()->subDay(2),
- 'dividend_amount' => .01
+ 'dividend_amount' => .01,
]);
-
+
Dividend::refreshDividendData('ACME');
$this->assertCount(1, $holding->dividends);
diff --git a/tests/EmailVerificationTest.php b/tests/EmailVerificationTest.php
index 7d8ab37..4baa2cf 100644
--- a/tests/EmailVerificationTest.php
+++ b/tests/EmailVerificationTest.php
@@ -8,7 +8,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;
use Laravel\Fortify\Features;
-use Tests\TestCase;
class EmailVerificationTest extends TestCase
{
diff --git a/tests/FallbackInterfaceTest.php b/tests/FallbackInterfaceTest.php
index f2a6af2..7354801 100644
--- a/tests/FallbackInterfaceTest.php
+++ b/tests/FallbackInterfaceTest.php
@@ -1,25 +1,24 @@
-set('investbrain.provider', 'yahoo,alphavantage');
config()->set('investbrain.interfaces', [
@@ -29,16 +28,16 @@ class FallbackInterfaceTest extends TestCase
$yahooMock = Mockery::mock(YahooMarketData::class);
$yahooMock->shouldReceive('quote')
- ->andThrow(new \Exception("Yahoo failed"));
+ ->andThrow(new \Exception('Yahoo failed'));
$alphaMock = Mockery::mock(AlphaVantageMarketData::class);
$alphaMock->shouldReceive('quote')
- ->andReturn(new Quote(['market_value' => 10]));
+ ->andReturn(new Quote(['market_value' => 10]));
$this->app->instance(YahooMarketData::class, $yahooMock);
$this->app->instance(AlphaVantageMarketData::class, $alphaMock);
- $fallbackInterface = new FallbackInterface();
+ $fallbackInterface = new FallbackInterface;
$result = $fallbackInterface->quote('ACME');
@@ -47,7 +46,7 @@ class FallbackInterfaceTest extends TestCase
Log::shouldHaveReceived('warning')->with('Failed calling method quote (yahoo): Yahoo failed');
}
- public function testAllProvidersFail()
+ public function test_all_providers_fail()
{
config()->set('investbrain.provider', 'yahoo,alpha');
config()->set('investbrain.interfaces', [
@@ -57,16 +56,16 @@ class FallbackInterfaceTest extends TestCase
$yahooMock = Mockery::mock(YahooMarketData::class);
$yahooMock->shouldReceive('quote')
- ->andThrow(new \Exception("Yahoo failed"));
+ ->andThrow(new \Exception('Yahoo failed'));
$alphaMock = Mockery::mock(AlphaVantageMarketData::class);
$alphaMock->shouldReceive('quote')
- ->andThrow(new \Exception("Alpha failed"));
+ ->andThrow(new \Exception('Alpha failed'));
$this->app->instance(YahooMarketData::class, $yahooMock);
$this->app->instance(AlphaVantageMarketData::class, $alphaMock);
- $fallbackInterface = new FallbackInterface();
+ $fallbackInterface = new FallbackInterface;
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Could not get market data: Provider [alpha] is not a valid market data interface.');
@@ -76,4 +75,4 @@ class FallbackInterfaceTest extends TestCase
Log::shouldHaveReceived('warning')->with('Failed calling method quote (yahoo): Yahoo failed');
Log::shouldHaveReceived('warning')->with('Failed calling method quote (alpha): Alpha failed');
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImportExportTest.php b/tests/ImportExportTest.php
index 74882a6..9e4446a 100644
--- a/tests/ImportExportTest.php
+++ b/tests/ImportExportTest.php
@@ -2,20 +2,17 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
-use App\Models\Transaction;
-use Maatwebsite\Excel\Facades\Excel;
use App\Exports\BackupExport;
use App\Models\BackupImport as BackupImportModel;
+use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Maatwebsite\Excel\Facades\Excel;
class ImportExportTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_can_create_exports(): void
{
Excel::fake();
@@ -24,22 +21,20 @@ class ImportExportTest extends TestCase
Transaction::factory(5)->buy()->lastYear()->symbol('AAPL')->create();
- Excel::download(new BackupExport, now()->format('Y_m_d') . '_investbrain_backup.xlsx');
+ Excel::download(new BackupExport, now()->format('Y_m_d').'_investbrain_backup.xlsx');
- Excel::assertDownloaded(now()->format('Y_m_d') . '_investbrain_backup.xlsx', function(BackupExport $export) {
+ Excel::assertDownloaded(now()->format('Y_m_d').'_investbrain_backup.xlsx', function (BackupExport $export) {
return true;
});
}
- /**
- */
public function test_backup_job_completes(): void
{
$this->actingAs($user = User::factory()->create());
$backup_job = BackupImportModel::create([
'user_id' => auth()->user()->id,
- 'path' => __DIR__.'/0000_00_00_import_test.xlsx'
+ 'path' => __DIR__.'/0000_00_00_import_test.xlsx',
]);
$backup_job->refresh();
@@ -47,29 +42,25 @@ class ImportExportTest extends TestCase
$this->assertEquals('success', $backup_job->status);
}
- /**
- */
public function test_backup_job_inserts_rows(): void
{
$this->actingAs($user = User::factory()->create());
BackupImportModel::create([
'user_id' => auth()->user()->id,
- 'path' => __DIR__.'/0000_00_00_import_test.xlsx'
+ 'path' => __DIR__.'/0000_00_00_import_test.xlsx',
]);
$this->assertEquals(3, $user->transactions->count());
}
- /**
- */
public function test_backup_job_calculates_correct_holding_data(): void
{
$this->actingAs($user = User::factory()->create());
BackupImportModel::create([
'user_id' => auth()->user()->id,
- 'path' => __DIR__.'/0000_00_00_import_test.xlsx'
+ 'path' => __DIR__.'/0000_00_00_import_test.xlsx',
]);
$holding = $user->holdings->first();
diff --git a/tests/PasswordConfirmationTest.php b/tests/PasswordConfirmationTest.php
index 8ba8ad7..f3cb707 100644
--- a/tests/PasswordConfirmationTest.php
+++ b/tests/PasswordConfirmationTest.php
@@ -4,7 +4,6 @@ namespace Tests;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
-use Tests\TestCase;
class PasswordConfirmationTest extends TestCase
{
diff --git a/tests/PasswordResetTest.php b/tests/PasswordResetTest.php
index 1335434..d2d059e 100644
--- a/tests/PasswordResetTest.php
+++ b/tests/PasswordResetTest.php
@@ -7,7 +7,6 @@ use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Laravel\Fortify\Features;
-use Tests\TestCase;
class PasswordResetTest extends TestCase
{
diff --git a/tests/PortfolioPolicyTest.php b/tests/PortfolioPolicyTest.php
index 4b040e5..00593d6 100644
--- a/tests/PortfolioPolicyTest.php
+++ b/tests/PortfolioPolicyTest.php
@@ -2,27 +2,29 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
use App\Policies\PortfolioPolicy;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Facades\Auth;
class PortfolioPolicyTest extends TestCase
{
use RefreshDatabase;
protected $policy;
+
protected $owner;
+
protected $user;
+
protected $portfolio;
protected function setUp(): void
{
parent::setUp();
- $this->policy = new PortfolioPolicy();
+ $this->policy = new PortfolioPolicy;
$this->owner = User::factory()->create();
Auth::login($this->owner);
@@ -34,7 +36,7 @@ class PortfolioPolicyTest extends TestCase
$this->user->id => [
'full_access' => false,
'owner' => false,
- ]
+ ],
]);
}
@@ -109,5 +111,4 @@ class PortfolioPolicyTest extends TestCase
$result = $this->policy->owner($this->user, $this->portfolio);
$this->assertFalse($result, 'User should not be the owner');
}
-
}
diff --git a/tests/PortfoliosTest.php b/tests/PortfoliosTest.php
index 607bc4a..b9de91e 100644
--- a/tests/PortfoliosTest.php
+++ b/tests/PortfoliosTest.php
@@ -2,17 +2,14 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Portfolio;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PortfoliosTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_owner_is_assigned_to_portfolio_on_create(): void
{
$this->actingAs($user = User::factory()->create());
@@ -22,8 +19,6 @@ class PortfoliosTest extends TestCase
$this->assertEquals($user->id, $portfolio->owner_id);
}
- /**
- */
public function test_owner_can_be_forced_on_create(): void
{
$this->actingAs($user = User::factory()->create());
@@ -35,8 +30,6 @@ class PortfoliosTest extends TestCase
$this->assertEquals($user->id, $portfolio->owner_id);
}
- /**
- */
public function test_owner_cannot_be_changed_on_update(): void
{
$this->actingAs($owner = User::factory()->create());
@@ -49,6 +42,4 @@ class PortfoliosTest extends TestCase
$this->assertEquals($owner->id, $portfolio->owner_id);
}
-
-
}
diff --git a/tests/ProfileInformationTest.php b/tests/ProfileInformationTest.php
index bf957b2..858983c 100644
--- a/tests/ProfileInformationTest.php
+++ b/tests/ProfileInformationTest.php
@@ -6,7 +6,6 @@ use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Jetstream\Http\Livewire\UpdateProfileInformationForm;
use Livewire\Livewire;
-use Tests\TestCase;
class ProfileInformationTest extends TestCase
{
diff --git a/tests/RegistrationTest.php b/tests/RegistrationTest.php
index 9cb0487..4cf1154 100644
--- a/tests/RegistrationTest.php
+++ b/tests/RegistrationTest.php
@@ -5,7 +5,6 @@ namespace Tests;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Fortify\Features;
use Laravel\Jetstream\Jetstream;
-use Tests\TestCase;
class RegistrationTest extends TestCase
{
diff --git a/tests/SplitsTest.php b/tests/SplitsTest.php
index ba1b5eb..e32b100 100644
--- a/tests/SplitsTest.php
+++ b/tests/SplitsTest.php
@@ -2,32 +2,29 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
-use App\Models\Split;
use App\Models\Holding;
use App\Models\Portfolio;
+use App\Models\Split;
use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class SplitsTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_splits_create_new_transaction(): void
{
$this->actingAs($user = User::factory()->create());
-
+
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
// manually reset the split last sync date (which is set when the holding is created)
Holding::query()->portfolio($portfolio->id)->symbol('ACME')->update([
- 'splits_synced_at' => null
+ 'splits_synced_at' => null,
]);
-
+
Split::refreshSplitData('ACME');
$transactions = Transaction::query()->symbol('ACME')->portfolio($portfolio->id)->get();
@@ -35,12 +32,10 @@ class SplitsTest extends TestCase
$this->assertCount(2, $transactions);
}
- /**
- */
public function test_splits_do_not_create_new_transaction_if_already_synced(): void
{
$this->actingAs($user = User::factory()->create());
-
+
$portfolio = Portfolio::factory()->create();
Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create();
diff --git a/tests/SyncDailyChangeTest.php b/tests/SyncDailyChangeTest.php
index a816b2d..759c852 100644
--- a/tests/SyncDailyChangeTest.php
+++ b/tests/SyncDailyChangeTest.php
@@ -2,23 +2,20 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
-use App\Models\Holding;
-use Carbon\CarbonPeriod;
-use App\Models\Portfolio;
use App\Models\DailyChange;
+use App\Models\Holding;
+use App\Models\Portfolio;
use App\Models\Transaction;
+use App\Models\User;
+use Carbon\CarbonPeriod;
+use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Artisan;
-use Illuminate\Foundation\Testing\RefreshDatabase;
class SyncDailyChangeTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_can_sync_daily_change_history(): void
{
$this->actingAs($user = User::factory()->create());
@@ -36,16 +33,14 @@ class SyncDailyChangeTest extends TestCase
$count_of_daily_changes = $portfolio->daily_change()->count('date');
$days_between_now_and_first_trans = (int) CarbonPeriod::create(
- $portfolio->transactions()->min('date'),
+ $portfolio->transactions()->min('date'),
now()->isBefore(Carbon::parse(config('investbrain.daily_change_time_of_day'))) ? now()->subDay() : now()
)->filter('isWeekday')
- ->count();
+ ->count();
$this->assertEquals($count_of_daily_changes, $days_between_now_and_first_trans);
}
- /**
- */
public function test_cost_basis_is_synced(): void
{
$this->actingAs($user = User::factory()->create());
@@ -56,33 +51,31 @@ class SyncDailyChangeTest extends TestCase
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$holding = Holding::symbol('ACME')->portfolio($portfolio->id)->first();
$daily_change = DailyChange::whereDate('date', '<=', $first_transaction->date->addDays(2))
- ->whereDate('date', '>=', $first_transaction->date->subDays(2))
- ->orderByDesc('date')
- ->first();
+ ->whereDate('date', '>=', $first_transaction->date->subDays(2))
+ ->orderByDesc('date')
+ ->first();
$this->assertEquals($holding->average_cost_basis, $daily_change->total_cost_basis);
$second_transaction = Transaction::factory()->buy()->lastYear()->portfolio($portfolio->id)->symbol('ACME')->create();
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$daily_change = DailyChange::whereDate('date', '<=', $second_transaction->date->addDays(2))
- ->whereDate('date', '>=', $second_transaction->date->subDays(2))
- ->orderByDesc('date')
- ->first();
-
- $this->assertEqualsWithDelta($first_transaction->cost_basis + $second_transaction->cost_basis, $daily_change->total_cost_basis, 0.01);
+ ->whereDate('date', '>=', $second_transaction->date->subDays(2))
+ ->orderByDesc('date')
+ ->first();
+
+ $this->assertEqualsWithDelta($first_transaction->cost_basis + $second_transaction->cost_basis, $daily_change->total_cost_basis, 0.01);
$third_transaction = Transaction::factory(2)->sell()->lastMonth()->portfolio($portfolio->id)->symbol('ACME')->create()->first();
Artisan::call('sync:daily-change', ['portfolio_id' => $portfolio->id]);
$daily_change = DailyChange::whereDate('date', '<=', $third_transaction->date->addDays(2))
- ->whereDate('date', '>=', $third_transaction->date->subDays(2))
- ->orderByDesc('date')
- ->first();
+ ->whereDate('date', '>=', $third_transaction->date->subDays(2))
+ ->orderByDesc('date')
+ ->first();
- $this->assertEquals(0, $daily_change->total_cost_basis);
+ $this->assertEquals(0, $daily_change->total_cost_basis);
}
- /**
- */
public function test_sales_are_captured_as_realized_gains(): void
{
$this->actingAs($user = User::factory()->create());
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 43559b9..f6e21ae 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -9,14 +9,14 @@ abstract class TestCase extends BaseTestCase
protected function setUp(): void
{
parent::setUp();
-
+
//
}
-
+
protected function tearDown(): void
{
parent::tearDown();
-
+
//
}
}
diff --git a/tests/TransactionsTest.php b/tests/TransactionsTest.php
index 8020499..8c387a3 100644
--- a/tests/TransactionsTest.php
+++ b/tests/TransactionsTest.php
@@ -2,19 +2,16 @@
namespace Tests;
-use Tests\TestCase;
-use App\Models\User;
use App\Models\Holding;
use App\Models\Portfolio;
use App\Models\Transaction;
+use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
class TransactionsTest extends TestCase
{
use RefreshDatabase;
- /**
- */
public function test_can_create_a_transaction(): void
{
$this->actingAs($user = User::factory()->create());
@@ -24,8 +21,6 @@ class TransactionsTest extends TestCase
$this->assertNotNull($transaction);
}
- /**
- */
public function test_sales_calculate_cost_basis(): void
{
$this->actingAs($user = User::factory()->create());
@@ -37,8 +32,6 @@ class TransactionsTest extends TestCase
$this->assertNotNull($transaction->cost_basis);
}
- /**
- */
public function test_purchases_dont_have_sale_price(): void
{
$this->actingAs($user = User::factory()->create());
@@ -48,8 +41,6 @@ class TransactionsTest extends TestCase
$this->assertNull($transaction->sale_price);
}
- /**
- */
public function test_transaction_synced_to_holding(): void
{
$this->actingAs($user = User::factory()->create());
@@ -62,17 +53,17 @@ class TransactionsTest extends TestCase
$this->assertDatabaseHas('holdings', [
'portfolio_id' => $portfolio->id,
'symbol' => 'AAPL',
- 'quantity' => 4
+ 'quantity' => 4,
]);
$holding = Holding::where([
'portfolio_id' => $portfolio->id,
- 'symbol' => 'AAPL'
+ 'symbol' => 'AAPL',
])->first();
$this->assertEqualsWithDelta(
- $holding->realized_gain_dollars,
- $transaction->sale_price - $transaction->cost_basis,
+ $holding->realized_gain_dollars,
+ $transaction->sale_price - $transaction->cost_basis,
0.01
);
}
diff --git a/tests/TwoFactorAuthenticationSettingsTest.php b/tests/TwoFactorAuthenticationSettingsTest.php
index c380b23..cd5da7e 100644
--- a/tests/TwoFactorAuthenticationSettingsTest.php
+++ b/tests/TwoFactorAuthenticationSettingsTest.php
@@ -7,7 +7,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Fortify\Features;
use Laravel\Jetstream\Http\Livewire\TwoFactorAuthenticationForm;
use Livewire\Livewire;
-use Tests\TestCase;
class TwoFactorAuthenticationSettingsTest extends TestCase
{
diff --git a/tests/UpdatePasswordTest.php b/tests/UpdatePasswordTest.php
index 6f5bbde..a9ac139 100644
--- a/tests/UpdatePasswordTest.php
+++ b/tests/UpdatePasswordTest.php
@@ -7,7 +7,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Laravel\Jetstream\Http\Livewire\UpdatePasswordForm;
use Livewire\Livewire;
-use Tests\TestCase;
class UpdatePasswordTest extends TestCase
{