Documentation
¶
Overview ¶
Package domain holds the identity bounded context's entities, value objects, and ports (interfaces): Household, Member with the unified owner/adult/child Role vocabulary, and Credential — the password half of a member's login. Nothing here imports either app; the ports are implemented by identity/adapter and consumed by identity/app.
household and member ARE the identity domain here, not a reference to Nestova's existing internal/household or internal/auth packages, which stay app-side. See identity/migrate's package doc for the schema this package's adapters run against.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrInvalidCredentials is returned by FindByEmail when no matching // credential is found. It is intentionally generic — callers must not // distinguish "user not found" from "wrong password" to prevent user // enumeration. ErrInvalidCredentials = errors.New("identity: invalid credentials") // ErrEmailAlreadyInUse is returned by SetCredential when the email is // already assigned to a different member (the email column is // unique). ErrEmailAlreadyInUse = errors.New("identity: email already in use") )
Domain errors returned by CredentialRepository implementations.
var ( // ErrMemberNotFound is returned when a member does not exist. ErrMemberNotFound = errors.New("identity: member not found") // ErrDuplicateMember is returned when adding a member whose display // name already exists (case-insensitively) within the household. ErrDuplicateMember = errors.New("identity: duplicate member display name in household") )
Domain errors returned by MemberRepository implementations.
var ErrHouseholdNotFound = errors.New("identity: household not found")
ErrHouseholdNotFound is returned when a household does not exist.
Functions ¶
This section is empty.
Types ¶
type Credential ¶
Credential pairs a MemberID with the stored argon2id password hash. It is looked up by email (the login form's identifier) and written against a known member id (the add-credential / change-password flow), backed by identity.member's own email and password_hash columns — see identity/migrate's baseline migration, not a separate credential table.
type CredentialRepository ¶
type CredentialRepository interface {
// FindByEmail looks up the credential for the given email address. It
// returns ErrInvalidCredentials when no member with that email and a
// password_hash exists, or when that member has been deactivated
// (Member.Active false), so callers cannot distinguish "no account"
// from "wrong password" from "deactivated".
FindByEmail(ctx context.Context, email string) (*Credential, error)
// SetCredential stores (or replaces) the email and password hash on
// the member row identified by memberID. Returns ErrMemberNotFound
// when the member does not exist.
SetCredential(ctx context.Context, memberID MemberID, email, passwordHash string) error
}
CredentialRepository is the outbound port for looking up and writing login credentials. Implementations live in identity/adapter.
Error contracts:
- FindByEmail returns ErrInvalidCredentials when no member with that email and a password_hash exists, and also when that member's Active is false (no user enumeration either way).
- SetCredential returns ErrMemberNotFound when the member id does not exist, and ErrEmailAlreadyInUse when the email belongs to another member.
type Household ¶
Household is the aggregate root for the identity bounded context. Presentation fields (e.g. Nestova's quiet hours, Nestorage's own per-app settings) stay out of this type — see identity/migrate's package doc for the app-side presentation boundary this schema enforces.
type HouseholdID ¶
HouseholdID uniquely identifies a household.
func NewHouseholdID ¶
func NewHouseholdID() HouseholdID
NewHouseholdID returns a new time-ordered (UUIDv7) household id, which gives better B-tree index locality than random v4 ids. uuid.NewV7 only errors if the crypto random source is unavailable — the same failure under which uuid.New itself panics — so Must is appropriate here.
func ParseHouseholdID ¶
func ParseHouseholdID(s string) (HouseholdID, error)
ParseHouseholdID parses a canonical UUID string into a HouseholdID.
func (HouseholdID) String ¶
func (id HouseholdID) String() string
String returns the canonical UUID string.
type HouseholdRepository ¶
type HouseholdRepository interface {
CreateHousehold(ctx context.Context, h *Household) error
GetHousehold(ctx context.Context, id HouseholdID) (*Household, error)
}
HouseholdRepository is the outbound port for persisting and retrieving households. Implementations live in identity/adapter.
Error contracts:
- CreateHousehold expects h.ID and h.Name set; it populates CreatedAt/UpdatedAt on h and surfaces any other failure (e.g. an id collision) as a wrapped error, not a sentinel.
- GetHousehold returns ErrHouseholdNotFound when id is unknown.
type Member ¶
type Member struct {
ID MemberID
HouseholdID HouseholdID
DisplayName string
Role Role
// Active is the IDENTITY-level deactivation flag: false cuts a
// member's access to every app sharing this schema. This package only
// reads and writes it as a plain field; the deactivation guards that
// consume it belong to NSTR-111.
Active bool
CreatedAt time.Time
UpdatedAt time.Time
}
Member is a person in a household. It is a child entity of the Household aggregate root. Email and password-hash credential state are deliberately not part of this type — they are Credential's concern, looked up and written through CredentialRepository, mirroring the household/auth split this schema's design is ported from.
type MemberID ¶
MemberID uniquely identifies a member.
func NewMemberID ¶
func NewMemberID() MemberID
NewMemberID returns a new time-ordered (UUIDv7) member id.
func ParseMemberID ¶
ParseMemberID parses a canonical UUID string into a MemberID.
type MemberRepository ¶
type MemberRepository interface {
CreateMember(ctx context.Context, m *Member) error
GetMember(ctx context.Context, id MemberID) (*Member, error)
ListMembers(ctx context.Context, householdID HouseholdID) ([]*Member, error)
}
MemberRepository persists members and looks them up. It depends only on HouseholdID/MemberID from this same package (ISP): callers needing credential state depend on CredentialRepository instead, not on this port.
Persistence contracts:
- CreateMember expects m.ID, m.HouseholdID, m.DisplayName, and a valid m.Role set; it populates CreatedAt/UpdatedAt on m. Active defaults to true at the database, so a freshly created member reads back Active true regardless of the zero value passed in.
Error contracts:
- CreateMember returns ErrDuplicateMember when the display name collides (case-insensitively) within the household, and ErrHouseholdNotFound when m.HouseholdID does not exist.
- GetMember returns ErrMemberNotFound when id is unknown.
- ListMembers returns an empty slice (not an error) for an unknown household.
type Role ¶
type Role string
Role is a member's role within a household, unified across Nestova and Nestorage (epic NSTR-112): apps derive their own admin-vs-member behavior from these three values and never store a role vocabulary of their own — an admin/member vocabulary would flatten child into adult and lose that distinction permanently. Stored as text, validated here.
func (Role) CanAdminister ¶
CanAdminister reports whether r carries household-admin privileges (owner or adult) — the derivation Nestorage (and any future consumer) uses instead of storing its own admin/member flag.