usecase

package
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

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

AcceptInviteLink adds actor as a member of the team an invite link belongs to. Joining happens once: someone who already belongs to the team is refused with domain.AlreadyMemberError rather than answered as though they had just joined.

func NewAcceptInviteLink(
	pool *pgxpool.Pool,
	actorID domain.ID,
	code domain.Code,
) AcceptInviteLink

func (AcceptInviteLink) Exec

Exec reports the team joined. Being a member already is not one of the ways this succeeds: it comes back as domain.AlreadyMemberError, carrying the team so that the refusal still answers what was asked.

Unlike creating a link, this cannot be one statement. Whether a link is usable is derived in Go, because listing links has to show that derivation too, and restating it in SQL here would make a second copy of the one rule. So the link is read, judged and then acted on — and the read locks the row, which is what stops a revoke from committing in between.

type CreateCode

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

CreateCode sends a one-time code to an address so that whoever reads that mailbox can sign in.

It asks nothing about who owns the address, and there is nowhere in it to find out: the answer is the same whether an account stands behind the address or not, and an account is only born later, when the code comes back.

func NewCreateCode

func NewCreateCode(pool *pgxpool.Pool, post Post, email domain.Email) CreateCode

func (CreateCode) Exec

Exec needs no transaction: the limits are checked by the same statement that writes the code, so no second request can pass a limit that was true a moment ago and false by the time of the write.

The letter goes out after the write, which means a code can exist that nobody received — the person is told the sending failed and, having spent one of five, waits a minute. The other order is worse: a letter no record backs would let its reader in against a code the system does not know.

type CreateIntroduction

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

CreateIntroduction gives actor the name others will know them by. It happens once: a person who already has a name is refused rather than renamed, because renaming is a different act with a different record behind it.

func NewCreateIntroduction

func NewCreateIntroduction(
	pool *pgxpool.Pool,
	actorID domain.ID,
	username domain.Username,
) CreateIntroduction

func (CreateIntroduction) Exec

Exec needs no transaction: the write is one statement, and it declines to touch a person who already has a name, so no two callers can name the same one twice.

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

CreateInviteLink creates a link that can be used to join a team. Only the team's owner may create one.

func NewCreateInviteLink(
	pool *pgxpool.Pool,
	actorID domain.ID,
	teamID domain.ID,
	validity domain.Validity,
) CreateInviteLink

func (CreateInviteLink) Exec

type CreateSession

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

CreateSession trades a code for a pass, and brings the account into being if the address had none. Signing in and signing up are one act here: to separate them the system would first have to say whether it knows the address, and that is the one thing it will not say.

The code is checked together with the address it was issued for. Six digits are not unique — the same ones stand for different codes at different mailboxes — so on their own they identify nobody.

func NewCreateSession

func NewCreateSession(
	pool *pgxpool.Pool,
	seal Seal,
	email domain.Email,
	code domain.SignInCode,
) CreateSession

func (CreateSession) Exec

Exec spends the code, finds or makes the person behind the address, opens a session and hands back its first pair of keys.

All of it is one transaction, and it has to be: a code spent without a session behind it would leave its owner holding a letter that no longer works and an account they cannot reach.

The token is stamped before the commit rather than after. Stamping can fail, and failing inside the transaction undoes the spending along with everything else — the letter still works, and the person simply tries again. After the commit there would be nothing left to undo.

type CreateTeam

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

CreateTeam creates a team and makes actor its owner.

func NewCreateTeam

func NewCreateTeam(
	pool *pgxpool.Pool,
	actorID domain.ID,
	name domain.TeamName,
) CreateTeam

func (CreateTeam) Exec

Exec needs no transaction: the team and its owner are written by one statement, so a team without an owner is not a state this can leave behind even if the write fails halfway.

type EndSession

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

EndSession closes the session the request came from, and only that one. The other devices a person is signed in on are untouched: leaving a room is not leaving the building.

It is named for what it does rather than for the operation that calls it. Nothing is deleted — the row stays and remembers when it ended — and the spec says as much in its own words: "ends the session".

func NewEndSession

func NewEndSession(pool *pgxpool.Pool, actor domain.Actor) EndSession

func (EndSession) Exec

Exec needs no transaction: it is one statement, and that statement declines to touch a session already closed, so nothing two callers can do together is worse than what one of them does alone.

The door has already asked after this session and found it open, which is why nothing here re-checks it. Between that question and this write it may close anyway — the same person leaving from two windows at once, or a theft caught in between — and the statement is written so that this changes nothing worth reporting.

type GetMe

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

GetMe returns actor as they stand: who they are and whether they have introduced themselves. Nothing here can be refused — one's own record is not something to be permitted — so the only failure on the caller's account is that no such caller exists.

