From 89b63cb9e9c53adf281505c8c6fc052661a9bcd5 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Sun, 17 May 2026 22:01:27 +0300 Subject: [PATCH] wire postgres session repo, migrations, seed, and dev serve --- .../app/Auth/PostgresSessionRepository.php | 64 +++++++++++++++++++ backend/bin/seed | 40 ++++++++++++ backend/bin/serve | 30 +++++++++ backend/config/container.php | 3 + backend/database/SessionModel.php | 34 ++++++++++ ...026_05_17_000002_create_sessions_table.php | 30 +++++++++ flake.nix | 19 ++++++ process-compose.yml | 17 ++++- 8 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 backend/app/Auth/PostgresSessionRepository.php create mode 100755 backend/bin/seed create mode 100755 backend/bin/serve create mode 100644 backend/database/SessionModel.php create mode 100644 backend/database/migrations/2026_05_17_000002_create_sessions_table.php diff --git a/backend/app/Auth/PostgresSessionRepository.php b/backend/app/Auth/PostgresSessionRepository.php new file mode 100644 index 0000000..728e094 --- /dev/null +++ b/backend/app/Auth/PostgresSessionRepository.php @@ -0,0 +1,64 @@ + $dto->token, + 'user_id' => $dto->user->getId(), + 'created_at' => $dto->createdAt->format('Y-m-d H:i:s'), + 'expires_at' => $dto->expiresAt->format('Y-m-d H:i:s'), + ]); + + return new Session( + token: $record->token, + user: $dto->user, + createdAt: $dto->createdAt, + expiresAt: $dto->expiresAt, + ); + } + + public function findByToken(string $token): ?Session + { + $record = SessionModel::where('token', $token)->first(); + + if ($record === null) { + return null; + } + + $userRecord = $record->user; + + if ($userRecord === null) { + return null; + } + + $user = new User( + id: $userRecord->id, + email: new EmailAddress($userRecord->email), + passwordHash: $userRecord->password_hash, + ); + + return new Session( + token: $record->token, + user: $user, + createdAt: $record->created_at instanceof \DateTimeImmutable + ? $record->created_at + : new \DateTimeImmutable($record->created_at->format('Y-m-d H:i:s')), + expiresAt: $record->expires_at instanceof \DateTimeImmutable + ? $record->expires_at + : new \DateTimeImmutable($record->expires_at->format('Y-m-d H:i:s')), + ); + } + + public function deleteByToken(string $token): void + { + SessionModel::where('token', $token)->delete(); + } +} diff --git a/backend/bin/seed b/backend/bin/seed new file mode 100755 index 0000000..46a5ca2 --- /dev/null +++ b/backend/bin/seed @@ -0,0 +1,40 @@ +#!/usr/bin/env php +safeLoad(); + + require $root . '/config/database.php'; + + $container = require $root . '/config/container.php'; + + $userRepo = $container->get(UserRepository::class); + $hasher = $container->get(PasswordHasher::class); + + $seedEmail = 'admin@rabbigerzi.test'; + $seedPassword = 'password'; + + $email = new EmailAddress($seedEmail); + + if ($userRepo->findByEmail($email) !== null) { + echo "Seed user {$seedEmail} already exists, skipping.\n"; + exit(0); + } + + $userRepo->create(new CreateUserDto( + email: $email, + passwordHash: $hasher->hash($seedPassword), + )); + + echo "Created seed user {$seedEmail} / {$seedPassword}\n"; +})(); diff --git a/backend/bin/serve b/backend/bin/serve new file mode 100755 index 0000000..5a04789 --- /dev/null +++ b/backend/bin/serve @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e + +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +export PGDATA="${PGDATA:-$REPO_ROOT/.postgres}" +export PGHOST="${PGHOST:-$PGDATA}" +export PGUSER="${PGUSER:-postgres}" +export PGDATABASE="${PGDATABASE:-rabbigerzi}" +export DB_HOST="${DB_HOST:-127.0.0.1}" +export DB_PORT="${DB_PORT:-5432}" +export DB_NAME="${DB_NAME:-rabbigerzi}" +export DB_USER="${DB_USER:-postgres}" +export DB_PASSWORD="${DB_PASSWORD:-}" + +echo "[serve] waiting for postgres..." +until pg_isready -h "$PGHOST" -q 2>/dev/null; do + sleep 1 +done + +echo "[serve] creating database if it does not exist..." +createdb --no-password "$PGDATABASE" 2>/dev/null || echo "[serve] database already exists" + +echo "[serve] running migrations..." +php "$REPO_ROOT/backend/bin/migrate" + +echo "[serve] seeding..." +php "$REPO_ROOT/backend/bin/seed" + +echo "[serve] starting PHP dev server..." +exec php -S 127.0.0.1:8000 -t "$REPO_ROOT/backend/public/" diff --git a/backend/config/container.php b/backend/config/container.php index 8bdab3f..0ceac29 100644 --- a/backend/config/container.php +++ b/backend/config/container.php @@ -3,7 +3,9 @@ use App\Auth\BcryptPasswordHasher; use App\Auth\Clock; use App\Auth\PasswordHasher; +use App\Auth\PostgresSessionRepository; use App\Auth\RandomTokenGenerator; +use App\Auth\SessionRepository; use App\Auth\SystemClock; use App\Auth\TokenGenerator; use App\Auth\UseCases\AuthenticateUser\AuthenticateUser; @@ -26,6 +28,7 @@ $builder->addDefinitions([ // Repositories UserRepository::class => DI\create(PostgresUserRepository::class), + SessionRepository::class => DI\create(PostgresSessionRepository::class), // Use cases AuthenticateUser::class => DI\autowire(), diff --git a/backend/database/SessionModel.php b/backend/database/SessionModel.php new file mode 100644 index 0000000..37bbacb --- /dev/null +++ b/backend/database/SessionModel.php @@ -0,0 +1,34 @@ + 'datetime', + 'created_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(UserModel::class, 'user_id'); + } +} diff --git a/backend/database/migrations/2026_05_17_000002_create_sessions_table.php b/backend/database/migrations/2026_05_17_000002_create_sessions_table.php new file mode 100644 index 0000000..0648531 --- /dev/null +++ b/backend/database/migrations/2026_05_17_000002_create_sessions_table.php @@ -0,0 +1,30 @@ +create('sessions', function ($table) { + $table->string('token')->primary(); + $table->integer('user_id')->unsigned(); + $table->foreign('user_id') + ->references('id') + ->on('users') + ->cascadeOnDelete(); + $table->dateTime('expires_at'); + $table->dateTime('created_at'); + }); + } + + public function down(): void + { + Capsule::schema()->dropIfExists('sessions'); + } +} + +return CreateSessionsTable::class; diff --git a/flake.nix b/flake.nix index c312b7c..945ae9d 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,25 @@ postgresql process-compose ]; + + shellHook = '' + REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + export PGDATA="$REPO_ROOT/.postgres" + export PGHOST="$PGDATA" + export PGUSER="postgres" + export PGDATABASE="rabbigerzi" + + if [ ! -d "$PGDATA" ]; then + echo "[pg] initializing cluster at $PGDATA" + initdb --auth=trust --username=postgres --no-locale --encoding=UTF8 >/dev/null + { + echo "listen_addresses = '127.0.0.1'" + echo "unix_socket_directories = '$PGDATA'" + } >> "$PGDATA/postgresql.conf" + fi + + echo "[dev] run 'process-compose up' to start postgres + backend + vite" + ''; }; } ); diff --git a/process-compose.yml b/process-compose.yml index fc0b4b3..7f71fe0 100644 --- a/process-compose.yml +++ b/process-compose.yml @@ -1,9 +1,22 @@ -version: "1" +version: "0.5" processes: + postgres: + command: postgres -D "$PGDATA" -k "$PGDATA" -c listen_addresses=127.0.0.1 + shutdown: + signal: 2 + readiness_probe: + exec: + command: pg_isready -h "$PGDATA" + initial_delay_seconds: 1 + period_seconds: 2 + backend: - command: php -S 127.0.0.1:8000 -t backend/public/ + command: bash backend/bin/serve working_dir: . + depends_on: + postgres: + condition: process_healthy availability: restart: always