Changeset 0.24.2 (#158)
This commit is contained in:
@@ -476,7 +476,7 @@ type KnowledgeBaseStore interface {
|
||||
type GroupStore interface {
|
||||
Create(ctx context.Context, g *models.Group) error
|
||||
GetByID(ctx context.Context, id string) (*models.Group, error)
|
||||
Update(ctx context.Context, id string, name, description *string) error
|
||||
Update(ctx context.Context, id string, patch models.GroupPatch) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// Scoped listing
|
||||
|
||||
@@ -3,6 +3,8 @@ package postgres
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
@@ -18,41 +20,79 @@ func (s *GroupStore) Create(ctx context.Context, g *models.Group) error {
|
||||
if g.Source == "" {
|
||||
g.Source = "manual"
|
||||
}
|
||||
if g.Permissions == nil {
|
||||
g.Permissions = []string{}
|
||||
}
|
||||
permsJSON, err := json.Marshal(g.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal permissions: %w", err)
|
||||
}
|
||||
return DB.QueryRowContext(ctx, `
|
||||
INSERT INTO groups (name, description, scope, team_id, created_by, source)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
INSERT INTO groups (name, description, scope, team_id, created_by, source, permissions)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, created_at, updated_at`,
|
||||
g.Name, g.Description, g.Scope,
|
||||
models.NullString(g.TeamID), g.CreatedBy, g.Source,
|
||||
models.NullString(g.TeamID), models.NullString(g.CreatedBy), g.Source, permsJSON,
|
||||
).Scan(&g.ID, &g.CreatedAt, &g.UpdatedAt)
|
||||
}
|
||||
|
||||
func (s *GroupStore) GetByID(ctx context.Context, id string) (*models.Group, error) {
|
||||
var g models.Group
|
||||
var teamID sql.NullString
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
row := DB.QueryRowContext(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
|
||||
COALESCE(g.source, 'manual')
|
||||
FROM groups g WHERE g.id = $1`, id).Scan(
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &g.CreatedBy,
|
||||
&g.CreatedAt, &g.UpdatedAt, &g.MemberCount, &g.Source,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.TeamID = NullableStringPtr(teamID)
|
||||
return &g, nil
|
||||
COALESCE(g.source, 'manual'),
|
||||
COALESCE(g.permissions, '[]'::jsonb),
|
||||
g.token_budget_daily,
|
||||
g.token_budget_monthly,
|
||||
g.allowed_models
|
||||
FROM groups g WHERE g.id = $1`, id)
|
||||
return scanGroup(row)
|
||||
}
|
||||
|
||||
func (s *GroupStore) Update(ctx context.Context, id string, name, description *string) error {
|
||||
b := NewUpdate("groups")
|
||||
if name != nil {
|
||||
b.Set("name", *name)
|
||||
// Update applies a patch to a group.
|
||||
// The Everyone group (source=system) allows permission/budget edits but not
|
||||
// name, description, or structural changes — those fields are silently ignored.
|
||||
func (s *GroupStore) Update(ctx context.Context, id string, patch models.GroupPatch) error {
|
||||
var source string
|
||||
if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = $1", id).Scan(&source); err != nil {
|
||||
return err
|
||||
}
|
||||
if description != nil {
|
||||
b.Set("description", *description)
|
||||
|
||||
b := NewUpdate("groups")
|
||||
if source != "system" {
|
||||
if patch.Name != nil {
|
||||
b.Set("name", *patch.Name)
|
||||
}
|
||||
if patch.Description != nil {
|
||||
b.Set("description", *patch.Description)
|
||||
}
|
||||
}
|
||||
if patch.Permissions != nil {
|
||||
permsJSON, err := json.Marshal(*patch.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal permissions: %w", err)
|
||||
}
|
||||
b.Set("permissions", permsJSON)
|
||||
}
|
||||
if patch.ClearBudgetDaily {
|
||||
b.SetNull("token_budget_daily")
|
||||
} else if patch.TokenBudgetDaily != nil {
|
||||
b.Set("token_budget_daily", *patch.TokenBudgetDaily)
|
||||
}
|
||||
if patch.ClearBudgetMonthly {
|
||||
b.SetNull("token_budget_monthly")
|
||||
} else if patch.TokenBudgetMonthly != nil {
|
||||
b.Set("token_budget_monthly", *patch.TokenBudgetMonthly)
|
||||
}
|
||||
if patch.ClearAllowedModels {
|
||||
b.SetNull("allowed_models")
|
||||
} else if patch.AllowedModels != nil {
|
||||
modelsJSON, err := json.Marshal(*patch.AllowedModels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal allowed_models: %w", err)
|
||||
}
|
||||
b.Set("allowed_models", modelsJSON)
|
||||
}
|
||||
if !b.HasSets() {
|
||||
return nil
|
||||
@@ -68,7 +108,18 @@ func (s *GroupStore) Update(ctx context.Context, id string, name, description *s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a group. Returns an error if source=system.
|
||||
func (s *GroupStore) Delete(ctx context.Context, id string) error {
|
||||
var source string
|
||||
if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = $1", id).Scan(&source); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return err
|
||||
}
|
||||
if source == "system" {
|
||||
return fmt.Errorf("system groups cannot be deleted")
|
||||
}
|
||||
res, err := DB.ExecContext(ctx, "DELETE FROM groups WHERE id = $1", id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -81,33 +132,31 @@ func (s *GroupStore) Delete(ctx context.Context, id string) error {
|
||||
|
||||
// ── Scoped Listing ──────────────────────────
|
||||
|
||||
const groupSelectCols = `
|
||||
g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
|
||||
COALESCE(g.source, 'manual'),
|
||||
COALESCE(g.permissions, '[]'::jsonb),
|
||||
g.token_budget_daily,
|
||||
g.token_budget_monthly,
|
||||
g.allowed_models`
|
||||
|
||||
func (s *GroupStore) ListAll(ctx context.Context) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT`+groupSelectCols+`
|
||||
FROM groups g
|
||||
ORDER BY g.scope, g.name`)
|
||||
}
|
||||
|
||||
func (s *GroupStore) ListForTeam(ctx context.Context, teamID string) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT`+groupSelectCols+`
|
||||
FROM groups g
|
||||
WHERE g.scope = 'team' AND g.team_id = $1
|
||||
ORDER BY g.name`, teamID)
|
||||
}
|
||||
|
||||
func (s *GroupStore) ListForUser(ctx context.Context, userID string) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT`+groupSelectCols+`
|
||||
FROM groups g
|
||||
WHERE g.id IN (SELECT group_id FROM group_members WHERE user_id = $1)
|
||||
ORDER BY g.scope, g.name`, userID)
|
||||
@@ -191,6 +240,40 @@ func (s *GroupStore) GetUserGroupIDs(ctx context.Context, userID string) ([]stri
|
||||
|
||||
// ── Scanners ────────────────────────────────
|
||||
|
||||
type groupScanner interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func scanGroup(row groupScanner) (*models.Group, error) {
|
||||
var g models.Group
|
||||
var teamID sql.NullString
|
||||
var createdBy sql.NullString
|
||||
var permsJSON []byte
|
||||
var budgetDaily, budgetMonthly sql.NullInt64
|
||||
var allowedJSON []byte
|
||||
|
||||
if err := row.Scan(
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
|
||||
&g.CreatedAt, &g.UpdatedAt, &g.MemberCount, &g.Source,
|
||||
&permsJSON, &budgetDaily, &budgetMonthly, &allowedJSON,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.TeamID = NullableStringPtr(teamID)
|
||||
g.CreatedBy = NullableStringPtr(createdBy)
|
||||
g.Permissions = unmarshalStringSlice(permsJSON)
|
||||
if budgetDaily.Valid {
|
||||
g.TokenBudgetDaily = &budgetDaily.Int64
|
||||
}
|
||||
if budgetMonthly.Valid {
|
||||
g.TokenBudgetMonthly = &budgetMonthly.Int64
|
||||
}
|
||||
if len(allowedJSON) > 0 && string(allowedJSON) != "null" {
|
||||
g.AllowedModels = unmarshalStringSlice(allowedJSON)
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
func queryGroups(ctx context.Context, q string, args ...interface{}) ([]models.Group, error) {
|
||||
rows, err := DB.QueryContext(ctx, q, args...)
|
||||
if err != nil {
|
||||
@@ -202,12 +285,42 @@ func queryGroups(ctx context.Context, q string, args ...interface{}) ([]models.G
|
||||
for rows.Next() {
|
||||
var g models.Group
|
||||
var teamID sql.NullString
|
||||
if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.Scope, &teamID,
|
||||
&g.CreatedBy, &g.CreatedAt, &g.UpdatedAt, &g.MemberCount, &g.Source); err != nil {
|
||||
var createdBy sql.NullString
|
||||
var permsJSON []byte
|
||||
var budgetDaily, budgetMonthly sql.NullInt64
|
||||
var allowedJSON []byte
|
||||
|
||||
if err := rows.Scan(
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
|
||||
&g.CreatedAt, &g.UpdatedAt, &g.MemberCount, &g.Source,
|
||||
&permsJSON, &budgetDaily, &budgetMonthly, &allowedJSON,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.TeamID = NullableStringPtr(teamID)
|
||||
g.CreatedBy = NullableStringPtr(createdBy)
|
||||
g.Permissions = unmarshalStringSlice(permsJSON)
|
||||
if budgetDaily.Valid {
|
||||
g.TokenBudgetDaily = &budgetDaily.Int64
|
||||
}
|
||||
if budgetMonthly.Valid {
|
||||
g.TokenBudgetMonthly = &budgetMonthly.Int64
|
||||
}
|
||||
if len(allowedJSON) > 0 && string(allowedJSON) != "null" {
|
||||
g.AllowedModels = unmarshalStringSlice(allowedJSON)
|
||||
}
|
||||
result = append(result, g)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func unmarshalStringSlice(data []byte) []string {
|
||||
if len(data) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
var s []string
|
||||
if err := json.Unmarshal(data, &s); err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -44,6 +44,13 @@ func (b *UpdateBuilder) Set(col string, val interface{}) *UpdateBuilder {
|
||||
return b
|
||||
}
|
||||
|
||||
// SetNull adds a col = NULL pair to the UPDATE.
|
||||
func (b *UpdateBuilder) SetNull(col string) *UpdateBuilder {
|
||||
b.argIdx++
|
||||
b.sets = append(b.sets, fmt.Sprintf("%s = NULL", col))
|
||||
return b
|
||||
}
|
||||
|
||||
// SetJSON adds a JSONB column from a map.
|
||||
func (b *UpdateBuilder) SetJSON(col string, val interface{}) *UpdateBuilder {
|
||||
data, err := json.Marshal(val)
|
||||
|
||||
@@ -3,6 +3,8 @@ package sqlite
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -24,11 +26,18 @@ func (s *GroupStore) Create(ctx context.Context, g *models.Group) error {
|
||||
if g.Source == "" {
|
||||
g.Source = "manual"
|
||||
}
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
INSERT INTO groups (id, name, description, scope, team_id, created_by, source, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
if g.Permissions == nil {
|
||||
g.Permissions = []string{}
|
||||
}
|
||||
permsJSON, err := json.Marshal(g.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal permissions: %w", err)
|
||||
}
|
||||
_, err = DB.ExecContext(ctx, `
|
||||
INSERT INTO groups (id, name, description, scope, team_id, created_by, source, permissions, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
g.ID, g.Name, g.Description, g.Scope,
|
||||
models.NullString(g.TeamID), g.CreatedBy, g.Source,
|
||||
models.NullString(g.TeamID), models.NullString(g.CreatedBy), g.Source, string(permsJSON),
|
||||
now.Format(timeFmt), now.Format(timeFmt),
|
||||
)
|
||||
return err
|
||||
@@ -37,29 +46,85 @@ func (s *GroupStore) Create(ctx context.Context, g *models.Group) error {
|
||||
func (s *GroupStore) GetByID(ctx context.Context, id string) (*models.Group, error) {
|
||||
var g models.Group
|
||||
var teamID sql.NullString
|
||||
var createdBy sql.NullString
|
||||
var permsStr sql.NullString
|
||||
var budgetDaily, budgetMonthly sql.NullInt64
|
||||
var allowedStr sql.NullString
|
||||
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
|
||||
COALESCE(g.source, 'manual')
|
||||
COALESCE(g.source, 'manual'),
|
||||
COALESCE(g.permissions, '[]'),
|
||||
g.token_budget_daily,
|
||||
g.token_budget_monthly,
|
||||
g.allowed_models
|
||||
FROM groups g WHERE g.id = ?`, id).Scan(
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &g.CreatedBy,
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
|
||||
st(&g.CreatedAt), st(&g.UpdatedAt), &g.MemberCount, &g.Source,
|
||||
&permsStr, &budgetDaily, &budgetMonthly, &allowedStr,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.TeamID = NullableStringPtr(teamID)
|
||||
g.CreatedBy = NullableStringPtr(createdBy)
|
||||
g.Permissions = unmarshalStringSlice(permsStr.String)
|
||||
if budgetDaily.Valid {
|
||||
g.TokenBudgetDaily = &budgetDaily.Int64
|
||||
}
|
||||
if budgetMonthly.Valid {
|
||||
g.TokenBudgetMonthly = &budgetMonthly.Int64
|
||||
}
|
||||
if allowedStr.Valid && allowedStr.String != "" {
|
||||
g.AllowedModels = unmarshalStringSlice(allowedStr.String)
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
func (s *GroupStore) Update(ctx context.Context, id string, name, description *string) error {
|
||||
b := NewUpdate("groups")
|
||||
if name != nil {
|
||||
b.Set("name", *name)
|
||||
// Update applies a patch to a group.
|
||||
// system groups allow permission/budget edits but not structural field changes.
|
||||
func (s *GroupStore) Update(ctx context.Context, id string, patch models.GroupPatch) error {
|
||||
var source string
|
||||
if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = ?", id).Scan(&source); err != nil {
|
||||
return err
|
||||
}
|
||||
if description != nil {
|
||||
b.Set("description", *description)
|
||||
|
||||
b := NewUpdate("groups")
|
||||
if source != "system" {
|
||||
if patch.Name != nil {
|
||||
b.Set("name", *patch.Name)
|
||||
}
|
||||
if patch.Description != nil {
|
||||
b.Set("description", *patch.Description)
|
||||
}
|
||||
}
|
||||
if patch.Permissions != nil {
|
||||
permsJSON, err := json.Marshal(*patch.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal permissions: %w", err)
|
||||
}
|
||||
b.Set("permissions", string(permsJSON))
|
||||
}
|
||||
if patch.ClearBudgetDaily {
|
||||
b.SetNull("token_budget_daily")
|
||||
} else if patch.TokenBudgetDaily != nil {
|
||||
b.Set("token_budget_daily", *patch.TokenBudgetDaily)
|
||||
}
|
||||
if patch.ClearBudgetMonthly {
|
||||
b.SetNull("token_budget_monthly")
|
||||
} else if patch.TokenBudgetMonthly != nil {
|
||||
b.Set("token_budget_monthly", *patch.TokenBudgetMonthly)
|
||||
}
|
||||
if patch.ClearAllowedModels {
|
||||
b.SetNull("allowed_models")
|
||||
} else if patch.AllowedModels != nil {
|
||||
modelsJSON, err := json.Marshal(*patch.AllowedModels)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal allowed_models: %w", err)
|
||||
}
|
||||
b.Set("allowed_models", string(modelsJSON))
|
||||
}
|
||||
if !b.HasSets() {
|
||||
return nil
|
||||
@@ -75,7 +140,18 @@ func (s *GroupStore) Update(ctx context.Context, id string, name, description *s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a group. Returns an error if source=system.
|
||||
func (s *GroupStore) Delete(ctx context.Context, id string) error {
|
||||
var source string
|
||||
if err := DB.QueryRowContext(ctx, "SELECT source FROM groups WHERE id = ?", id).Scan(&source); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
return err
|
||||
}
|
||||
if source == "system" {
|
||||
return fmt.Errorf("system groups cannot be deleted")
|
||||
}
|
||||
res, err := DB.ExecContext(ctx, "DELETE FROM groups WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -88,33 +164,31 @@ func (s *GroupStore) Delete(ctx context.Context, id string) error {
|
||||
|
||||
// ── Scoped Listing ──────────────────────────
|
||||
|
||||
const groupSelectCols = `
|
||||
g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id) AS member_count,
|
||||
COALESCE(g.source, 'manual'),
|
||||
COALESCE(g.permissions, '[]'),
|
||||
g.token_budget_daily,
|
||||
g.token_budget_monthly,
|
||||
g.allowed_models`
|
||||
|
||||
func (s *GroupStore) ListAll(ctx context.Context) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT `+groupSelectCols+`
|
||||
FROM groups g
|
||||
ORDER BY g.scope, g.name`)
|
||||
}
|
||||
|
||||
func (s *GroupStore) ListForTeam(ctx context.Context, teamID string) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT `+groupSelectCols+`
|
||||
FROM groups g
|
||||
WHERE g.scope = 'team' AND g.team_id = ?
|
||||
ORDER BY g.name`, teamID)
|
||||
}
|
||||
|
||||
func (s *GroupStore) ListForUser(ctx context.Context, userID string) ([]models.Group, error) {
|
||||
return queryGroups(ctx, `
|
||||
SELECT g.id, g.name, g.description, g.scope, g.team_id, g.created_by,
|
||||
g.created_at, g.updated_at,
|
||||
(SELECT COUNT(*) FROM group_members WHERE group_id = g.id),
|
||||
COALESCE(g.source, 'manual')
|
||||
return queryGroups(ctx, `SELECT `+groupSelectCols+`
|
||||
FROM groups g
|
||||
WHERE g.id IN (SELECT group_id FROM group_members WHERE user_id = ?)
|
||||
ORDER BY g.scope, g.name`, userID)
|
||||
@@ -209,12 +283,42 @@ func queryGroups(ctx context.Context, q string, args ...interface{}) ([]models.G
|
||||
for rows.Next() {
|
||||
var g models.Group
|
||||
var teamID sql.NullString
|
||||
if err := rows.Scan(&g.ID, &g.Name, &g.Description, &g.Scope, &teamID,
|
||||
&g.CreatedBy, st(&g.CreatedAt), st(&g.UpdatedAt), &g.MemberCount, &g.Source); err != nil {
|
||||
var createdBy sql.NullString
|
||||
var permsStr sql.NullString
|
||||
var budgetDaily, budgetMonthly sql.NullInt64
|
||||
var allowedStr sql.NullString
|
||||
|
||||
if err := rows.Scan(
|
||||
&g.ID, &g.Name, &g.Description, &g.Scope, &teamID, &createdBy,
|
||||
st(&g.CreatedAt), st(&g.UpdatedAt), &g.MemberCount, &g.Source,
|
||||
&permsStr, &budgetDaily, &budgetMonthly, &allowedStr,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.TeamID = NullableStringPtr(teamID)
|
||||
g.CreatedBy = NullableStringPtr(createdBy)
|
||||
g.Permissions = unmarshalStringSlice(permsStr.String)
|
||||
if budgetDaily.Valid {
|
||||
g.TokenBudgetDaily = &budgetDaily.Int64
|
||||
}
|
||||
if budgetMonthly.Valid {
|
||||
g.TokenBudgetMonthly = &budgetMonthly.Int64
|
||||
}
|
||||
if allowedStr.Valid && allowedStr.String != "" {
|
||||
g.AllowedModels = unmarshalStringSlice(allowedStr.String)
|
||||
}
|
||||
result = append(result, g)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func unmarshalStringSlice(s string) []string {
|
||||
if s == "" {
|
||||
return []string{}
|
||||
}
|
||||
var out []string
|
||||
if err := json.Unmarshal([]byte(s), &out); err != nil {
|
||||
return []string{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -97,6 +97,11 @@ func (b *UpdateBuilder) Set(col string, val interface{}) *UpdateBuilder {
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *UpdateBuilder) SetNull(col string) *UpdateBuilder {
|
||||
b.sets = append(b.sets, col+" = NULL")
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *UpdateBuilder) SetJSON(col string, val interface{}) *UpdateBuilder {
|
||||
data, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user