add Session entity, persistence, fake
Session: immutable holder of token, owning User, createdAt, expiresAt. isExpired(now) compares >= expiresAt. SessionModel keys on token (string primary, non-incrementing). migration adds sessions table with foreign user_id (cascade on user delete) and indexed expires_at for cleanup queries. EloquentSessionRepository takes UserRepository to rehydrate the owning User on findByToken; sessions for deleted users return null. FakeSessionRepository mirrors with an in-memory map keyed by token, defensive copies on read.
This commit is contained in:
parent
bb38e544ee
commit
05f935f275
7 changed files with 246 additions and 0 deletions
16
backend/app/Auth/CreateSessionDto.php
Normal file
16
backend/app/Auth/CreateSessionDto.php
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
use App\User\User;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
class CreateSessionDto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $token,
|
||||||
|
public User $user,
|
||||||
|
public DateTimeImmutable $createdAt,
|
||||||
|
public DateTimeImmutable $expiresAt,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
60
backend/app/Auth/EloquentSessionRepository.php
Normal file
60
backend/app/Auth/EloquentSessionRepository.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
use App\User\UserRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
class EloquentSessionRepository implements SessionRepository
|
||||||
|
{
|
||||||
|
public function __construct(private UserRepository $userRepo) {}
|
||||||
|
|
||||||
|
public function create(CreateSessionDto $dto): Session
|
||||||
|
{
|
||||||
|
SessionModel::create([
|
||||||
|
'token' => $dto->token,
|
||||||
|
'user_id' => $dto->user->getId(),
|
||||||
|
'created_at' => $dto->createdAt,
|
||||||
|
'expires_at' => $dto->expiresAt,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new Session(
|
||||||
|
token: $dto->token,
|
||||||
|
user: $dto->user,
|
||||||
|
createdAt: $dto->createdAt,
|
||||||
|
expiresAt: $dto->expiresAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByToken(string $token): ?Session
|
||||||
|
{
|
||||||
|
$model = SessionModel::find($token);
|
||||||
|
if ($model === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$user = $this->userRepo->find($model->user_id);
|
||||||
|
if ($user === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$utc = new DateTimeZone('UTC');
|
||||||
|
|
||||||
|
return new Session(
|
||||||
|
token: $model->token,
|
||||||
|
user: $user,
|
||||||
|
createdAt: new DateTimeImmutable(
|
||||||
|
$model->created_at->toDateTimeString(),
|
||||||
|
$utc
|
||||||
|
),
|
||||||
|
expiresAt: new DateTimeImmutable(
|
||||||
|
$model->expires_at->toDateTimeString(),
|
||||||
|
$utc
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteByToken(string $token): void
|
||||||
|
{
|
||||||
|
SessionModel::where('token', $token)->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
41
backend/app/Auth/Session.php
Normal file
41
backend/app/Auth/Session.php
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
use App\User\User;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
|
class Session
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private string $token,
|
||||||
|
private User $user,
|
||||||
|
private DateTimeImmutable $createdAt,
|
||||||
|
private DateTimeImmutable $expiresAt,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getToken(): string
|
||||||
|
{
|
||||||
|
return $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUser(): User
|
||||||
|
{
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExpiresAt(): DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isExpired(DateTimeImmutable $now): bool
|
||||||
|
{
|
||||||
|
return $now >= $this->expiresAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
backend/app/Auth/SessionModel.php
Normal file
44
backend/app/Auth/SessionModel.php
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $token
|
||||||
|
* @property int $user_id
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $expires_at
|
||||||
|
*
|
||||||
|
* @method static Builder<static>|SessionModel newModelQuery()
|
||||||
|
* @method static Builder<static>|SessionModel newQuery()
|
||||||
|
* @method static Builder<static>|SessionModel query()
|
||||||
|
*
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class SessionModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'sessions';
|
||||||
|
|
||||||
|
protected $primaryKey = 'token';
|
||||||
|
|
||||||
|
public $incrementing = false;
|
||||||
|
|
||||||
|
protected $keyType = 'string';
|
||||||
|
|
||||||
|
public $timestamps = false;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'token',
|
||||||
|
'user_id',
|
||||||
|
'created_at',
|
||||||
|
'expires_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'expires_at' => 'datetime',
|
||||||
|
];
|
||||||
|
}
|
||||||
12
backend/app/Auth/SessionRepository.php
Normal file
12
backend/app/Auth/SessionRepository.php
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Auth;
|
||||||
|
|
||||||
|
interface SessionRepository
|
||||||
|
{
|
||||||
|
public function create(CreateSessionDto $dto): Session;
|
||||||
|
|
||||||
|
public function findByToken(string $token): ?Session;
|
||||||
|
|
||||||
|
public function deleteByToken(string $token): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?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::create('sessions', function (Blueprint $table) {
|
||||||
|
$table->string('token')->primary();
|
||||||
|
$table->foreignId('user_id')
|
||||||
|
->constrained('users')
|
||||||
|
->cascadeOnDelete();
|
||||||
|
$table->dateTime('created_at');
|
||||||
|
$table->dateTime('expires_at')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('sessions');
|
||||||
|
}
|
||||||
|
};
|
||||||
48
backend/tests/Fakes/FakeSessionRepository.php
Normal file
48
backend/tests/Fakes/FakeSessionRepository.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Fakes;
|
||||||
|
|
||||||
|
use App\Auth\CreateSessionDto;
|
||||||
|
use App\Auth\Session;
|
||||||
|
use App\Auth\SessionRepository;
|
||||||
|
|
||||||
|
class FakeSessionRepository implements SessionRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Session[]
|
||||||
|
*/
|
||||||
|
private array $sessions = [];
|
||||||
|
|
||||||
|
public function create(CreateSessionDto $dto): Session
|
||||||
|
{
|
||||||
|
$session = new Session(
|
||||||
|
token: $dto->token,
|
||||||
|
user: $dto->user,
|
||||||
|
createdAt: $dto->createdAt,
|
||||||
|
expiresAt: $dto->expiresAt,
|
||||||
|
);
|
||||||
|
$this->sessions[$dto->token] = $session;
|
||||||
|
|
||||||
|
return $session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByToken(string $token): ?Session
|
||||||
|
{
|
||||||
|
$session = $this->sessions[$token] ?? null;
|
||||||
|
if ($session === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Session(
|
||||||
|
token: $session->getToken(),
|
||||||
|
user: $session->getUser(),
|
||||||
|
createdAt: $session->getCreatedAt(),
|
||||||
|
expiresAt: $session->getExpiresAt(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteByToken(string $token): void
|
||||||
|
{
|
||||||
|
unset($this->sessions[$token]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue