mirror of
https://github.com/ThisTine/Snip.git
synced 2026-08-18 23:18:47 +07:00
feat: first commit
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// 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)
|
||||
}
|
||||
Reference in New Issue
Block a user