mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add enable/disable support for user secrets (#27537)
Users can now disable a secret to stop it from being injected into workspaces without deleting it, and re-enable it later. Disabled secrets stay visible and editable everywhere they already appear. An enabled secret must have at least one injection target; a secret with no target can be stored only while disabled. Existing target-less secrets are migrated to disabled to preserve current behavior. Support spans the REST API, SDK, CLI, dashboard, and audit log.
This commit is contained in:
Generated
+1
@@ -56,6 +56,7 @@ const (
|
||||
CheckUsageEventTypeCheck CheckConstraint = "usage_event_type_check" // usage_events
|
||||
CheckUserAIBudgetOverridesSpendLimitMicrosCheck CheckConstraint = "user_ai_budget_overrides_spend_limit_micros_check" // user_ai_budget_overrides
|
||||
CheckUserAIProviderKeysAPIKeyCheck CheckConstraint = "user_ai_provider_keys_api_key_check" // user_ai_provider_keys
|
||||
CheckUserSecretsEnabledRequiresTarget CheckConstraint = "user_secrets_enabled_requires_target" // user_secrets
|
||||
CheckUserSkillsContentSize CheckConstraint = "user_skills_content_size" // user_skills
|
||||
CheckUserSkillsDescriptionSize CheckConstraint = "user_skills_description_size" // user_skills
|
||||
CheckUserSkillsNameFormat CheckConstraint = "user_skills_name_format" // user_skills
|
||||
|
||||
@@ -2098,6 +2098,7 @@ func UserSecret(secret database.ListUserSecretsRow) codersdk.UserSecret {
|
||||
Description: secret.Description,
|
||||
EnvName: secret.EnvName,
|
||||
FilePath: secret.FilePath,
|
||||
Enabled: secret.Enabled,
|
||||
CreatedAt: secret.CreatedAt,
|
||||
UpdatedAt: secret.UpdatedAt,
|
||||
}
|
||||
@@ -2112,6 +2113,7 @@ func UserSecretFromFull(secret database.UserSecret) codersdk.UserSecret {
|
||||
Description: secret.Description,
|
||||
EnvName: secret.EnvName,
|
||||
FilePath: secret.FilePath,
|
||||
Enabled: secret.Enabled,
|
||||
CreatedAt: secret.CreatedAt,
|
||||
UpdatedAt: secret.UpdatedAt,
|
||||
}
|
||||
|
||||
@@ -1962,6 +1962,7 @@ func UserSecret(t testing.TB, db database.Store, seed database.UserSecret, mutat
|
||||
ValueKeyID: seed.ValueKeyID,
|
||||
EnvName: takeFirst(seed.EnvName, "SECRET_ENV_NAME"),
|
||||
FilePath: takeFirst(seed.FilePath, "~/secret/file/path"),
|
||||
Enabled: takeFirst(seed.Enabled, true),
|
||||
}
|
||||
for _, mut := range mutators {
|
||||
mut(¶ms)
|
||||
|
||||
Generated
+3
-1
@@ -3624,7 +3624,9 @@ CREATE TABLE user_secrets (
|
||||
file_path text DEFAULT ''::text NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
value_key_id text
|
||||
value_key_id text,
|
||||
enabled boolean DEFAULT true NOT NULL,
|
||||
CONSTRAINT user_secrets_enabled_requires_target CHECK (((NOT enabled) OR (env_name <> ''::text) OR (file_path <> ''::text)))
|
||||
);
|
||||
|
||||
CREATE TABLE user_skills (
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE user_secrets
|
||||
DROP COLUMN enabled;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Add an explicit enabled flag to user_secrets.
|
||||
--
|
||||
-- A disabled secret stays visible and editable in the management UI, CLI,
|
||||
-- and API, but is not injected into workspaces and does not satisfy any
|
||||
-- "secret present" predicate. This is the single source of truth for
|
||||
-- "not injected"; the agent manifest layer no longer skips rows based
|
||||
-- on having both env_name and file_path empty.
|
||||
--
|
||||
-- Existing rows whose env_name and file_path are both empty are flipped
|
||||
-- to enabled = false. Today those rows are silently skipped during agent
|
||||
-- manifest assembly, so flipping them preserves observable behavior
|
||||
-- while letting the manifest stop encoding the both-empty special case.
|
||||
ALTER TABLE user_secrets
|
||||
ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT true;
|
||||
|
||||
UPDATE user_secrets
|
||||
SET enabled = false
|
||||
WHERE env_name = '' AND file_path = '';
|
||||
|
||||
-- Enforce the injection-target invariant in the database: an enabled
|
||||
-- secret must have at least one of env_name / file_path non-empty.
|
||||
-- Disabled secrets may have no targets (bulk imports use that state for
|
||||
-- keys that cannot be env-injected). The API also checks this on write,
|
||||
-- but the constraint is the source of truth: it closes a read-modify-write
|
||||
-- race where two concurrent PATCHes each clear a different target, both
|
||||
-- pass the API's post-state check, and serialize to an enabled row with
|
||||
-- no targets.
|
||||
ALTER TABLE user_secrets
|
||||
ADD CONSTRAINT user_secrets_enabled_requires_target
|
||||
CHECK (NOT enabled OR env_name <> '' OR file_path <> '');
|
||||
@@ -2251,3 +2251,100 @@ func TestMigration000543ChatSearchSchemaBehavior(t *testing.T) {
|
||||
"search must exclude deleted, model-only, and tool-role rows (%d %d %d)",
|
||||
toolMsg.ID, modelOnly.ID, deletedMsg.ID)
|
||||
}
|
||||
|
||||
func TestMigration000556UserSecretsEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const migrationVersion = 556
|
||||
|
||||
sqlDB := testSQLDB(t)
|
||||
|
||||
// Migrate up to the migration before the one that adds the enabled
|
||||
// column.
|
||||
next, err := migrations.Stepper(sqlDB)
|
||||
require.NoError(t, err)
|
||||
for {
|
||||
version, more, err := next()
|
||||
require.NoError(t, err)
|
||||
if !more {
|
||||
t.Fatalf("migration %d not found", migrationVersion)
|
||||
}
|
||||
if version == migrationVersion-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitSuperLong)
|
||||
|
||||
userID := uuid.New()
|
||||
envSecretID := uuid.New()
|
||||
fileSecretID := uuid.New()
|
||||
bothEmptySecretID := uuid.New()
|
||||
|
||||
now := time.Now().UTC().Truncate(time.Microsecond)
|
||||
|
||||
tx, err := sqlDB.BeginTx(ctx, nil)
|
||||
require.NoError(t, err)
|
||||
defer tx.Rollback()
|
||||
|
||||
fixtures := []struct {
|
||||
query string
|
||||
args []any
|
||||
}{
|
||||
{
|
||||
`INSERT INTO users (id, username, email, hashed_password, created_at, updated_at, status, rbac_roles, login_type)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[]any{userID, "user-secrets-enabled", "user-secrets-enabled@test.com", []byte{}, now, now, "active", pq.StringArray{}, "password"},
|
||||
},
|
||||
// env-only secret: should remain enabled after migration.
|
||||
{
|
||||
`INSERT INTO user_secrets (id, user_id, name, description, value, env_name, file_path, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[]any{envSecretID, userID, "env-secret", "", "v1", "ENV_SECRET", "", now, now},
|
||||
},
|
||||
// file-only secret: should remain enabled after migration.
|
||||
{
|
||||
`INSERT INTO user_secrets (id, user_id, name, description, value, env_name, file_path, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[]any{fileSecretID, userID, "file-secret", "", "v2", "", "/tmp/file-secret", now, now},
|
||||
},
|
||||
// Both env_name and file_path empty: silently skipped today by
|
||||
// the agent manifest layer. Should be flipped to enabled=false
|
||||
// by the migration so the behavior is preserved exactly under
|
||||
// the new "always inject when enabled" rule.
|
||||
{
|
||||
`INSERT INTO user_secrets (id, user_id, name, description, value, env_name, file_path, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[]any{bothEmptySecretID, userID, "both-empty", "", "v3", "", "", now, now},
|
||||
},
|
||||
}
|
||||
|
||||
for i, f := range fixtures {
|
||||
_, err := tx.ExecContext(ctx, f.query, f.args...)
|
||||
require.NoError(t, err, "fixture %d", i)
|
||||
}
|
||||
require.NoError(t, tx.Commit())
|
||||
|
||||
// Run the migration.
|
||||
version, _, err := next()
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, migrationVersion, version)
|
||||
|
||||
getEnabled := func(t *testing.T, id uuid.UUID) bool {
|
||||
t.Helper()
|
||||
var enabled bool
|
||||
err := sqlDB.QueryRowContext(ctx,
|
||||
"SELECT enabled FROM user_secrets WHERE id = $1", id,
|
||||
).Scan(&enabled)
|
||||
require.NoError(t, err)
|
||||
return enabled
|
||||
}
|
||||
|
||||
require.True(t, getEnabled(t, envSecretID),
|
||||
"env-only secret should remain enabled")
|
||||
require.True(t, getEnabled(t, fileSecretID),
|
||||
"file-only secret should remain enabled")
|
||||
require.False(t, getEnabled(t, bothEmptySecretID),
|
||||
"secret with both targets empty should be flipped to disabled "+
|
||||
"to preserve the previous implicit-skip behavior")
|
||||
}
|
||||
|
||||
Generated
+1
@@ -6244,6 +6244,7 @@ type UserSecret struct {
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
}
|
||||
|
||||
type UserSkill struct {
|
||||
|
||||
@@ -8483,7 +8483,9 @@ func TestUserSecretsCRUDOperations(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "duplicate key value")
|
||||
|
||||
// Create secret with empty env_name and file_path (should succeed)
|
||||
// Create secret with empty env_name and file_path. A target-less
|
||||
// secret must be disabled to satisfy the
|
||||
// user_secrets_enabled_requires_target constraint.
|
||||
secret2 := dbgen.UserSecret(t, db, database.UserSecret{
|
||||
UserID: testUser.ID,
|
||||
Name: "unique-test-4",
|
||||
@@ -8491,6 +8493,8 @@ func TestUserSecretsCRUDOperations(t *testing.T) {
|
||||
Value: "value2",
|
||||
EnvName: "", // Empty env_name
|
||||
FilePath: "", // Empty file_path
|
||||
}, func(params *database.CreateUserSecretParams) {
|
||||
params.Enabled = false
|
||||
})
|
||||
|
||||
// Verify both secrets exist
|
||||
@@ -8505,6 +8509,83 @@ func TestUserSecretsCRUDOperations(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestUserSecretsEnabledRequiresTargetConstraint verifies the
|
||||
// user_secrets_enabled_requires_target CHECK constraint. It is the
|
||||
// race-safe backstop for the injection-target invariant: the API's
|
||||
// post-state check can be defeated by two concurrent PATCHes that each
|
||||
// clear a different target, so the database must reject an enabled row
|
||||
// with no target.
|
||||
func TestUserSecretsEnabledRequiresTargetConstraint(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
|
||||
// A disabled secret may have no target.
|
||||
disabled, err := db.CreateUserSecret(ctx, database.CreateUserSecretParams{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Name: "disabled-no-target",
|
||||
Value: "v",
|
||||
EnvName: "",
|
||||
FilePath: "",
|
||||
Enabled: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Enabling a target-less secret must be rejected by the constraint.
|
||||
_, err = db.UpdateUserSecretByUserIDAndName(ctx, database.UpdateUserSecretByUserIDAndNameParams{
|
||||
UserID: user.ID,
|
||||
Name: disabled.Name,
|
||||
UpdateEnabled: true,
|
||||
Enabled: true,
|
||||
})
|
||||
require.True(t, database.IsCheckViolation(err, database.CheckUserSecretsEnabledRequiresTarget),
|
||||
"enabling a target-less secret should violate the constraint, got: %v", err)
|
||||
|
||||
// An enabled secret with both targets set.
|
||||
enabled, err := db.CreateUserSecret(ctx, database.CreateUserSecretParams{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Name: "enabled-both",
|
||||
Value: "v",
|
||||
EnvName: "ENABLED_BOTH",
|
||||
FilePath: "~/enabled-both",
|
||||
Enabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Clearing both targets while the secret stays enabled (the race
|
||||
// outcome) must be rejected.
|
||||
_, err = db.UpdateUserSecretByUserIDAndName(ctx, database.UpdateUserSecretByUserIDAndNameParams{
|
||||
UserID: user.ID,
|
||||
Name: enabled.Name,
|
||||
UpdateEnvName: true,
|
||||
EnvName: "",
|
||||
UpdateFilePath: true,
|
||||
FilePath: "",
|
||||
})
|
||||
require.True(t, database.IsCheckViolation(err, database.CheckUserSecretsEnabledRequiresTarget),
|
||||
"clearing both targets of an enabled secret should violate the constraint, got: %v", err)
|
||||
|
||||
// Clearing both targets and disabling in the same update is allowed.
|
||||
updated, err := db.UpdateUserSecretByUserIDAndName(ctx, database.UpdateUserSecretByUserIDAndNameParams{
|
||||
UserID: user.ID,
|
||||
Name: enabled.Name,
|
||||
UpdateEnvName: true,
|
||||
EnvName: "",
|
||||
UpdateFilePath: true,
|
||||
FilePath: "",
|
||||
UpdateEnabled: true,
|
||||
Enabled: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, updated.Enabled)
|
||||
require.Empty(t, updated.EnvName)
|
||||
require.Empty(t, updated.FilePath)
|
||||
}
|
||||
|
||||
// TestUserSecretsSoftDeleteTrigger verifies that a user's secrets
|
||||
// are deleted when the user is soft-deleted.
|
||||
func TestUserSecretsSoftDeleteTrigger(t *testing.T) {
|
||||
|
||||
Generated
+27
-10
@@ -29754,7 +29754,8 @@ INSERT INTO user_secrets (
|
||||
value,
|
||||
value_key_id,
|
||||
env_name,
|
||||
file_path
|
||||
file_path,
|
||||
enabled
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
@@ -29763,8 +29764,9 @@ INSERT INTO user_secrets (
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8
|
||||
) RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
$8,
|
||||
$9
|
||||
) RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
`
|
||||
|
||||
type CreateUserSecretParams struct {
|
||||
@@ -29776,6 +29778,7 @@ type CreateUserSecretParams struct {
|
||||
ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"`
|
||||
EnvName string `db:"env_name" json:"env_name"`
|
||||
FilePath string `db:"file_path" json:"file_path"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) CreateUserSecret(ctx context.Context, arg CreateUserSecretParams) (UserSecret, error) {
|
||||
@@ -29788,6 +29791,7 @@ func (q *sqlQuerier) CreateUserSecret(ctx context.Context, arg CreateUserSecretP
|
||||
arg.ValueKeyID,
|
||||
arg.EnvName,
|
||||
arg.FilePath,
|
||||
arg.Enabled,
|
||||
)
|
||||
var i UserSecret
|
||||
err := row.Scan(
|
||||
@@ -29801,6 +29805,7 @@ func (q *sqlQuerier) CreateUserSecret(ctx context.Context, arg CreateUserSecretP
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -29808,7 +29813,7 @@ func (q *sqlQuerier) CreateUserSecret(ctx context.Context, arg CreateUserSecretP
|
||||
const deleteUserSecretByUserIDAndName = `-- name: DeleteUserSecretByUserIDAndName :one
|
||||
DELETE FROM user_secrets
|
||||
WHERE user_id = $1 AND name = $2
|
||||
RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
`
|
||||
|
||||
type DeleteUserSecretByUserIDAndNameParams struct {
|
||||
@@ -29830,12 +29835,13 @@ func (q *sqlQuerier) DeleteUserSecretByUserIDAndName(ctx context.Context, arg De
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserSecretByID = `-- name: GetUserSecretByID :one
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
FROM user_secrets
|
||||
WHERE id = $1
|
||||
`
|
||||
@@ -29854,12 +29860,13 @@ func (q *sqlQuerier) GetUserSecretByID(ctx context.Context, id uuid.UUID) (UserS
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserSecretByUserIDAndName = `-- name: GetUserSecretByUserIDAndName :one
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
FROM user_secrets
|
||||
WHERE user_id = $1 AND name = $2
|
||||
`
|
||||
@@ -29883,6 +29890,7 @@ func (q *sqlQuerier) GetUserSecretByUserIDAndName(ctx context.Context, arg GetUs
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -29983,7 +29991,7 @@ func (q *sqlQuerier) GetUserSecretsTelemetrySummary(ctx context.Context) (GetUse
|
||||
const listUserSecrets = `-- name: ListUserSecrets :many
|
||||
SELECT
|
||||
id, user_id, name, description,
|
||||
env_name, file_path,
|
||||
env_name, file_path, enabled,
|
||||
created_at, updated_at
|
||||
FROM user_secrets
|
||||
WHERE user_id = $1
|
||||
@@ -29997,6 +30005,7 @@ type ListUserSecretsRow struct {
|
||||
Description string `db:"description" json:"description"`
|
||||
EnvName string `db:"env_name" json:"env_name"`
|
||||
FilePath string `db:"file_path" json:"file_path"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
@@ -30019,6 +30028,7 @@ func (q *sqlQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]L
|
||||
&i.Description,
|
||||
&i.EnvName,
|
||||
&i.FilePath,
|
||||
&i.Enabled,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
@@ -30036,7 +30046,7 @@ func (q *sqlQuerier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]L
|
||||
}
|
||||
|
||||
const listUserSecretsWithValues = `-- name: ListUserSecretsWithValues :many
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
SELECT id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
FROM user_secrets
|
||||
WHERE user_id = $1
|
||||
ORDER BY name ASC
|
||||
@@ -30065,6 +30075,7 @@ func (q *sqlQuerier) ListUserSecretsWithValues(ctx context.Context, userID uuid.
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -30087,9 +30098,10 @@ SET
|
||||
description = CASE WHEN $4::bool THEN $5 ELSE description END,
|
||||
env_name = CASE WHEN $6::bool THEN $7 ELSE env_name END,
|
||||
file_path = CASE WHEN $8::bool THEN $9 ELSE file_path END,
|
||||
enabled = CASE WHEN $10::bool THEN $11 ELSE enabled END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = $10 AND name = $11
|
||||
RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id
|
||||
WHERE user_id = $12 AND name = $13
|
||||
RETURNING id, user_id, name, description, value, env_name, file_path, created_at, updated_at, value_key_id, enabled
|
||||
`
|
||||
|
||||
type UpdateUserSecretByUserIDAndNameParams struct {
|
||||
@@ -30102,6 +30114,8 @@ type UpdateUserSecretByUserIDAndNameParams struct {
|
||||
EnvName string `db:"env_name" json:"env_name"`
|
||||
UpdateFilePath bool `db:"update_file_path" json:"update_file_path"`
|
||||
FilePath string `db:"file_path" json:"file_path"`
|
||||
UpdateEnabled bool `db:"update_enabled" json:"update_enabled"`
|
||||
Enabled bool `db:"enabled" json:"enabled"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
}
|
||||
@@ -30117,6 +30131,8 @@ func (q *sqlQuerier) UpdateUserSecretByUserIDAndName(ctx context.Context, arg Up
|
||||
arg.EnvName,
|
||||
arg.UpdateFilePath,
|
||||
arg.FilePath,
|
||||
arg.UpdateEnabled,
|
||||
arg.Enabled,
|
||||
arg.UserID,
|
||||
arg.Name,
|
||||
)
|
||||
@@ -30132,6 +30148,7 @@ func (q *sqlQuerier) UpdateUserSecretByUserIDAndName(ctx context.Context, arg Up
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.ValueKeyID,
|
||||
&i.Enabled,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ WHERE id = @id;
|
||||
-- REST API list and get endpoints.
|
||||
SELECT
|
||||
id, user_id, name, description,
|
||||
env_name, file_path,
|
||||
env_name, file_path, enabled,
|
||||
created_at, updated_at
|
||||
FROM user_secrets
|
||||
WHERE user_id = @user_id
|
||||
@@ -37,7 +37,8 @@ INSERT INTO user_secrets (
|
||||
value,
|
||||
value_key_id,
|
||||
env_name,
|
||||
file_path
|
||||
file_path,
|
||||
enabled
|
||||
) VALUES (
|
||||
@id,
|
||||
@user_id,
|
||||
@@ -46,7 +47,8 @@ INSERT INTO user_secrets (
|
||||
@value,
|
||||
@value_key_id,
|
||||
@env_name,
|
||||
@file_path
|
||||
@file_path,
|
||||
@enabled
|
||||
) RETURNING *;
|
||||
|
||||
-- name: UpdateUserSecretByUserIDAndName :one
|
||||
@@ -57,6 +59,7 @@ SET
|
||||
description = CASE WHEN @update_description::bool THEN @description ELSE description END,
|
||||
env_name = CASE WHEN @update_env_name::bool THEN @env_name ELSE env_name END,
|
||||
file_path = CASE WHEN @update_file_path::bool THEN @file_path ELSE file_path END,
|
||||
enabled = CASE WHEN @update_enabled::bool THEN @enabled ELSE enabled END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE user_id = @user_id AND name = @name
|
||||
RETURNING *;
|
||||
|
||||
Reference in New Issue
Block a user