func NewGetMe

func NewGetMe(pool *pgxpool.Pool, actorID domain.ID) GetMe

func (GetMe) Exec

func (u GetMe) Exec(ctx context.Context) (domain.User, domain.GetMeError)

type GetTeam

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

GetTeam returns a team as seen by actor. It fails with TeamNotFoundError when the team doesn't exist, when actor never belonged to it and when actor has left it, so that none of the three can be told from the others: a team is visible only to the people currently in it, and past membership grants nothing.

func NewGetTeam

func NewGetTeam(pool *pgxpool.Pool, actorID, teamID domain.ID) GetTeam

func (GetTeam) Exec

type InspectPass

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

InspectPass answers the two things the door needs about a caller and nothing else: whether their pass still stands, and whether its holder has a name. It is the one usecase that asks a question instead of doing something, and it exists for the door standing in front of almost every other one.

A signature says who is asking but cannot say whether the session behind them is still open: signing out and catching a theft both close one, and neither can reach a token already handed out. That is why this question is asked of the database on every guarded request, and why asking it makes the answer current rather than as of some minutes ago.

Being nameless is not a failure here — it is the answer. The refusal it leads to is the door's to give.

func NewInspectPass

func NewInspectPass(pool *pgxpool.Pool, actor domain.Actor) InspectPass

func (InspectPass) Exec

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

ListInviteLinks lists a team's invite links, optionally filtered by status. Only the team's owner may list them.

func NewListInviteLinks(
	pool *pgxpool.Pool,

	actorID domain.ID,
	teamID domain.ID,
	status *domain.LinkStatus,
) ListInviteLinks

func (ListInviteLinks) Exec

Exec asks about ownership separately from reading the links, because an empty result cannot carry both answers: an owner whose team has no links deserves an empty list, not the refusal a stranger gets.

The two reads are not in a transaction. Losing ownership between them would only mean showing links the actor was entitled to a moment earlier, which is what any read races with anyway.

type ListMyTeams

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

ListMyTeams lists the teams actor belongs to, whatever their role in each. A team actor has left is not among them: this list and GetTeam answer the same question, so a team here is one actor can open, and a team missing here is one they cannot.

func NewListMyTeams

func NewListMyTeams(pool *pgxpool.Pool, actorID domain.ID) ListMyTeams

func (ListMyTeams) Exec

Exec returns an empty slice rather than nil when actor belongs to no team: having no teams is an ordinary state, not an absence of answer.

type Post

type Post interface {
	Send(ctx context.Context, to domain.Email, code domain.SignInCode) error
}

Post carries a sign-in code to an address. It is the one thing in the way of signing in that this system does not own: whether the letter arrives is decided by mail servers, spam filters and the person's provider, and none of them report back.

Send returning nil means the letter was handed over, not that it was read or even delivered.

type RefreshSession

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

RefreshSession trades a key for a fresh pair. The session is not replaced: it lasts from signing in until signing out, and these are only the keys to it.

Nothing is asked of the caller beyond the key itself, and nothing can be: an access token is expired at exactly the moment renewal is wanted, so the door this comes through stands open and everything a door would have checked is checked here instead — that the key is known, unspent, in date, and that the session behind it is still open.

func NewRefreshSession

func NewRefreshSession(
	pool *pgxpool.Pool,
	seal Seal,
	token domain.RefreshToken,
) RefreshSession

func (RefreshSession) Exec

Exec spends the key shown and writes its replacement, both inside one transaction. A key spent with no replacement behind it would leave its holder locked out of a session that is still open, holding something that no longer works.

The token is stamped before the commit for the same reason it is when signing in: stamping can fail, and failing inside the transaction puts the spent key back, so the holder simply tries again.

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

RevokeInviteLink revokes an invite link. Only the owner of the team it belongs to may revoke it.

func NewRevokeInviteLink(
	pool *pgxpool.Pool,
	actorID domain.ID,
	code domain.Code,
) RevokeInviteLink

func (RevokeInviteLink) Exec

Exec reads the link, judges it and only then writes, which is why the read locks the row: nothing else may revoke it in between and leave this call reporting success over someone else's work.

type Seal

type Seal interface {
	Stamp(
		personID domain.ID,
		sessionID domain.ID,
	) (domain.AccessToken, time.Duration, error)
}

Seal turns a person and a session into an access token, and is the one thing in the system holding the key that token is signed with. Nothing else can make one, and nothing else needs to.

The lifetime comes back alongside the token because the deadline is written inside it, by this. Naming the same span in a second place is how the two would eventually disagree.

Only stamping is declared here. Reading a token back needs the same key but happens elsewhere, at the door, and what is needed there will be said there.

Jump to

Keyboard shortcuts

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