diff --git a/.env.deploy b/.env.deploy new file mode 100644 index 0000000..ef6133e --- /dev/null +++ b/.env.deploy @@ -0,0 +1,70 @@ +# Laravel config to use with the .lando.yml file +# Replace baslac with your app name +APP_NAME="Scout Baslac" +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_TIMEZONE=Europe/Zurich +APP_URL=https://baslac.lndo.site + +APP_LOCALE=fr +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=database +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=laravel +DB_PASSWORD=laravel + +SESSION_DRIVER=file + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +CACHE_PREFIX= + +# memcached is not configured +#MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=redis +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mail.infomaniak.com +MAIL_PORT=465 +MAIL_USERNAME=noreply@scout-baslac.ch +MAIL_PASSWORD=GroupeScoutBaslac1986 +MAIL_ENCRYPTION=tls +MAIL_FROM_ADDRESS="noreply@scout-baslac.ch" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_URL="https://s3-baslac.lndo.site" +AWS_ENDPOINT="https://s3-baslac.lndo.site" +AWS_ACCESS_KEY_ID=minio +AWS_SECRET_ACCESS_KEY=miniosecret +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET=laravel +AWS_USE_PATH_STYLE_ENDPOINT=true + +VITE_APP_NAME="${APP_NAME}" + + + diff --git a/.env.example b/.env.example index 277e9e8..da56e5c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ # Laravel config to use with the .lando.yml file # Replace baslac with your app name -APP_NAME=baslac +APP_NAME="Scout Baslac" APP_ENV=local APP_KEY= APP_DEBUG=true @@ -57,7 +57,7 @@ MAIL_FROM_ADDRESS="noreply@baslac.com" MAIL_FROM_NAME="${APP_NAME}" AWS_URL="https://s3-baslac.lndo.site" -AWS_ENDPOINT="http://minio:9000" +AWS_ENDPOINT="https://s3-baslac.lndo.site" AWS_ACCESS_KEY_ID=minio AWS_SECRET_ACCESS_KEY=miniosecret AWS_DEFAULT_REGION=us-east-1 @@ -67,3 +67,4 @@ AWS_USE_PATH_STYLE_ENDPOINT=true VITE_APP_NAME="${APP_NAME}" + diff --git a/app/Http/Controllers/AlbumController.php b/app/Http/Controllers/AlbumController.php new file mode 100644 index 0000000..12f7c66 --- /dev/null +++ b/app/Http/Controllers/AlbumController.php @@ -0,0 +1,95 @@ + Album::orderBy("created_at", "DESC")->get()->jsonSerialize(), + ]); + } + + /** + * Display the specified resource. + */ + public function show(string $id) + { + $album = Album::where("uuid", $id)->first(); + return Inertia::render('Album/Show', [ + "album" => $album->jsonSerialize(), + "photos" => $album->photos->jsonSerialize() + ]); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + // + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + "name" => "required|string|max:255", + "path" => "required|string", + ]); + + if(!Storage::disk("s3")->exists($request->path)) + return redirect()->back()->withErrors(["path" => "Probleme with the file transfert"]); + + $uuid = Str::uuid(); + $path = "albums/" . $uuid . "-" . $request->name . "." . pathinfo($request->path, PATHINFO_EXTENSION); + Storage::disk("s3")->move($request->path, $path); + Album::create([ + "uuid" => $uuid, + "name" => $request->name, + "path" => $path, + "user_id" => Auth::user()->id + ]); + return redirect(route("album.index"))->with(["message" => "Photo ajouté avec success"]); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, string $id) + { + // + } + + public function addPhoto(Request $request) + { + $album = Album::where("uuid", $request->id)->first(); + $photo = Photo::where("uuid", $request->uuid)->first(); + if(!$photo) redirect()->back()->withErrors(["uuid" => "Photo introuvable" ]); + if(!$album) redirect()->back()->withErrors(["uuid" => "Album introuvable" ]); + $album->photos()->attach($photo); + return redirect()->back(); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(string $id) + { + // + } +} diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php index d44fe97..eabd778 100644 --- a/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -33,7 +33,7 @@ public function store(LoginRequest $request): RedirectResponse $request->session()->regenerate(); - return redirect()->intended(route('dashboard', absolute: false)); + return redirect()->intended(route('photo.index', absolute: false)); } /** diff --git a/app/Http/Controllers/Auth/ConfirmablePasswordController.php b/app/Http/Controllers/Auth/ConfirmablePasswordController.php index d2b1f14..2e92a08 100644 --- a/app/Http/Controllers/Auth/ConfirmablePasswordController.php +++ b/app/Http/Controllers/Auth/ConfirmablePasswordController.php @@ -36,6 +36,6 @@ public function store(Request $request): RedirectResponse $request->session()->put('auth.password_confirmed_at', time()); - return redirect()->intended(route('dashboard', absolute: false)); + return redirect()->intended(route('photo.index', absolute: false)); } } diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php index f64fa9b..f953032 100644 --- a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php +++ b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php @@ -14,7 +14,7 @@ class EmailVerificationNotificationController extends Controller public function store(Request $request): RedirectResponse { if ($request->user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false)); + return redirect()->intended(route('photo.index', absolute: false)); } $request->user()->sendEmailVerificationNotification(); diff --git a/app/Http/Controllers/Auth/EmailVerificationPromptController.php b/app/Http/Controllers/Auth/EmailVerificationPromptController.php index b42e0d5..99e15ee 100644 --- a/app/Http/Controllers/Auth/EmailVerificationPromptController.php +++ b/app/Http/Controllers/Auth/EmailVerificationPromptController.php @@ -16,7 +16,7 @@ class EmailVerificationPromptController extends Controller public function __invoke(Request $request): RedirectResponse|Response { return $request->user()->hasVerifiedEmail() - ? redirect()->intended(route('dashboard', absolute: false)) + ? redirect()->intended(route('home', absolute: false)) : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); } } diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php index 53a546b..31befa1 100644 --- a/app/Http/Controllers/Auth/RegisteredUserController.php +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -46,6 +46,6 @@ public function store(Request $request): RedirectResponse Auth::login($user); - return redirect(route('dashboard', absolute: false)); + return redirect(route('photo.index', absolute: false)); } } diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php index 784765e..ea39306 100644 --- a/app/Http/Controllers/Auth/VerifyEmailController.php +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -15,13 +15,13 @@ class VerifyEmailController extends Controller public function __invoke(EmailVerificationRequest $request): RedirectResponse { if ($request->user()->hasVerifiedEmail()) { - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + return redirect()->intended(route('home', absolute: false)); } if ($request->user()->markEmailAsVerified()) { event(new Verified($request->user())); } - return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); + return redirect()->intended(route('photo.index', absolute: false)); } } diff --git a/app/Http/Controllers/PhotoController.php b/app/Http/Controllers/PhotoController.php index afee5dc..093a9c5 100644 --- a/app/Http/Controllers/PhotoController.php +++ b/app/Http/Controllers/PhotoController.php @@ -17,7 +17,7 @@ class PhotoController extends Controller public function index() { return Inertia::render('Photo/Index', [ - "photos" => Photo::all()->jsonSerialize(), + "photos" => Photo::orderBy("created_at", "DESC")->get()->jsonSerialize(), ]); } @@ -45,13 +45,18 @@ public function store(Request $request) $uuid = Str::uuid(); $path = "photos/" . $uuid . "-" . $request->name . "." . pathinfo($request->path, PATHINFO_EXTENSION); Storage::disk("s3")->move($request->path, $path); - Photo::create([ + $photo = Photo::create([ "uuid" => $uuid, "name" => $request->name, "path" => $path, "user_id" => Auth::user()->id ]); - return redirect(route("photo.index"))->with(["message" => "Photo ajouté avec success"]); + if($request->redirect) { + return redirect()->back(); + } + return response()->json([ + "uuid" => $photo->uuid + ]); } /** @@ -77,6 +82,7 @@ public function destroy(string $id) { $photo = Photo::where("uuid", $id)->first(); if(!$photo) redirect()->back()->withErrors(["uuid" => "Photo introuvable" ]); + $photo->albums()->detach(); $photo->delete(); return redirect(route("photo.index"))->with(["message" => "Photo supprimée avec success"]); } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 873b4f7..ca2de3c 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -3,11 +3,14 @@ namespace App\Http\Controllers; use App\Http\Requests\ProfileUpdateRequest; +use App\Models\User; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Str; use Illuminate\Support\Facades\Redirect; +use Illuminate\Support\Facades\Storage; use Inertia\Inertia; use Inertia\Response; @@ -21,7 +24,7 @@ public function edit(Request $request): Response return Inertia::render('Profile/Edit', [ 'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail, 'status' => session('status'), - ]); + ])->with("user", ((object)Auth::user())->jsonSerialize()); } /** @@ -29,15 +32,19 @@ public function edit(Request $request): Response */ public function update(ProfileUpdateRequest $request): RedirectResponse { - $request->user()->fill($request->validated()); - - if ($request->user()->isDirty('email')) { - $request->user()->email_verified_at = null; - } - - $request->user()->save(); + $request->validated(); + $user = User::find(Auth::user()->id); + $file = 'profiles/'. Str::random(8) . "_" . pathinfo($request->path, PATHINFO_BASENAME); + Storage::disk("s3")->move($request->path, $file); + $user->update([ + "name" => $request->name, + "totem" => $request->totem, + "tel" => $request->tel, + "contactable" => intval($request->contactable), + "path" => $file, + ]); - return Redirect::route('profile.edit'); + return redirect(route('profile.edit')); } /** diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..c7be022 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,91 @@ + User::orderBy("created_at", "DESC")->get()->jsonSerialize(), + ]); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + return Inertia::render('Admin/User/Create'); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + "name" => "required|string|max:255", + "email" => "required|string|email|max:255|unique:users" + ]); + + $user = User::create([ + "password" => bcrypt(Str::Random(10)), + "email" => $request->email, + "name" => $request->name, + "totem" => $request->totem, + ]); + + $token = Token::create($user->email); + Mail::send((object)[ + "user" => $user, + "template" => "email.auth.reset", + "data" => [ "token" => $token ], + "subject" => "Nouveau compte" + ]); + return redirect(route("admin.user.index")); + } + + /** + * Display the specified resource. + */ + public function show(string $id) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(string $id) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, string $id) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(string $id) + { + // + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 3867f22..ccb8c09 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Models\User; use Illuminate\Http\Request; use Inertia\Middleware; @@ -31,6 +32,7 @@ public function share(Request $request): array { return [ ...parent::share($request), + 'user' => $request->user() ? User::find($request->user()->id)->jsonSerialize() : null, 'auth' => [ 'user' => $request->user(), ], diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php index 3622a8f..a8ba731 100644 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ b/app/Http/Requests/ProfileUpdateRequest.php @@ -17,14 +17,6 @@ public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], - 'email' => [ - 'required', - 'string', - 'lowercase', - 'email', - 'max:255', - Rule::unique(User::class)->ignore($this->user()->id), - ], ]; } } diff --git a/app/Mail/Communication.php b/app/Mail/Communication.php new file mode 100644 index 0000000..16a274e --- /dev/null +++ b/app/Mail/Communication.php @@ -0,0 +1,59 @@ +template = $data->template; + $this->user = $data->user; + $this->subject = $data->subject; + $this->annexes = $data->annexes ?? []; + $this->data = $data->data; + } + + public function content(): Content + { + return new Content( + view: $this->template, + text: $this->template . "-text", + with: $this->data, + ); + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + $mail = $this; + if(count($this->annexes)) { + foreach($this->annexes as $annexe) { + $mail = $mail->attachData($annexe->data, $annexe->name, [ "mime" => $annexe->type ]); + } + } + return $mail->content(); + } + +} diff --git a/app/Models/Album.php b/app/Models/Album.php new file mode 100644 index 0000000..ad64767 --- /dev/null +++ b/app/Models/Album.php @@ -0,0 +1,43 @@ + $this->uuid, + 'name' => $this->name, + 'path' => $this->path, + 'image' => S3::signUrl($this->path), + 'user' => $this->user, + 'created_at' => date("d.m.Y", strtotime($this->created_at)), + ]; + } + + public function user() + { + return $this->belongsTo(User::class); + } + + public function photos() + { + return $this->belongsToMany(Photo::class); + } + + public function tags() + { + return $this->belongsToMany(Tag::class); + } +} diff --git a/app/Models/Photo.php b/app/Models/Photo.php index 7e0a80a..63d4e42 100644 --- a/app/Models/Photo.php +++ b/app/Models/Photo.php @@ -15,20 +15,25 @@ class Photo extends Model 'user_id' ]; - public function user() - { - return $this->belongsTo(User::class); - } - public function jsonSerialize():array { return [ - 'id' => $this->id, 'uuid' => $this->uuid, 'name' => $this->name, 'path' => S3::signUrl($this->path), 'user' => $this->user, + 'created_at' => date("d.m.Y", strtotime($this->created_at)), ]; } + public function user() + { + return $this->belongsTo(User::class); + } + + public function albums() + { + return $this->belongsToMany(Album::class); + } + } diff --git a/app/Models/Tag.php b/app/Models/Tag.php new file mode 100644 index 0000000..c048d52 --- /dev/null +++ b/app/Models/Tag.php @@ -0,0 +1,18 @@ +belongsToMany(Album::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index df948e0..48e9986 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,8 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; + +use App\Utils\S3; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; @@ -23,11 +25,29 @@ class User extends Authenticatable 'password', 'email_verified_at', 'totem', + 'path', 'tel', 'contactable', 'role', ]; + public function jsonSerialize():array + { + return [ + 'id' => $this->id, + 'name' => $this->name, + 'email' => $this->email, + 'pic' => S3::signUrl($this->path), + 'path' => $this->path, + 'totem' => $this->totem, + 'tel' => $this->tel, + 'contactable' => $this->contactable, + 'role' => $this->role, + 'created_at' => date("d.m.Y", strtotime($this->created_at)), + ]; + } + + /** * The attributes that should be hidden for serialization. * @@ -55,4 +75,9 @@ public function photos() { return $this->hasMany(Photo::class); } + + public function albums() + { + return $this->hasMany(Album::class); + } } diff --git a/app/Utils/Mail.php b/app/Utils/Mail.php new file mode 100644 index 0000000..ddfb2bc --- /dev/null +++ b/app/Utils/Mail.php @@ -0,0 +1,16 @@ +user->email) + ->send(new Communication($data)); + } +} \ No newline at end of file diff --git a/app/Utils/Token.php b/app/Utils/Token.php new file mode 100644 index 0000000..996051a --- /dev/null +++ b/app/Utils/Token.php @@ -0,0 +1,21 @@ +insert([ + 'email' => $email, + 'token' => $token, + 'created_at' => Carbon::now() + ]); + return $token; + } +} + diff --git a/database/migrations/2025_01_20_201356_create_albums.php b/database/migrations/2025_01_20_201356_create_albums.php new file mode 100644 index 0000000..4a2c992 --- /dev/null +++ b/database/migrations/2025_01_20_201356_create_albums.php @@ -0,0 +1,33 @@ +id(); + $table->uuid('uuid')->unique(); + $table->string("path", 511)->default("albums/none.svg"); + $table->string("name"); + $table->unsignedBigInteger("user_id"); + $table->timestamps(); + + $table->foreign("user_id")->references("id")->on("users"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('albums'); + } +}; diff --git a/database/migrations/2025_01_20_213150_create_tags.php b/database/migrations/2025_01_20_213150_create_tags.php new file mode 100644 index 0000000..523d034 --- /dev/null +++ b/database/migrations/2025_01_20_213150_create_tags.php @@ -0,0 +1,29 @@ +id(); + $table->uuid('uuid')->unique(); + $table->string("name"); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tags'); + } +}; diff --git a/database/migrations/2025_01_20_213227_create_tags_albums.php b/database/migrations/2025_01_20_213227_create_tags_albums.php new file mode 100644 index 0000000..c7f2be9 --- /dev/null +++ b/database/migrations/2025_01_20_213227_create_tags_albums.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger("tag_id"); + $table->unsignedBigInteger("album_id"); + $table->timestamps(); + + $table->foreign("tag_id")->references("id")->on("tags"); + $table->foreign("album_id")->references("id")->on("albums"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tags_albums'); + } +}; diff --git a/database/migrations/2025_01_20_213326_create_albums_photos.php b/database/migrations/2025_01_20_213326_create_albums_photos.php new file mode 100644 index 0000000..4ebac4f --- /dev/null +++ b/database/migrations/2025_01_20_213326_create_albums_photos.php @@ -0,0 +1,32 @@ +id(); + $table->unsignedBigInteger("photo_id"); + $table->unsignedBigInteger("album_id"); + $table->timestamps(); + + $table->foreign("photo_id")->references("id")->on("photos"); + $table->foreign("album_id")->references("id")->on("albums"); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('albums_photos'); + } +}; diff --git a/database/migrations/2025_01_21_041345_rename_table.php b/database/migrations/2025_01_21_041345_rename_table.php new file mode 100644 index 0000000..36f471e --- /dev/null +++ b/database/migrations/2025_01_21_041345_rename_table.php @@ -0,0 +1,26 @@ + - - -
- - - - + + + + + diff --git a/resources/js/Components/DropdownLink.vue b/resources/js/Components/DropdownLink.vue index e5ab50b..132d6e8 100644 --- a/resources/js/Components/DropdownLink.vue +++ b/resources/js/Components/DropdownLink.vue @@ -1,18 +1,24 @@ \ No newline at end of file diff --git a/resources/js/Pages/Profile/Edit.vue b/resources/js/Pages/Profile/Edit.vue index d3d322a..a1f30df 100644 --- a/resources/js/Pages/Profile/Edit.vue +++ b/resources/js/Pages/Profile/Edit.vue @@ -1,5 +1,5 @@