mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add personal skill storage, API, and SDK (#25363)
> Mux updated this PR on behalf of Mike. ## Stack Context This PR is the storage, permissions, API, and SDK layer for experimental personal skills. #25362 has landed on `main`, so this branch is restacked directly on `main`. Stack order: 1. #25363 storage, permissions, API, and SDK 2. #25365 API test coverage 3. #25366 chattool and chatd integration 4. #25066 settings UI and docs 5. #25386 personal skills slash menu ## What? Adds the `user_skills` database table, generated queries, RBAC resources and scopes, audit resource handling, experimental user-scoped CRUD endpoints, SDK types, and generated API/site types. Follow-up review and restack fixes: - Enforce a bounded personal skill description in parser and database constraints. - Return `403 Forbidden` for unauthorized create and update attempts. - Return explicit conflict responses when soft-deleted users are targeted. - Keep user admins out of personal skills, while site owners can read and delete but not create or update. - Document trigger-raised constraint names and keep schema constants covered by tests. - Reuse `UserSkillMetadata` in the full `UserSkill` SDK response type. - Generate user skill IDs in Go instead of relying on a database default. - Rebase on latest `main` and renumber the user skills migration to `000502_user_skills`. ## Why? Personal skills need durable user-owned storage with owner authorization, limited site-owner moderation, and a hidden API surface before chatd can consume them. ## Validation - `make gen` - `go test ./coderd/database -run '^TestUserSkillSchemaConstants$' -count=1` - `go test ./coderd/database/dbauthz -run '^TestMethodTestSuite/TestUserSkills$' -count=1` - `go test ./coderd -run '^TestPatchUserSkill$' -count=1` - `go test ./codersdk ./coderd/database/db2sdk` - `make lint` - pre-commit hook on `97fd58108d`
This commit is contained in:
@@ -312,6 +312,11 @@ const (
|
||||
ApiKeyScopeAiProviderRead APIKeyScope = "ai_provider:read"
|
||||
ApiKeyScopeAiProviderUpdate APIKeyScope = "ai_provider:update"
|
||||
ApiKeyScopeChatShare APIKeyScope = "chat:share"
|
||||
ApiKeyScopeUserSkillCreate APIKeyScope = "user_skill:create"
|
||||
ApiKeyScopeUserSkillRead APIKeyScope = "user_skill:read"
|
||||
ApiKeyScopeUserSkillUpdate APIKeyScope = "user_skill:update"
|
||||
ApiKeyScopeUserSkillDelete APIKeyScope = "user_skill:delete"
|
||||
ApiKeyScopeUserSkill APIKeyScope = "user_skill:*"
|
||||
)
|
||||
|
||||
func (e *APIKeyScope) Scan(src interface{}) error {
|
||||
@@ -567,7 +572,12 @@ func (e APIKeyScope) Valid() bool {
|
||||
ApiKeyScopeAiProviderDelete,
|
||||
ApiKeyScopeAiProviderRead,
|
||||
ApiKeyScopeAiProviderUpdate,
|
||||
ApiKeyScopeChatShare:
|
||||
ApiKeyScopeChatShare,
|
||||
ApiKeyScopeUserSkillCreate,
|
||||
ApiKeyScopeUserSkillRead,
|
||||
ApiKeyScopeUserSkillUpdate,
|
||||
ApiKeyScopeUserSkillDelete,
|
||||
ApiKeyScopeUserSkill:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -792,6 +802,11 @@ func AllAPIKeyScopeValues() []APIKeyScope {
|
||||
ApiKeyScopeAiProviderRead,
|
||||
ApiKeyScopeAiProviderUpdate,
|
||||
ApiKeyScopeChatShare,
|
||||
ApiKeyScopeUserSkillCreate,
|
||||
ApiKeyScopeUserSkillRead,
|
||||
ApiKeyScopeUserSkillUpdate,
|
||||
ApiKeyScopeUserSkillDelete,
|
||||
ApiKeyScopeUserSkill,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3322,6 +3337,7 @@ const (
|
||||
ResourceTypeAiProvider ResourceType = "ai_provider"
|
||||
ResourceTypeAiProviderKey ResourceType = "ai_provider_key"
|
||||
ResourceTypeGroupAiBudget ResourceType = "group_ai_budget"
|
||||
ResourceTypeUserSkill ResourceType = "user_skill"
|
||||
)
|
||||
|
||||
func (e *ResourceType) Scan(src interface{}) error {
|
||||
@@ -3392,7 +3408,8 @@ func (e ResourceType) Valid() bool {
|
||||
ResourceTypeUserSecret,
|
||||
ResourceTypeAiProvider,
|
||||
ResourceTypeAiProviderKey,
|
||||
ResourceTypeGroupAiBudget:
|
||||
ResourceTypeGroupAiBudget,
|
||||
ResourceTypeUserSkill:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -3432,6 +3449,7 @@ func AllResourceTypeValues() []ResourceType {
|
||||
ResourceTypeAiProvider,
|
||||
ResourceTypeAiProviderKey,
|
||||
ResourceTypeGroupAiBudget,
|
||||
ResourceTypeUserSkill,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5725,6 +5743,16 @@ type UserSecret struct {
|
||||
ValueKeyID sql.NullString `db:"value_key_id" json:"value_key_id"`
|
||||
}
|
||||
|
||||
type UserSkill struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Content string `db:"content" json:"content"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
// Tracks the history of user status changes
|
||||
type UserStatusChange struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
|
||||
Reference in New Issue
Block a user