feat: ations
This commit is contained in:
parent
1964b60949
commit
2b6a680d22
|
|
@ -80,6 +80,7 @@ class AuthController extends Controller
|
|||
{
|
||||
$user = $request->user();
|
||||
$data = $request->validated();
|
||||
$nutritionGoals = $data['nutritionGoals'] ?? null;
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
if ($user->avatar_url) {
|
||||
|
|
@ -91,6 +92,14 @@ class AuthController extends Controller
|
|||
}
|
||||
|
||||
unset($data['avatar']);
|
||||
unset($data['nutritionGoals']);
|
||||
|
||||
if (is_array($nutritionGoals)) {
|
||||
$data['daily_calorie_goal'] = $nutritionGoals['calories'];
|
||||
$data['daily_protein_goal'] = $nutritionGoals['proteins'];
|
||||
$data['daily_carbs_goal'] = $nutritionGoals['carbs'];
|
||||
$data['daily_fats_goal'] = $nutritionGoals['fats'];
|
||||
}
|
||||
|
||||
if (! empty($data)) {
|
||||
$user->update($data);
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ class UpdateUserRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'avatar' => ['sometimes', 'nullable', 'image', 'max:2048'],
|
||||
'nutritionGoals' => ['sometimes', 'array'],
|
||||
'nutritionGoals.calories' => ['required_with:nutritionGoals', 'integer', 'min:0', 'max:100000'],
|
||||
'nutritionGoals.proteins' => ['required_with:nutritionGoals', 'numeric', 'min:0', 'max:10000'],
|
||||
'nutritionGoals.carbs' => ['required_with:nutritionGoals', 'numeric', 'min:0', 'max:10000'],
|
||||
'nutritionGoals.fats' => ['required_with:nutritionGoals', 'numeric', 'min:0', 'max:10000'],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -22,6 +27,10 @@ class UpdateUserRequest extends FormRequest
|
|||
{
|
||||
return [
|
||||
'avatar.max' => "L'image ne doit pas dépasser 2 Mo.",
|
||||
'nutritionGoals.calories.integer' => 'Les calories doivent etre un nombre entier.',
|
||||
'nutritionGoals.*.required_with' => 'Renseigne tous les objectifs nutritionnels.',
|
||||
'nutritionGoals.*.min' => 'Les objectifs nutritionnels doivent etre positifs.',
|
||||
'nutritionGoals.*.max' => 'La valeur de cet objectif nutritionnel est trop elevee.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ class UserResource extends JsonResource
|
|||
'email' => $this->email,
|
||||
'avatarUrl' => $this->avatar_url ? asset(Storage::url($this->avatar_url)) : null,
|
||||
'bio' => $this->bio,
|
||||
'nutritionGoals' => [
|
||||
'calories' => (int) $this->daily_calorie_goal,
|
||||
'proteins' => (float) $this->daily_protein_goal,
|
||||
'carbs' => (float) $this->daily_carbs_goal,
|
||||
'fats' => (float) $this->daily_fats_goal,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ class User extends Authenticatable implements FilamentUser
|
|||
'password',
|
||||
'avatar_url',
|
||||
'bio',
|
||||
'daily_calorie_goal',
|
||||
'daily_protein_goal',
|
||||
'daily_carbs_goal',
|
||||
'daily_fats_goal',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -50,6 +54,10 @@ class User extends Authenticatable implements FilamentUser
|
|||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'daily_calorie_goal' => 'integer',
|
||||
'daily_protein_goal' => 'float',
|
||||
'daily_carbs_goal' => 'float',
|
||||
'daily_fats_goal' => 'float',
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ class UserFactory extends Factory
|
|||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'avatar_url' => null,
|
||||
'daily_calorie_goal' => 2000,
|
||||
'daily_protein_goal' => 120,
|
||||
'daily_carbs_goal' => 250,
|
||||
'daily_fats_goal' => 70,
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?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::table('users', function (Blueprint $table): void {
|
||||
$table->unsignedInteger('daily_calorie_goal')->default(2000)->after('avatar_url');
|
||||
$table->decimal('daily_protein_goal', 8, 2)->default(120)->after('daily_calorie_goal');
|
||||
$table->decimal('daily_carbs_goal', 8, 2)->default(250)->after('daily_protein_goal');
|
||||
$table->decimal('daily_fats_goal', 8, 2)->default(70)->after('daily_carbs_goal');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$table->dropColumn([
|
||||
'daily_calorie_goal',
|
||||
'daily_protein_goal',
|
||||
'daily_carbs_goal',
|
||||
'daily_fats_goal',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
services:
|
||||
app:
|
||||
image: gitea.leonmorival.com/leon-morival/bemeal-api:latest
|
||||
container_name: bemeal-app
|
||||
restart: unless-stopped
|
||||
|
||||
depends_on:
|
||||
pgsql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
environment:
|
||||
APP_NAME: "BeMeal"
|
||||
APP_ENV: production
|
||||
APP_DEBUG: "false"
|
||||
APP_KEY: ${APP_KEY}
|
||||
APP_URL: ${APP_URL}
|
||||
|
||||
APP_LOCALE: fr
|
||||
|
||||
SERVER_NAME: ":80"
|
||||
APP_RUNTIME: "Laravel\\FrankenPHP\\Runtime"
|
||||
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: pgsql
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: ${DB_DATABASE}
|
||||
DB_USERNAME: ${DB_USERNAME}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
REDIS_HOST: redis
|
||||
REDIS_CLIENT: predis
|
||||
QUEUE_CONNECTION: redis
|
||||
|
||||
SCOUT_DRIVER: meilisearch
|
||||
MEILISEARCH_HOST: http://meilisearch:7700
|
||||
MEILISEARCH_KEY: ${MEILISEARCH_KEY}
|
||||
|
||||
volumes:
|
||||
- bemeal_storage:/var/www/html/storage/app
|
||||
- bemeal_public:/var/www/html/storage/app/public
|
||||
|
||||
networks:
|
||||
- nginx
|
||||
- internal
|
||||
|
||||
horizon:
|
||||
image: gitea.leonmorival.com/leon-morival/bemeal-api:latest
|
||||
container_name: bemeal-horizon
|
||||
restart: unless-stopped
|
||||
|
||||
command: php artisan horizon
|
||||
|
||||
depends_on:
|
||||
pgsql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
environment:
|
||||
APP_ENV: production
|
||||
APP_KEY: ${APP_KEY}
|
||||
|
||||
DB_CONNECTION: pgsql
|
||||
DB_HOST: pgsql
|
||||
DB_PORT: 5432
|
||||
DB_DATABASE: ${DB_DATABASE}
|
||||
DB_USERNAME: ${DB_USERNAME}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
REDIS_HOST: redis
|
||||
REDIS_CLIENT: predis
|
||||
QUEUE_CONNECTION: redis
|
||||
|
||||
volumes:
|
||||
- bemeal_storage:/var/www/html/storage/app
|
||||
|
||||
networks:
|
||||
- internal
|
||||
|
||||
pgsql:
|
||||
image: postgres:15-alpine
|
||||
container_name: bemeal-pgsql
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
POSTGRES_DB: ${DB_DATABASE}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
volumes:
|
||||
- bemeal_pgsql:/var/lib/postgresql/data
|
||||
|
||||
networks:
|
||||
- internal
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: bemeal-redis
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
- bemeal_redis:/data
|
||||
|
||||
networks:
|
||||
- internal
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:latest
|
||||
container_name: bemeal-meilisearch
|
||||
restart: unless-stopped
|
||||
|
||||
environment:
|
||||
MEILI_NO_ANALYTICS: "true"
|
||||
MEILI_MASTER_KEY: ${MEILISEARCH_KEY}
|
||||
|
||||
volumes:
|
||||
- bemeal_meili:/meili_data
|
||||
|
||||
networks:
|
||||
- internal
|
||||
|
||||
volumes:
|
||||
bemeal_pgsql:
|
||||
bemeal_redis:
|
||||
bemeal_storage:
|
||||
bemeal_public:
|
||||
bemeal_meili:
|
||||
|
||||
networks:
|
||||
nginx:
|
||||
external: true
|
||||
|
||||
internal:
|
||||
driver: bridge
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
services:
|
||||
starter-api:
|
||||
image: gitea.leonmorival.com/leon-morival/laravel-starter:latest
|
||||
container_name: starter-starter-api-1
|
||||
image: gitea.leonmorival.com/leon-morival/bemeal-api:latest
|
||||
container_name: bemeal-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
pgsql:
|
||||
|
|
@ -37,12 +37,12 @@ services:
|
|||
- storage-data:/var/www/html/storage/app
|
||||
- storage-public-data:/var/www/html/storage/app/public
|
||||
networks:
|
||||
- nginx
|
||||
- nginx-bemeal
|
||||
- internal
|
||||
|
||||
pgsql:
|
||||
image: postgres:15-alpine
|
||||
container_name: starter-pgsql-1
|
||||
container_name: bemeal-pgsql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: "${DB_DATABASE}"
|
||||
|
|
@ -60,7 +60,7 @@ services:
|
|||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: starter-redis-1
|
||||
container_name: bemeal-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
|
|
@ -68,11 +68,14 @@ services:
|
|||
- internal
|
||||
|
||||
horizon:
|
||||
image: gitea.leonmorival.com/leon-morival/laravel-starter:latest
|
||||
container_name: starter-horizon-1
|
||||
image: gitea.leonmorival.com/leon-morival/bemeal-api:latest
|
||||
container_name: bemeal-horizon
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- redis
|
||||
pgsql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
environment:
|
||||
APP_ENV: "production"
|
||||
APP_KEY: "${APP_KEY}"
|
||||
|
|
@ -93,18 +96,17 @@ services:
|
|||
|
||||
adminer:
|
||||
image: adminer:latest
|
||||
container_name: starter-adminer-1
|
||||
container_name: bemeal-adminer
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Optionnel : définit PostgreSQL par défaut à la connexion
|
||||
ADMINER_DEFAULT_SERVER: pgsql
|
||||
networks:
|
||||
- internal
|
||||
- nginx
|
||||
|
||||
# - nginx
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:latest
|
||||
container_name: starter-meilisearch-1
|
||||
container_name: bemeal-meilisearch
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MEILI_NO_ANALYTICS: "true"
|
||||
|
|
@ -113,22 +115,23 @@ services:
|
|||
- meilisearch-data:/meili_data
|
||||
networks:
|
||||
- internal
|
||||
- nginx
|
||||
# - nginx
|
||||
|
||||
volumes:
|
||||
pgsql-data:
|
||||
external: true
|
||||
name: starter_pgsql_data
|
||||
name: bemeal_pgsql_data
|
||||
redis-data:
|
||||
name: starter_redis_data
|
||||
name: bemeal_redis_data
|
||||
storage-data:
|
||||
name: bemeal_storage_data
|
||||
storage-public-data:
|
||||
name: starter_storage_public_data
|
||||
name: bemeal_storage_public_data
|
||||
meilisearch-data:
|
||||
name: starter_meilisearch_data
|
||||
name: bemeal_meilisearch_data
|
||||
|
||||
networks:
|
||||
nginx:
|
||||
nginx-bemeal:
|
||||
external: true
|
||||
internal:
|
||||
driver: bridge
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('returns nutrition goals with the authenticated user', function () {
|
||||
$user = User::factory()->create([
|
||||
'daily_calorie_goal' => 2400,
|
||||
'daily_protein_goal' => 140,
|
||||
'daily_carbs_goal' => 280,
|
||||
'daily_fats_goal' => 80,
|
||||
]);
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$this->getJson('/api/auth/me')
|
||||
->assertOk()
|
||||
->assertJsonPath('data.nutritionGoals.calories', 2400)
|
||||
->assertJsonPath('data.nutritionGoals.proteins', 140)
|
||||
->assertJsonPath('data.nutritionGoals.carbs', 280)
|
||||
->assertJsonPath('data.nutritionGoals.fats', 80);
|
||||
});
|
||||
|
||||
it('updates nutrition goals for the authenticated user', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
Sanctum::actingAs($user);
|
||||
|
||||
$response = $this->patchJson('/api/auth/me', [
|
||||
'nutritionGoals' => [
|
||||
'calories' => 2200,
|
||||
'proteins' => 135.5,
|
||||
'carbs' => 260,
|
||||
'fats' => 75.25,
|
||||
],
|
||||
]);
|
||||
|
||||
$response
|
||||
->assertOk()
|
||||
->assertJsonPath('data.nutritionGoals.calories', 2200)
|
||||
->assertJsonPath('data.nutritionGoals.proteins', 135.5)
|
||||
->assertJsonPath('data.nutritionGoals.carbs', 260)
|
||||
->assertJsonPath('data.nutritionGoals.fats', 75.25);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'daily_calorie_goal' => 2200,
|
||||
'daily_protein_goal' => 135.5,
|
||||
'daily_carbs_goal' => 260,
|
||||
'daily_fats_goal' => 75.25,
|
||||
]);
|
||||
});
|
||||
|
||||
it('validates nutrition goals', function () {
|
||||
Sanctum::actingAs(User::factory()->create());
|
||||
|
||||
$this->patchJson('/api/auth/me', [
|
||||
'nutritionGoals' => [
|
||||
'calories' => -1,
|
||||
'proteins' => 'abc',
|
||||
],
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors([
|
||||
'nutritionGoals.calories',
|
||||
'nutritionGoals.proteins',
|
||||
'nutritionGoals.carbs',
|
||||
'nutritionGoals.fats',
|
||||
]);
|
||||
});
|
||||
Loading…
Reference in New Issue