store

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Apr 15, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Authorization Store — Design & Rationale

## Why

The initial authorization model filtered resources by user ID to return only the caller's own resources. The partner feature introduces the need for customers to share resources (assessments) with partner organizations. This requires evolving from simple ownership filtering to a relationship- based authorization model where access is expressed as tuples and permissions are derived from those relationships.

The first implementation uses a PostgreSQL relations table with Go-side permission resolution. The store and service interfaces follow the same mental model as SpiceDB/Kessel (resources, relations, subjects, permission resolution) so that when we migrate to Kessel/SpiceDB the service layer should not change significantly — only the store implementation swaps out.

## Model

All authorization data lives in the relations table:

relations (
    resource           — resource type (e.g. "assessment", "org")
    resource_id        — resource instance ID
    relation           — relationship name: owner, viewer, member
    subject_namespace  — subject type: user, org
    subject_id         — subject instance ID
)

A row represents a single tuple:

resource_type:resource_id#relation@subject_type:subject_id

### Relation types

  • owner — full control over a resource (read, edit, share, delete)
  • viewer — read-only access to a resource
  • member — org membership (resource=org, subject_namespace=user)

### Subject namespaces

  • user — a direct user reference
  • org — an org reference; access is expanded to all org members

When a resource is shared with an org (subject_namespace=org), every user who is a member of that org automatically has access, resolved at query time via a JOIN on the same table.

## Permission Resolution

Permissions are derived from relations in Go code (not stored):

  • owner → read, edit, share, delete
  • viewer → read

Both Relation.Permissions() and Permission.Relations() encode these rules as methods on the model types.

ListResources and GetPermissions resolve access through two paths:

  1. Direct: the user is an owner or viewer of the resource
  2. Indirect: the resource is shared with an org the user is a member of

Both paths are combined in a single SQL UNION query. The returned Resource carries the resolved Permissions alongside Type and ID.

## Extensibility

The builder API (With/Without/Build) and the Authz interface are backend-agnostic. They operate on model.Resource, model.Relation, and model.Subject — no SQL or storage details leak into callers. To add a new storage backend (e.g. SpiceDB), implement the Authz interface and swap the constructor in NewStoreWithAuthz. New relation types and permissions can be added by extending the model constants and the resolution methods.

Usage

## Writing relationships

Use the RelationshipBuilder to construct a batch of relationship updates, then pass them to WriteRelationships:

updates := store.NewRelationshipBuilder().
    With(model.NewAssessmentResource(assessmentID), model.OwnerRelation, model.NewUserSubject(userID)).
    Build()

ctx, _ = s.NewTransactionContext(ctx)
err := s.Authz().WriteRelationships(ctx, updates)
ctx, _ = store.Commit(ctx)

## Sharing with another user

updates := store.NewRelationshipBuilder().
    With(model.NewAssessmentResource(assessmentID), model.ViewerRelation, model.NewUserSubject(targetUserID)).
    Build()

ctx, _ = s.NewTransactionContext(ctx)
err := s.Authz().WriteRelationships(ctx, updates)
ctx, _ = store.Commit(ctx)

## Sharing with a partner org

updates := store.NewRelationshipBuilder().
    With(model.NewAssessmentResource(assessmentID), model.ViewerRelation, model.NewOrgSubject(partnerOrgID)).
    Build()

## Adding org members

updates := store.NewRelationshipBuilder().
    With(model.NewOrgResource(orgID), model.MemberRelation, model.NewUserSubject(userID)).
    Build()

## Revoking access

updates := store.NewRelationshipBuilder().
    Without(model.NewAssessmentResource(assessmentID), model.ViewerRelation, model.NewUserSubject(targetUserID)).
    Build()

ctx, _ = s.NewTransactionContext(ctx)
err := s.Authz().WriteRelationships(ctx, updates)
ctx, _ = store.Commit(ctx)

## Listing all assessments a user can access (with permissions)

ctx, _ = s.NewTransactionContext(ctx)
resources, err := s.Authz().ListResources(ctx, userID, model.AssessmentResource)
ctx, _ = store.Commit(ctx)
// resources[0].ID          => "assess1"
// resources[0].Permissions => [ReadPermission, EditPermission, SharePermission, DeletePermission]

## Checking permissions on a single resource

ctx, _ = s.NewTransactionContext(ctx)
resource, err := s.Authz().GetPermissions(ctx, userID, model.NewAssessmentResource("assess1"))
ctx, _ = store.Commit(ctx)
// resource.Permissions => [ReadPermission]

## Deleting all relationships for a resource

ctx, _ = s.NewTransactionContext(ctx)
err := s.Authz().DeleteRelationships(ctx, model.NewAssessmentResource(assessmentID))
ctx, _ = store.Commit(ctx)

## Listing relationships on a resource

ctx, _ = s.NewTransactionContext(ctx)
rels, err := s.Authz().ListRelationships(ctx, model.NewAssessmentResource(assessmentID))
ctx, _ = store.Commit(ctx)
// rels[0].String() => "assessment:assess1#owner@user:jane"

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrRecordNotFound = errors.New("record not found")
	ErrDuplicateKey   = errors.New("already exists")
)

Functions

func Commit

func Commit(ctx context.Context) (context.Context, error)

func FromContext

func FromContext(ctx context.Context) *gorm.DB

func InitDB

func InitDB(cfg *config.Config) (*gorm.DB, error)

func Rollback

func Rollback(ctx context.Context) (context.Context, error)

Types

type Accounts added in v0.11.0

type Accounts interface {
	// Groups
	ListGroups(ctx context.Context, filter *GroupQueryFilter) (model.GroupList, error)
	GetGroup(ctx context.Context, id uuid.UUID) (model.Group, error)
	CreateGroup(ctx context.Context, group model.Group) (model.Group, error)
	UpdateGroup(ctx context.Context, group model.Group) (model.Group, error)
	DeleteGroup(ctx context.Context, id uuid.UUID) error

	// Members
	ListMembers(ctx context.Context, filter *MemberQueryFilter) (model.MemberList, error)
	GetMember(ctx context.Context, username string) (model.Member, error)
	CreateMember(ctx context.Context, member model.Member) (model.Member, error)
	UpdateMember(ctx context.Context, member model.Member) (model.Member, error)
	DeleteMember(ctx context.Context, id uuid.UUID) error
}

func NewAccountsStore added in v0.11.0

func NewAccountsStore(db *gorm.DB) Accounts

type AccountsStore added in v0.11.0

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

func (*AccountsStore) CreateGroup added in v0.11.0

func (s *AccountsStore) CreateGroup(ctx context.Context, group model.Group) (model.Group, error)

func (*AccountsStore) CreateMember added in v0.11.0

func (s *AccountsStore) CreateMember(ctx context.Context, member model.Member) (model.Member, error)

func (*AccountsStore) DeleteGroup added in v0.11.0

func (s *AccountsStore) DeleteGroup(ctx context.Context, id uuid.UUID) error

func (*AccountsStore) DeleteMember added in v0.11.0

func (s *AccountsStore) DeleteMember(ctx context.Context, id uuid.UUID) error

func (*AccountsStore) GetGroup added in v0.11.0

func (s *AccountsStore) GetGroup(ctx context.Context, id uuid.UUID) (model.Group, error)

func (*AccountsStore) GetMember added in v0.11.0

func (s *AccountsStore) GetMember(ctx context.Context, username string) (model.Member, error)

func (*AccountsStore) ListGroups added in v0.11.0

func (s *AccountsStore) ListGroups(ctx context.Context, filter *GroupQueryFilter) (model.GroupList, error)

func (*AccountsStore) ListMembers added in v0.11.0

func (s *AccountsStore) ListMembers(ctx context.Context, filter *MemberQueryFilter) (model.MemberList, error)

func (*AccountsStore) UpdateGroup added in v0.11.0

func (s *AccountsStore) UpdateGroup(ctx context.Context, group model.Group) (model.Group, error)

func (*AccountsStore) UpdateMember added in v0.11.0

func (s *AccountsStore) UpdateMember(ctx context.Context, member model.Member) (model.Member, error)

type Agent

type Agent interface {
	List(ctx context.Context, filter *AgentQueryFilter, opts *AgentQueryOptions) (model.AgentList, error)
	Get(ctx context.Context, id uuid.UUID) (*model.Agent, error)
	Update(ctx context.Context, agent model.Agent) (*model.Agent, error)
	Create(ctx context.Context, agent model.Agent) (*model.Agent, error)
}

func NewAgentSource

func NewAgentSource(db *gorm.DB) Agent

type AgentQueryFilter

type AgentQueryFilter BaseQuerier

func NewAgentQueryFilter

func NewAgentQueryFilter() *AgentQueryFilter

func (*AgentQueryFilter) ByID

func (qf *AgentQueryFilter) ByID(ids []string) *AgentQueryFilter

func (*AgentQueryFilter) BySourceID

func (qf *AgentQueryFilter) BySourceID(sourceID string) *AgentQueryFilter

type AgentQueryOptions

type AgentQueryOptions BaseQuerier

func NewAgentQueryOptions

func NewAgentQueryOptions() *AgentQueryOptions

func (*AgentQueryOptions) WithSortOrder

func (o *AgentQueryOptions) WithSortOrder(sort SortOrder) *AgentQueryOptions

type AgentStore

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

func (*AgentStore) Create

func (a *AgentStore) Create(ctx context.Context, agent model.Agent) (*model.Agent, error)

Create creates an agent.

func (*AgentStore) Get

func (a *AgentStore) Get(ctx context.Context, id uuid.UUID) (*model.Agent, error)

Get returns an agent based on its id.

func (*AgentStore) List

List lists all the agents.

func (*AgentStore) Update

func (a *AgentStore) Update(ctx context.Context, agent model.Agent) (*model.Agent, error)

Update updates an agent.

type Assessment

type Assessment interface {
	List(ctx context.Context, filter *AssessmentQueryFilter) (model.AssessmentList, error)
	Get(ctx context.Context, id uuid.UUID) (*model.Assessment, error)
	Create(ctx context.Context, assessment model.Assessment, inventory []byte) (*model.Assessment, error)
	Update(ctx context.Context, assessmentID uuid.UUID, name *string, inventory []byte) (*model.Assessment, error)
	Delete(ctx context.Context, id uuid.UUID) error
}

func NewAssessmentStore

func NewAssessmentStore(db *gorm.DB) Assessment

type AssessmentQueryFilter

type AssessmentQueryFilter struct {
	QueryFn []func(*gorm.DB) *gorm.DB
}

func NewAssessmentQueryFilter

func NewAssessmentQueryFilter() *AssessmentQueryFilter

func (*AssessmentQueryFilter) WithIDs added in v0.10.0

func (*AssessmentQueryFilter) WithNameLike

func (f *AssessmentQueryFilter) WithNameLike(pattern string) *AssessmentQueryFilter

Filter by name pattern

func (*AssessmentQueryFilter) WithOrgID

func (f *AssessmentQueryFilter) WithOrgID(orgID string) *AssessmentQueryFilter

Filter by organization ID

func (*AssessmentQueryFilter) WithSourceID

func (f *AssessmentQueryFilter) WithSourceID(sourceID string) *AssessmentQueryFilter

Filter by source ID

func (*AssessmentQueryFilter) WithSourceType

func (f *AssessmentQueryFilter) WithSourceType(sourceType string) *AssessmentQueryFilter

Filter by source

func (*AssessmentQueryFilter) WithUsername added in v0.3.0

func (f *AssessmentQueryFilter) WithUsername(username string) *AssessmentQueryFilter

Filter by username

type AssessmentQueryOptions

type AssessmentQueryOptions struct {
	QueryFn []func(*gorm.DB) *gorm.DB
}

func NewAssessmentQueryOptions

func NewAssessmentQueryOptions() *AssessmentQueryOptions

func (*AssessmentQueryOptions) WithLimit

func (o *AssessmentQueryOptions) WithLimit(limit int) *AssessmentQueryOptions

Limit results

func (*AssessmentQueryOptions) WithOffset

func (o *AssessmentQueryOptions) WithOffset(offset int) *AssessmentQueryOptions

Offset results

func (*AssessmentQueryOptions) WithOrder

Order by specific field

type AssessmentStore

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

func (*AssessmentStore) Create

func (a *AssessmentStore) Create(ctx context.Context, assessment model.Assessment, inventory []byte) (*model.Assessment, error)

func (*AssessmentStore) Delete

func (a *AssessmentStore) Delete(ctx context.Context, id uuid.UUID) error

func (*AssessmentStore) Get

func (*AssessmentStore) List

func (*AssessmentStore) Update

func (a *AssessmentStore) Update(ctx context.Context, assessmentID uuid.UUID, name *string, inventory []byte) (*model.Assessment, error)

type Authz added in v0.10.0

type Authz interface {
	WriteRelationships(ctx context.Context, updates []model.RelationshipUpdate) error
	DeleteRelationships(ctx context.Context, resource model.Resource) error
	ListResources(ctx context.Context, userID string, resourceType model.ResourceType) ([]model.Resource, error)
	GetPermissions(ctx context.Context, userID string, resource model.Resource) (model.Resource, error)
	ListRelationships(ctx context.Context, resource model.Resource) ([]model.Relationship, error)
	Close() error
}

func NewAuthzStore added in v0.10.0

func NewAuthzStore(db *gorm.DB) Authz

type AuthzStore added in v0.10.0

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

func (*AuthzStore) Close added in v0.10.0

func (a *AuthzStore) Close() error

func (*AuthzStore) DeleteRelationships added in v0.10.0

func (a *AuthzStore) DeleteRelationships(ctx context.Context, resource model.Resource) error

func (*AuthzStore) GetPermissions added in v0.10.0

func (a *AuthzStore) GetPermissions(ctx context.Context, userID string, resource model.Resource) (model.Resource, error)

func (*AuthzStore) ListRelationships added in v0.10.0

func (a *AuthzStore) ListRelationships(ctx context.Context, resource model.Resource) ([]model.Relationship, error)

func (*AuthzStore) ListResources added in v0.10.0

func (a *AuthzStore) ListResources(ctx context.Context, userID string, resourceType model.ResourceType) ([]model.Resource, error)

func (*AuthzStore) WriteRelationships added in v0.10.0

func (a *AuthzStore) WriteRelationships(ctx context.Context, updates []model.RelationshipUpdate) error

type BaseQuerier

type BaseQuerier struct {
	QueryFn []func(tx *gorm.DB) *gorm.DB
}

type CacheKeyStore

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

CacheKeyStore is wrapper around PrivateKeyStore which provide basic caching of the public keys.

func (*CacheKeyStore) Create

func (p *CacheKeyStore) Create(ctx context.Context, privateKey model.Key) (*model.Key, error)

func (*CacheKeyStore) Delete

func (p *CacheKeyStore) Delete(ctx context.Context, orgID string) error

func (*CacheKeyStore) Get

func (p *CacheKeyStore) Get(ctx context.Context, orgID string) (*model.Key, error)

func (*CacheKeyStore) GetPublicKey added in v0.2.2

func (p *CacheKeyStore) GetPublicKey(ctx context.Context, id string) (crypto.PublicKey, error)

type DataStore

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

func (*DataStore) Accounts added in v0.11.0

func (s *DataStore) Accounts() Accounts

func (*DataStore) Agent

func (s *DataStore) Agent() Agent

func (*DataStore) Assessment

func (s *DataStore) Assessment() Assessment

func (*DataStore) Authz added in v0.10.0

func (s *DataStore) Authz() Authz

func (*DataStore) Close

func (s *DataStore) Close() error

func (*DataStore) ImageInfra

func (s *DataStore) ImageInfra() ImageInfra

func (*DataStore) Job added in v0.3.0

func (s *DataStore) Job() Job

func (*DataStore) Label

func (s *DataStore) Label() Label

func (*DataStore) NewTransactionContext

func (s *DataStore) NewTransactionContext(ctx context.Context) (context.Context, error)

func (*DataStore) PrivateKey

func (s *DataStore) PrivateKey() PrivateKey

func (*DataStore) Source

func (s *DataStore) Source() Source

func (*DataStore) Statistics

func (s *DataStore) Statistics(ctx context.Context) (model.InventoryStats, error)

type GroupQueryFilter added in v0.11.0

type GroupQueryFilter BaseQuerier

func NewGroupQueryFilter added in v0.11.0

func NewGroupQueryFilter() *GroupQueryFilter

func (*GroupQueryFilter) ByCompany added in v0.11.0

func (f *GroupQueryFilter) ByCompany(company string) *GroupQueryFilter

func (*GroupQueryFilter) ByKind added in v0.11.0

func (f *GroupQueryFilter) ByKind(kind string) *GroupQueryFilter

func (*GroupQueryFilter) ByName added in v0.11.0

func (f *GroupQueryFilter) ByName(name string) *GroupQueryFilter

func (*GroupQueryFilter) ByParentID added in v0.11.0

func (f *GroupQueryFilter) ByParentID(id uuid.UUID) *GroupQueryFilter

type ImageInfra

type ImageInfra interface {
	Create(ctx context.Context, imageInfra model.ImageInfra) (*model.ImageInfra, error)
	Update(ctx context.Context, imageInfra model.ImageInfra) (*model.ImageInfra, error)
	UpdateAgentVersion(ctx context.Context, sourceID string, agentVersion string) error
}

func NewImageInfraStore

func NewImageInfraStore(db *gorm.DB) ImageInfra

type ImageInfraStore

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

func (*ImageInfraStore) Create

func (*ImageInfraStore) Update

func (*ImageInfraStore) UpdateAgentVersion added in v0.8.0

func (i *ImageInfraStore) UpdateAgentVersion(ctx context.Context, sourceID string, agentVersion string) error

UpdateAgentVersion atomically updates only the agent_version field for a given source_id. This prevents race conditions when multiple concurrent downloads occur.

type Job added in v0.3.0

type Job interface {
	Get(ctx context.Context, id int64) (*JobRow, error)
	UpdateMetadata(ctx context.Context, id int64, metadataJSON []byte) error
}

Job interface for job-related database operations

func NewJobStore added in v0.3.0

func NewJobStore(db *gorm.DB) Job

NewJobStore creates a new job store

type JobRow added in v0.3.0

type JobRow struct {
	ID           int64              `gorm:"column:id;primaryKey"`
	State        rivertype.JobState `gorm:"column:state"`
	ArgsJSON     []byte             `gorm:"column:args"`
	MetadataJSON []byte             `gorm:"column:metadata"`
}

JobRow represents a row from the river_job table

func (JobRow) TableName added in v0.3.0

func (JobRow) TableName() string

TableName specifies the table name for GORM

type JobStore added in v0.3.0

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

JobStore implements the Job interface

func (*JobStore) Get added in v0.3.0

func (s *JobStore) Get(ctx context.Context, id int64) (*JobRow, error)

Get retrieves a job by ID from the river_job table

func (*JobStore) UpdateMetadata added in v0.3.0

func (s *JobStore) UpdateMetadata(ctx context.Context, id int64, metadataJSON []byte) error

UpdateMetadata updates the metadata of a job

type Label

type Label interface {
	DeleteBySourceID(ctx context.Context, sourceID string) error
	UpdateLabels(ctx context.Context, sourceID uuid.UUID, labels []model.Label) error
}

func NewLabelStore

func NewLabelStore(db *gorm.DB) Label

type MemberQueryFilter added in v0.11.0

type MemberQueryFilter BaseQuerier

func NewMemberQueryFilter added in v0.11.0

func NewMemberQueryFilter() *MemberQueryFilter

func (*MemberQueryFilter) ByGroupID added in v0.11.0

func (f *MemberQueryFilter) ByGroupID(id uuid.UUID) *MemberQueryFilter

type PrivateKey

type PrivateKey interface {
	Create(ctx context.Context, privateKey model.Key) (*model.Key, error)
	Get(ctx context.Context, orgID string) (*model.Key, error)
	Delete(ctx context.Context, orgID string) error
	GetPublicKey(ctx context.Context, id string) (crypto.PublicKey, error)
}

func NewCacheKeyStore

func NewCacheKeyStore(delegate PrivateKey) PrivateKey

func NewPrivateKey

func NewPrivateKey(db *gorm.DB) PrivateKey

type PrivateKeyStore

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

func (*PrivateKeyStore) Create

func (p *PrivateKeyStore) Create(ctx context.Context, privateKey model.Key) (*model.Key, error)

func (*PrivateKeyStore) Delete

func (p *PrivateKeyStore) Delete(ctx context.Context, orgID string) error

func (*PrivateKeyStore) Get

func (p *PrivateKeyStore) Get(ctx context.Context, orgID string) (*model.Key, error)

func (*PrivateKeyStore) GetPublicKey added in v0.2.2

func (p *PrivateKeyStore) GetPublicKey(ctx context.Context, kid string) (crypto.PublicKey, error)

type RelationshipBuilder added in v0.10.0

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

RelationshipBuilder constructs a batch of relationship updates. Use With to add (touch) relationships and Without to remove (delete) them.

func NewRelationshipBuilder added in v0.10.0

func NewRelationshipBuilder() *RelationshipBuilder

func (*RelationshipBuilder) Build added in v0.10.0

Build returns the accumulated relationship updates.

func (*RelationshipBuilder) With added in v0.10.0

func (b *RelationshipBuilder) With(resource model.Resource, relation model.Relation, subject model.Subject) *RelationshipBuilder

With appends a TOUCH operation (create or idempotent update).

func (*RelationshipBuilder) Without added in v0.10.0

func (b *RelationshipBuilder) Without(resource model.Resource, relation model.Relation, subject model.Subject) *RelationshipBuilder

Without appends a DELETE operation.

type SortOrder

type SortOrder int
const (
	Unsorted SortOrder = iota
	SortByID
	SortByUpdatedTime
	SortByCreatedTime
)

type Source

type Source interface {
	List(ctx context.Context, filter *SourceQueryFilter) (model.SourceList, error)
	Create(ctx context.Context, source model.Source) (*model.Source, error)
	DeleteAll(ctx context.Context) error
	Get(ctx context.Context, id uuid.UUID) (*model.Source, error)
	Delete(ctx context.Context, id uuid.UUID) error
	Update(ctx context.Context, source model.Source) (*model.Source, error)
}

func NewSource

func NewSource(db *gorm.DB) Source

type SourceQueryFilter

type SourceQueryFilter BaseQuerier

func NewSourceQueryFilter

func NewSourceQueryFilter() *SourceQueryFilter

func (*SourceQueryFilter) ByOnPremises

func (qf *SourceQueryFilter) ByOnPremises(isOnPremises bool) *SourceQueryFilter

func (*SourceQueryFilter) ByOrgID

func (sf *SourceQueryFilter) ByOrgID(id string) *SourceQueryFilter

func (*SourceQueryFilter) ByUsername

func (sf *SourceQueryFilter) ByUsername(username string) *SourceQueryFilter

type SourceStore

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

func (*SourceStore) Create

func (s *SourceStore) Create(ctx context.Context, source model.Source) (*model.Source, error)

func (*SourceStore) Delete

func (s *SourceStore) Delete(ctx context.Context, id uuid.UUID) error

func (*SourceStore) DeleteAll

func (s *SourceStore) DeleteAll(ctx context.Context) error

func (*SourceStore) Get

func (s *SourceStore) Get(ctx context.Context, id uuid.UUID) (*model.Source, error)

func (*SourceStore) List

func (*SourceStore) Update

func (s *SourceStore) Update(ctx context.Context, source model.Source) (*model.Source, error)

type Store

type Store interface {
	NewTransactionContext(ctx context.Context) (context.Context, error)
	Agent() Agent
	Authz() Authz
	Source() Source
	ImageInfra() ImageInfra
	PrivateKey() PrivateKey
	Label() Label
	Assessment() Assessment
	Job() Job
	Accounts() Accounts
	Statistics(ctx context.Context) (model.InventoryStats, error)
	Close() error
}

func NewStore

func NewStore(db *gorm.DB) Store

type Tx

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

func (*Tx) Commit

func (t *Tx) Commit() error

func (*Tx) Db

func (t *Tx) Db() (*gorm.DB, error)

func (*Tx) Rollback

func (t *Tx) Rollback() error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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