mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
86 lines
3.1 KiB
Go
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)
|
|
}
|