summaryrefslogtreecommitdiff
path: root/internal/api/roles.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/api/roles.go')
-rw-r--r--internal/api/roles.go121
1 files changed, 121 insertions, 0 deletions
diff --git a/internal/api/roles.go b/internal/api/roles.go
new file mode 100644
index 0000000..700bec4
--- /dev/null
+++ b/internal/api/roles.go
@@ -0,0 +1,121 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/thepeterstone/claudomator/internal/role"
+ "github.com/thepeterstone/claudomator/internal/storage"
+)
+
+// roleVersionView is the JSON shape returned for a single role_configs
+// version — decodes the stored config_json back into a role.RoleConfig so
+// clients get structured fields rather than an opaque string.
+type roleVersionView struct {
+ ID string `json:"id"`
+ Role string `json:"role"`
+ Version int `json:"version"`
+ Status string `json:"status"`
+ Config role.RoleConfig `json:"config"`
+ CreatedAt string `json:"created_at"`
+ ActivatedAt string `json:"activated_at,omitempty"`
+ RetiredAt string `json:"retired_at,omitempty"`
+ ProposedBy string `json:"proposed_by,omitempty"`
+}
+
+// handleCreateRoleVersion handles POST /api/roles/{role}/versions. The
+// request body is a role.RoleConfig (config_json shape, plus an optional
+// proposed_by field); a new draft version is created with the next version
+// number for that role. Mirrors handleCreateProject's shape (see
+// internal/api/projects.go).
+func (s *Server) handleCreateRoleVersion(w http.ResponseWriter, r *http.Request) {
+ roleName := r.PathValue("role")
+ if roleName == "" {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "role is required"})
+ return
+ }
+
+ var input struct {
+ role.RoleConfig
+ ProposedBy string `json:"proposed_by"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON: " + err.Error()})
+ return
+ }
+ input.RoleConfig.Role = roleName
+
+ proposedBy := input.ProposedBy
+ if proposedBy == "" {
+ proposedBy = "human"
+ }
+
+ configJSON, err := json.Marshal(input.RoleConfig)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+
+ created, err := s.store.CreateRoleConfig(roleName, string(configJSON), proposedBy)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ writeJSON(w, http.StatusCreated, toRoleVersionView(created))
+}
+
+// handleListRoleVersions handles GET /api/roles/{role}/versions.
+func (s *Server) handleListRoleVersions(w http.ResponseWriter, r *http.Request) {
+ roleName := r.PathValue("role")
+ rows, err := s.store.ListRoleConfigVersions(roleName)
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ views := make([]*roleVersionView, 0, len(rows))
+ for _, row := range rows {
+ views = append(views, toRoleVersionView(row))
+ }
+ writeJSON(w, http.StatusOK, views)
+}
+
+// handleActivateRoleVersion handles POST /api/roles/{role}/activate?version=N.
+func (s *Server) handleActivateRoleVersion(w http.ResponseWriter, r *http.Request) {
+ roleName := r.PathValue("role")
+ versionStr := r.URL.Query().Get("version")
+ version, err := strconv.Atoi(versionStr)
+ if err != nil || version < 1 {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid or missing version query param"})
+ return
+ }
+ if err := s.store.ActivateRoleConfigVersion(roleName, version); err != nil {
+ writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
+ return
+ }
+ writeJSON(w, http.StatusOK, map[string]interface{}{
+ "message": "role config version activated",
+ "role": roleName,
+ "version": version,
+ })
+}
+
+func toRoleVersionView(row *storage.RoleConfigRow) *roleVersionView {
+ v := &roleVersionView{
+ ID: row.ID,
+ Role: row.Role,
+ Version: row.Version,
+ Status: row.Status,
+ CreatedAt: row.CreatedAt.Format(time.RFC3339),
+ ProposedBy: row.ProposedBy,
+ }
+ if row.ActivatedAt != nil {
+ v.ActivatedAt = row.ActivatedAt.Format(time.RFC3339)
+ }
+ if row.RetiredAt != nil {
+ v.RetiredAt = row.RetiredAt.Format(time.RFC3339)
+ }
+ _ = json.Unmarshal([]byte(row.ConfigJSON), &v.Config)
+ return v
+}