mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:07:31 +00:00
add documentation
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
|
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@@ -1,12 +1,21 @@
|
|||||||
// the test used the manly relies on the following patterns:
|
// the test used the manly relies on the following patterns:
|
||||||
|
// - api:
|
||||||
|
// - some example stubs for the grpc api, it maps the calls and responses to the domain objects
|
||||||
|
//
|
||||||
// - domain:
|
// - domain:
|
||||||
// - hexagonal architecture, it defines its dependencies as interfaces and the dependencies must use the objects defined by this package
|
// - hexagonal architecture, it defines its dependencies as interfaces and the dependencies must use the objects defined by this package
|
||||||
// - command pattern which implements the changes
|
// - command pattern which implements the changes
|
||||||
// - the invoker decorates the commands by checking for events and tracing
|
// - the invoker decorates the commands by checking for events, tracing, logging, potentially caching, etc.
|
||||||
// - the database connections are manged in this package
|
// - the database connections are manged in this package
|
||||||
// - the database connections are passed to the repositories
|
// - the database connections are passed to the repositories
|
||||||
//
|
//
|
||||||
// - storage:
|
// - storage:
|
||||||
// - repository pattern, the repositories are defined as interfaces and the implementations are in the storage package
|
// - repository pattern, the repositories are defined as interfaces and the implementations are in the storage package
|
||||||
// - the repositories are used by the domain package to access the database
|
// - the repositories are used by the domain package to access the database
|
||||||
|
// - the eventstore to store events. At the beginning it writes to the same events table as the /internal package, afterwards it writes to a different table
|
||||||
|
//
|
||||||
|
// - telemetry:
|
||||||
|
// - logging for standard output
|
||||||
|
// - tracing for distributed tracing
|
||||||
|
// - metrics for monitoring
|
||||||
package v3
|
package v3
|
||||||
|
@@ -7,15 +7,22 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Commander is the all it needs to implement the command pattern.
|
||||||
|
// It is the interface all manipulations need to implement.
|
||||||
|
// If possible it should also be used for queries. We will find out if this is possible in the future.
|
||||||
type Commander interface {
|
type Commander interface {
|
||||||
Execute(ctx context.Context, opts *CommandOpts) (err error)
|
Execute(ctx context.Context, opts *CommandOpts) (err error)
|
||||||
fmt.Stringer
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoker is part of the command pattern.
|
||||||
|
// It is the interface that is used to execute commands.
|
||||||
type Invoker interface {
|
type Invoker interface {
|
||||||
Invoke(ctx context.Context, command Commander, opts *CommandOpts) error
|
Invoke(ctx context.Context, command Commander, opts *CommandOpts) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommandOpts are passed to each command
|
||||||
|
// the provide common fields used by commands like the database client.
|
||||||
type CommandOpts struct {
|
type CommandOpts struct {
|
||||||
DB database.QueryExecutor
|
DB database.QueryExecutor
|
||||||
Invoker Invoker
|
Invoker Invoker
|
||||||
@@ -95,6 +102,8 @@ func DefaultOpts(invoker Invoker) *CommandOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// commandBatch is a batch of commands.
|
||||||
|
// It uses the [Invoker] provided by the opts to execute each command.
|
||||||
type commandBatch struct {
|
type commandBatch struct {
|
||||||
Commands []Commander
|
Commands []Commander
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateUserCommand adds a new user including the email verification for humans.
|
||||||
|
// In the future it might make sense to separate the command into two commands:
|
||||||
|
// - CreateHumanCommand: creates a new human user
|
||||||
|
// - CreateMachineCommand: creates a new machine user
|
||||||
type CreateUserCommand struct {
|
type CreateUserCommand struct {
|
||||||
user *User
|
user *User
|
||||||
email *SetEmailCommand
|
email *SetEmailCommand
|
||||||
@@ -16,6 +20,7 @@ var (
|
|||||||
_ eventer = (*CreateUserCommand)(nil)
|
_ eventer = (*CreateUserCommand)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// opts heavily reduces the complexity for email verification because each type of verification is a simple option which implements the [Commander] interface.
|
||||||
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
|
func NewCreateHumanCommand(username string, opts ...CreateHumanOpt) *CreateUserCommand {
|
||||||
cmd := &CreateUserCommand{
|
cmd := &CreateUserCommand{
|
||||||
user: &User{
|
user: &User{
|
||||||
|
@@ -11,6 +11,10 @@ type generateCodeCommand struct {
|
|||||||
value *crypto.CryptoValue
|
value *crypto.CryptoValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I didn't update this repository to the solution proposed please view one of the following interfaces for correct usage:
|
||||||
|
// - [UserRepository]
|
||||||
|
// - [InstanceRepository]
|
||||||
|
// - [OrgRepository]
|
||||||
type CryptoRepository interface {
|
type CryptoRepository interface {
|
||||||
GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error)
|
GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error)
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The variables could also be moved to a struct.
|
||||||
|
// I just started with the singleton pattern and kept it like this.
|
||||||
var (
|
var (
|
||||||
pool database.Pool
|
pool database.Pool
|
||||||
userCodeAlgorithm crypto.EncryptionAlgorithm
|
userCodeAlgorithm crypto.EncryptionAlgorithm
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// These tests give an overview of how to use the domain package.
|
||||||
func TestExample(t *testing.T) {
|
func TestExample(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// EmailVerifiedCommand verifies an email address for a user.
|
||||||
type EmailVerifiedCommand struct {
|
type EmailVerifiedCommand struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
Email *Email `json:"email"`
|
Email *Email `json:"email"`
|
||||||
@@ -42,6 +43,8 @@ func (cmd *EmailVerifiedCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
|
|||||||
setEmailCmd.verification = cmd
|
setEmailCmd.verification = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendCodeCommand sends a verification code to the user's email address.
|
||||||
|
// If the URLTemplate is not set it will use the default of the organization / instance.
|
||||||
type SendCodeCommand struct {
|
type SendCodeCommand struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
@@ -113,6 +116,8 @@ func (cmd *SendCodeCommand) applyOnSetEmail(setEmailCmd *SetEmailCommand) {
|
|||||||
setEmailCmd.verification = cmd
|
setEmailCmd.verification = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReturnCodeCommand creates the code and returns it to the caller.
|
||||||
|
// The caller gets the code by calling the Code field after the command got executed.
|
||||||
type ReturnCodeCommand struct {
|
type ReturnCodeCommand struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
@@ -33,6 +33,7 @@ func (i *Instance) Keys(index instanceCacheIndex) (key []string) {
|
|||||||
|
|
||||||
var _ cache.Entry[instanceCacheIndex, string] = (*Instance)(nil)
|
var _ cache.Entry[instanceCacheIndex, string] = (*Instance)(nil)
|
||||||
|
|
||||||
|
// instanceColumns define all the columns of the instance table.
|
||||||
type instanceColumns interface {
|
type instanceColumns interface {
|
||||||
// IDColumn returns the column for the id field.
|
// IDColumn returns the column for the id field.
|
||||||
IDColumn() database.Column
|
IDColumn() database.Column
|
||||||
@@ -46,6 +47,7 @@ type instanceColumns interface {
|
|||||||
DeletedAtColumn() database.Column
|
DeletedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// instanceConditions define all the conditions for the instance table.
|
||||||
type instanceConditions interface {
|
type instanceConditions interface {
|
||||||
// IDCondition returns an equal filter on the id field.
|
// IDCondition returns an equal filter on the id field.
|
||||||
IDCondition(instanceID string) database.Condition
|
IDCondition(instanceID string) database.Condition
|
||||||
@@ -53,16 +55,19 @@ type instanceConditions interface {
|
|||||||
NameCondition(op database.TextOperation, name string) database.Condition
|
NameCondition(op database.TextOperation, name string) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// instanceChanges define all the changes for the instance table.
|
||||||
type instanceChanges interface {
|
type instanceChanges interface {
|
||||||
// SetName sets the name column.
|
// SetName sets the name column.
|
||||||
SetName(name string) database.Change
|
SetName(name string) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstanceRepository is the interface for the instance repository.
|
||||||
type InstanceRepository interface {
|
type InstanceRepository interface {
|
||||||
instanceColumns
|
instanceColumns
|
||||||
instanceConditions
|
instanceConditions
|
||||||
instanceChanges
|
instanceChanges
|
||||||
|
|
||||||
|
// Member returns the member repository which is a sub repository of the instance repository.
|
||||||
Member() MemberRepository
|
Member() MemberRepository
|
||||||
|
|
||||||
Get(ctx context.Context, opts ...database.QueryOption) (*Instance, error)
|
Get(ctx context.Context, opts ...database.QueryOption) (*Instance, error)
|
||||||
|
@@ -7,6 +7,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Invoke provides a way to execute commands within the domain package.
|
||||||
|
// It uses a chain of responsibility pattern to handle the command execution.
|
||||||
|
// The default chain includes logging, tracing, and event publishing.
|
||||||
|
// If you want to invoke multiple commands in a single transaction, you can use the [commandBatch].
|
||||||
func Invoke(ctx context.Context, cmd Commander) error {
|
func Invoke(ctx context.Context, cmd Commander) error {
|
||||||
invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil)))
|
invoker := newEventStoreInvoker(newLoggingInvoker(newTraceInvoker(nil)))
|
||||||
opts := &CommandOpts{
|
opts := &CommandOpts{
|
||||||
@@ -16,6 +20,8 @@ func Invoke(ctx context.Context, cmd Commander) error {
|
|||||||
return invoker.Invoke(ctx, cmd, opts)
|
return invoker.Invoke(ctx, cmd, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventStoreInvoker checks if the command implements the [eventer] interface.
|
||||||
|
// If it does, it collects the events and publishes them to the event store.
|
||||||
type eventStoreInvoker struct {
|
type eventStoreInvoker struct {
|
||||||
collector *eventCollector
|
collector *eventCollector
|
||||||
}
|
}
|
||||||
@@ -38,6 +44,7 @@ func (i *eventStoreInvoker) Invoke(ctx context.Context, command Commander, opts
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eventCollector collects events from all commands. The [eventStoreInvoker] pushes the collected events after all commands are executed.
|
||||||
type eventCollector struct {
|
type eventCollector struct {
|
||||||
next Invoker
|
next Invoker
|
||||||
events []*eventstore.Event
|
events []*eventstore.Event
|
||||||
@@ -64,6 +71,7 @@ func (i *eventCollector) Invoke(ctx context.Context, command Commander, opts *Co
|
|||||||
return command.Execute(ctx, opts)
|
return command.Execute(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// traceInvoker decorates each command with tracing.
|
||||||
type traceInvoker struct {
|
type traceInvoker struct {
|
||||||
next Invoker
|
next Invoker
|
||||||
}
|
}
|
||||||
@@ -87,6 +95,8 @@ func (i *traceInvoker) Invoke(ctx context.Context, command Commander, opts *Comm
|
|||||||
return command.Execute(ctx, opts)
|
return command.Execute(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loggingInvoker decorates each command with logging.
|
||||||
|
// It is an example implementation and logs the command name at the beginning and success or failure after the command got executed.
|
||||||
type loggingInvoker struct {
|
type loggingInvoker struct {
|
||||||
next Invoker
|
next Invoker
|
||||||
}
|
}
|
||||||
@@ -123,6 +133,10 @@ func (i *noopInvoker) Invoke(ctx context.Context, command Commander, opts *Comma
|
|||||||
return command.Execute(ctx, opts)
|
return command.Execute(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cacheInvoker could be used in the future to do the caching.
|
||||||
|
// My goal would be to have two interfaces:
|
||||||
|
// - cacheSetter: which caches an object
|
||||||
|
// - cacheGetter: which gets an object from the cache, this should also skip the command execution
|
||||||
type cacheInvoker struct {
|
type cacheInvoker struct {
|
||||||
next Invoker
|
next Invoker
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,7 @@ const (
|
|||||||
OrgStateInactive
|
OrgStateInactive
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Org is used by all other packages to represent an organization.
|
||||||
type Org struct {
|
type Org struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -42,6 +43,7 @@ func (o *Org) Keys(index orgCacheIndex) (key []string) {
|
|||||||
|
|
||||||
var _ cache.Entry[orgCacheIndex, string] = (*Org)(nil)
|
var _ cache.Entry[orgCacheIndex, string] = (*Org)(nil)
|
||||||
|
|
||||||
|
// orgColumns define all the columns of the org table.
|
||||||
type orgColumns interface {
|
type orgColumns interface {
|
||||||
// InstanceIDColumn returns the column for the instance id field.
|
// InstanceIDColumn returns the column for the instance id field.
|
||||||
InstanceIDColumn() database.Column
|
InstanceIDColumn() database.Column
|
||||||
@@ -59,6 +61,7 @@ type orgColumns interface {
|
|||||||
DeletedAtColumn() database.Column
|
DeletedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// orgConditions define all the conditions for the org table.
|
||||||
type orgConditions interface {
|
type orgConditions interface {
|
||||||
// InstanceIDCondition returns an equal filter on the instance id field.
|
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||||
InstanceIDCondition(instanceID string) database.Condition
|
InstanceIDCondition(instanceID string) database.Condition
|
||||||
@@ -70,6 +73,7 @@ type orgConditions interface {
|
|||||||
StateCondition(op database.NumberOperation, state OrgState) database.Condition
|
StateCondition(op database.NumberOperation, state OrgState) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// orgChanges define all the changes for the org table.
|
||||||
type orgChanges interface {
|
type orgChanges interface {
|
||||||
// SetName sets the name column.
|
// SetName sets the name column.
|
||||||
SetName(name string) database.Change
|
SetName(name string) database.Change
|
||||||
@@ -77,12 +81,14 @@ type orgChanges interface {
|
|||||||
SetState(state OrgState) database.Change
|
SetState(state OrgState) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrgRepository is the interface for the org repository.
|
||||||
|
// It is used to interact with the org table in the database.
|
||||||
type OrgRepository interface {
|
type OrgRepository interface {
|
||||||
orgColumns
|
orgColumns
|
||||||
orgConditions
|
orgConditions
|
||||||
orgChanges
|
orgChanges
|
||||||
|
|
||||||
// Member returns the admin repository.
|
// Member returns the member repository.
|
||||||
Member() MemberRepository
|
Member() MemberRepository
|
||||||
// Domain returns the domain repository.
|
// Domain returns the domain repository.
|
||||||
Domain() DomainRepository
|
Domain() DomainRepository
|
||||||
@@ -99,19 +105,14 @@ type OrgRepository interface {
|
|||||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrgOperation interface {
|
// MemberRepository is a sub repository of the org repository and maybe the instance repository.
|
||||||
MemberRepository
|
|
||||||
DomainRepository
|
|
||||||
Update(ctx context.Context, org *Org) error
|
|
||||||
Delete(ctx context.Context) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemberRepository interface {
|
type MemberRepository interface {
|
||||||
AddMember(ctx context.Context, orgID, userID string, roles []string) error
|
AddMember(ctx context.Context, orgID, userID string, roles []string) error
|
||||||
SetMemberRoles(ctx context.Context, orgID, userID string, roles []string) error
|
SetMemberRoles(ctx context.Context, orgID, userID string, roles []string) error
|
||||||
RemoveMember(ctx context.Context, orgID, userID string) error
|
RemoveMember(ctx context.Context, orgID, userID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DomainRepository is a sub repository of the org repository and maybe the instance repository.
|
||||||
type DomainRepository interface {
|
type DomainRepository interface {
|
||||||
AddDomain(ctx context.Context, domain string) error
|
AddDomain(ctx context.Context, domain string) error
|
||||||
SetDomainVerified(ctx context.Context, domain string) error
|
SetDomainVerified(ctx context.Context, domain string) error
|
||||||
|
@@ -6,6 +6,8 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AddOrgCommand adds a new organization.
|
||||||
|
// I'm unsure if we should add the Admins here or if this should be a separate command.
|
||||||
type AddOrgCommand struct {
|
type AddOrgCommand struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -86,6 +88,8 @@ func (cmd *AddOrgCommand) ensureID() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddMemberCommand adds a new member to an organization.
|
||||||
|
// I'm not sure if we should make it more generic to also use it for instances.
|
||||||
type AddMemberCommand struct {
|
type AddMemberCommand struct {
|
||||||
orgID string
|
orgID string
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
|
@@ -6,6 +6,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SetEmailCommand sets the email address of a user.
|
||||||
|
// If allows verification as a sub command.
|
||||||
|
// The verification command is executed after the email address is set.
|
||||||
|
// The verification command is executed in the same transaction as the email address update.
|
||||||
type SetEmailCommand struct {
|
type SetEmailCommand struct {
|
||||||
UserID string `json:"userId"`
|
UserID string `json:"userId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// userColumns define all the columns of the user table.
|
||||||
type userColumns interface {
|
type userColumns interface {
|
||||||
// InstanceIDColumn returns the column for the instance id field.
|
// InstanceIDColumn returns the column for the instance id field.
|
||||||
InstanceIDColumn() database.Column
|
InstanceIDColumn() database.Column
|
||||||
@@ -24,6 +25,7 @@ type userColumns interface {
|
|||||||
DeletedAtColumn() database.Column
|
DeletedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// userConditions define all the conditions for the user table.
|
||||||
type userConditions interface {
|
type userConditions interface {
|
||||||
// InstanceIDCondition returns an equal filter on the instance id field.
|
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||||
InstanceIDCondition(instanceID string) database.Condition
|
InstanceIDCondition(instanceID string) database.Condition
|
||||||
@@ -43,11 +45,13 @@ type userConditions interface {
|
|||||||
DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition
|
DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// userChanges define all the changes for the user table.
|
||||||
type userChanges interface {
|
type userChanges interface {
|
||||||
// SetUsername sets the username column.
|
// SetUsername sets the username column.
|
||||||
SetUsername(username string) database.Change
|
SetUsername(username string) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserRepository is the interface for the user repository.
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
userColumns
|
userColumns
|
||||||
userConditions
|
userConditions
|
||||||
@@ -66,6 +70,7 @@ type UserRepository interface {
|
|||||||
Machine() MachineRepository
|
Machine() MachineRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// humanColumns define all the columns of the human table which inherits the user table.
|
||||||
type humanColumns interface {
|
type humanColumns interface {
|
||||||
userColumns
|
userColumns
|
||||||
// FirstNameColumn returns the column for the first name field.
|
// FirstNameColumn returns the column for the first name field.
|
||||||
@@ -82,6 +87,7 @@ type humanColumns interface {
|
|||||||
PhoneVerifiedAtColumn() database.Column
|
PhoneVerifiedAtColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// humanConditions define all the conditions for the human table which inherits the user table.
|
||||||
type humanConditions interface {
|
type humanConditions interface {
|
||||||
userConditions
|
userConditions
|
||||||
// FirstNameCondition returns a filter on the first name field.
|
// FirstNameCondition returns a filter on the first name field.
|
||||||
@@ -103,6 +109,7 @@ type humanConditions interface {
|
|||||||
PhoneVerifiedAtCondition(op database.NumberOperation, phoneVerifiedAt time.Time) database.Condition
|
PhoneVerifiedAtCondition(op database.NumberOperation, phoneVerifiedAt time.Time) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// humanChanges define all the changes for the human table which inherits the user table.
|
||||||
type humanChanges interface {
|
type humanChanges interface {
|
||||||
userChanges
|
userChanges
|
||||||
// SetFirstName sets the first name field of the human.
|
// SetFirstName sets the first name field of the human.
|
||||||
@@ -129,6 +136,7 @@ type humanChanges interface {
|
|||||||
SetPhoneVerifiedAt(at time.Time) database.Change
|
SetPhoneVerifiedAt(at time.Time) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HumanRepository is the interface for the human repository it inherits the user repository.
|
||||||
type HumanRepository interface {
|
type HumanRepository interface {
|
||||||
humanColumns
|
humanColumns
|
||||||
humanConditions
|
humanConditions
|
||||||
@@ -140,24 +148,28 @@ type HumanRepository interface {
|
|||||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// machineColumns define all the columns of the machine table which inherits the user table.
|
||||||
type machineColumns interface {
|
type machineColumns interface {
|
||||||
userColumns
|
userColumns
|
||||||
// DescriptionColumn returns the column for the description field.
|
// DescriptionColumn returns the column for the description field.
|
||||||
DescriptionColumn() database.Column
|
DescriptionColumn() database.Column
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// machineConditions define all the conditions for the machine table which inherits the user table.
|
||||||
type machineConditions interface {
|
type machineConditions interface {
|
||||||
userConditions
|
userConditions
|
||||||
// DescriptionCondition returns a filter on the description field.
|
// DescriptionCondition returns a filter on the description field.
|
||||||
DescriptionCondition(op database.TextOperation, description string) database.Condition
|
DescriptionCondition(op database.TextOperation, description string) database.Condition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// machineChanges define all the changes for the machine table which inherits the user table.
|
||||||
type machineChanges interface {
|
type machineChanges interface {
|
||||||
userChanges
|
userChanges
|
||||||
// SetDescription sets the description field of the machine.
|
// SetDescription sets the description field of the machine.
|
||||||
SetDescription(description string) database.Change
|
SetDescription(description string) database.Change
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MachineRepository is the interface for the machine repository it inherits the user repository.
|
||||||
type MachineRepository interface {
|
type MachineRepository interface {
|
||||||
// Update updates machine users based on the given condition and changes.
|
// Update updates machine users based on the given condition and changes.
|
||||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||||
@@ -167,6 +179,7 @@ type MachineRepository interface {
|
|||||||
machineChanges
|
machineChanges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserTraits is implemented by [Human] and [Machine].
|
||||||
type UserTraits interface {
|
type UserTraits interface {
|
||||||
Type() UserType
|
Type() UserType
|
||||||
}
|
}
|
||||||
|
2
backend/v3/storage/cache/doc.go
vendored
Normal file
2
backend/v3/storage/cache/doc.go
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// this package is copy pasted from the internal/cache package
|
||||||
|
package cache
|
@@ -1,5 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
|
// Change represents a change to a column in a database table.
|
||||||
|
// Its written in the SET clause of an UPDATE statement.
|
||||||
type Change interface {
|
type Change interface {
|
||||||
Write(builder *StatementBuilder)
|
Write(builder *StatementBuilder)
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ func (m Columns) Write(builder *StatementBuilder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Column represents a column in a database table.
|
||||||
type Column interface {
|
type Column interface {
|
||||||
Write(builder *StatementBuilder)
|
Write(builder *StatementBuilder)
|
||||||
}
|
}
|
||||||
@@ -31,6 +32,8 @@ func (c column) Write(builder *StatementBuilder) {
|
|||||||
|
|
||||||
var _ Column = (*column)(nil)
|
var _ Column = (*column)(nil)
|
||||||
|
|
||||||
|
// ignoreCaseColumn represents two database columns, one for the
|
||||||
|
// original value and one for the lower case value.
|
||||||
type ignoreCaseColumn interface {
|
type ignoreCaseColumn interface {
|
||||||
Column
|
Column
|
||||||
WriteIgnoreCase(builder *StatementBuilder)
|
WriteIgnoreCase(builder *StatementBuilder)
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
|
// Condition represents a SQL condition.
|
||||||
|
// Its written after the WHERE keyword in a SQL statement.
|
||||||
type Condition interface {
|
type Condition interface {
|
||||||
Write(builder *StatementBuilder)
|
Write(builder *StatementBuilder)
|
||||||
}
|
}
|
||||||
@@ -22,6 +24,7 @@ func (a *and) Write(builder *StatementBuilder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// And combines multiple conditions with AND.
|
||||||
func And(conditions ...Condition) *and {
|
func And(conditions ...Condition) *and {
|
||||||
return &and{conditions: conditions}
|
return &and{conditions: conditions}
|
||||||
}
|
}
|
||||||
@@ -46,6 +49,7 @@ func (o *or) Write(builder *StatementBuilder) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Or combines multiple conditions with OR.
|
||||||
func Or(conditions ...Condition) *or {
|
func Or(conditions ...Condition) *or {
|
||||||
return &or{conditions: conditions}
|
return &or{conditions: conditions}
|
||||||
}
|
}
|
||||||
@@ -62,6 +66,7 @@ func (i *isNull) Write(builder *StatementBuilder) {
|
|||||||
builder.WriteString(" IS NULL")
|
builder.WriteString(" IS NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNull creates a condition that checks if a column is NULL.
|
||||||
func IsNull(column Column) *isNull {
|
func IsNull(column Column) *isNull {
|
||||||
return &isNull{column: column}
|
return &isNull{column: column}
|
||||||
}
|
}
|
||||||
@@ -78,6 +83,7 @@ func (i *isNotNull) Write(builder *StatementBuilder) {
|
|||||||
builder.WriteString(" IS NOT NULL")
|
builder.WriteString(" IS NOT NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNotNull creates a condition that checks if a column is NOT NULL.
|
||||||
func IsNotNull(column Column) *isNotNull {
|
func IsNotNull(column Column) *isNotNull {
|
||||||
return &isNotNull{column: column.(Column)}
|
return &isNotNull{column: column.(Column)}
|
||||||
}
|
}
|
||||||
@@ -86,18 +92,21 @@ var _ Condition = (*isNotNull)(nil)
|
|||||||
|
|
||||||
type valueCondition func(builder *StatementBuilder)
|
type valueCondition func(builder *StatementBuilder)
|
||||||
|
|
||||||
|
// NewTextCondition creates a condition that compares a text column with a value.
|
||||||
func NewTextCondition[V Text](col Column, op TextOperation, value V) Condition {
|
func NewTextCondition[V Text](col Column, op TextOperation, value V) Condition {
|
||||||
return valueCondition(func(builder *StatementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeTextOperation(builder, col, op, value)
|
writeTextOperation(builder, col, op, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDateCondition creates a condition that compares a numeric column with a value.
|
||||||
func NewNumberCondition[V Number](col Column, op NumberOperation, value V) Condition {
|
func NewNumberCondition[V Number](col Column, op NumberOperation, value V) Condition {
|
||||||
return valueCondition(func(builder *StatementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeNumberOperation(builder, col, op, value)
|
writeNumberOperation(builder, col, op, value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDateCondition creates a condition that compares a boolean column with a value.
|
||||||
func NewBooleanCondition[V Boolean](col Column, value V) Condition {
|
func NewBooleanCondition[V Boolean](col Column, value V) Condition {
|
||||||
return valueCondition(func(builder *StatementBuilder) {
|
return valueCondition(func(builder *StatementBuilder) {
|
||||||
writeBooleanOperation(builder, col, value)
|
writeBooleanOperation(builder, col, value)
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Connector abstracts the database driver.
|
||||||
type Connector interface {
|
type Connector interface {
|
||||||
Connect(ctx context.Context) (Pool, error)
|
Connect(ctx context.Context) (Pool, error)
|
||||||
}
|
}
|
||||||
|
@@ -4,15 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// Pool is a connection pool. e.g. pgxpool
|
||||||
db *database
|
|
||||||
)
|
|
||||||
|
|
||||||
type database struct {
|
|
||||||
connector Connector
|
|
||||||
pool Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pool interface {
|
type Pool interface {
|
||||||
Beginner
|
Beginner
|
||||||
QueryExecutor
|
QueryExecutor
|
||||||
@@ -21,6 +13,7 @@ type Pool interface {
|
|||||||
Close(ctx context.Context) error
|
Close(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client is a single database connection which can be released back to the pool.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
Beginner
|
Beginner
|
||||||
QueryExecutor
|
QueryExecutor
|
||||||
@@ -28,33 +21,37 @@ type Client interface {
|
|||||||
Release(ctx context.Context) error
|
Release(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Querier is a database client that can execute queries and return rows.
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
Query(ctx context.Context, stmt string, args ...any) (Rows, error)
|
Query(ctx context.Context, stmt string, args ...any) (Rows, error)
|
||||||
QueryRow(ctx context.Context, stmt string, args ...any) Row
|
QueryRow(ctx context.Context, stmt string, args ...any) Row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Executor is a database client that can execute statements.
|
||||||
type Executor interface {
|
type Executor interface {
|
||||||
Exec(ctx context.Context, stmt string, args ...any) error
|
Exec(ctx context.Context, stmt string, args ...any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryExecutor is a database client that can execute queries and statements.
|
||||||
type QueryExecutor interface {
|
type QueryExecutor interface {
|
||||||
Querier
|
Querier
|
||||||
Executor
|
Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scanner scans a single row of data into the destination.
|
||||||
type Scanner interface {
|
type Scanner interface {
|
||||||
Scan(dest ...any) error
|
Scan(dest ...any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Row is an abstraction of sql.Row.
|
||||||
type Row interface {
|
type Row interface {
|
||||||
Scanner
|
Scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rows is an abstraction of sql.Rows.
|
||||||
type Rows interface {
|
type Rows interface {
|
||||||
Row
|
Row
|
||||||
Next() bool
|
Next() bool
|
||||||
Close() error
|
Close() error
|
||||||
Err() error
|
Err() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query[T any] func(querier Querier) (result T, err error)
|
|
||||||
|
2
backend/v3/storage/database/dialect/postgres/doc.go
Normal file
2
backend/v3/storage/database/dialect/postgres/doc.go
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// pgxpool v5 implementation of the interfaces defined in the database package.
|
||||||
|
package postgres
|
@@ -18,6 +18,7 @@ type Text interface {
|
|||||||
~string | ~[]byte
|
~string | ~[]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TextOperation are operations that can be performed on text values.
|
||||||
type TextOperation uint8
|
type TextOperation uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -89,6 +90,7 @@ type Number interface {
|
|||||||
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumberOperation are operations that can be performed on number values.
|
||||||
type NumberOperation uint8
|
type NumberOperation uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -125,6 +127,7 @@ type Boolean interface {
|
|||||||
~bool
|
~bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BooleanOperation are operations that can be performed on boolean values.
|
||||||
type BooleanOperation uint8
|
type BooleanOperation uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
5
backend/v3/storage/database/repository/doc.go
Normal file
5
backend/v3/storage/database/repository/doc.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// Package implements the repositories defined in the domain package.
|
||||||
|
// The repositories are used by the domain package to access the database.
|
||||||
|
// the inheritance.sql file is me over-engineering table inheritance.
|
||||||
|
// I would create a user table which is inherited by human_user and machine_user and the same for objects like idps.
|
||||||
|
package repository
|
@@ -2,6 +2,7 @@ package database
|
|||||||
|
|
||||||
import "context"
|
import "context"
|
||||||
|
|
||||||
|
// Transaction is an SQL transaction.
|
||||||
type Transaction interface {
|
type Transaction interface {
|
||||||
Commit(ctx context.Context) error
|
Commit(ctx context.Context) error
|
||||||
Rollback(ctx context.Context) error
|
Rollback(ctx context.Context) error
|
||||||
@@ -12,6 +13,7 @@ type Transaction interface {
|
|||||||
QueryExecutor
|
QueryExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Beginner can start a new transaction.
|
||||||
type Beginner interface {
|
type Beginner interface {
|
||||||
Begin(ctx context.Context, opts *TransactionOptions) (Transaction, error)
|
Begin(ctx context.Context, opts *TransactionOptions) (Transaction, error)
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package logging
|
|||||||
|
|
||||||
import "log/slog"
|
import "log/slog"
|
||||||
|
|
||||||
|
// Logger abstracts [slog.Logger] not sure if thats needed
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
*slog.Logger
|
*slog.Logger
|
||||||
}
|
}
|
||||||
|
2
backend/v3/telemetry/metric/doc.go
Normal file
2
backend/v3/telemetry/metric/doc.go
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// implementation of otel metrics
|
||||||
|
package metric
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace/noop"
|
"go.opentelemetry.io/otel/trace/noop"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Tracer is a wrapper around the OpenTelemetry Tracer interface.
|
||||||
type Tracer struct {
|
type Tracer struct {
|
||||||
trace.Tracer
|
trace.Tracer
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user