package datastore import ( "context" "crypto/sha256" "encoding/hex" "fmt" "time" "epigas.gitea.cloud/RiskRancher/core/pkg/domain" ) func (s *SQLiteStore) GetTickets(ctx context.Context) ([]domain.Ticket, error) { rows, err := s.DB.QueryContext(ctx, "SELECT id, title, severity, status FROM tickets LIMIT 100") if err != nil { return nil, err } defer rows.Close() var tickets []domain.Ticket for rows.Next() { var t domain.Ticket rows.Scan(&t.ID, &t.Title, &t.Severity, &t.Status) tickets = append(tickets, t) } return tickets, nil } func (s *SQLiteStore) CreateTicket(ctx context.Context, t *domain.Ticket) error { if t.Status == "" { t.Status = "Waiting to be Triaged" } if t.Domain == "" { t.Domain = "Vulnerability" } if t.Source == "" { t.Source = "Manual" } if t.AssetIdentifier == "" { t.AssetIdentifier = "Default" } rawHash := fmt.Sprintf("%s-%s-%s-%s", t.Source, t.AssetIdentifier, t.Title, t.Severity) hashBytes := sha256.Sum256([]byte(rawHash)) t.DedupeHash = hex.EncodeToString(hashBytes[:]) query := ` INSERT INTO tickets ( domain, source, asset_identifier, title, description, recommended_remediation, severity, status, dedupe_hash, triage_due_date, remediation_due_date, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, DATETIME('now', '+3 days'), DATETIME('now', '+14 days'), CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ` res, err := s.DB.ExecContext(ctx, query, t.Domain, t.Source, t.AssetIdentifier, t.Title, t.Description, t.RecommendedRemediation, t.Severity, t.Status, t.DedupeHash, ) if err != nil { return err } id, _ := res.LastInsertId() t.ID = int(id) return nil } // UpdateTicketInline handles a single UI edit and updates the flattened comment tracking func (s *SQLiteStore) UpdateTicketInline(ctx context.Context, ticketID int, severity, description, remediation, comment, actor, status, assignee string) error { query := ` UPDATE tickets SET severity = ?, description = ?, recommended_remediation = ?, status = ?, assignee = ?, latest_comment = CASE WHEN ? != '' THEN ? ELSE latest_comment END, updated_at = CURRENT_TIMESTAMP WHERE id = ?` formattedComment := "" if comment != "" { formattedComment = "[" + actor + "] " + comment } _, err := s.DB.ExecContext(ctx, query, severity, description, remediation, status, assignee, formattedComment, formattedComment, ticketID) return err } // RejectTicketFromWrangler puts a ticket back into the Holding Pen func (s *SQLiteStore) RejectTicketFromWrangler(ctx context.Context, ticketIDs []int, reason, comment string) error { tx, err := s.DB.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() for _, id := range ticketIDs { fullComment := "[Wrangler Reject: " + reason + "] " + comment _, err := tx.ExecContext(ctx, "UPDATE tickets SET status = 'Returned to Security', assignee = 'Unassigned', latest_comment = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", fullComment, id) if err != nil { return err } } return tx.Commit() } func (s *SQLiteStore) GetTicketByID(ctx context.Context, id int) (domain.Ticket, error) { var t domain.Ticket var triageDue, remDue, created, updated string var patchedAt *string query := `SELECT id, domain, source, asset_identifier, title, description, recommended_remediation, severity, status, dedupe_hash, triage_due_date, remediation_due_date, created_at, updated_at, patched_at, assignee, latest_comment FROM tickets WHERE id = ?` err := s.DB.QueryRowContext(ctx, query, id).Scan( &t.ID, &t.Domain, &t.Source, &t.AssetIdentifier, &t.Title, &t.Description, &t.RecommendedRemediation, &t.Severity, &t.Status, &t.DedupeHash, &triageDue, &remDue, &created, &updated, &patchedAt, &t.Assignee, &t.LatestComment, ) if err != nil { return t, err } t.TriageDueDate, _ = time.Parse(time.RFC3339, triageDue) t.RemediationDueDate, _ = time.Parse(time.RFC3339, remDue) t.CreatedAt, _ = time.Parse(time.RFC3339, created) t.UpdatedAt, _ = time.Parse(time.RFC3339, updated) if patchedAt != nil { pTime, _ := time.Parse(time.RFC3339, *patchedAt) t.PatchedAt = &pTime } return t, nil }