Add fixtures and picks

This commit is contained in:
Hector Villarreal 2026-03-28 12:36:12 -06:00
parent 2f34917652
commit 09a1cb8c8e
10 changed files with 172 additions and 15 deletions

View file

@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -18,23 +19,25 @@ class AuthController extends Controller
'last_name' => ['required', 'string', 'max:255'], 'last_name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
'phone' => ['required', 'string', 'max:25'], 'phone' => ['required', 'string', 'max:25'],
'password' => ['required', 'string', 'min:8'], 'password' => ['required', 'string', 'min:8', 'confirmed'],
'device_name' => ['nullable', 'string', 'max:255'], 'username' => ['required', 'string', 'max:32', 'unique:users,username']
]); ]);
$user = User::create([ $user = User::create([
'username' => $validated['username'],
'first_name' => $validated['first_name'], 'first_name' => $validated['first_name'],
'last_name' => $validated['last_name'], 'last_name' => $validated['last_name'],
'email' => strtolower($validated['email']), 'email' => strtolower($validated['email']),
'phone' => $validated['phone'], 'phone' => $validated['phone'],
'password' => $validated['password'], 'password' => Hash::make($validated['password']),
]); ]);
Auth::login($user);
$request->session()->regenerate();
return response()->json([ return response()->json([
'message' => 'User registered successfully.', 'message' => 'User registered successfully.',
'token' => $user->createToken($validated['device_name'] ?? 'react-app')->plainTextToken, 'user' => $request->user(),
'token_type' => 'Bearer',
'user' => $user,
], 201); ], 201);
} }
@ -43,28 +46,33 @@ class AuthController extends Controller
$validated = $request->validate([ $validated = $request->validate([
'email' => ['required', 'string', 'email'], 'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'], 'password' => ['required', 'string'],
'device_name' => ['nullable', 'string', 'max:255'],
]); ]);
$user = User::query()->where('email', strtolower($validated['email']))->first(); $credentials = [
'email' => strtolower($validated['email']),
'password' => $validated['password'],
];
if ($user === null || ! Hash::check($validated['password'], $user->password)) { if (! Auth::attempt($credentials, $request->boolean('remember'))) {
throw ValidationException::withMessages([ throw ValidationException::withMessages([
'email' => ['Los datos no coinciden con nuestros registros.'], 'email' => ['Los datos no coinciden con nuestros registros.'],
]); ]);
} }
$request->session()->regenerate();
return response()->json([ return response()->json([
'message' => 'Login successful.', 'message' => 'Login successful.',
'token' => $user->createToken($validated['device_name'] ?? 'react-app')->plainTextToken, 'user' => $request->user(),
'token_type' => 'Bearer',
'user' => $user,
]); ]);
} }
public function logout(Request $request): JsonResponse public function logout(Request $request): JsonResponse
{ {
$request->user()->currentAccessToken()?->delete(); Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return response()->json([ return response()->json([
'message' => 'Logout successful.', 'message' => 'Logout successful.',

View file

@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Resources\FixtureResource;
use App\Models\Fixture;
class FixturesController extends Controller
{
public function index() {
$fixtures = Fixture::query()->get();
return response()->json(['fixtures' => FixtureResource::collection($fixtures)]);
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Pick;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class PicksController extends Controller
{
public function index(Request $request): JsonResponse
{
$picks = Pick::query()
->where('user_id', $request->user()->id)
->get([
'id',
'match_id',
'selection',
'points_awarded',
'submitted_at',
'graded_at',
]);
return response()->json([
'picks' => $picks,
]);
}
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'match_id' => ['required', 'integer', 'exists:matches,id'],
'selection' => ['required', Rule::in(['home', 'draw', 'away'])],
]);
$pick = Pick::updateOrCreate(
[
'user_id' => $request->user()->id,
'match_id' => $validated['match_id'],
],
[
'selection' => $validated['selection'],
'submitted_at' => now(),
]
);
return response()->json(
['pick' => $pick],
$pick->wasRecentlyCreated ? 201 : 200
);
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Resources;
use App\Models\Fixture;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* Class FixtureResource
*
* @mixin Fixture
*/
class FixtureResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'home_team' => $this->homeTeam->name,
'away_team' => $this->awayTeam->name,
'starts_at' => $this->starts_at->timestamp,
'venue' => $this->venue,
'status' => $this->status,
'stage' => $this->stage,
'home_short_name' => $this->homeTeam->short_name,
'away_short_name' => $this->awayTeam->short_name,
];
}
}

View file

@ -20,6 +20,13 @@ class Pick extends Model
/** /**
* @return array<string, string> * @return array<string, string>
*/ */
protected $fillable = [
'user_id',
'match_id',
'selection',
'submitted_at',
];
protected function casts(): array protected function casts(): array
{ {
return [ return [

View file

@ -25,7 +25,7 @@ class User extends Authenticatable implements FilamentUser
protected $fillable = [ protected $fillable = [
'first_name', 'first_name',
'last_name', 'last_name',
'username',
'email', 'email',
'phone', 'phone',
'is_admin', 'is_admin',

View file

@ -19,7 +19,7 @@ return [
'allowed_methods' => ['*'], 'allowed_methods' => ['*'],
'allowed_origins' => ['*'], 'allowed_origins' => ['http://localhost:3001'],
'allowed_origins_patterns' => [], 'allowed_origins_patterns' => [],

View file

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('username', 32)->nullable()->after('id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('username');
});
}
};

View file

@ -20,6 +20,8 @@ services:
REDIS_CLIENT: phpredis REDIS_CLIENT: phpredis
REDIS_HOST: redis REDIS_HOST: redis
REDIS_PORT: 6379 REDIS_PORT: 6379
SANCTUM_STATEFUL_DOMAINS: "localhost:3001,127.0.0.1:3001,localhost:8080,127.0.0.1:8080"
SESSION_DOMAIN: localhost
volumes: volumes:
- ./:/var/www/html - ./:/var/www/html
- ./docker/php/conf.d/local.ini:/usr/local/etc/php/conf.d/local.ini:ro - ./docker/php/conf.d/local.ini:/usr/local/etc/php/conf.d/local.ini:ro

View file

@ -1,6 +1,8 @@
<?php <?php
use App\Http\Controllers\Api\AuthController; use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\FixturesController;
use App\Http\Controllers\Api\PicksController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -15,3 +17,10 @@ Route::prefix('auth')->group(function (): void {
}); });
}); });
}); });
Route::middleware('auth:sanctum')->group(function(): void {
Route::get('/fixtures', [FixturesController::class, 'index']);
Route::get('/picks', [PicksController::class, 'index']);
Route::post('/picks', [PicksController::class, 'store']);
});