Initial commit
This commit is contained in:
53
laravel/app/Console/Commands/NotifyOverdueTasks.php
Normal file
53
laravel/app/Console/Commands/NotifyOverdueTasks.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Enums\TaskStatus;
|
||||
use App\Models\Task;
|
||||
use App\Notifications\TaskOverdueNotification;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
final class NotifyOverdueTasks extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'tasks:notify-overdue';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
Task::query()
|
||||
->whereNotNull('due_date')
|
||||
->where('due_date', '<', now()->toDateString())
|
||||
->where('status', '!=', TaskStatus::COMPLETED)
|
||||
->whereNull('notified_at')
|
||||
->with('user')
|
||||
->chunkById(100, function ($tasks) {
|
||||
foreach ($tasks as $task) {
|
||||
$task->user->notify(
|
||||
new TaskOverdueNotification($task)
|
||||
);
|
||||
|
||||
$task->update([
|
||||
'notified_at' => now(),
|
||||
]);
|
||||
}
|
||||
});
|
||||
$this->info('Tasks overdue notification send)');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
14
laravel/app/Contracts/AuthServiceContract.php
Normal file
14
laravel/app/Contracts/AuthServiceContract.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Data\Auth\LoginData;
|
||||
use App\Data\Auth\LoginResult;
|
||||
|
||||
interface AuthServiceContract
|
||||
{
|
||||
public function attemptLogin(LoginData $credentials): LoginResult;
|
||||
|
||||
}
|
||||
22
laravel/app/Data/Auth/LoginData.php
Normal file
22
laravel/app/Data/Auth/LoginData.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Auth;
|
||||
|
||||
final readonly class LoginData
|
||||
{
|
||||
public function __construct(
|
||||
public string $email,
|
||||
public string $password
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
return new self(
|
||||
email: strtolower(trim($data['email'])),
|
||||
password: $data['password']
|
||||
);
|
||||
}
|
||||
}
|
||||
29
laravel/app/Data/Auth/LoginResult.php
Normal file
29
laravel/app/Data/Auth/LoginResult.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Data\Auth;
|
||||
|
||||
use App\Enums\LoginError;
|
||||
use App\Models\User;
|
||||
|
||||
final readonly class LoginResult
|
||||
{
|
||||
public function __construct(
|
||||
public ?User $user,
|
||||
public ?string $token,
|
||||
public ?LoginError $error = null
|
||||
) {
|
||||
}
|
||||
|
||||
public static function success(User $user, string $token): self
|
||||
{
|
||||
return new self($user, $token, null);
|
||||
}
|
||||
|
||||
public static function error(LoginError $error): self
|
||||
{
|
||||
return new self(null, null, $error);
|
||||
}
|
||||
|
||||
}
|
||||
27
laravel/app/Enums/LoginError.php
Normal file
27
laravel/app/Enums/LoginError.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
enum LoginError
|
||||
{
|
||||
case INVALID_CREDENTIALS;
|
||||
case SERVER_ERROR;
|
||||
|
||||
public function httpStatusCode(): int
|
||||
{
|
||||
return match ($this) {
|
||||
self::INVALID_CREDENTIALS => Response::HTTP_UNAUTHORIZED,
|
||||
self::SERVER_ERROR => Response::HTTP_INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::INVALID_CREDENTIALS => 'Invalid credentials',
|
||||
self::SERVER_ERROR => 'Authentication failed',
|
||||
};
|
||||
}
|
||||
}
|
||||
38
laravel/app/Enums/TaskStatus.php
Normal file
38
laravel/app/Enums/TaskStatus.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum TaskStatus: string
|
||||
{
|
||||
case PENDING = 'pending';
|
||||
case IN_PROGRESS = 'in_progress';
|
||||
case COMPLETED = 'completed';
|
||||
|
||||
public static function values(): array
|
||||
{
|
||||
return array_map(fn($case) => $case->value, self::cases());
|
||||
}
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return [
|
||||
self::PENDING->value => 'В ожидании',
|
||||
self::IN_PROGRESS->value => 'В процессе',
|
||||
self::COMPLETED->value => 'Завершена',
|
||||
];
|
||||
}
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => 'В ожидании',
|
||||
self::IN_PROGRESS => 'В процессе',
|
||||
self::COMPLETED => 'Завершена',
|
||||
};
|
||||
}
|
||||
|
||||
public function isCompleted(): bool
|
||||
{
|
||||
return $this === self::COMPLETED;
|
||||
}
|
||||
}
|
||||
36
laravel/app/Events/TaskCreated.php
Normal file
36
laravel/app/Events/TaskCreated.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
final class TaskCreated
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Task $task
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('channel-name'),
|
||||
];
|
||||
}
|
||||
}
|
||||
84
laravel/app/Http/Controllers/Api/V1/TaskController.php
Normal file
84
laravel/app/Http/Controllers/Api/V1/TaskController.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Events\TaskCreated;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreTaskRequest;
|
||||
use App\Http\Requests\UpdateTaskRequest;
|
||||
use App\Http\Resources\TaskResource;
|
||||
use App\Http\Resources\TaskResourceCollection;
|
||||
use App\Models\Task;
|
||||
use Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class TaskController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Task::class, 'task');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): TaskResourceCollection
|
||||
{
|
||||
$tasks = request()->user()
|
||||
->tasks()
|
||||
->latest()
|
||||
->paginate(10);
|
||||
|
||||
return new TaskResourceCollection($tasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreTaskRequest $request)
|
||||
{
|
||||
$task = Auth::user()->tasks()->create($request->validated());
|
||||
|
||||
event(new TaskCreated($task)); // вынести бы из контроллера
|
||||
|
||||
return new TaskResource($task)
|
||||
->response()
|
||||
->setStatusCode(Response::HTTP_CREATED)
|
||||
->header('Location', route('tasks.show', $task));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Task $task): TaskResource
|
||||
{
|
||||
return new TaskResource($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateTaskRequest $request, Task $task): TaskResource
|
||||
{
|
||||
$task->update($request->validated());
|
||||
|
||||
if ($task->status->isCompleted()) {
|
||||
$task->notified_at = null;
|
||||
$task->save();
|
||||
}
|
||||
|
||||
return new TaskResource($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Task $task)
|
||||
{
|
||||
$task->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
65
laravel/app/Http/Controllers/AuthController.php
Normal file
65
laravel/app/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Contracts\AuthServiceContract;
|
||||
use App\Data\Auth\LoginData;
|
||||
use App\Http\Requests\LoginRequest;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Throwable;
|
||||
|
||||
final class AuthController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthServiceContract $authService
|
||||
) {
|
||||
}
|
||||
|
||||
public function login(LoginRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$credentials = LoginData::fromArray($request->validated());
|
||||
$result = $this->authService->attemptLogin($credentials);
|
||||
|
||||
if ($result->error) {
|
||||
return response()->json([
|
||||
'message' => $result->error->message(),
|
||||
], $result->error->httpStatusCode());
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'token' => $result->token,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
Log::error('AuthController.login: '.$e->getMessage(), [
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
return response()->json([
|
||||
'message' => 'Unavailable'
|
||||
], Response::HTTP_SERVICE_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->user()->tokens()->delete();
|
||||
return response()->json([
|
||||
'message' => 'Logged out'
|
||||
], Response::HTTP_OK);
|
||||
} catch (Throwable $e) {
|
||||
Log::error('AuthController.logout: '.$e->getMessage(), [
|
||||
'ip' => $request->ip()
|
||||
]);
|
||||
return response()->json([
|
||||
'message' => 'Unavailable'
|
||||
], Response::HTTP_SERVICE_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
laravel/app/Http/Controllers/Controller.php
Normal file
11
laravel/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
}
|
||||
55
laravel/app/Http/Requests/LoginRequest.php
Normal file
55
laravel/app/Http/Requests/LoginRequest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
],
|
||||
'password' => [
|
||||
'required',
|
||||
'string',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'email.required' => 'Email is required',
|
||||
'password.required' => 'Password is required',
|
||||
'email.email' => 'Please enter a valid email address',
|
||||
'email.exists' => 'Please enter a valid email address',
|
||||
];
|
||||
}
|
||||
}
|
||||
33
laravel/app/Http/Requests/StoreTaskRequest.php
Normal file
33
laravel/app/Http/Requests/StoreTaskRequest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\TaskStatus;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreTaskRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'status' => ['required', Rule::in(TaskStatus::values())],
|
||||
'due_date' => ['required', 'date'],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
laravel/app/Http/Requests/UpdateTaskRequest.php
Normal file
33
laravel/app/Http/Requests/UpdateTaskRequest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\TaskStatus;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaskRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['sometimes', 'string', 'max:255'],
|
||||
'description' => ['sometimes', 'string'],
|
||||
'status' => ['sometimes', Rule::in(TaskStatus::values())],
|
||||
'due_date' => ['sometimes', 'date', 'after_or_equal:today'],
|
||||
];
|
||||
}
|
||||
}
|
||||
32
laravel/app/Http/Resources/TaskResource.php
Normal file
32
laravel/app/Http/Resources/TaskResource.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin Task
|
||||
*/
|
||||
class TaskResource extends JsonResource
|
||||
{
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'status' => $this->status->value,
|
||||
'due_date' => $this->due_date?->toDateString(),
|
||||
'created_at' => $this->created_at?->toDateTimeString(),
|
||||
'updated_at' => $this->updated_at?->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
28
laravel/app/Http/Resources/TaskResourceCollection.php
Normal file
28
laravel/app/Http/Resources/TaskResourceCollection.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\ResourceCollection;
|
||||
|
||||
class TaskResourceCollection extends ResourceCollection
|
||||
{
|
||||
/**
|
||||
* Указывает, что коллекция содержит ресурсы TaskResource
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $collects = TaskResource::class;
|
||||
|
||||
/**
|
||||
* Transform the resource collection into an array.
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'data' => $this->collection,
|
||||
];
|
||||
}
|
||||
}
|
||||
29
laravel/app/Listeners/SendTaskCreatedNotification.php
Normal file
29
laravel/app/Listeners/SendTaskCreatedNotification.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\TaskCreated;
|
||||
use App\Notifications\TaskCreatedNotification;
|
||||
|
||||
final class SendTaskCreatedNotification
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(TaskCreated $event): void
|
||||
{
|
||||
$event->task
|
||||
->user
|
||||
->notify(new TaskCreatedNotification($event->task));
|
||||
}
|
||||
}
|
||||
72
laravel/app/Models/Task.php
Normal file
72
laravel/app/Models/Task.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\TaskStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property string $title
|
||||
* @property string|null $description
|
||||
* @property TaskStatus $status
|
||||
* @property \Illuminate\Support\Carbon|null $due_date
|
||||
* @property \Illuminate\Support\Carbon|null $notified_at
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property-read \App\Models\User $user
|
||||
* @method static \Database\Factories\TaskFactory factory($count = null, $state = [])
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task newModelQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task newQuery()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task query()
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereCreatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereDescription($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereDueDate($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereNotifiedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereStatus($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereTitle($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereUpdatedAt($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Task whereUserId($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class Task extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'title',
|
||||
'description',
|
||||
'status',
|
||||
'due_date',
|
||||
'notified_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'due_date' => 'date',
|
||||
'notified_at' => 'datetime',
|
||||
'status' => TaskStatus::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
}
|
||||
88
laravel/app/Models/User.php
Normal file
88
laravel/app/Models/User.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
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 $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\Task> $tasks
|
||||
* @property-read int|null $tasks_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 whereUpdatedAt($value)
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasApiTokens;
|
||||
|
||||
/**
|
||||
* 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 tasks(): \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->hasMany(Task::class);
|
||||
}
|
||||
|
||||
// public function sendEmailVerificationNotification()
|
||||
// {
|
||||
// $this->notify();
|
||||
// } TODO тут сделать нотификации или как ?
|
||||
}
|
||||
57
laravel/app/Notifications/TaskCreatedNotification.php
Normal file
57
laravel/app/Notifications/TaskCreatedNotification.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
final class TaskCreatedNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Task $task
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject('Task created')
|
||||
->line("Task «{$this->task->title}» successfully created");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
56
laravel/app/Notifications/TaskOverdueNotification.php
Normal file
56
laravel/app/Notifications/TaskOverdueNotification.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Models\Task;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
final class TaskOverdueNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Task $task
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function via(): array
|
||||
{
|
||||
return ['mail'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mail representation of the notification.
|
||||
*/
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
return (new MailMessage)
|
||||
->subject('Task overdue')
|
||||
->line("Task «{$this->task->title}» overdue");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
65
laravel/app/Policies/TaskPolicy.php
Normal file
65
laravel/app/Policies/TaskPolicy.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Task;
|
||||
use App\Models\User;
|
||||
|
||||
class TaskPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, Task $task): bool
|
||||
{
|
||||
return $user->id === $task->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, Task $task): bool
|
||||
{
|
||||
return $user->id === $task->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, Task $task): bool
|
||||
{
|
||||
return $user->id === $task->user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, Task $task): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, Task $task): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
26
laravel/app/Providers/AppServiceProvider.php
Normal file
26
laravel/app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Contracts\AuthServiceContract;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->singleton(AuthServiceContract::class, AuthService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
46
laravel/app/Services/AuthService.php
Normal file
46
laravel/app/Services/AuthService.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Contracts\AuthServiceContract;
|
||||
use App\Data\Auth\LoginData;
|
||||
use App\Data\Auth\LoginResult;
|
||||
use App\Enums\LoginError;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Log;
|
||||
use Throwable;
|
||||
|
||||
final class AuthService implements AuthServiceContract
|
||||
{
|
||||
public function attemptLogin(LoginData $credentials): LoginResult
|
||||
{
|
||||
try {
|
||||
if (!Auth::attempt(['email' => $credentials->email, 'password' => $credentials->password])) {
|
||||
Log::warning('Failed login attempt', [
|
||||
'email' => $credentials->email,
|
||||
'ip' => request()->ip()
|
||||
]);
|
||||
return LoginResult::error(LoginError::INVALID_CREDENTIALS);
|
||||
}
|
||||
|
||||
$user = User::where('email', $credentials->email)->firstOrFail();
|
||||
$token = $user->createToken('auth_token')->plainTextToken;
|
||||
|
||||
Log::info('User logged in', [
|
||||
'user_id' => $user->id,
|
||||
'email' => $user->email,
|
||||
'ip' => request()->ip()
|
||||
]);
|
||||
|
||||
return LoginResult::success($user, $token);
|
||||
} catch (Throwable $e) {
|
||||
Log::error($e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
return LoginResult::error(LoginError::SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user