Commit graph

45 commits

Author SHA1 Message Date
2902addc76
bind mailjet mailer to emailer 2026-05-11 10:37:54 +03:00
d726da1e04
add mailjet mailer 2026-05-11 10:37:45 +03:00
30738b163d
add user:promote artisan command
Marks the user with the given email as an admin. Used by the
cypress harness to bootstrap an admin without a public promote
endpoint and is also useful for ops.
2026-05-06 23:23:00 +03:00
a3f90d1e85
implement user search and admin promote endpoints
GET /users?q=... is public; POST /admin/users/promote is auth
required and admin-checked inside the use case.
2026-05-06 22:36:15 +03:00
ac7295faf3
implement PromoteUserToAdmin use case 2026-05-06 22:34:53 +03:00
d917e76f1b
implement SearchUsers use case 2026-05-06 22:34:08 +03:00
8ac5a5b18a
implement featured post admin endpoints
Adds POST /admin/posts/feature, POST /admin/posts/unfeature
(both auth-required, admin-checked inside controller via the
use case's ForbiddenException), and public GET /posts/featured.
Post serialization now includes featureSlot.
2026-05-06 22:32:46 +03:00
e4791de81a
implement ListFeaturedPosts use case 2026-05-06 22:30:49 +03:00
a8f59afc30
implement ClearFeaturedPost use case 2026-05-06 22:30:17 +03:00
ee95bcafc9
implement SetFeaturedPost use case 2026-05-06 22:29:34 +03:00
f73e5a1af5
extend Post entity with feature slot
Adds nullable feature_slot column (unique) plus repo
findByFeatureSlot/findFeatured/update methods so admins can
pin a post into one of two slots.
2026-05-06 22:28:45 +03:00
59d4ed88c4
implement post and comment controllers
Wires PostController (recent, show, create, delete, listByUser)
and CommentController (listForPost, create, delete) to the
existing use cases. Posts and comments expose author display
names alongside user IDs. CommentRepository binding added to
RepositoryServiceProvider.
2026-05-06 22:26:35 +03:00
d24bde3761
implement DeleteComment use case 2026-05-06 22:16:34 +03:00
a59fc4890f
implement ListCommentsForPost use case
Renames seed() helper to seedComment() to avoid clashing with
Illuminate\Foundation\Testing\TestCase::seed().
2026-05-06 22:15:49 +03:00
e8d2ff3fdf
implement CreateComment use case 2026-05-06 22:14:55 +03:00
93da08756c
add Comment persistence: model, migration, eloquent + fake repo 2026-05-06 22:14:11 +03:00
0d589340d9
add Comment entity, dto, repository interface 2026-05-06 22:13:37 +03:00
9049f1581b
implement auth controller and routes
Wires AuthController (signup, confirmEmail, login, me, logout)
to the existing auth use cases. Routes mounted under /api with
AuthMiddleware on logout/me. RepositoryServiceProvider gains
EmailConfirmationToken and Post bindings; AppServiceProvider
binds the Emailer/EmailFactory and constructs SignupUser with
the configured from-address.
2026-05-06 22:12:51 +03:00
f3c6e2e000
implement SignupUser two-step confirm flow
Signup now collects only email + displayName, creates an
unconfirmed user with empty password hash, mints an
EmailConfirmationToken, and dispatches a confirmation email.
Password is set during ConfirmUserEmail.
2026-05-06 22:08:54 +03:00
6823bdeb50
implement ConfirmUserEmail use case 2026-05-06 22:07:30 +03:00
2890781a56
add Emailer and EmailFactory interfaces with laravel + fake impls 2026-05-06 22:06:30 +03:00
e16cb45387
add EmailConfirmationToken persistence: model, migration, eloquent + fake repo 2026-05-06 22:05:52 +03:00
4b1689d17e
implement SignupUser displayname requirement 2026-05-06 22:03:49 +03:00
298b8634ec
extend User entity with displayname and email confirmation
Add display_name (unique) and email_confirmed_at columns plus
matching getters, DTO fields, repo methods (findByDisplayName,
update), and migration. Existing auth tests updated to construct
User with the new params.
2026-05-06 22:03:19 +03:00
e9ac16377f
implement DeletePost use case 2026-05-06 21:58:25 +03:00
yisroel
7fda18dde3
implement GetPost use case
validates id > 0, delegates to PostRepository->find. 58 tests
pass.
2026-05-06 15:25:56 +03:00
yisroel
32cbf4229c
implement ListUserPosts use case
validates userId > 0, delegates to PostRepository->findByUserId.
54 tests pass.
2026-05-06 15:25:07 +03:00
yisroel
7ec46aa8f9
implement ListRecentPosts use case
validates limit > 0 (zero or negative -> BadRequest), then
delegates to PostRepository->findRecent. 49 tests pass.
2026-05-06 15:24:15 +03:00
yisroel
4a4e046de4
implement CreatePost use case
trims title and body, rejects empty (post-trim) values with
BadRequest. supplies createdAt from injected Clock. persists
through PostRepository->create and returns the resulting Post.
44 tests pass.
2026-05-06 15:23:21 +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
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
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
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
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
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
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
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
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
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