This commit is contained in:
2026-08-10 19:18:25 +03:00
parent a9923f72ce
commit 7c501d1e9f
35 changed files with 2185 additions and 78 deletions
+5
View File
@@ -0,0 +1,5 @@
<?php
use Illuminate\Support\Facades\Route;
Route::prefix('v1')->group(base_path('routes/api_v1.php'));
+112
View File
@@ -0,0 +1,112 @@
<?php
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\DashboardController;
use App\Http\Controllers\Api\DeviceController;
use App\Http\Controllers\Api\EmailVerificationController;
use App\Http\Controllers\Api\ExerciseAliasController;
use App\Http\Controllers\Api\ExerciseController;
use App\Http\Controllers\Api\FavoriteController;
use App\Http\Controllers\Api\PasswordResetController;
use App\Http\Controllers\Api\ProfileController;
use App\Http\Controllers\Api\ProgramController;
use App\Http\Controllers\Api\TrainerController;
use App\Http\Controllers\Api\WeightLogController;
use App\Http\Controllers\Api\WorkoutController;
use App\Http\Controllers\Api\WorkoutSessionController;
use App\Http\Controllers\Api\YogaPoseController;
use Illuminate\Support\Facades\Route;
Route::post('/register', [AuthController::class, 'register'])->name('api.v1.register');
Route::post('/login', [AuthController::class, 'login'])->name('api.v1.login');
Route::post('/forgot-password', [PasswordResetController::class, 'forgotPassword'])
->middleware('throttle:6,1')
->name('api.v1.password.email');
Route::post('/reset-password', [PasswordResetController::class, 'resetPassword'])
->middleware('throttle:6,1')
->name('api.v1.password.reset');
// Public catalog browsing (no auth required, matches web's public exercise/yoga pages)
Route::get('/exercises', [ExerciseController::class, 'index'])->name('api.v1.exercises.index');
Route::get('/exercises/{exercise:slug}', [ExerciseController::class, 'show'])->name('api.v1.exercises.show');
Route::get('/yoga', [YogaPoseController::class, 'index'])->name('api.v1.yoga.index');
Route::get('/yoga/{pose:slug}', [YogaPoseController::class, 'show'])->name('api.v1.yoga.show');
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout'])->name('api.v1.logout');
Route::get('/me', [AuthController::class, 'me'])->name('api.v1.me');
// Profile
Route::get('/profile', [ProfileController::class, 'show'])->name('api.v1.profile.show');
Route::patch('/profile', [ProfileController::class, 'update'])->name('api.v1.profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('api.v1.profile.destroy');
// Dashboard
Route::get('/dashboard', [DashboardController::class, 'index'])->name('api.v1.dashboard');
// Email verification (resend). The verify link itself is a public,
// signed route registered in routes/auth.php - it works without a
// Sanctum token since it's opened from an email client.
Route::post('/email/verification-notification', [EmailVerificationController::class, 'store'])
->middleware('throttle:6,1')
->name('api.v1.verification.send');
// Devices (Sanctum tokens)
Route::get('/devices', [DeviceController::class, 'index'])->name('api.v1.devices.index');
Route::delete('/devices/{tokenId}', [DeviceController::class, 'destroy'])->name('api.v1.devices.destroy');
// Favorites
Route::post('/exercises/{exercise}/favorite', [FavoriteController::class, 'toggleExercise'])->name('api.v1.exercises.favorite');
Route::post('/workouts/{workout}/favorite', [FavoriteController::class, 'toggleWorkout'])->name('api.v1.workouts.favorite');
Route::post('/yoga/{pose}/favorite', [FavoriteController::class, 'toggleYogaPose'])->name('api.v1.yoga.favorite');
// Exercise alias (per-user rename)
Route::post('/exercises/{exercise}/alias', [ExerciseAliasController::class, 'store'])->name('api.v1.exercises.alias');
// Body weight log
Route::get('/weight-logs', [WeightLogController::class, 'index'])->name('api.v1.weight-logs.index');
Route::post('/weight-logs', [WeightLogController::class, 'store'])->name('api.v1.weight-logs.store');
Route::delete('/weight-logs/{weightLog}', [WeightLogController::class, 'destroy'])->name('api.v1.weight-logs.destroy');
// Workouts
Route::prefix('/workouts')->group(function () {
Route::get('/', [WorkoutController::class, 'index'])->name('api.v1.workouts.index');
Route::get('/community', [WorkoutController::class, 'community'])->name('api.v1.workouts.community');
Route::post('/', [WorkoutController::class, 'store'])->name('api.v1.workouts.store');
Route::get('/{workout}', [WorkoutController::class, 'show'])->name('api.v1.workouts.show');
Route::delete('/{workout}', [WorkoutController::class, 'destroy'])->name('api.v1.workouts.destroy');
Route::post('/{workout}/toggle-public', [WorkoutController::class, 'togglePublic'])->name('api.v1.workouts.toggle-public');
Route::post('/{workout}/copy', [WorkoutController::class, 'copy'])->name('api.v1.workouts.copy');
Route::get('/{workout}/exercises/search', [WorkoutController::class, 'searchExercises'])->name('api.v1.workouts.exercises.search');
Route::post('/{workout}/exercises', [WorkoutController::class, 'addExercise'])->name('api.v1.workouts.exercises.add');
Route::patch('/{workout}/exercises/{item}', [WorkoutController::class, 'updateExercise'])->name('api.v1.workouts.exercises.update');
Route::delete('/{workout}/exercises/{item}', [WorkoutController::class, 'removeExercise'])->name('api.v1.workouts.exercises.remove');
Route::post('/{workout}/sessions', [WorkoutSessionController::class, 'store'])->name('api.v1.sessions.store');
});
// Sessions
Route::get('/sessions/{session}', [WorkoutSessionController::class, 'show'])->name('api.v1.sessions.show');
Route::patch('/sessions/{session}/items/{item}', [WorkoutSessionController::class, 'toggleItem'])->name('api.v1.sessions.items.toggle');
Route::post('/sessions/{session}/complete', [WorkoutSessionController::class, 'complete'])->name('api.v1.sessions.complete');
// Programs
Route::prefix('/programs')->group(function () {
Route::get('/', [ProgramController::class, 'index'])->name('api.v1.programs.index');
Route::post('/', [ProgramController::class, 'store'])->name('api.v1.programs.store');
Route::get('/{program}', [ProgramController::class, 'show'])->name('api.v1.programs.show');
Route::delete('/{program}', [ProgramController::class, 'destroy'])->name('api.v1.programs.destroy');
Route::post('/{program}/workouts', [ProgramController::class, 'addWorkout'])->name('api.v1.programs.workouts.add');
});
// Trainer: manage clients and assign workouts to them
Route::middleware('trainer')->prefix('/trainer')->group(function () {
Route::get('/clients', [TrainerController::class, 'clients'])->name('api.v1.trainer.clients');
Route::post('/clients', [TrainerController::class, 'addClient'])->name('api.v1.trainer.clients.add');
Route::delete('/clients/{client}', [TrainerController::class, 'removeClient'])->name('api.v1.trainer.clients.remove');
Route::post('/clients/{client}/workouts', [TrainerController::class, 'storeWorkout'])->name('api.v1.trainer.workouts.store');
});
});
+8 -4
View File
@@ -35,14 +35,18 @@ Route::middleware('guest')->group(function () {
->name('password.store');
});
// Not session-gated: this link is opened from an email client, which may not
// carry the web session of whichever device/browser registered the account
// (e.g. a user who signed up through the Android app). Identity is proven by
// the signed URL + hash, same as the auth-gated version this replaced.
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::middleware('auth')->group(function () {
Route::get('verify-email', EmailVerificationPromptController::class)
->name('verification.notice');
Route::get('verify-email/{id}/{hash}', VerifyEmailController::class)
->middleware(['signed', 'throttle:6,1'])
->name('verification.verify');
Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store'])
->middleware('throttle:6,1')
->name('verification.send');