Changeset 0.28.6 (#192)

This commit is contained in:
2026-03-15 01:33:38 +00:00
parent 6f0ad1355c
commit bffda043db
59 changed files with 3022 additions and 77 deletions

View File

@@ -183,6 +183,7 @@ type UserStore interface {
List(ctx context.Context, opts ListOptions) ([]models.User, int, error)
UpdateLastLogin(ctx context.Context, id string) error
SetActive(ctx context.Context, id string, active bool) error
ListActiveUserIDs(ctx context.Context) ([]string, error)
// Refresh tokens
CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error

View File

@@ -13,29 +13,32 @@ type GitCredentialStore struct{}
func (s *GitCredentialStore) Create(ctx context.Context, cred *models.GitCredential) error {
if cred.ID != "" {
return DB.QueryRowContext(ctx, `
INSERT INTO git_credentials (id, user_id, name, auth_type, encrypted_data, nonce)
VALUES ($1, $2, $3, $4, $5, $6)
INSERT INTO git_credentials (id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING created_at`,
cred.ID, cred.UserID, cred.Name, cred.AuthType,
cred.EncryptedData, cred.Nonce,
cred.PublicKey, cred.Fingerprint, cred.PersonaID,
).Scan(&cred.CreatedAt)
}
return DB.QueryRowContext(ctx, `
INSERT INTO git_credentials (user_id, name, auth_type, encrypted_data, nonce)
VALUES ($1, $2, $3, $4, $5)
INSERT INTO git_credentials (user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, created_at`,
cred.UserID, cred.Name, cred.AuthType,
cred.EncryptedData, cred.Nonce,
cred.PublicKey, cred.Fingerprint, cred.PersonaID,
).Scan(&cred.ID, &cred.CreatedAt)
}
func (s *GitCredentialStore) GetByID(ctx context.Context, id string) (*models.GitCredential, error) {
var c models.GitCredential
err := DB.QueryRowContext(ctx, `
SELECT id, user_id, name, auth_type, encrypted_data, nonce, created_at
SELECT id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id, created_at
FROM git_credentials WHERE id = $1`, id,
).Scan(&c.ID, &c.UserID, &c.Name, &c.AuthType,
&c.EncryptedData, &c.Nonce, &c.CreatedAt)
&c.EncryptedData, &c.Nonce,
&c.PublicKey, &c.Fingerprint, &c.PersonaID, &c.CreatedAt)
if err != nil {
return nil, err
}
@@ -44,7 +47,7 @@ func (s *GitCredentialStore) GetByID(ctx context.Context, id string) (*models.Gi
func (s *GitCredentialStore) ListByUser(ctx context.Context, userID string) ([]models.GitCredential, error) {
rows, err := DB.QueryContext(ctx, `
SELECT id, user_id, name, auth_type, encrypted_data, nonce, created_at
SELECT id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id, created_at
FROM git_credentials WHERE user_id = $1 ORDER BY created_at DESC`, userID)
if err != nil {
return nil, err
@@ -55,7 +58,8 @@ func (s *GitCredentialStore) ListByUser(ctx context.Context, userID string) ([]m
for rows.Next() {
var c models.GitCredential
if err := rows.Scan(&c.ID, &c.UserID, &c.Name, &c.AuthType,
&c.EncryptedData, &c.Nonce, &c.CreatedAt); err != nil {
&c.EncryptedData, &c.Nonce,
&c.PublicKey, &c.Fingerprint, &c.PersonaID, &c.CreatedAt); err != nil {
return nil, err
}
creds = append(creds, c)

View File

@@ -23,7 +23,8 @@ func NewTaskStore() *TaskStore { return &TaskStore{} }
// (string, json.RawMessage). database/sql returns "unsupported Scan,
// storing driver.Value type <nil>" for these without COALESCE.
const taskColumns = `id, owner_id, team_id, name, description, scope,
task_type, persona_id, model_id, system_prompt, user_prompt,
task_type, COALESCE(system_function, '') AS system_function,
persona_id, model_id, system_prompt, user_prompt,
workflow_id, COALESCE(tool_grants, '[]'::jsonb) AS tool_grants,
schedule, timezone, is_active,
COALESCE(trigger_token, '') AS trigger_token,
@@ -40,7 +41,8 @@ const taskColumns = `id, owner_id, team_id, name, description, scope,
func scanTask(scanner interface{ Scan(...interface{}) error }, t *models.Task) error {
return scanner.Scan(
&t.ID, &t.OwnerID, &t.TeamID, &t.Name, &t.Description, &t.Scope,
&t.TaskType, &t.PersonaID, &t.ModelID, &t.SystemPrompt, &t.UserPrompt,
&t.TaskType, &t.SystemFunction,
&t.PersonaID, &t.ModelID, &t.SystemPrompt, &t.UserPrompt,
&t.WorkflowID, &t.ToolGrants, &t.Schedule, &t.Timezone, &t.IsActive,
&t.TriggerToken,
&t.MaxTokens, &t.MaxToolCalls, &t.MaxWallClock, &t.OutputMode,
@@ -56,16 +58,18 @@ func (s *TaskStore) Create(ctx context.Context, t *models.Task) error {
toolGrants := jsonOrNull(t.ToolGrants)
return DB.QueryRowContext(ctx, `
INSERT INTO tasks (owner_id, team_id, name, description, scope,
task_type, persona_id, model_id, system_prompt, user_prompt,
task_type, system_function,
persona_id, model_id, system_prompt, user_prompt,
workflow_id, tool_grants, schedule, timezone, is_active,
trigger_token,
max_tokens, max_tool_calls, max_wall_clock, output_mode,
output_channel_id, webhook_url, webhook_secret, provider_config_id,
notify_on_complete, notify_on_failure, next_run_at)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28)
RETURNING id, created_at, updated_at`,
t.OwnerID, t.TeamID, t.Name, t.Description, t.Scope,
t.TaskType, t.PersonaID, t.ModelID, t.SystemPrompt, t.UserPrompt,
t.TaskType, t.SystemFunction,
t.PersonaID, t.ModelID, t.SystemPrompt, t.UserPrompt,
t.WorkflowID, toolGrants, t.Schedule, t.Timezone, t.IsActive,
nilIfEmpty(t.TriggerToken),
t.MaxTokens, t.MaxToolCalls, t.MaxWallClock, t.OutputMode,

View File

@@ -122,6 +122,23 @@ func (s *UserStore) SetActive(ctx context.Context, id string, active bool) error
return err
}
func (s *UserStore) ListActiveUserIDs(ctx context.Context) ([]string, error) {
rows, err := DB.QueryContext(ctx, "SELECT id FROM users WHERE is_active = true")
if err != nil {
return nil, err
}
defer rows.Close()
var ids []string
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, rows.Err()
}
// ── Refresh Tokens ──────────────────────────
func (s *UserStore) CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error {

View File

@@ -18,11 +18,12 @@ func (s *GitCredentialStore) Create(ctx context.Context, cred *models.GitCredent
cred.ID = uuid.NewString()
}
_, err := DB.ExecContext(ctx, `
INSERT INTO git_credentials (id, user_id, name, auth_type, encrypted_data, nonce, created_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))`,
INSERT INTO git_credentials (id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
cred.ID, cred.UserID, cred.Name, cred.AuthType,
base64.StdEncoding.EncodeToString(cred.EncryptedData),
base64.StdEncoding.EncodeToString(cred.Nonce),
cred.PublicKey, cred.Fingerprint, cred.PersonaID,
)
if err != nil {
return err
@@ -37,10 +38,10 @@ func (s *GitCredentialStore) GetByID(ctx context.Context, id string) (*models.Gi
var c models.GitCredential
var encData, nonce string
err := DB.QueryRowContext(ctx, `
SELECT id, user_id, name, auth_type, encrypted_data, nonce, created_at
SELECT id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id, created_at
FROM git_credentials WHERE id = ?`, id,
).Scan(&c.ID, &c.UserID, &c.Name, &c.AuthType,
&encData, &nonce, st(&c.CreatedAt))
&encData, &nonce, &c.PublicKey, &c.Fingerprint, &c.PersonaID, st(&c.CreatedAt))
if err != nil {
return nil, err
}
@@ -51,7 +52,7 @@ func (s *GitCredentialStore) GetByID(ctx context.Context, id string) (*models.Gi
func (s *GitCredentialStore) ListByUser(ctx context.Context, userID string) ([]models.GitCredential, error) {
rows, err := DB.QueryContext(ctx, `
SELECT id, user_id, name, auth_type, encrypted_data, nonce, created_at
SELECT id, user_id, name, auth_type, encrypted_data, nonce, public_key, fingerprint, persona_id, created_at
FROM git_credentials WHERE user_id = ? ORDER BY created_at DESC`, userID)
if err != nil {
return nil, err
@@ -63,7 +64,7 @@ func (s *GitCredentialStore) ListByUser(ctx context.Context, userID string) ([]m
var c models.GitCredential
var encData, nonce string
if err := rows.Scan(&c.ID, &c.UserID, &c.Name, &c.AuthType,
&encData, &nonce, st(&c.CreatedAt)); err != nil {
&encData, &nonce, &c.PublicKey, &c.Fingerprint, &c.PersonaID, st(&c.CreatedAt)); err != nil {
return nil, err
}
c.EncryptedData, _ = base64.StdEncoding.DecodeString(encData)

View File

@@ -19,7 +19,8 @@ func NewTaskStore() *TaskStore { return &TaskStore{} }
// COALESCE wraps nullable columns that scan into non-pointer Go types.
// tool_grants uses COALESCE to '[]' so the string intermediate never sees NULL.
const taskColumns = `id, owner_id, team_id, name, description, scope,
task_type, persona_id, model_id, system_prompt, user_prompt,
task_type, COALESCE(system_function, '') AS system_function,
persona_id, model_id, system_prompt, user_prompt,
workflow_id, COALESCE(tool_grants, '[]') AS tool_grants,
schedule, timezone, is_active,
COALESCE(trigger_token, '') AS trigger_token,
@@ -47,7 +48,8 @@ func scanTask(scanner interface{ Scan(...interface{}) error }, t *models.Task) e
var isActive, notifyComplete, notifyFailure int
err := scanner.Scan(
&t.ID, &t.OwnerID, &t.TeamID, &t.Name, &t.Description, &t.Scope,
&t.TaskType, &t.PersonaID, &t.ModelID, &t.SystemPrompt, &t.UserPrompt,
&t.TaskType, &t.SystemFunction,
&t.PersonaID, &t.ModelID, &t.SystemPrompt, &t.UserPrompt,
&t.WorkflowID, &toolGrantsStr, &t.Schedule, &t.Timezone, &isActive,
&t.TriggerToken,
&t.MaxTokens, &t.MaxToolCalls, &t.MaxWallClock, &t.OutputMode,
@@ -74,15 +76,17 @@ func (s *TaskStore) Create(ctx context.Context, t *models.Task) error {
t.UpdatedAt = now
_, err := DB.ExecContext(ctx, `
INSERT INTO tasks (id, owner_id, team_id, name, description, scope,
task_type, persona_id, model_id, system_prompt, user_prompt,
task_type, system_function,
persona_id, model_id, system_prompt, user_prompt,
workflow_id, tool_grants, schedule, timezone, is_active,
trigger_token,
max_tokens, max_tool_calls, max_wall_clock, output_mode,
output_channel_id, webhook_url, webhook_secret, provider_config_id,
notify_on_complete, notify_on_failure, next_run_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
t.ID, t.OwnerID, t.TeamID, t.Name, t.Description, t.Scope,
t.TaskType, t.PersonaID, t.ModelID, t.SystemPrompt, t.UserPrompt,
t.TaskType, t.SystemFunction,
t.PersonaID, t.ModelID, t.SystemPrompt, t.UserPrompt,
t.WorkflowID, nullableJSON(t.ToolGrants), t.Schedule, t.Timezone, boolToInt(t.IsActive),
nilIfEmpty(t.TriggerToken),
t.MaxTokens, t.MaxToolCalls, t.MaxWallClock, t.OutputMode,

View File

@@ -128,6 +128,23 @@ func (s *UserStore) SetActive(ctx context.Context, id string, active bool) error
return err
}
func (s *UserStore) ListActiveUserIDs(ctx context.Context) ([]string, error) {
rows, err := DB.QueryContext(ctx, "SELECT id FROM users WHERE is_active = 1")
if err != nil {
return nil, err
}
defer rows.Close()
var ids []string
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
ids = append(ids, id)
}
return ids, rows.Err()
}
// ── Refresh Tokens ──────────────────────────
func (s *UserStore) CreateRefreshToken(ctx context.Context, userID, tokenHash string, expiresAt time.Time) error {