comments

package
v1.19.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 29, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package comments owns the goldmark+bluemonday pipeline that turns operator-authored markdown into the sanitized HTML cached in the comments table. Renderer instances are safe for concurrent use; the package exposes a default instance via Default() for the daemon's hot path.

Allowed markdown subset (per v1.8 issue scope):

  • bold + italic + inline code + code fences
  • bulleted + numbered lists
  • links (with rel="noopener noreferrer" + target="_blank")
  • headings (h1–h3)
  • blockquotes

Disallowed:

  • raw HTML (stripped by bluemonday before rendering)
  • images (potential pixel-tracking, off-domain fetches)
  • tables / footnotes (kept out at v1.8; revisit if operators ask)
  • scripts / styles (stripped by bluemonday)

Index

Constants

View Source
const (
	SourceUI       = "ui"
	SourceSlack    = "slack"
	SourceTeams    = "teams"
	SourceGitHubPR = "github-pr"
	SourceJira     = "jira"
	SourceLinear   = "linear"
)

Source values flow into the comments.source column. The allowed set matches the CHECK constraint in migration 0008.

Variables

View Source
var ErrEmptyBody = errors.New("comment body is empty")

ErrEmptyBody is returned by Add and Edit when the markdown source is empty after trimming. The UI surfaces this as a flash.

Functions

func ExtractMentions

func ExtractMentions(body string) []string

ExtractMentions returns the unique @handles referenced in body, preserving insertion order. The handle is everything between the "@" and the first non-handle character — typically a username, email local-part, or "team-<slug>".

The pattern is conservative: a token starting with a dot or dash is dropped, and minimum length is 2 characters so common false positives ("@", "@.", "@@") drop out.

func SetClock

func SetClock(fn func() time.Time) func() time.Time

SetClock overrides the clock used by the package; returns the previous value so callers can restore it. Used by tests.

Types

type AddOptions

type AddOptions struct {
	Source     string
	ExternalID string
	CreatedAt  *time.Time
}

AddOptions is the optional non-UI metadata bag for Add. The UI path leaves this zero-valued; sink ingest paths populate Source + ExternalID so two-way sync can dedup re-delivery.

type Comment

type Comment struct {
	ID                 string
	FindingFingerprint string
	AuthorID           string
	AuthorEmail        string
	AuthorDisplayName  string
	Body               string
	BodyHTML           string
	CreatedAt          time.Time
	UpdatedAt          time.Time
	EditedAt           *time.Time
	Source             string
	ExternalID         string
}

Comment is the per-row shape held by the daemon and exposed to the UI templates. AuthorEmail / AuthorDisplayName resolve out of the users table via JOIN at read time; comments table itself only stores the foreign key.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer wraps goldmark + bluemonday with a fixed allowlist policy. Reusable across requests; no per-render state escapes.

func Default

func Default() *Renderer

Default returns the package-global renderer, lazily constructed on first use. Daemon handlers should call this rather than allocating their own Renderer per request.

func New

func New() *Renderer

New returns a Renderer with the v1.8 allowlist policy applied.

func (*Renderer) Render

func (r *Renderer) Render(src string) (string, error)

Render converts markdown source into sanitized HTML. The output is safe to embed in an HTML response without further escaping.

Empty input returns an empty string; invalid markdown still renders (goldmark is lenient) — bluemonday is the final gate that strips anything outside the allowlist.

type Repo

type Repo struct {
	// contains filtered or unexported fields
}

Repo is the persistence layer over the v1.8 comments table.

func NewRepo

func NewRepo(s *store.Store) *Repo

NewRepo wires a Repo against the given store. The default Renderer is used for markdown→HTML conversion.

func (*Repo) Add

func (r *Repo) Add(ctx context.Context, fingerprint, authorID, body string, opts AddOptions) (string, error)

Add inserts a comment authored by `authorID` against the finding identified by `fingerprint`. Returns the new row's ID.

`body` is the markdown source as the operator typed it. The renderer produces body_html in the same write so list rendering stays cheap. ErrEmptyBody is returned if `body` trims to "".

func (*Repo) ByExternalID

func (r *Repo) ByExternalID(ctx context.Context, source, externalID string) (Comment, error)

ByExternalID resolves a comment via its sink-native identifier. Used by inbound two-way sync paths to detect re-delivery.

func (*Repo) ByID

func (r *Repo) ByID(ctx context.Context, id string) (Comment, error)

ByID loads a single comment by primary key.

func (*Repo) CountByFingerprint

func (r *Repo) CountByFingerprint(ctx context.Context, fingerprint string) (int, error)

CountByFingerprint returns how many comments are attached to the fingerprint. Drives the badge on the finding row.

func (*Repo) Delete

func (r *Repo) Delete(ctx context.Context, id string) error

Delete removes a comment. Returns sql.ErrNoRows if no row matched.

func (*Repo) Edit

func (r *Repo) Edit(ctx context.Context, id, body string) error

Edit rewrites the comment body. EditedAt is stamped on first edit; subsequent edits keep the original EditedAt? No — every edit updates EditedAt so "edited Nm ago" tracks the latest write. Returns ErrEmptyBody for empty trimmed bodies.

func (*Repo) ListByFingerprint

func (r *Repo) ListByFingerprint(ctx context.Context, fingerprint string) ([]Comment, error)

ListByFingerprint returns the ordered conversation attached to the given fingerprint, oldest-first. The UI renders bottom-to-top or top-to-bottom from the same slice.

func (*Repo) WithRenderer

func (r *Repo) WithRenderer(rr *Renderer) *Repo

WithRenderer overrides the markdown renderer. Useful for tests that want to assert specific HTML output or skip rendering.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL