Help us shape the future of Go! Take the 2026 Go Developer Survey

user

package
v0.0.12 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 2026 License: Apache-2.0, MIT Imports: 31 Imported by: 0

Documentation

Overview

Package user is the mandatory accounts core: user entities, their CRUD surface, and persistence. Every other feature hangs off user accounts; the composition root always passes it first to identity.New.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned when no user matches the ID.
	ErrNotFound = errors.New("user not found")
	// ErrDuplicate is returned when username or email already exists.
	ErrDuplicate = errors.New("username or email already exists")
	// ErrInvalidUsername rejects usernames outside ^[a-zA-Z0-9_]{3,32}$.
	ErrInvalidUsername = errors.New("username must be 3-32 characters: letters, digits, underscores")
	// ErrInvalidEmail rejects emails missing a local part, domain, or TLD.
	ErrInvalidEmail = errors.New("email is not a valid address")
)

Errors surfaced by stores and mapped to HTTP statuses by handlers.

View Source
var UsernamePattern = regexp.MustCompile(`^[a-zA-Z0-9_]{3,32}$`)

UsernamePattern mirrors the database CHECK constraint; admin DTOs reuse it for validation.

Functions

func ProtoView added in v0.0.12

func ProtoView(u User) *identityv1.User

ProtoView maps the account projection onto the shared RPC message. Optional fields stay unset when nil; timestamps render RFC 3339 UTC.

Types

type AdminUpdateParams

type AdminUpdateParams struct {
	Email       *string
	FirstName   *string
	LastName    *string
	DisplayName *string
	IsAdmin     *bool
	Disabled    *bool
}

AdminUpdateParams patches administrative fields. Nil pointers keep the current value.

type CreateParams

type CreateParams struct {
	Username    string
	Email       string
	FirstName   string
	LastName    string
	DisplayName string
	IsAdmin     bool
}

CreateParams carries the fields a caller supplies; the store fills the rest (ID, timestamps, defaults).

func (CreateParams) Validate

func (p CreateParams) Validate() (displayName string, err error)

Validate normalizes params and enforces the same rules as the database constraints, so invalid writes fail before reaching Postgres. Returns the display name resolved from first/last name or the email local part.

type CredentialAdminPort added in v0.0.12

type CredentialAdminPort interface {
	// CredentialsForUser lists the user's registered credentials.
	CredentialsForUser(ctx context.Context, userID UserID) ([]CredentialView, error)
	// RenameCredential updates one credential's display name;
	// unknown IDs surface ErrNotFound.
	RenameCredential(ctx context.Context, userID UserID, credentialID, name string) (*CredentialView, error)
	// DeleteCredential removes one credential; unknown IDs surface
	// ErrNotFound.
	DeleteCredential(ctx context.Context, userID UserID, credentialID string) error
}

CredentialAdminPort serves the WebAuthn credential management surface; implemented by the passkey feature.

type CredentialView added in v0.0.12

type CredentialView struct {
	ID              string
	Name            string
	CredentialID    string // base64url of the raw credential id
	AttestationType string
	Transport       []string
	BackupEligible  bool
	BackupState     bool
	CreatedAt       string
	LastUsedAt      *string
}

CredentialView is the neutral WebAuthn credential projection for the admin/self management surface.

type DefaultPictureFunc

type DefaultPictureFunc func(ctx context.Context) (reader io.ReadCloser, size int64, mime string, ok bool)

DefaultPictureFunc serves the bundled default profile picture; ok=false when none is set.

type GroupBinding added in v0.0.12

type GroupBinding struct {
	ID          string
	Name        string
	DisplayName string
	CreatedAt   string
	UpdatedAt   *string
}

GroupBinding is the neutral user↔group projection: the group fields the binding surface needs, without importing the usergroup package (which imports this one).

type GroupBindingPort added in v0.0.12

type GroupBindingPort interface {
	// GroupsForUser lists the groups the user belongs to.
	GroupsForUser(ctx context.Context, userID UserID) ([]GroupBinding, error)
	// ReplaceGroupsForUser atomically rebinds the user to exactly
	// the named group IDs.
	ReplaceGroupsForUser(ctx context.Context, userID UserID, groupIDs []string) error
}

GroupBindingPort serves the user↔group bindings; implemented by the usergroup feature over its stores.

type ImageStore

type ImageStore interface {
	Save(ctx context.Context, path string, data io.Reader) error
	Open(ctx context.Context, path string) (io.ReadCloser, int64, error)
	Delete(ctx context.Context, path string) error
}

ImageStore is the consumer-side blob adapter (internal/storage backend): write, read, remove. Keeping it an interface stops the identity tree from importing internal/storage directly.

type ListParams

type ListParams struct {
	Query string // matches username, email, display name (ILIKE)
	Page
}

ListParams narrows and pages the admin listing.

type Page

type Page struct {
	Page  int
	Limit int
}

Page is the store-level paging window: plain ints with no HTTP dependency. The handler converts the request query into it.

func (Page) All

func (p Page) All() bool

All reports whether the listing skips paging (page or limit is the all marker -1).

func (Page) Offset

func (p Page) Offset() int

Offset returns the SQL offset for the current page.

type PostgresStore

type PostgresStore struct {
	// contains filtered or unexported fields
}

PostgresStore persists users in public.users. It builds on the shared Executor (pool or transaction) — never owns connections. Queries are composed with go-sqlbuilder (PostgreSQL flavor).

func NewPostgresStore

func NewPostgresStore(exec datastore.Executor) *PostgresStore

NewPostgresStore builds the production user store.

func (*PostgresStore) Create

func (s *PostgresStore) Create(ctx context.Context, params CreateParams) (User, error)

Create inserts a user and returns the stored row. Optional names store as NULL (nil pointers); the display name falls back to the email local part before the insert.

func (*PostgresStore) Delete

func (s *PostgresStore) Delete(ctx context.Context, id UserID) error

Delete removes the user row; the database fn_soft_delete trigger archives the row into public.deleted_records. Unknown IDs surface ErrNotFound.

func (*PostgresStore) GetByID

func (s *PostgresStore) GetByID(ctx context.Context, id UserID) (User, error)

GetByID resolves one user; unknown IDs surface ErrNotFound.

func (*PostgresStore) HasAnyUser

func (s *PostgresStore) HasAnyUser(ctx context.Context) (bool, error)

HasAnyUser reports whether any account row exists. The initial setup contract counts every user, not only admins.

func (*PostgresStore) List

func (s *PostgresStore) List(ctx context.Context, params ListParams) ([]User, int, error)

List returns matching users newest first plus the total count. Query matches username, email, or display name (case-insensitive).

func (*PostgresStore) MarkEmailVerified

func (s *PostgresStore) MarkEmailVerified(ctx context.Context, id UserID) error

MarkEmailVerified stamps the email-verified timestamp.

func (*PostgresStore) MarkLogin

func (s *PostgresStore) MarkLogin(ctx context.Context, id UserID) error

MarkLogin records a successful sign-in timestamp.

func (*PostgresStore) SetProfilePicturePath

func (s *PostgresStore) SetProfilePicturePath(ctx context.Context, id UserID, picturePath *string) error

SetProfilePicturePath stores or clears (nil) the picture blob path.

func (*PostgresStore) UpdateAdmin

func (s *PostgresStore) UpdateAdmin(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)

UpdateAdmin patches administrative fields (email, names, flags) and returns the fresh row.

func (*PostgresStore) UpdateProfile

func (s *PostgresStore) UpdateProfile(ctx context.Context, id UserID, params UpdateProfileParams) (User, error)

UpdateProfile patches profile columns and returns the fresh row. Nil params keep their column; named fields stay constraint-checked by the database (display_name > 0).

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service holds the user business rules and HTTP surface. It is the identity module's mandatory core feature. Guards are not stored: the mount call receives the route groups.

func NewService

func NewService(store Store, recorder identity.Recorder, opts ...ServiceOption) *Service

NewService builds the user core on top of the given store. The optional recorder captures audit events.

func (*Service) APIRoutes

func (s *Service) APIRoutes(r chi.Router, _ identity.RouteGroups)

APIRoutes mounts the retained user endpoints inside the shared /api group: the bare .png profile-picture read. All other user surfaces serve ConnectRPC below /rpc; email flows stay on their feature routes.

func (*Service) BindRPCPorts added in v0.0.12

func (s *Service) BindRPCPorts(groups GroupBindingPort, credentials CredentialAdminPort)

BindRPCPorts wires the cross-feature ports the Connect surface needs; called by the composition root after every feature exists.

func (*Service) Create

func (s *Service) Create(ctx context.Context, params CreateParams) (User, error)

Create validates the payload, persists the user, and records an audit event when a recorder is wired.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, id UserID) error

