Skip to content

Commit

Permalink
Merge pull request #440 from range-of-motion/404-create-prototype-for…
Browse files Browse the repository at this point in the history
…-spa

Create prototype for SPA
  • Loading branch information
range-of-motion authored Nov 18, 2023
2 parents f2b27fe + 6e3b27f commit a04d106
Show file tree
Hide file tree
Showing 26 changed files with 679 additions and 13 deletions.
61 changes: 61 additions & 0 deletions app/Http/Controllers/Api/LogInController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\ApiKey;
use App\Models\LoginAttempt;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;

class LogInController extends Controller
{
public function __invoke(Request $request): JsonResponse
{
$request->validate([
'email' => ['required'],
'password' => ['required'],
]);

if (
Auth::attempt([
'email' => $request->input('email'),
'password' => $request->input('password'),
])
) {
$userId = Auth::user()->id;

LoginAttempt::create([
'user_id' => $userId,
'ip' => $request->ip(),
'failed' => false,
]);

$apiKey = ApiKey::create([
'user_id' => $userId,
'token' => Str::random(32),
]);

return response()
->json([
'token' => $apiKey->token,
]);
} else {
$userByEmail = User::query()
->where('email', $request->input('email'))
->first();

LoginAttempt::create([
'user_id' => $userByEmail ? $userByEmail->id : null,
'ip' => $request->ip(),
'failed' => true,
]);

return response()
->json(['error' => 'UNABLE_TO_LOG_IN']);
}
}
}
67 changes: 67 additions & 0 deletions app/Http/Controllers/Api/TransactionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Resources\TransactionResource;
use App\Models\Earning;
use App\Models\Spending;
use Illuminate\Http\Request;

class TransactionController extends Controller
{
public function index(Request $request)
{
/** @var ApiKey $apiKey */
$apiKey = $request->get('apiKey');

$transactions = collect();

foreach (Earning::query()->where('space_id', $apiKey->user->spaces()->first()->id)->get() as $earning) {
$transactions->push($earning);
}

foreach (Spending::query()->where('space_id', $apiKey->user->spaces()->first()->id)->get() as $spending) {
$transactions->push($spending);
}

return TransactionResource::collection($transactions);
}

public function store(Request $request)
{
/** @var ApiKey $apiKey */
$apiKey = $request->get('apiKey');

$spaceId = $apiKey->user->spaces()->first()->id;

$request->validate([
'type' => 'in:earning,spending',
// TODO
]);

if ($request->input('type') === 'earning') {
Earning::create([
'space_id' => $spaceId,
'recurring_id' => null, // TODO
'happened_on' => $request->input('happened_on'),
'description' => $request->input('description'),
'amount' => $request->input('amount')
]);
}

if ($request->input('type') === 'spending') {
Spending::create([
'space_id' => $spaceId,
'import_id' => null, // TODO
'recurring_id' => null, // TODO
'tag_id' => null, // TODO
'happened_on' => $request->input('happened_on'),
'description' => $request->input('description'),
'amount' => $request->input('amount')
]);
}

return [];
}
}
11 changes: 11 additions & 0 deletions app/Http/Controllers/PrototypeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Http\Controllers;

class PrototypeController extends Controller
{
public function __invoke()
{
return view('prototype');
}
}
2 changes: 2 additions & 0 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Kernel extends HttpKernel
'api' => [
'throttle:60,1',
'bindings',
\App\Http\Middleware\CheckIfSpaPrototypeIsEnabled::class,
],
];

Expand All @@ -58,6 +59,7 @@ class Kernel extends HttpKernel
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'resolve-api-key' => \App\Http\Middleware\ResolveApiKey::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'stripe' => \App\Http\Middleware\RedirectIfStripeAbsent::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
Expand Down
19 changes: 19 additions & 0 deletions app/Http/Middleware/CheckIfSpaPrototypeIsEnabled.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class CheckIfSpaPrototypeIsEnabled
{
public function handle(Request $request, Closure $next): Response
{
if (!config('app.spa_prototype_enabled')) {
abort(404);
}

return $next($request);
}
}
26 changes: 26 additions & 0 deletions app/Http/Middleware/ResolveApiKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace App\Http\Middleware;

use App\Models\ApiKey;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ResolveApiKey
{
public function handle(Request $request, Closure $next): Response
{
$apiKey = ApiKey::query()
->where('token', $request->header('api-key'))
->first();

if (!$apiKey) {
abort(401);
}

$request->attributes->add(['apiKey' => $apiKey]);

return $next($request);
}
}
23 changes: 23 additions & 0 deletions app/Http/Resources/TransactionResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Http\Resources;

use App\Models\Earning;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

class TransactionResource extends JsonResource
{
public function toArray(Request $request): array
{
$isEarning = get_class($this->resource) === Earning::class;

return [
'id' => $this->id,
'type' => $isEarning ? 'earning' : 'spending',
'happened_on' => $this->happened_on,
'description' => $this->description,
'amount' => $this->amount,
];
}
}
23 changes: 23 additions & 0 deletions app/Models/ApiKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ApiKey extends Model
{
use HasFactory;

protected $guarded = [];

/**
* Relationships
*/

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
3 changes: 3 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

use App\Helper;
use App\Models\Space;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\ServiceProvider;
use Auth;

class AppServiceProvider extends ServiceProvider
{
public function boot()
{
JsonResource::withoutWrapping();

view()->composer('*', function ($view) {
$selectedSpace = session('space_id') ? Space::find(session('space_id')) : null;

Expand Down
2 changes: 2 additions & 0 deletions config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,6 @@
*/

'disable_registration' => env('DISABLE_REGISTRATION', false),

'spa_prototype_enabled' => env('SPA_PROTOTYPE_ENABLED', true),
];
16 changes: 16 additions & 0 deletions database/factories/ApiKeyFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class ApiKeyFactory extends Factory
{
public function definition(): array
{
return [
'token' => Str::random(32),
];
}
}
23 changes: 23 additions & 0 deletions database/migrations/2023_11_05_012524_create_api_keys_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('api_keys', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('token')->unique();
$table->timestamps();
});
}

public function down(): void
{
Schema::dropIfExists('api_keys');
}
};
Loading

0 comments on commit a04d106

Please sign in to comment.