package handler import ( "context" "encoding/json" "github.com/jackc/pgx/v5/pgtype" skillpkg "github.com/multica-ai/multica/server/internal/skill" db "github.com/multica-ai/multica/server/pkg/db/generated" ) type skillCreateInput struct { WorkspaceID pgtype.UUID CreatorID pgtype.UUID Name string Description string Content string Config any Files []CreateSkillFileRequest } // createSkillWithFilesInTx writes a skill plus its supporting files using the // provided sqlc Queries handle, which must already be bound to an open // transaction. Callers compose skill creation with other writes (e.g. agent // template materialization) inside one outer transaction. For standalone // skill creation, prefer createSkillWithFiles, which manages its own tx. func createSkillWithFilesInTx(ctx context.Context, qtx *db.Queries, input skillCreateInput) (SkillWithFilesResponse, error) { config, err := json.Marshal(input.Config) if err != nil { return SkillWithFilesResponse{}, err } if input.Config == nil { config = []byte("{}") } skill, err := qtx.CreateSkill(ctx, db.CreateSkillParams{ WorkspaceID: input.WorkspaceID, Name: sanitizeNullBytes(input.Name), Description: sanitizeNullBytes(input.Description), Content: sanitizeNullBytes(input.Content), Config: config, CreatedBy: input.CreatorID, }) if err != nil { return SkillWithFilesResponse{}, err } fileResps := make([]SkillFileResponse, 0, len(input.Files)) for _, f := range input.Files { // SKILL.md is reserved for the primary skill content (skill.Content). // Supporting files must carry additional assets, not duplicate the main file. if skillpkg.IsReservedContentPath(f.Path) { continue } sf, err := qtx.UpsertSkillFile(ctx, db.UpsertSkillFileParams{ SkillID: skill.ID, Path: sanitizeNullBytes(f.Path), Content: sanitizeNullBytes(f.Content), }) if err != nil { return SkillWithFilesResponse{}, err } fileResps = append(fileResps, skillFileToResponse(sf)) } return SkillWithFilesResponse{ SkillResponse: skillToResponse(skill), Files: fileResps, }, nil } func (h *Handler) createSkillWithFiles(ctx context.Context, input skillCreateInput) (SkillWithFilesResponse, error) { tx, err := h.TxStarter.Begin(ctx) if err != nil { return SkillWithFilesResponse{}, err } defer tx.Rollback(ctx) qtx := h.Queries.WithTx(tx) result, err := createSkillWithFilesInTx(ctx, qtx, input) if err != nil { return SkillWithFilesResponse{}, err } if err := tx.Commit(ctx); err != nil { return SkillWithFilesResponse{}, err } return result, nil }