Files

170 lines
4.6 KiB
Go

package main
import (
"database/sql"
"time"
"github.com/google/uuid"
)
type DodItem struct {
ID string `json:"id"`
RunID string `json:"run_id"`
ItemKey string `json:"item_key"`
Kind string `json:"kind"`
Expected string `json:"expected"`
Required bool `json:"required"`
Status string `json:"status"`
CreatedAt int64 `json:"created_at"`
}
type DodEvidence struct {
ID string `json:"id"`
DodItemID string `json:"dod_item_id"`
Kind string `json:"kind"`
PayloadPath *string `json:"payload_path,omitempty"`
PayloadURL *string `json:"payload_url,omitempty"`
PayloadText *string `json:"payload_text,omitempty"`
AttachedAt int64 `json:"attached_at"`
ValidatedAt *int64 `json:"validated_at,omitempty"`
ValidatedBy *string `json:"validated_by,omitempty"`
}
func createDodItem(db *sql.DB, runID, key, kind, expected string, required bool) (DodItem, error) {
id := "dod_" + uuid.New().String()[:12]
now := time.Now().Unix()
reqInt := 0
if required {
reqInt = 1
}
_, err := db.Exec(`INSERT INTO dod_items
(id, run_id, item_key, kind, expected, required, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
id, runID, key, kind, expected, reqInt, now)
if err != nil {
return DodItem{}, err
}
return DodItem{
ID: id, RunID: runID, ItemKey: key, Kind: kind, Expected: expected,
Required: required, Status: "pending", CreatedAt: now,
}, nil
}
func listDodItems(db *sql.DB, runID string) ([]DodItem, error) {
rows, err := db.Query(`SELECT id, run_id, item_key, kind, expected, required, status, created_at
FROM dod_items WHERE run_id = ? ORDER BY created_at`, runID)
if err != nil {
return nil, err
}
defer rows.Close()
out := []DodItem{}
for rows.Next() {
var it DodItem
var reqInt int
if err := rows.Scan(&it.ID, &it.RunID, &it.ItemKey, &it.Kind, &it.Expected, &reqInt, &it.Status, &it.CreatedAt); err != nil {
return nil, err
}
it.Required = reqInt != 0
out = append(out, it)
}
return out, nil
}
func attachEvidence(db *sql.DB, itemID, kind string, path, url, text *string) (DodEvidence, error) {
id := "ev_" + uuid.New().String()[:12]
now := time.Now().Unix()
_, err := db.Exec(`INSERT INTO dod_evidence
(id, dod_item_id, kind, payload_path, payload_url, payload_text, attached_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
id, itemID, kind, nullStr(path), nullStr(url), nullStr(text), now)
if err != nil {
return DodEvidence{}, err
}
// Auto-bump item status to 'done' on first evidence
_, _ = db.Exec(`UPDATE dod_items SET status = 'done' WHERE id = ? AND status = 'pending'`, itemID)
return DodEvidence{
ID: id, DodItemID: itemID, Kind: kind,
PayloadPath: path, PayloadURL: url, PayloadText: text,
AttachedAt: now,
}, nil
}
func validateEvidence(db *sql.DB, evID, validatedBy string) error {
now := time.Now().Unix()
res, err := db.Exec(`UPDATE dod_evidence
SET validated_at = ?, validated_by = ? WHERE id = ?`, now, validatedBy, evID)
if err != nil {
return err
}
n, _ := res.RowsAffected()
if n == 0 {
return sql.ErrNoRows
}
// Bump item status to validated
_, _ = db.Exec(`UPDATE dod_items SET status = 'validated'
WHERE id = (SELECT dod_item_id FROM dod_evidence WHERE id = ?)`, evID)
return nil
}
func listEvidence(db *sql.DB, itemID string) ([]DodEvidence, error) {
rows, err := db.Query(`SELECT id, dod_item_id, kind, payload_path, payload_url, payload_text,
attached_at, validated_at, validated_by
FROM dod_evidence WHERE dod_item_id = ? ORDER BY attached_at`, itemID)
if err != nil {
return nil, err
}
defer rows.Close()
out := []DodEvidence{}
for rows.Next() {
var ev DodEvidence
var path, url, text, valBy sql.NullString
var valAt sql.NullInt64
if err := rows.Scan(&ev.ID, &ev.DodItemID, &ev.Kind, &path, &url, &text,
&ev.AttachedAt, &valAt, &valBy); err != nil {
return nil, err
}
if path.Valid {
s := path.String
ev.PayloadPath = &s
}
if url.Valid {
s := url.String
ev.PayloadURL = &s
}
if text.Valid {
s := text.String
ev.PayloadText = &s
}
if valAt.Valid {
v := valAt.Int64
ev.ValidatedAt = &v
}
if valBy.Valid {
s := valBy.String
ev.ValidatedBy = &s
}
out = append(out, ev)
}
return out, nil
}
// dodGateOpen returns true when every required item has at least one validated evidence.
func dodGateOpen(db *sql.DB, runID string) (bool, error) {
row := db.QueryRow(`
SELECT COUNT(*) FROM dod_items
WHERE run_id = ? AND required = 1
AND status != 'validated'`, runID)
var n int
if err := row.Scan(&n); err != nil {
return false, err
}
return n == 0, nil
}
func nullStr(p *string) interface{} {
if p == nil {
return nil
}
return *p
}