Files
2026-06-15 21:25:57 +07:00

86 lines
3.1 KiB
Go

// 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)
}