finished laravel migration

This commit is contained in:
2025-12-29 18:19:24 +01:00
parent bdfbe3ffd6
commit 63bd166a65
218 changed files with 21830 additions and 15154 deletions

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
* @param array<string, string> $input
*/
public function create(array $input): User
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique(User::class),
],
'password' => $this->passwordRules(),
])->validate();
return User::create([
'name' => $input['name'],
'email' => $input['email'],
'password' => Hash::make($input['password']),
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Actions\Fortify;
use Illuminate\Validation\Rules\Password;
trait PasswordValidationRules
{
/**
* Get the validation rules used to validate passwords.
*
* @return array<int, \Illuminate\Contracts\Validation\Rule|array<mixed>|string>
*/
protected function passwordRules(): array
{
return ['required', 'string', Password::default(), 'confirmed'];
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\ResetsUserPasswords;
class ResetUserPassword implements ResetsUserPasswords
{
use PasswordValidationRules;
/**
* Validate and reset the user's forgotten password.
*
* @param array<string, string> $input
*/
public function reset(User $user, array $input): void
{
Validator::make($input, [
'password' => $this->passwordRules(),
])->validate();
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Fortify\Contracts\UpdatesUserPasswords;
class UpdateUserPassword implements UpdatesUserPasswords
{
use PasswordValidationRules;
/**
* Validate and update the user's password.
*
* @param array<string, string> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current_password:web'],
'password' => $this->passwordRules(),
], [
'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword');
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* @param array<string, string> $input
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore($user->id),
],
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}
/**
* Update the given verified user's profile information.
*
* @param array<string, string> $input
*/
protected function updateVerifiedUser(User $user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
'email_verified_at' => null,
])->save();
$user->sendEmailVerificationNotification();
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Actions\Fortify\CreateNewUser;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request, CreateNewUser $creator): JsonResponse
{
$input = [
'name' => $request->input('name') ?? $request->input('username'),
'email' => $request->input('email'),
'password' => $request->input('password') ?? $request->input('plainPassword'),
'password_confirmation' => $request->input('password_confirmation') ?? $request->input('plainPassword'),
];
$user = $creator->create($input);
$user->sendEmailVerificationNotification();
return response()->json([
'user_id' => $user->id,
'email' => $user->email,
'message' => 'Verification email sent.',
]);
}
public function login(Request $request): JsonResponse
{
$request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
]);
$user = User::where('email', $request->input('email'))->first();
if (!$user || !Hash::check($request->input('password'), $user->password)) {
throw ValidationException::withMessages([
'email' => ['Invalid credentials.'],
]);
}
if (!$user->hasVerifiedEmail()) {
return response()->json([
'message' => 'Email not verified.',
], 403);
}
$token = $user->createToken('api')->plainTextToken;
return response()->json([
'token' => $token,
'user_id' => $user->id,
'email' => $user->email,
'roles' => $user->roles()->pluck('name')->values(),
]);
}
public function logout(Request $request): JsonResponse
{
$request->user()?->currentAccessToken()?->delete();
return response()->json(null, 204);
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,179 @@
<?php
namespace App\Http\Controllers;
use App\Models\Forum;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class ForumController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Forum::query();
$parentParam = $request->query('parent');
if (is_array($parentParam) && array_key_exists('exists', $parentParam)) {
$exists = filter_var($parentParam['exists'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($exists === false) {
$query->whereNull('parent_id');
} elseif ($exists === true) {
$query->whereNotNull('parent_id');
}
} elseif (is_string($parentParam)) {
$parentId = $this->parseIriId($parentParam);
if ($parentId !== null) {
$query->where('parent_id', $parentId);
}
}
if ($request->filled('type')) {
$query->where('type', $request->query('type'));
}
$forums = $query
->orderBy('position')
->orderBy('name')
->get()
->map(fn (Forum $forum) => $this->serializeForum($forum));
return response()->json($forums);
}
public function show(Forum $forum): JsonResponse
{
return response()->json($this->serializeForum($forum));
}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'name' => ['required', 'string', 'max:100'],
'description' => ['nullable', 'string'],
'type' => ['required', Rule::in(['category', 'forum'])],
'parent' => ['nullable', 'string'],
]);
$parentId = $this->parseIriId($data['parent'] ?? null);
if ($parentId) {
$parent = Forum::findOrFail($parentId);
if ($parent->type !== 'category') {
return response()->json(['message' => 'Parent must be a category.'], 422);
}
}
$position = Forum::where('parent_id', $parentId)->max('position');
$forum = Forum::create([
'name' => $data['name'],
'description' => $data['description'] ?? null,
'type' => $data['type'],
'parent_id' => $parentId,
'position' => ($position ?? 0) + 1,
]);
return response()->json($this->serializeForum($forum), 201);
}
public function update(Request $request, Forum $forum): JsonResponse
{
$data = $request->validate([
'name' => ['sometimes', 'required', 'string', 'max:100'],
'description' => ['nullable', 'string'],
'type' => ['sometimes', Rule::in(['category', 'forum'])],
'parent' => ['nullable', 'string'],
]);
$parentId = $this->parseIriId($data['parent'] ?? null);
if (array_key_exists('parent', $data)) {
if ($parentId) {
$parent = Forum::findOrFail($parentId);
if ($parent->type !== 'category') {
return response()->json(['message' => 'Parent must be a category.'], 422);
}
}
$forum->parent_id = $parentId;
}
if (array_key_exists('name', $data)) {
$forum->name = $data['name'];
}
if (array_key_exists('description', $data)) {
$forum->description = $data['description'];
}
if (array_key_exists('type', $data)) {
$forum->type = $data['type'];
}
$forum->save();
return response()->json($this->serializeForum($forum));
}
public function destroy(Forum $forum): JsonResponse
{
$forum->delete();
return response()->json(null, 204);
}
public function reorder(Request $request): JsonResponse
{
$data = $request->validate([
'parentId' => ['nullable'],
'orderedIds' => ['required', 'array'],
'orderedIds.*' => ['integer'],
]);
$parentId = $data['parentId'] ?? null;
if ($parentId === '' || $parentId === 'null') {
$parentId = null;
} elseif ($parentId !== null) {
$parentId = (int) $parentId;
}
foreach ($data['orderedIds'] as $index => $forumId) {
Forum::where('id', $forumId)
->where('parent_id', $parentId)
->update(['position' => $index + 1]);
}
return response()->json(['status' => 'ok']);
}
private function parseIriId(?string $value): ?int
{
if (!$value) {
return null;
}
if (preg_match('#/forums/(\d+)$#', $value, $matches)) {
return (int) $matches[1];
}
if (is_numeric($value)) {
return (int) $value;
}
return null;
}
private function serializeForum(Forum $forum): array
{
return [
'id' => $forum->id,
'name' => $forum->name,
'description' => $forum->description,
'type' => $forum->type,
'parent' => $forum->parent_id ? "/api/forums/{$forum->parent_id}" : null,
'position' => $forum->position,
'created_at' => $forum->created_at?->toIso8601String(),
'updated_at' => $forum->updated_at?->toIso8601String(),
];
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\File;
class I18nController extends Controller
{
public function __invoke(string $locale): JsonResponse
{
$path = resource_path("lang/{$locale}.json");
if (!File::exists($path)) {
return response()->json([], 404);
}
$contents = File::get($path);
return response()->json(json_decode($contents, true, 512, JSON_THROW_ON_ERROR));
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Thread;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Post::query();
$threadParam = $request->query('thread');
if (is_string($threadParam)) {
$threadId = $this->parseIriId($threadParam);
if ($threadId !== null) {
$query->where('thread_id', $threadId);
}
}
$posts = $query
->oldest('created_at')
->get()
->map(fn (Post $post) => $this->serializePost($post));
return response()->json($posts);
}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'body' => ['required', 'string'],
'thread' => ['required', 'string'],
]);
$threadId = $this->parseIriId($data['thread']);
$thread = Thread::findOrFail($threadId);
$post = Post::create([
'thread_id' => $thread->id,
'user_id' => $request->user()?->id,
'body' => $data['body'],
]);
return response()->json($this->serializePost($post), 201);
}
public function destroy(Post $post): JsonResponse
{
$post->delete();
return response()->json(null, 204);
}
private function parseIriId(?string $value): ?int
{
if (!$value) {
return null;
}
if (preg_match('#/threads/(\d+)$#', $value, $matches)) {
return (int) $matches[1];
}
if (is_numeric($value)) {
return (int) $value;
}
return null;
}
private function serializePost(Post $post): array
{
return [
'id' => $post->id,
'body' => $post->body,
'thread' => "/api/threads/{$post->thread_id}",
'user_id' => $post->user_id,
'created_at' => $post->created_at?->toIso8601String(),
'updated_at' => $post->updated_at?->toIso8601String(),
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class SettingController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Setting::query();
if ($request->filled('key')) {
$query->where('key', $request->query('key'));
}
$settings = $query->get()->map(fn (Setting $setting) => [
'id' => $setting->id,
'key' => $setting->key,
'value' => $setting->value,
]);
return response()->json($settings);
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers;
use App\Models\Forum;
use App\Models\Thread;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ThreadController extends Controller
{
public function index(Request $request): JsonResponse
{
$query = Thread::query();
$forumParam = $request->query('forum');
if (is_string($forumParam)) {
$forumId = $this->parseIriId($forumParam);
if ($forumId !== null) {
$query->where('forum_id', $forumId);
}
}
$threads = $query
->latest('created_at')
->get()
->map(fn (Thread $thread) => $this->serializeThread($thread));
return response()->json($threads);
}
public function show(Thread $thread): JsonResponse
{
return response()->json($this->serializeThread($thread));
}
public function store(Request $request): JsonResponse
{
$data = $request->validate([
'title' => ['required', 'string'],
'body' => ['required', 'string'],
'forum' => ['required', 'string'],
]);
$forumId = $this->parseIriId($data['forum']);
$forum = Forum::findOrFail($forumId);
if ($forum->type !== 'forum') {
return response()->json(['message' => 'Threads can only be created inside forums.'], 422);
}
$thread = Thread::create([
'forum_id' => $forum->id,
'user_id' => $request->user()?->id,
'title' => $data['title'],
'body' => $data['body'],
]);
return response()->json($this->serializeThread($thread), 201);
}
public function destroy(Thread $thread): JsonResponse
{
$thread->delete();
return response()->json(null, 204);
}
private function parseIriId(?string $value): ?int
{
if (!$value) {
return null;
}
if (preg_match('#/forums/(\d+)$#', $value, $matches)) {
return (int) $matches[1];
}
if (is_numeric($value)) {
return (int) $value;
}
return null;
}
private function serializeThread(Thread $thread): array
{
return [
'id' => $thread->id,
'title' => $thread->title,
'body' => $thread->body,
'forum' => "/api/forums/{$thread->forum_id}",
'user_id' => $thread->user_id,
'created_at' => $thread->created_at?->toIso8601String(),
'updated_at' => $thread->updated_at?->toIso8601String(),
];
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\JsonResponse;
class UserController extends Controller
{
public function index(): JsonResponse
{
$users = User::query()
->with('roles')
->orderBy('id')
->get()
->map(fn (User $user) => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'roles' => $user->roles->pluck('name')->values(),
]);
return response()->json($users);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\Models\Setting;
use Illuminate\Http\JsonResponse;
class VersionController extends Controller
{
public function __invoke(): JsonResponse
{
$version = Setting::where('key', 'version')->value('value');
$build = Setting::where('key', 'build')->value('value');
return response()->json([
'version' => $version,
'build' => $build !== null ? (int) $build : null,
]);
}
}

60
app/Models/Forum.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name
* @property string|null $description
* @property string $type
* @property int|null $parent_id
* @property int $position
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, Forum> $children
* @property-read int|null $children_count
* @property-read Forum|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Thread> $threads
* @property-read int|null $threads_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereDescription($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereParentId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum wherePosition($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Forum whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Forum extends Model
{
protected $fillable = [
'name',
'description',
'type',
'parent_id',
'position',
];
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id')->orderBy('position');
}
public function threads(): HasMany
{
return $this->hasMany(Thread::class);
}
}

45
app/Models/Post.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int $thread_id
* @property int|null $user_id
* @property string $body
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Thread $thread
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereBody($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereThreadId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Post whereUserId($value)
* @mixin \Eloquent
*/
class Post extends Model
{
protected $fillable = [
'thread_id',
'user_id',
'body',
];
public function thread(): BelongsTo
{
return $this->belongsTo(Thread::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

34
app/Models/Role.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read int|null $users_count
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Role whereUpdatedAt($value)
* @mixin \Eloquent
*/
class Role extends Model
{
protected $fillable = [
'name',
];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
}

29
app/Models/Setting.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property string $key
* @property string $value
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereKey($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Setting whereValue($value)
* @mixin \Eloquent
*/
class Setting extends Model
{
protected $fillable = [
'key',
'value',
];
}

56
app/Models/Thread.php Normal file
View File

@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int $forum_id
* @property int|null $user_id
* @property string $title
* @property string $body
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Forum $forum
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Post> $posts
* @property-read int|null $posts_count
* @property-read \App\Models\User|null $user
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereBody($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereForumId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Thread whereUserId($value)
* @mixin \Eloquent
*/
class Thread extends Model
{
protected $fillable = [
'forum_id',
'user_id',
'title',
'body',
];
public function forum(): BelongsTo
{
return $this->belongsTo(Forum::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
}

90
app/Models/User.php Normal file
View File

@@ -0,0 +1,90 @@
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
/**
* @property int $id
* @property string $name
* @property string $email
* @property \Illuminate\Support\Carbon|null $email_verified_at
* @property string $password
* @property string|null $two_factor_secret
* @property string|null $two_factor_recovery_codes
* @property string|null $two_factor_confirmed_at
* @property string|null $remember_token
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Role> $roles
* @property-read int|null $roles_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \Laravel\Sanctum\PersonalAccessToken> $tokens
* @property-read int|null $tokens_count
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorConfirmedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorRecoveryCodes($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereTwoFactorSecret($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
* @mixin \Eloquent
*/
class User extends Authenticatable implements MustVerifyEmail
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Providers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Str;
use Laravel\Fortify\Actions\RedirectIfTwoFactorAuthenticatable;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
Fortify::redirectUserForTwoFactorAuthenticationUsing(RedirectIfTwoFactorAuthenticatable::class);
RateLimiter::for('login', function (Request $request) {
$throttleKey = Str::transliterate(Str::lower($request->input(Fortify::username())).'|'.$request->ip());
return Limit::perMinute(5)->by($throttleKey);
});
RateLimiter::for('two-factor', function (Request $request) {
return Limit::perMinute(5)->by($request->session()->get('login.id'));
});
}
}