feat: seeder
This commit is contained in:
parent
a421d21d47
commit
0f86c78248
|
|
@ -38,4 +38,99 @@ class HuntStepsController extends Controller
|
||||||
|
|
||||||
return response()->json($step, 201);
|
return response()->json($step, 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a hunt step.
|
||||||
|
*/
|
||||||
|
public function validateStep(\Illuminate\Http\Request $request, \App\Models\Hunts $hunt, \App\Models\HuntSteps $step)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'latitude' => 'required|numeric',
|
||||||
|
'longitude' => 'required|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
// 1. Check participation
|
||||||
|
$participation = $user->participations()->where('hunt_id', $hunt->id)->first();
|
||||||
|
|
||||||
|
if (!$participation) {
|
||||||
|
return response()->json(['message' => 'Not participating in this hunt'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if this is the current step
|
||||||
|
if ($participation->pivot->current_step_number !== $step->step_number) {
|
||||||
|
return response()->json(['message' => 'This is not your current step'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check proximity
|
||||||
|
// Haversine formula
|
||||||
|
$earthRadius = 6371000; // meters
|
||||||
|
|
||||||
|
$latFrom = deg2rad($request->latitude);
|
||||||
|
$lonFrom = deg2rad($request->longitude);
|
||||||
|
$latTo = deg2rad($step->latitude);
|
||||||
|
$lonTo = deg2rad($step->longitude);
|
||||||
|
|
||||||
|
$latDelta = $latTo - $latFrom;
|
||||||
|
$lonDelta = $lonTo - $lonFrom;
|
||||||
|
|
||||||
|
$angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
|
||||||
|
cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
|
||||||
|
|
||||||
|
$distance = $angle * $earthRadius;
|
||||||
|
|
||||||
|
if ($distance > $step->radius_m) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'You are too far from the target.',
|
||||||
|
'distance' => round($distance) . 'm'
|
||||||
|
], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Update progress
|
||||||
|
// Check if there is a next step
|
||||||
|
$nextStepNumber = $step->step_number + 1;
|
||||||
|
$nextStep = $hunt->steps()->where('step_number', $nextStepNumber)->first();
|
||||||
|
|
||||||
|
$updateData = [];
|
||||||
|
|
||||||
|
if ($nextStep) {
|
||||||
|
$updateData['current_step_number'] = $nextStepNumber;
|
||||||
|
} else {
|
||||||
|
$updateData['status'] = 'completed';
|
||||||
|
// Keep current_step_number as is (last step) or increment it to indicate "done"?
|
||||||
|
// Usually keeping it at last step or marking status completed is enough.
|
||||||
|
// Let's verify existing logic in HuntsController::show which filters based on step number.
|
||||||
|
// If we increment, show() might need adjustment to show everything if completed.
|
||||||
|
// For now, let's just mark completed and maybe keep same step number or increment.
|
||||||
|
// If we increment, $nextStep is null, so user matches nothing in filter?
|
||||||
|
// Let's increment current_step_number so that in show(), $step->step_number < $currentStepNumber becomes true for all steps.
|
||||||
|
$updateData['current_step_number'] = $nextStepNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hunt->participants()->updateExistingPivot($user->id, $updateData);
|
||||||
|
|
||||||
|
// 5. Return response
|
||||||
|
$response = [
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Step validated!',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($nextStep) {
|
||||||
|
$response['next_step'] = [
|
||||||
|
'step_number' => $nextStep->step_number,
|
||||||
|
'title' => $nextStep->title,
|
||||||
|
'description' => $nextStep->description,
|
||||||
|
'hint_text' => $nextStep->hint_text,
|
||||||
|
'hint_media_url' => $nextStep->hint_media_url,
|
||||||
|
// Do NOT include lat/long
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$response['message'] = 'Hunt completed! Congratulations!';
|
||||||
|
$response['completed'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json($response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class HuntSteps extends Model
|
class HuntSteps extends Model
|
||||||
{
|
{
|
||||||
|
use HasFactory;
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'hunt_id',
|
'hunt_id',
|
||||||
'step_number',
|
'step_number',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\HuntSteps>
|
||||||
|
*/
|
||||||
|
class HuntStepsFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'hunt_id' => \App\Models\Hunts::factory(),
|
||||||
|
'step_number' => $this->faker->numberBetween(1, 10),
|
||||||
|
'title' => $this->faker->sentence(2),
|
||||||
|
'description' => $this->faker->paragraph(),
|
||||||
|
'hint_text' => $this->faker->sentence(),
|
||||||
|
'hint_media_url' => $this->faker->imageUrl(),
|
||||||
|
'latitude' => $this->faker->latitude(),
|
||||||
|
'longitude' => $this->faker->longitude(),
|
||||||
|
'radius_m' => 20,
|
||||||
|
'xp' => 100,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Hunts>
|
||||||
|
*/
|
||||||
|
class HuntsFactory extends Factory
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'creator_id' => \App\Models\User::factory(),
|
||||||
|
'title' => $this->faker->sentence(3),
|
||||||
|
'slug' => $this->faker->slug(),
|
||||||
|
'description' => $this->faker->paragraph(),
|
||||||
|
'status' => \App\Enums\HuntStatus::PUBLISHED,
|
||||||
|
'difficulty' => \App\Enums\HuntDifficulty::EASY,
|
||||||
|
'city' => $this->faker->city(),
|
||||||
|
'latitude' => $this->faker->latitude(),
|
||||||
|
'longitude' => $this->faker->longitude(),
|
||||||
|
'is_public' => true,
|
||||||
|
'estimated_duration_min' => $this->faker->numberBetween(30, 180),
|
||||||
|
'image' => $this->faker->imageUrl(),
|
||||||
|
'start_at' => now(),
|
||||||
|
'end_at' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class BordeauxHuntsSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// 1. Create an organizer user (creator)
|
||||||
|
$creator = \App\Models\User::factory()->create([
|
||||||
|
'username' => 'Bordeaux Organizer',
|
||||||
|
'email' => 'orga@bordeaux.fr',
|
||||||
|
'password' => bcrypt('password'),
|
||||||
|
'role' => \App\Enums\UserRole::ORGA,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 2. Create Hunts in Bordeaux
|
||||||
|
$hunts = \App\Models\Hunts::factory()->count(3)->create([
|
||||||
|
'creator_id' => $creator->id,
|
||||||
|
'city' => 'Bordeaux',
|
||||||
|
'latitude' => 44.837789,
|
||||||
|
'longitude' => -0.57918,
|
||||||
|
'status' => \App\Enums\HuntStatus::PUBLISHED,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 3. Create Steps for each Hunt
|
||||||
|
foreach ($hunts as $hunt) {
|
||||||
|
// Step 1: Place de la Bourse
|
||||||
|
\App\Models\HuntSteps::factory()->create([
|
||||||
|
'hunt_id' => $hunt->id,
|
||||||
|
'step_number' => 1,
|
||||||
|
'title' => 'Place de la Bourse',
|
||||||
|
'description' => 'Rendez-vous sur cette place emblématique.',
|
||||||
|
'hint_text' => 'Miroir d\'eau',
|
||||||
|
'latitude' => 44.8417,
|
||||||
|
'longitude' => -0.5706,
|
||||||
|
'radius_m' => 50,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Step 2: Grand Théâtre
|
||||||
|
\App\Models\HuntSteps::factory()->create([
|
||||||
|
'hunt_id' => $hunt->id,
|
||||||
|
'step_number' => 2,
|
||||||
|
'title' => 'Grand Théâtre',
|
||||||
|
'description' => 'Un opéra du XVIIIe siècle.',
|
||||||
|
'hint_text' => 'Douze colonnes corinthiennes',
|
||||||
|
'latitude' => 44.8427,
|
||||||
|
'longitude' => -0.5746,
|
||||||
|
'radius_m' => 50,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Step 3: Cathédrale Saint-André
|
||||||
|
\App\Models\HuntSteps::factory()->create([
|
||||||
|
'hunt_id' => $hunt->id,
|
||||||
|
'step_number' => 3,
|
||||||
|
'title' => 'Cathédrale Saint-André',
|
||||||
|
'description' => 'Le plus beau monument religieux de Bordeaux.',
|
||||||
|
'hint_text' => 'Tour Pey Berland',
|
||||||
|
'latitude' => 44.8378,
|
||||||
|
'longitude' => -0.5775,
|
||||||
|
'radius_m' => 50,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,11 +15,9 @@ class DatabaseSeeder extends Seeder
|
||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
// User::factory(10)->create();
|
|
||||||
|
|
||||||
User::factory()->create([
|
$this->call([
|
||||||
'name' => 'Test User',
|
BordeauxHuntsSeeder::class,
|
||||||
'email' => 'test@example.com',
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ Route::middleware('auth:sanctum')->group(function (): void {
|
||||||
Route::post('users/{user}/avatar', [UsersController::class, 'updateAvatar']);
|
Route::post('users/{user}/avatar', [UsersController::class, 'updateAvatar']);
|
||||||
Route::post('hunts/{hunt}/participate', [HuntsController::class, 'participate']);
|
Route::post('hunts/{hunt}/participate', [HuntsController::class, 'participate']);
|
||||||
Route::delete('hunts/{hunt}/participate', [HuntsController::class, 'unparticipate']);
|
Route::delete('hunts/{hunt}/participate', [HuntsController::class, 'unparticipate']);
|
||||||
|
Route::post('hunts/{hunt}/steps/{step}/validate', [HuntStepsController::class, 'validateStep']);
|
||||||
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']);
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue