first commit
This commit is contained in:
553
go-api/internal/repo/definitions.go
Normal file
553
go-api/internal/repo/definitions.go
Normal file
@@ -0,0 +1,553 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/krow/krow-backend/go-api/internal/authctx"
|
||||
"github.com/krow/krow-backend/go-api/internal/domain"
|
||||
)
|
||||
|
||||
// DefinitionListParams holds the query parameters for listing authored definitions.
|
||||
type DefinitionListParams struct {
|
||||
Visibility string
|
||||
Status string
|
||||
DefinitionID string
|
||||
Sort string
|
||||
Desc bool
|
||||
Limit int
|
||||
Offset int
|
||||
}
|
||||
|
||||
// AgentInsertInput holds the fields to insert into agent_definitions.
|
||||
type AgentInsertInput struct {
|
||||
DefinitionID string
|
||||
OrgID string
|
||||
Visibility string
|
||||
OwnerUserID *string
|
||||
CreatedBy *string
|
||||
Markdown string
|
||||
Status string
|
||||
Version int
|
||||
Name string
|
||||
Description string
|
||||
Pages []string
|
||||
}
|
||||
|
||||
// AgentUpdateInput holds the fields to update in agent_definitions.
|
||||
type AgentUpdateInput struct {
|
||||
DefinitionID *string
|
||||
Name *string
|
||||
Description *string
|
||||
Status *string
|
||||
Version *int
|
||||
Pages []string
|
||||
Markdown *string
|
||||
Visibility *string
|
||||
OwnerUserID *string
|
||||
}
|
||||
|
||||
// SkillInsertInput holds the fields to insert into skill_definitions.
|
||||
type SkillInsertInput struct {
|
||||
DefinitionID string
|
||||
OrgID string
|
||||
Visibility string
|
||||
OwnerUserID *string
|
||||
CreatedBy *string
|
||||
Markdown string
|
||||
Status string
|
||||
Name string
|
||||
Description string
|
||||
Pages []string
|
||||
}
|
||||
|
||||
// SkillUpdateInput holds the fields to update in skill_definitions.
|
||||
type SkillUpdateInput struct {
|
||||
DefinitionID *string
|
||||
Name *string
|
||||
Description *string
|
||||
Status *string
|
||||
Pages []string
|
||||
Markdown *string
|
||||
Visibility *string
|
||||
OwnerUserID *string
|
||||
}
|
||||
|
||||
const agentDefinitionColumns = `id::text AS id,
|
||||
definition_id,
|
||||
org_id::text AS org_id,
|
||||
visibility,
|
||||
owner_user_id::text AS owner_user_id,
|
||||
created_by::text AS created_by,
|
||||
markdown,
|
||||
status,
|
||||
version,
|
||||
name,
|
||||
description,
|
||||
pages,
|
||||
to_char(created_date AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_date,
|
||||
to_char(updated_date AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS updated_date`
|
||||
|
||||
const skillDefinitionColumns = `id::text AS id,
|
||||
definition_id,
|
||||
org_id::text AS org_id,
|
||||
visibility,
|
||||
owner_user_id::text AS owner_user_id,
|
||||
created_by::text AS created_by,
|
||||
markdown,
|
||||
status,
|
||||
name,
|
||||
description,
|
||||
pages,
|
||||
to_char(created_date AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS created_date,
|
||||
to_char(updated_date AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS updated_date`
|
||||
|
||||
// DefinitionsRepo handles persistence for agent_definitions and skill_definitions.
|
||||
type DefinitionsRepo struct {
|
||||
db Querier
|
||||
}
|
||||
|
||||
// NewDefinitionsRepo builds a repository over a pool or transaction.
|
||||
func NewDefinitionsRepo(db Querier) *DefinitionsRepo {
|
||||
return &DefinitionsRepo{db: db}
|
||||
}
|
||||
|
||||
/* ── Agents ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
// ListAgents lists agent definitions matching the criteria with tenant isolation.
|
||||
func (r *DefinitionsRepo) ListAgents(ctx context.Context, ident authctx.Identity, p DefinitionListParams) ([]domain.Record, int, error) {
|
||||
b := &builder{}
|
||||
b.where = append(b.where, fmt.Sprintf("org_id = %s::uuid", b.add(ident.OrgID)))
|
||||
|
||||
switch p.Visibility {
|
||||
case "personal":
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'personal' AND owner_user_id = %s::uuid)", b.add(ident.UserID)))
|
||||
case "organization":
|
||||
b.where = append(b.where, "visibility = 'organization'")
|
||||
default:
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = %s::uuid))", b.add(ident.UserID)))
|
||||
}
|
||||
|
||||
if p.Status != "" {
|
||||
b.where = append(b.where, fmt.Sprintf("status = %s::text", b.add(p.Status)))
|
||||
}
|
||||
if p.DefinitionID != "" {
|
||||
b.where = append(b.where, fmt.Sprintf("definition_id = %s::text", b.add(p.DefinitionID)))
|
||||
}
|
||||
|
||||
countSQL := "SELECT count(*)::int FROM agent_definitions" + b.clause()
|
||||
var total int
|
||||
if err := r.db.QueryRow(ctx, countSQL, b.args...).Scan(&total); err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
|
||||
sortCol := "created_date"
|
||||
switch p.Sort {
|
||||
case "created_date", "updated_date", "name", "definition_id", "status", "version":
|
||||
sortCol = p.Sort
|
||||
}
|
||||
dir := "ASC"
|
||||
if p.Desc {
|
||||
dir = "DESC"
|
||||
}
|
||||
orderClause := fmt.Sprintf(" ORDER BY %s %s, id %s", sortCol, dir, dir)
|
||||
|
||||
limit := p.Limit
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
offset := p.Offset
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
limitClause := fmt.Sprintf(" LIMIT %s OFFSET %s", b.add(limit), b.add(offset))
|
||||
querySQL := "SELECT " + agentDefinitionColumns + " FROM agent_definitions" + b.clause() + orderClause + limitClause
|
||||
|
||||
rows, err := r.db.Query(ctx, querySQL, b.args...)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
return records, total, nil
|
||||
}
|
||||
|
||||
// GetAgent retrieves one agent definition by id within the caller's tenant and ownership scope.
|
||||
func (r *DefinitionsRepo) GetAgent(ctx context.Context, ident authctx.Identity, id string) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`SELECT %s FROM agent_definitions
|
||||
WHERE id = $1::uuid
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))
|
||||
LIMIT 1`,
|
||||
agentDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, id, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// GetAgentByDefinitionID retrieves an agent definition by definition_id within the caller's scope,
|
||||
// giving personal definitions precedence over organization definitions when shadowed.
|
||||
func (r *DefinitionsRepo) GetAgentByDefinitionID(ctx context.Context, ident authctx.Identity, defID string) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`SELECT %s FROM agent_definitions
|
||||
WHERE definition_id = $1::text
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))
|
||||
ORDER BY CASE WHEN visibility = 'personal' THEN 1 ELSE 2 END
|
||||
LIMIT 1`,
|
||||
agentDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, defID, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// InsertAgent writes an agent definition and returns the stored row.
|
||||
func (r *DefinitionsRepo) InsertAgent(ctx context.Context, ident authctx.Identity, in AgentInsertInput) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`INSERT INTO agent_definitions (
|
||||
definition_id, org_id, visibility, owner_user_id, created_by,
|
||||
markdown, status, version, name, description, pages
|
||||
) VALUES (
|
||||
$1::text, $2::uuid, $3::text, $4::uuid, $5::uuid,
|
||||
$6::text, $7::text, $8::integer, $9::text, $10::text, $11::text[]
|
||||
) RETURNING %s`, agentDefinitionColumns)
|
||||
|
||||
pages := in.Pages
|
||||
if pages == nil {
|
||||
pages = []string{}
|
||||
}
|
||||
|
||||
rows, err := r.db.Query(ctx, q,
|
||||
in.DefinitionID, in.OrgID, in.Visibility, in.OwnerUserID, in.CreatedBy,
|
||||
in.Markdown, in.Status, in.Version, in.Name, in.Description, pages)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, fmt.Errorf("insert agent_definitions returned no row")
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// UpdateAgent updates fields on an agent definition and returns the full updated record.
|
||||
func (r *DefinitionsRepo) UpdateAgent(ctx context.Context, ident authctx.Identity, id string, in AgentUpdateInput) (domain.Record, error) {
|
||||
b := &builder{}
|
||||
sets := []string{"updated_date = now()"}
|
||||
|
||||
if in.DefinitionID != nil {
|
||||
sets = append(sets, fmt.Sprintf("definition_id = %s::text", b.add(*in.DefinitionID)))
|
||||
}
|
||||
if in.Name != nil {
|
||||
sets = append(sets, fmt.Sprintf("name = %s::text", b.add(*in.Name)))
|
||||
}
|
||||
if in.Description != nil {
|
||||
sets = append(sets, fmt.Sprintf("description = %s::text", b.add(*in.Description)))
|
||||
}
|
||||
if in.Status != nil {
|
||||
sets = append(sets, fmt.Sprintf("status = %s::text", b.add(*in.Status)))
|
||||
}
|
||||
if in.Version != nil {
|
||||
sets = append(sets, fmt.Sprintf("version = %s::integer", b.add(*in.Version)))
|
||||
}
|
||||
if in.Pages != nil {
|
||||
sets = append(sets, fmt.Sprintf("pages = %s::text[]", b.add(in.Pages)))
|
||||
}
|
||||
if in.Markdown != nil {
|
||||
sets = append(sets, fmt.Sprintf("markdown = %s::text", b.add(*in.Markdown)))
|
||||
}
|
||||
if in.Visibility != nil {
|
||||
sets = append(sets, fmt.Sprintf("visibility = %s::text", b.add(*in.Visibility)))
|
||||
sets = append(sets, fmt.Sprintf("owner_user_id = %s::uuid", b.add(in.OwnerUserID)))
|
||||
}
|
||||
|
||||
b.where = append(b.where, fmt.Sprintf("id = %s::uuid", b.add(id)))
|
||||
b.where = append(b.where, fmt.Sprintf("org_id = %s::uuid", b.add(ident.OrgID)))
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = %s::uuid))", b.add(ident.UserID)))
|
||||
|
||||
q := fmt.Sprintf("UPDATE agent_definitions SET %s%s RETURNING %s",
|
||||
strings.Join(sets, ", "), b.clause(), agentDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, b.args...)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// DeleteAgent removes an agent definition matching id and access scope.
|
||||
func (r *DefinitionsRepo) DeleteAgent(ctx context.Context, ident authctx.Identity, id string) (int64, error) {
|
||||
q := `DELETE FROM agent_definitions
|
||||
WHERE id = $1::uuid
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))`
|
||||
|
||||
tag, err := r.db.Exec(ctx, q, id, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return 0, translate(err)
|
||||
}
|
||||
return tag.RowsAffected(), nil
|
||||
}
|
||||
|
||||
/* ── Skills ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
// ListSkills lists skill definitions matching the criteria with tenant isolation.
|
||||
func (r *DefinitionsRepo) ListSkills(ctx context.Context, ident authctx.Identity, p DefinitionListParams) ([]domain.Record, int, error) {
|
||||
b := &builder{}
|
||||
b.where = append(b.where, fmt.Sprintf("org_id = %s::uuid", b.add(ident.OrgID)))
|
||||
|
||||
switch p.Visibility {
|
||||
case "personal":
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'personal' AND owner_user_id = %s::uuid)", b.add(ident.UserID)))
|
||||
case "organization":
|
||||
b.where = append(b.where, "visibility = 'organization'")
|
||||
default:
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = %s::uuid))", b.add(ident.UserID)))
|
||||
}
|
||||
|
||||
if p.Status != "" {
|
||||
b.where = append(b.where, fmt.Sprintf("status = %s::text", b.add(p.Status)))
|
||||
}
|
||||
if p.DefinitionID != "" {
|
||||
b.where = append(b.where, fmt.Sprintf("definition_id = %s::text", b.add(p.DefinitionID)))
|
||||
}
|
||||
|
||||
countSQL := "SELECT count(*)::int FROM skill_definitions" + b.clause()
|
||||
var total int
|
||||
if err := r.db.QueryRow(ctx, countSQL, b.args...).Scan(&total); err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
|
||||
sortCol := "created_date"
|
||||
switch p.Sort {
|
||||
case "created_date", "updated_date", "name", "definition_id", "status":
|
||||
sortCol = p.Sort
|
||||
}
|
||||
dir := "ASC"
|
||||
if p.Desc {
|
||||
dir = "DESC"
|
||||
}
|
||||
orderClause := fmt.Sprintf(" ORDER BY %s %s, id %s", sortCol, dir, dir)
|
||||
|
||||
limit := p.Limit
|
||||
if limit <= 0 {
|
||||
limit = 100
|
||||
}
|
||||
offset := p.Offset
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
limitClause := fmt.Sprintf(" LIMIT %s OFFSET %s", b.add(limit), b.add(offset))
|
||||
querySQL := "SELECT " + skillDefinitionColumns + " FROM skill_definitions" + b.clause() + orderClause + limitClause
|
||||
|
||||
rows, err := r.db.Query(ctx, querySQL, b.args...)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
return records, total, nil
|
||||
}
|
||||
|
||||
// GetSkill retrieves one skill definition by id within the caller's tenant and ownership scope.
|
||||
func (r *DefinitionsRepo) GetSkill(ctx context.Context, ident authctx.Identity, id string) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`SELECT %s FROM skill_definitions
|
||||
WHERE id = $1::uuid
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))
|
||||
LIMIT 1`,
|
||||
skillDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, id, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// GetSkillByDefinitionID retrieves a skill definition by definition_id within the caller's scope,
|
||||
// giving personal definitions precedence over organization definitions when shadowed.
|
||||
func (r *DefinitionsRepo) GetSkillByDefinitionID(ctx context.Context, ident authctx.Identity, defID string) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`SELECT %s FROM skill_definitions
|
||||
WHERE definition_id = $1::text
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))
|
||||
ORDER BY CASE WHEN visibility = 'personal' THEN 1 ELSE 2 END
|
||||
LIMIT 1`,
|
||||
skillDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, defID, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// InsertSkill writes a skill definition and returns the stored row.
|
||||
func (r *DefinitionsRepo) InsertSkill(ctx context.Context, ident authctx.Identity, in SkillInsertInput) (domain.Record, error) {
|
||||
q := fmt.Sprintf(
|
||||
`INSERT INTO skill_definitions (
|
||||
definition_id, org_id, visibility, owner_user_id, created_by,
|
||||
markdown, status, name, description, pages
|
||||
) VALUES (
|
||||
$1::text, $2::uuid, $3::text, $4::uuid, $5::uuid,
|
||||
$6::text, $7::text, $8::text, $9::text, $10::text[]
|
||||
) RETURNING %s`, skillDefinitionColumns)
|
||||
|
||||
pages := in.Pages
|
||||
if pages == nil {
|
||||
pages = []string{}
|
||||
}
|
||||
|
||||
rows, err := r.db.Query(ctx, q,
|
||||
in.DefinitionID, in.OrgID, in.Visibility, in.OwnerUserID, in.CreatedBy,
|
||||
in.Markdown, in.Status, in.Name, in.Description, pages)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, fmt.Errorf("insert skill_definitions returned no row")
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// UpdateSkill updates fields on a skill definition and returns the full updated record.
|
||||
func (r *DefinitionsRepo) UpdateSkill(ctx context.Context, ident authctx.Identity, id string, in SkillUpdateInput) (domain.Record, error) {
|
||||
b := &builder{}
|
||||
sets := []string{"updated_date = now()"}
|
||||
|
||||
if in.DefinitionID != nil {
|
||||
sets = append(sets, fmt.Sprintf("definition_id = %s::text", b.add(*in.DefinitionID)))
|
||||
}
|
||||
if in.Name != nil {
|
||||
sets = append(sets, fmt.Sprintf("name = %s::text", b.add(*in.Name)))
|
||||
}
|
||||
if in.Description != nil {
|
||||
sets = append(sets, fmt.Sprintf("description = %s::text", b.add(*in.Description)))
|
||||
}
|
||||
if in.Status != nil {
|
||||
sets = append(sets, fmt.Sprintf("status = %s::text", b.add(*in.Status)))
|
||||
}
|
||||
if in.Pages != nil {
|
||||
sets = append(sets, fmt.Sprintf("pages = %s::text[]", b.add(in.Pages)))
|
||||
}
|
||||
if in.Markdown != nil {
|
||||
sets = append(sets, fmt.Sprintf("markdown = %s::text", b.add(*in.Markdown)))
|
||||
}
|
||||
if in.Visibility != nil {
|
||||
sets = append(sets, fmt.Sprintf("visibility = %s::text", b.add(*in.Visibility)))
|
||||
sets = append(sets, fmt.Sprintf("owner_user_id = %s::uuid", b.add(in.OwnerUserID)))
|
||||
}
|
||||
|
||||
b.where = append(b.where, fmt.Sprintf("id = %s::uuid", b.add(id)))
|
||||
b.where = append(b.where, fmt.Sprintf("org_id = %s::uuid", b.add(ident.OrgID)))
|
||||
b.where = append(b.where, fmt.Sprintf("(visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = %s::uuid))", b.add(ident.UserID)))
|
||||
|
||||
q := fmt.Sprintf("UPDATE skill_definitions SET %s%s RETURNING %s",
|
||||
strings.Join(sets, ", "), b.clause(), skillDefinitionColumns)
|
||||
|
||||
rows, err := r.db.Query(ctx, q, b.args...)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
records, err := collect(rows)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
if len(records) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return records[0], nil
|
||||
}
|
||||
|
||||
// DeleteSkill removes a skill definition matching id and access scope.
|
||||
func (r *DefinitionsRepo) DeleteSkill(ctx context.Context, ident authctx.Identity, id string) (int64, error) {
|
||||
q := `DELETE FROM skill_definitions
|
||||
WHERE id = $1::uuid
|
||||
AND org_id = $2::uuid
|
||||
AND (visibility = 'organization' OR (visibility = 'personal' AND owner_user_id = $3::uuid))`
|
||||
|
||||
tag, err := r.db.Exec(ctx, q, id, ident.OrgID, ident.UserID)
|
||||
if err != nil {
|
||||
return 0, translate(err)
|
||||
}
|
||||
return tag.RowsAffected(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user