// Package port declares the hexagon's boundaries. The core (service) depends // only on these interfaces; adapters (postgres, redis, oidc, …) implement them. package port import ( "context" "time" "github.com/snip/backend/internal/domain" ) // LinkRepository is the source of truth (Postgres in prod, in-memory in tests). type LinkRepository interface { Create(ctx context.Context, l *domain.Link) error GetByID(ctx context.Context, id string) (*domain.Link, error) GetByCode(ctx context.Context, code string) (*domain.Link, error) // ListByOwner returns one page of the owner's links matching the (optional) // query, newest first, plus the total count for that filter — all done in // the database so the API never loads every row. ListByOwner(ctx context.Context, ownerID, query string, limit, offset int) (items []domain.Link, total int, err error) // StatsByOwner returns the aggregate dashboard tiles for an owner. StatsByOwner(ctx context.Context, ownerID string) (domain.OwnerStats, error) Update(ctx context.Context, l *domain.Link) error Delete(ctx context.Context, id, ownerID string) error ExistsCode(ctx context.Context, code string) (bool, error) // NextSequence returns a monotonically increasing int used to mint the // shortest-possible unique base62 code for ModeRandom. NextSequence(ctx context.Context) (int64, error) // RecordClicks adds n clicks for a code on a given day (batched, off the // hot path) and bumps the running total. RecordClicks(ctx context.Context, code string, day time.Time, n int64) error } // Resolution is the tiny payload the redirect hot path needs. Cached in Redis // so the common case never touches Postgres. type Resolution struct { LongURL string HasPin bool PinHash string Found bool // false = negatively cached (known-missing code) } // LinkCache fronts the repository for read-heavy redirect traffic. type LinkCache interface { GetResolution(ctx context.Context, code string) (res Resolution, hit bool, err error) SetResolution(ctx context.Context, code string, r Resolution, ttl time.Duration) error Invalidate(ctx context.Context, code string) error } // UserRepository persists authenticated accounts. type UserRepository interface { Upsert(ctx context.Context, u *domain.User) (*domain.User, error) GetByID(ctx context.Context, id string) (*domain.User, error) } // Identity is the normalized result of an OAuth/OIDC exchange. type Identity struct { Subject string Email string Name string } // IdentityProvider abstracts a login method (Google OAuth, generic OIDC). type IdentityProvider interface { Name() string AuthURL(state string) string Exchange(ctx context.Context, code string) (*Identity, error) } // SessionManager mints and verifies stateless session tokens. type SessionManager interface { Issue(userID string, ttl time.Duration) (string, error) Verify(token string) (userID string, err error) } // PasswordHasher hashes/compares PINs (bcrypt in prod). type PasswordHasher interface { Hash(plain string) (string, error) Compare(hash, plain string) bool } // ClickRecorder accepts clicks off the hot path; implementations batch them. type ClickRecorder interface { Record(code string) }