feat: join hunt

This commit is contained in:
Leon 2025-12-19 12:40:19 +01:00
parent a323489a6f
commit 66cf5e1de8
6 changed files with 118 additions and 21 deletions

View File

@ -0,0 +1,9 @@
<?php
namespace App\Enums;
enum ParticipateStatus: string
{
case Started = 'started';
case Completed = 'completed';
}

View File

@ -57,17 +57,72 @@ class HuntsController extends Controller
/** /**
* Display the specified resource. * Display the specified resource.
*/ */
public function show($id) /**
* Display the specified resource.
*/
public function show(Request $request, $id)
{ {
$hunt = Hunts::find($id); $hunt = Hunts::with('steps')->find($id);
if (!$hunt) { if (!$hunt) {
return response()->json(['message' => 'Hunt not found'], 404); return response()->json(['message' => 'Hunt not found'], 404);
} }
$user = $request->user('sanctum');
if ($user) {
$participation = $user->participations()->where('hunt_id', $hunt->id)->first();
if ($participation) {
// User is a participant
$currentStepNumber = $participation->pivot->current_step_number;
// Filter steps
$hunt->setRelation('steps', $hunt->steps->filter(function ($step) use ($currentStepNumber) {
if ($step->step_number < $currentStepNumber) {
return true; // Show past steps fully
}
if ($step->step_number == $currentStepNumber) {
// Hide hints for current step
$step->makeHidden(['hint_text', 'hint_media_url']);
return true;
}
return false; // Hide future steps
})->values());
$hunt->participation_status = $participation->pivot;
} elseif ($hunt->creator_id !== $user->id) {
// User is logged in but not participant and not creator
// Show only first step or nothing specific (keep as is for now, maybe hide all steps or just show basic info)
// For now, let's just hide hints of all steps if not participating?
// Or maybe just show nothing of steps if not participating?
// Requirement: "fetch the hunt in question it retrieves me the hunt steps already passed and the next one" implying context of participation.
// If not participating, maybe just return basic hunt info without steps?
$hunt->unsetRelation('steps');
}
// If creator, show everything (default)
} else {
// Guest
$hunt->unsetRelation('steps');
}
return response()->json($hunt); return response()->json($hunt);
} }
public function participate(Request $request, $id)
{
$hunt = Hunts::findOrFail($id);
$user = $request->user();
if ($user->participations()->where('hunt_id', $hunt->id)->exists()) {
return response()->json(['message' => 'Already participating'], 409);
}
$hunt->participants()->attach($user->id, ['current_step_number' => 1, 'status' => 'started']);
return response()->json(['message' => 'Participation started'], 201);
}
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
*/ */

View File

@ -43,4 +43,11 @@ class Hunts extends Model
{ {
return $this->hasMany(HuntSteps::class, 'hunt_id'); return $this->hasMany(HuntSteps::class, 'hunt_id');
} }
public function participants()
{
return $this->belongsToMany(User::class, 'hunt_participants', 'hunt_id', 'user_id')
->withPivot(['current_step_number', 'status'])
->withTimestamps();
}
} }

View File

@ -54,4 +54,11 @@ class User extends Authenticatable
'role'=> UserRole::class 'role'=> UserRole::class
]; ];
} }
public function participations()
{
return $this->belongsToMany(Hunts::class, 'hunt_participants', 'user_id', 'hunt_id')
->withPivot(['current_step_number', 'status'])
->withTimestamps();
}
} }

View File

@ -0,0 +1,32 @@
<?php
use App\Enums\ParticipateStatus;
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::create('hunt_participants', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('hunt_id')->constrained('hunts')->cascadeOnDelete();
$table->unsignedSmallInteger('current_step_number')->default(1);
$table->string('status')->default(ParticipateStatus::Started->value);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('hunt_participants');
}
};

View File

@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HuntsController; use App\Http\Controllers\HuntsController;
use App\Http\Controllers\HuntStepsController;
use App\Http\Middleware\EnsureUserIsOrga; use App\Http\Middleware\EnsureUserIsOrga;
Route::prefix('auth')->group(function (): void { Route::prefix('auth')->group(function (): void {
@ -17,36 +18,22 @@ Route::prefix('auth')->group(function (): void {
// Route::get('{provider}/callback', [ProviderCallbackController::class, 'callback']); // Route::get('{provider}/callback', [ProviderCallbackController::class, 'callback']);
}); });
// List hunts
Route::get('hunts', [HuntsController::class, 'index']); Route::get('hunts', [HuntsController::class, 'index']);
Route::get('hunts/{hunt}', [HuntsController::class, 'show']); Route::get('hunts/{hunt}', [HuntsController::class, 'show']);
// Ensure is Orga
Route::middleware(['auth:sanctum', EnsureUserIsOrga::class])->group(function () { Route::middleware(['auth:sanctum', EnsureUserIsOrga::class])->group(function () {
Route::post('hunts', [HuntsController::class, 'store']); Route::post('hunts', [HuntsController::class, 'store']);
Route::post('hunts/{hunt}/steps', [\App\Http\Controllers\HuntStepsController::class, 'store']); Route::post('hunts/{hunt}/steps', [HuntStepsController::class, 'store']);
Route::match(['put', 'patch'], 'hunts/{hunt}', [HuntsController::class, 'update']); Route::match(['put', 'patch'], 'hunts/{hunt}', [HuntsController::class, 'update']);
Route::delete('hunts/{hunt}', [HuntsController::class, 'destroy']); Route::delete('hunts/{hunt}', [HuntsController::class, 'destroy']);
}); });
// Auth middleware
Route::middleware('auth:sanctum')->group(function (): void { Route::middleware('auth:sanctum')->group(function (): void {
// Route::get('/user', function (Request $request) {
// return $request->user();
// });
// User // User
Route::post('hunts/{hunt}/participate', [HuntsController::class, 'participate']);
Route::get('auth/me', [AuthController::class, 'me']); Route::get('auth/me', [AuthController::class, 'me']);
Route::post('auth/logout', [AuthController::class, 'logout']); Route::post('auth/logout', [AuthController::class, 'logout']);
// Route::middleware([EnsureAdmin::class])->group(function (): void {
// Route::apiResource('users', UserController::class);
// Route::apiResource('main-categories', MainCategoryController::class)->except(['index', 'show']);
// Route::apiResource('categories', CategoryController::class)->except(['index', 'show']);
// Route::apiResource('products', ProductsController::class)->except(['index', 'show']);
// Route::apiResource('product-variants', ProductVariantController::class);
// Route::apiResource('product-variant-images', ProductVariantImagesController::class);
// Route::apiResource('pickup-points', PickupPointsController::class)->only(['destroy']);
// Route::apiResource('pickup-point-schedules', PickupPointsSchedulesController::class)->except(['index', 'show']);
// });
}); });