domain

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: AGPL-3.0 Imports: 5 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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

type Credential struct {
	MemberID     MemberID
	PasswordHash string
}

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

type Household struct {
	ID        HouseholdID
	Name      string
	CreatedAt time.Time
	UpdatedAt time.Time
}

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

type HouseholdID uuid.UUID

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

type MemberID uuid.UUID

MemberID uniquely identifies a member.

func NewMemberID

func NewMemberID() MemberID

NewMemberID returns a new time-ordered (UUIDv7) member id.

func ParseMemberID

func ParseMemberID(s string) (MemberID, error)

ParseMemberID parses a canonical UUID string into a MemberID.

func (MemberID) String

func (id MemberID) String() string

String returns the canonical UUID string.

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.

const (
	RoleOwner Role = "owner"
	RoleAdult Role = "adult"
	RoleChild Role = "child"
)

Member roles.

func ParseRole

func ParseRole(s string) (Role, error)

ParseRole validates and returns a Role, or an error for an unknown value.

func (Role) CanAdminister

func (r Role) CanAdminister() bool

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.

func (Role) String

func (r Role) String() string

String returns the role's stored value.

func (Role) Valid

func (r Role) Valid() bool

Valid reports whether r is a known role.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL