package sqlite import ( "context" "database/sql" "fmt" "time" "forge.cadoles.com/Cadoles/emissary/internal/datastore" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) type TenantRepository struct { repository } // Create implements datastore.TenantRepository. func (r *TenantRepository) Create(ctx context.Context, label string) (*datastore.Tenant, error) { var tenant datastore.Tenant err := r.withTxRetry(ctx, func(tx *sql.Tx) error { now := time.Now().UTC() query := ` INSERT INTO tenants (id, label, created_at, updated_at) VALUES($1, $2, $3, $3) RETURNING "id", "label", "created_at", "updated_at" ` tenantID := datastore.NewTenantID() row := tx.QueryRowContext( ctx, query, tenantID, label, now, ) if err := row.Scan(&tenant.ID, &tenant.Label, &tenant.CreatedAt, &tenant.UpdatedAt); err != nil { return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.WithStack(err) } return &tenant, nil } // Delete implements datastore.TenantRepository. func (r *TenantRepository) Delete(ctx context.Context, id datastore.TenantID) error { err := r.withTxRetry(ctx, func(tx *sql.Tx) error { if exists, err := r.tenantExists(ctx, tx, id); !exists { return errors.WithStack(err) } query := `DELETE FROM tenants WHERE id = $1` _, err := tx.ExecContext(ctx, query, id) if err != nil { return errors.WithStack(err) } query = `DELETE FROM agents WHERE tenant_id = $1` _, err = tx.ExecContext(ctx, query, id) if err != nil { return errors.WithStack(err) } query = `DELETE FROM specs WHERE tenant_id = $1` _, err = tx.ExecContext(ctx, query, id) if err != nil { return errors.WithStack(err) } return nil }) if err != nil { return errors.WithStack(err) } return nil } // Get implements datastore.TenantRepository. func (r *TenantRepository) Get(ctx context.Context, id datastore.TenantID) (*datastore.Tenant, error) { var tenant datastore.Tenant err := r.withTxRetry(ctx, func(tx *sql.Tx) error { query := ` SELECT "id", "label", "created_at", "updated_at" FROM tenants WHERE id = $1 ` row := tx.QueryRowContext(ctx, query, id) if err := row.Scan(&tenant.ID, &tenant.Label, &tenant.CreatedAt, &tenant.UpdatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return errors.WithStack(datastore.ErrNotFound) } return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.WithStack(err) } return &tenant, nil } // Update implements datastore.TenantRepository. func (r *TenantRepository) Update(ctx context.Context, id datastore.TenantID, updates ...datastore.TenantUpdateOptionFunc) (*datastore.Tenant, error) { options := &datastore.TenantUpdateOptions{} for _, fn := range updates { fn(options) } var tenant datastore.Tenant err := r.withTxRetry(ctx, func(tx *sql.Tx) error { if exists, err := r.tenantExists(ctx, tx, id); !exists { return errors.WithStack(err) } query := ` UPDATE tenants SET updated_at = $1 ` args := []any{id} index := 2 if options.Label != nil { query += fmt.Sprintf(`, label = $%d`, index) args = append(args, *options.Label) index++ } updated := options.Label != nil if updated { now := time.Now().UTC() query += fmt.Sprintf(`, updated_at = $%d`, index) args = append(args, now) index++ } query += ` WHERE id = $1 RETURNING "id", "label", "created_at", "updated_at" ` logger.Debug(ctx, "executing query", logger.F("query", query), logger.F("args", args)) row := tx.QueryRowContext(ctx, query, args...) if err := row.Scan(&tenant.ID, &tenant.Label, &tenant.CreatedAt, &tenant.UpdatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return errors.WithStack(datastore.ErrNotFound) } return errors.WithStack(err) } return nil }) if err != nil { return nil, errors.WithStack(err) } return &tenant, nil } func (r *TenantRepository) tenantExists(ctx context.Context, tx *sql.Tx, tenantID datastore.TenantID) (bool, error) { row := tx.QueryRowContext(ctx, `SELECT count(id) FROM tenants WHERE id = $1`, tenantID) var count int if err := row.Scan(&count); err != nil { if errors.Is(err, sql.ErrNoRows) { return false, errors.WithStack(datastore.ErrNotFound) } return false, errors.WithStack(err) } if count == 0 { return false, errors.WithStack(datastore.ErrNotFound) } return true, nil } func NewTenantRepository(db *sql.DB, sqliteBusyRetryMaxAttempts int) *TenantRepository { return &TenantRepository{ repository: repository{db, sqliteBusyRetryMaxAttempts}, } } var _ datastore.TenantRepository = &TenantRepository{}