Delete removes the account (the DB trigger archives it) and records the event.

func (*Service) GetByID

func (s *Service) GetByID(ctx context.Context, id UserID) (User, error)

GetByID resolves one user; unknown IDs surface ErrNotFound.

func (*Service) List

func (s *Service) List(ctx context.Context, params ListParams) ([]User, int, error)

List returns matching users, newest first, with pagination metadata support.

func (*Service) Name

func (s *Service) Name() string

Name names the feature for logs.

func (*Service) RPCService added in v0.0.12

func (s *Service) RPCService(auth kernel.AccessAuthenticator) (string, http.Handler)

RPCService returns the Connect registration for the user surface: the procedure prefix and the handler. Admin CRUD and the self profile mix in one service, so the guard is per procedure; the access authenticator comes from the composition root. The self procedures refuse a machine credential — a leaked key must not edit its owner's profile.

func (*Service) Update

func (s *Service) Update(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)

Update patches administrative fields and records the change.

type ServiceOption

type ServiceOption func(*Service)

ServiceOption configures the user core.

func WithImages

func WithImages(images ImageStore, defaults DefaultPictureFunc) ServiceOption

WithImages wires the blob backend and the bundled-default provider. Without both, the picture surface mounts read-only or not at all (see APIRoutes).

type Store

type Store interface {
	List(ctx context.Context, params ListParams) ([]User, int, error)
	// HasAnyUser reports whether any account exists; the setup
	// contract counts every user, not only admins.
	HasAnyUser(ctx context.Context) (bool, error)
	Create(ctx context.Context, params CreateParams) (User, error)
	GetByID(ctx context.Context, id UserID) (User, error)
	UpdateAdmin(ctx context.Context, id UserID, params AdminUpdateParams) (User, error)
	UpdateProfile(ctx context.Context, id UserID, params UpdateProfileParams) (User, error)
	// SetProfilePicturePath stores or clears (nil) the blob path of the user's profile picture.
	SetProfilePicturePath(ctx context.Context, id UserID, path *string) error
	MarkLogin(ctx context.Context, id UserID) error
	MarkEmailVerified(ctx context.Context, id UserID) error
	Delete(ctx context.Context, id UserID) error
}

Store abstracts user persistence: the memory store backs tests, the Postgres store backs production. Implementations assign IDs and timestamps.

type UpdateProfileParams

type UpdateProfileParams struct {
	FirstName   *string
	LastName    *string
	DisplayName *string
	AvatarURL   *string
	Locale      *string
}

UpdateProfileParams patches the profile fields. Nil pointers keep the current value; a non-nil pointer replaces the column (an empty string clears nullable columns; display name must stay non-empty).

type User

type User struct {
	ID        UserID  `json:"id"`
	Username  string  `json:"username"`
	Email     string  `json:"email"`
	FirstName *string `json:"first_name,omitzero"`
	LastName  *string `json:"last_name,omitzero"`
	AvatarURL *string `json:"avatar_url,omitzero"`
	Locale    *string `json:"locale,omitzero"`

	DisplayName string `json:"display_name"`
	IsAdmin     bool   `json:"is_admin"`
	Disabled    bool   `json:"disabled"`

	EmailVerifiedAt *time.Time `json:"email_verified_at,omitzero"`
	CreatedAt       time.Time  `json:"created_at"`
	UpdatedAt       *time.Time `json:"updated_at,omitzero"`
	LastLoginAt     *time.Time `json:"last_login_at,omitzero"`

	// ProfilePicturePath is the blob-store path of the custom picture;
	// nil falls back to the bundled default.
	// Internal: never serialized (the .png route serves bytes).
	ProfilePicturePath *string `json:"-"`
}

User is the core identity entity, shared by all features. Nullable columns use pointers; empty string is never stored for them.

type UserID

type UserID = typeid.TypeID[userPrefix]

Typed IDs for the user tables: UUIDv7 suffix, snake_case prefix matching the singular table name.

func MustID

func MustID(uuidText string) UserID

MustID converts a stored UUID to the typed ID. TypeIDs wrap UUIDs losslessly, so failure is a programmer error. Exported for stores that join the users table.

type UserPhoneID

type UserPhoneID = typeid.TypeID[userPhonePrefix]

Typed IDs for the user tables: UUIDv7 suffix, snake_case prefix matching the singular table name.

Jump to

Keyboard shortcuts

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