feat(modules): jira scoped a project=DATA + board=33 con seed CLI desde pass

Cambios:
- jiraConfig: nuevo campo BoardID. TestConnection valida que board.location.projectKey
  coincide con ProjectKey declarado. Refuse mismatched scopes so a typo in
  project_key cannot create issues in the wrong project.
- backend/seed_jira.go: subcomando 'kanban seed-jira-data' lee credenciales
  desde pass (jira/anjana/{email,api-token,domain}) e inserta module row con
  kind=jira, project_key=DATA, board_id=33, event_filter sensible. Idempotente
  (upsert por name). status_map vacio por defecto (operator lo edita por UI).
- main.go: wire del nuevo subcomando.

Requiere KANBAN_MODULE_KEY env var para encriptar/desencriptar config. El
servidor que ejecuta el dispatcher debe usar el mismo valor.
This commit is contained in:
egutierrez
2026-05-28 12:56:33 +02:00
parent 65771ebb12
commit ef197236db
3 changed files with 164 additions and 2 deletions
+29 -2
View File
@@ -484,6 +484,7 @@ type jiraConfig struct {
Email string `json:"email"`
APIToken string `json:"api_token"`
ProjectKey string `json:"project_key"`
BoardID int `json:"board_id"`
StatusMap map[string]string `json:"status_map"`
}
@@ -549,7 +550,33 @@ func (h *jiraHandler) TestConnection(ctx context.Context, m Module) (int, error)
return 0, err
}
status, _, err := h.jiraRequest(ctx, c, http.MethodGet, "/rest/api/3/myself", nil)
return status, err
if err != nil {
return status, err
}
// If a board scope is configured, verify the board exists AND lives in
// the declared project. Refuse silently-mismatched configurations so a
// typo in project_key cannot create issues outside the intended board.
if c.BoardID > 0 {
bStatus, body, err := h.jiraRequest(ctx, c, http.MethodGet,
fmt.Sprintf("/rest/agile/1.0/board/%d", c.BoardID), nil)
if err != nil {
return bStatus, fmt.Errorf("board %d lookup: %w", c.BoardID, err)
}
var board struct {
Type string `json:"type"`
Location struct {
ProjectKey string `json:"projectKey"`
} `json:"location"`
}
if err := json.Unmarshal(body, &board); err != nil {
return bStatus, fmt.Errorf("decode board %d: %w", c.BoardID, err)
}
if c.ProjectKey != "" && !strings.EqualFold(board.Location.ProjectKey, c.ProjectKey) {
return 0, fmt.Errorf("board %d belongs to project %q, config declares %q",
c.BoardID, board.Location.ProjectKey, c.ProjectKey)
}
}
return status, nil
}
func (h *jiraHandler) Handle(ctx context.Context, db *DB, m Module, ev Event) (int, error) {
@@ -586,7 +613,7 @@ func (h *jiraHandler) create(ctx context.Context, db *DB, c jiraConfig, ev Event
return h.update(ctx, db, c, ev)
}
if c.ProjectKey == "" {
return 0, fmt.Errorf("project_key required for create")
return 0, fmt.Errorf("project_key required for create (configure module before pushing)")
}
body := map[string]interface{}{
"fields": map[string]interface{}{