Commit graph

32 commits

Author SHA1 Message Date
yisroel
504554bf7f
test CreatePost use case
7 cases: null + whitespace title -> BadRequest; null +
whitespace body -> BadRequest; valid request returns Post with
correct userId/title/body and createdAt = clock.now(); the post
is findable via the repo afterwards; title and body get trimmed
of leading/trailing whitespace. fails red - CreatePost class
absent.
2026-05-06 15:22:57 +03:00
yisroel
e3dddc60aa
add Post persistence: model, migration, eloquent + fake repo
PostModel maps posts table (id, user_id fk, title, body text,
created_at indexed). EloquentPostRepository: create, find,
findByUserId (desc by created_at), findRecent (limit, desc),
delete - chain via ::query() to keep larastan happy.
FakePostRepository sorts on read (defensive copy each return).
cascade-on-delete on user_id so removing a user nukes their
posts.

phpstan.neon suppresses staticMethod.dynamicCall under
app/*/Eloquent*Repository.php - phpstan-strict-rules flags
Eloquent's fluent builder idiom (Model::query()->orderBy())
because the static methods become instance calls mid-chain.
suppression scoped to repo files only so the rule still
applies elsewhere.
2026-05-06 15:22:22 +03:00
yisroel
73a3acd39f
add Post entity, dto, repository interface
Post: id, userId (fk -> User), title, body, createdAt as
DateTimeImmutable. CreatePostDto readonly with explicit
createdAt (use case supplies it via Clock; entity remains pure).
PostRepository: create, find, findByUserId, findRecent (limit),
delete.
2026-05-06 15:19:00 +03:00
Yisroel Baum
9ca58f3a9d
merge user-and-auth-domains
phase 2: User + Auth foundations.

User domain: entity (id, email, passwordHash, isAdmin), dto,
repository interface, eloquent + fake impls, users migration,
SignupUser use case (validates email, length, uniqueness; hashes
password; isAdmin always false on signup).

Auth domain: Clock + TokenGenerator + PasswordHasher utility
interfaces with system + bcrypt + random impls and matching
fakes. Session entity + dto + repository interface, eloquent +
fake impls, sessions migration (token primary, fk user_id
cascade, expires_at indexed). AuthenticateUser, CreateSession,
Logout use cases. AuthMiddleware reads auth_token cookie and
attaches User to request attributes.

bindings wired in AppServiceProvider + new
RepositoryServiceProvider.

37 tests, 59 assertions; stan + cs clean.
2026-05-06 15:18:23 +03:00
yisroel
2e3265e568
wire repository and utility bindings
AppServiceProvider binds Clock -> SystemClock, TokenGenerator ->
RandomTokenGenerator, PasswordHasher -> BcryptPasswordHasher.
new RepositoryServiceProvider binds UserRepository ->
EloquentUserRepository, SessionRepository ->
EloquentSessionRepository. bootstrap/providers.php registers
both. verified container resolves: app(Clock::class) returns
SystemClock. migrations create users + sessions tables with
proper unique/foreign-key/index constraints (sqlite roundtrip
confirmed).
2026-05-06 15:18:06 +03:00
yisroel
ca8a2066de
implement AuthMiddleware
reads auth_token cookie (constant COOKIE_NAME for cross-layer
sharing with the AuthController). missing/empty cookie or
unknown token -> 401 json {error: unauthenticated}. expired
session is deleted then 401 returned. valid session attaches
the User entity to request attributes under 'user' so
downstream controllers can read it via request attributes. 37
tests pass.
2026-05-06 15:16:59 +03:00
yisroel
d87215ff9b
test AuthMiddleware
4 cases: missing auth_token cookie -> 401 json
{error: unauthenticated}; unknown token -> 401; expired token
-> 401 + repo cleanup; valid token -> 200 with the User attached
to request->attributes['user']. fails red - middleware class
absent.
2026-05-06 15:16:35 +03:00
yisroel
526a1b8f61
implement Logout use case
thin pass-through to SessionRepository->deleteByToken. test
green; 33 tests pass.
2026-05-06 15:16:03 +03:00
yisroel
30e97864c8
test Logout use case
2 cases: existing token's session gets removed; unknown token
is a no-op (deleteByToken stays idempotent).
2026-05-06 15:15:46 +03:00
yisroel
0697e4af69
implement CreateSession use case
generates token via injected TokenGenerator, asks Clock for now,
sets expiry to now+7d, persists through SessionRepository->create
and returns the resulting Session. all 31 tests pass.
2026-05-06 15:15:25 +03:00
yisroel
adc60a8059
test CreateSession use case
4 cases: returns Session with the generated token + supplied
user; createdAt matches injected Clock now; expiresAt is now+7d;
session is findable via SessionRepository->findByToken. fails
red - CreateSession class missing.
2026-05-06 15:15:04 +03:00
yisroel
5b74e9d76a
implement AuthenticateUser use case
input validation: email + password required. constructs
EmailAddress vo (BadRequest on bad format). looks up user; absent
or password-mismatch -> UnauthorizedException with constant
'invalid credentials' message (no enumeration leak). password
verified through PasswordHasher->verify against stored hash on
the User entity (no separate profile lookup -> tide keeps
password on the user row). returns the User entity for the
caller (typically CreateSession + AuthController). 27 tests
pass.
2026-05-06 15:14:34 +03:00
yisroel
2731e610e5
test AuthenticateUser use case
9 cases: null/empty/malformed email -> BadRequest; null/empty
password -> BadRequest; unknown email -> Unauthorized; wrong
password -> Unauthorized; valid creds return the User entity;
isAdmin flag survives the auth round-trip. fails red - the
AuthenticateUser class does not exist yet.
2026-05-06 15:14:03 +03:00
yisroel
a108b29d19
implement SignupUser use case
validates email present + format (wraps EmailAddress vo's
InvalidArgumentException as BadRequest), password present +
>= 8 chars, then ensures email not already registered. hashes
password through injected PasswordHasher and persists via
UserRepository->create with isAdmin=false (admins are seeder-
only per plan). throws DomainException on duplicate email so
the controller layer can map it to 409. all 18 tests pass.
2026-05-06 15:13:26 +03:00
yisroel
fefc992431
test SignupUser use case
9 cases: null/empty/malformed email -> BadRequest; null or
sub-8-char password -> BadRequest; duplicate email -> DomainException;
valid signup returns User with hashed password and isAdmin=false;
user is findable by email afterwards; EmailAddress vo lowercases
the domain. fails red - SignupUser class not yet defined.
2026-05-06 15:12:52 +03:00
yisroel
05f935f275
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.
2026-05-06 15:12:07 +03:00
yisroel
bb38e544ee
add auth utility interfaces and impls
Clock + SystemClock (DateTimeImmutable in UTC), TokenGenerator +
RandomTokenGenerator (bin2hex(random_bytes(32)) -> 64-char hex),
PasswordHasher + BcryptPasswordHasher (password_hash with
PASSWORD_DEFAULT, password_verify). matching fakes:
FakeClock with mutable setTime, FakeTokenGenerator with a
pre-seeded queue (throws once exhausted), FakePasswordHasher
returns 'hashed:<plain>' for deterministic test assertions.
composer stan now passes --memory-limit=512M (default 128M
overflows once larastan loads more rules).
2026-05-06 15:11:19 +03:00
yisroel
eca73213f5
add User persistence: model, migration, eloquent + fake repo
UserModel maps users table (id, email unique, password_hash,
is_admin bool default false). EloquentUserRepository implements
UserRepository: create from CreateUserDto, find by id,
findByEmail. toDomain() materializes a User entity wrapping email
in EmailAddress vo. FakeUserRepository: in-memory map keyed by
auto-incrementing id, returns defensive copies on read (per
youngstartup pattern). composer stan script now passes
--no-progress for cleaner ci output.
2026-05-06 15:10:21 +03:00
yisroel
533320fcac
add User entity, dto, repository interface
User holds email (EmailAddress vo), passwordHash, isAdmin - tide
keeps password and admin flag on the user row directly (no
separate profile entity like youngstartup). UserRepository
exposes find, findByEmail, create. CreateUserDto is readonly with
explicit isAdmin (per shared.md no-default-args rule).
2026-05-06 15:00:49 +03:00
Yisroel Baum
f47ea1c73d
merge harden-devshell
hot-fix: pgdata anchored to repo root, .postgres/.direnv ignored
at any nesting. resolves stray backend/.postgres tracking caught
during phase 1 merge.
2026-05-06 14:58:44 +03:00
yisroel
ee1b30d0ec
harden devshell: anchor pgdata to repo root, broaden gitignore
shellHook now derives PGDATA from $(git rev-parse --show-toplevel)
instead of $PWD. nix develop / direnv from a subdir (e.g.
backend/) used to seed a duplicate cluster at backend/.postgres
that leaked into git tracking. .gitignore loses its leading slash
on .postgres/ + .direnv/ + .pc.* so any nested cluster also gets
ignored. fallback to pwd preserves behavior outside a git repo.

968 stray backend/.postgres/* blobs already pruned from history
via git-filter-repo before this commit.
2026-05-06 14:58:27 +03:00
Yisroel Baum
0178654535 merge backend-skeleton
phase 1: laravel 12 + ddd tooling + base scaffolding
(exceptions, EmailAddress vo, dev tooling aligned with
youngstartup conventions).
2026-05-06 14:55:27 +03:00
yisroel
4f6ac876ce implement EmailAddress value object
immutable readonly. trims whitespace, splits on @, lowercases the
domain (local-part case preserved per RFC 5321), validates with
FILTER_VALIDATE_EMAIL after normalization. throws
InvalidArgumentException on empty / missing-@ / malformed input.
exposes value(), getDomain(), equals(), __toString(). all 7
EmailAddressTest cases green; 9 tests total pass.
2026-05-06 14:52:45 +03:00
yisroel
f862348a31 test EmailAddress value object
7 cases: rejects spaces, double-@, empty input; trims whitespace;
lowercases domain only (preserving local-part case); equality by
normalized value; __toString and getDomain. fails red - class
App\\Shared\\ValueObject\\EmailAddress not yet defined.
2026-05-06 14:52:14 +03:00
yisroel
fb5f8d4f02 add domain exception classes
BadRequestException, UnauthorizedException, ForbiddenException -
all extend DomainException. use cases throw these to signal HTTP
4xx categories; controllers translate to JsonResponse status
codes (400, 401, 403).
2026-05-06 14:51:41 +03:00
yisroel
c2ba02cc6f drop laravel scaffold defaults
removed app/Models/User.php (laravel auth model - tide authors a
ddd User entity in app/User/), app/Http/Controllers/Controller.php
(controllers live flat in app/Controllers/ per youngstartup), and
all three 0001_01_01_* migrations (default users schema, cache,
jobs - tide writes its own users migration with is_admin and
password_hash). routes/api.php stripped of the sanctum-bound
/user demo route - left as an empty stub for incoming domains.
2026-05-06 14:51:21 +03:00
yisroel
d58bc9036d align dev tooling with youngstartup conventions
require-dev: phpstan ^2.1 + strict rules, larastan ^3, php-cs-fixer
^3.91 + jubeki/laravel-code-style ^2.18, ide-helper ^3.7. add
phpstan.neon (level max, app/ scope) and .php-cs-fixer.dist.php
(jubeki preset, modifier_keywords rule). phpunit.xml mirrors
youngstartup (sqlite :memory:, bcrypt rounds 4, enforceTimeLimit).
gitignore picks up _ide_helper.php, *~, .php-cs-fixer.cache. drop
sail's phpactor.json line. composer scripts: drop npm bits from
setup/dev (frontend lives separately). composer test + stan +
cs:check all green on default scaffold.
2026-05-06 14:49:27 +03:00
yisroel
ffd102a1cc drop inline laravel frontend assets
frontend lives separately at frontend/blog_portal (vue 3 spa).
removed backend/{vite.config.js,package.json,resources/css,
resources/js}.
2026-05-06 14:47:31 +03:00
yisroel
c03ffc8941 scaffold laravel 12 backend
composer create-project laravel/laravel + artisan install:api.
sanctum removed (custom session-cookie auth per ai/backend-context).
personal_access_tokens migration + config/sanctum.php deleted.
default .gitignore excludes vendor/, .env, etc - composer install
recreates vendor at setup time.
2026-05-06 14:46:41 +03:00
Yisroel Baum
5425df0eae merge nix-devshell
phase 0: nix flake devshell + process-compose orchestrator.
2026-05-06 14:44:13 +03:00
yisroel
d1df7a6a42 add nix flake devshell and process-compose
mirrors youngstartup setup. flake provides php 8.4, composer,
nodejs, postgresql, cypress, typescript, process-compose. shellHook
seeds a per-repo postgres cluster at .postgres/. process-compose
orchestrates postgres + backend (laravel) + vite (vue spa) for
local dev. .envrc auto-loads the flake via direnv. mailpit and
gitlab-ci-local omitted - not needed for tide blogging app.
2026-05-06 14:38:29 +03:00
yisroel
91cc08614d init: agent context files 2026-05-06 14:37:39 +03:00