Documentation
¶
Overview ¶
Package store owns the SQLite mirror: schema, migrations, transactions, full-text index and the derived fields the source does not provide.
The schema is a public contract documented in specs/000-product/data-model.md — agents query this database directly, so a column change belongs in that document before it belongs here.
Index ¶
- Constants
- Variables
- func EnsureLocal(mirrorPath string) error
- func LocalPath(mirrorPath string) string
- func Now() string
- func OpenReadOnly(path string) (*sql.DB, error)
- type APIUsageDay
- type APIUsageDelta
- type APIUsageSum
- type APIUsageSummary
- type Attachment
- type AuthorComment
- type Batch
- type ChangeEntry
- type Comment
- type CommentsByAuthorResult
- type DB
- func (db *DB) APIUsage(ctx context.Context, days int) ([]APIUsageDay, error)
- func (db *DB) APIUsageSummary(ctx context.Context) (APIUsageSummary, error)
- func (db *DB) AddAPIUsage(ctx context.Context, day string, u APIUsageDelta) error
- func (db *DB) AppendSyncRun(ctx context.Context, sourceID string, r SyncRun) error
- func (db *DB) AttachmentBelongs(ctx context.Context, issueKey, attachmentID string) (bool, error)
- func (db *DB) Close() error
- func (db *DB) CommentsByAuthor(ctx context.Context, authorID string, limit int) (CommentsByAuthorResult, error)
- func (db *DB) ComputeFieldUsage(ctx context.Context, aliases []string) ([]FieldUsageRow, error)
- func (db *DB) ConfluenceSpaceWatermarks(ctx context.Context, sourceID string) (map[string]string, error)
- func (db *DB) DeleteItems(ctx context.Context, sourceID string, keys []string) (int, error)
- func (db *DB) DeleteSavedView(ctx context.Context, id string) error
- func (db *DB) DeletedKeysSince(ctx context.Context, since string) ([]string, error)
- func (db *DB) Detail(ctx context.Context, key string) (*Detail, error)
- func (db *DB) DistinctCount(ctx context.Context, table, column string) (int, error)
- func (db *DB) EnrichmentsByKind(ctx context.Context, kind string) (map[string]json.RawMessage, error)
- func (db *DB) EnrichmentsFor(ctx context.Context, key string) (map[string]json.RawMessage, error)
- func (db *DB) Favorites(ctx context.Context) ([]string, error)
- func (db *DB) Feed(ctx context.Context, opts FeedOpts) (FeedResult, error)
- func (db *DB) FieldUsage(ctx context.Context) ([]FieldUsageRow, error)
- func (db *DB) FreshenSyncClock(ctx context.Context) error
- func (db *DB) HasCustomFieldKeysInRaw(ctx context.Context) (bool, error)
- func (db *DB) HasSource(ctx context.Context, id string) (bool, error)
- func (db *DB) History(ctx context.Context, opts HistoryOpts) (HistoryPage, error)
- func (db *DB) IssueLites(ctx context.Context) ([]IssueLite, error)
- func (db *DB) IssueLitesByKeys(ctx context.Context, keys []string) ([]IssueLite, error)
- func (db *DB) IssueLitesSince(ctx context.Context, since string) ([]IssueLite, error)
- func (db *DB) MarkFeedRead(ctx context.Context, opts MarkFeedReadOpts) (MarkFeedReadResult, error)
- func (db *DB) PageDetail(ctx context.Context, key string) (*PageDetail, error)
- func (db *DB) PageLites(ctx context.Context) ([]PageLite, error)
- func (db *DB) PageStamps(ctx context.Context, sourceID, spaceKey string) (map[string]PageStamp, error)
- func (db *DB) PruneConfluenceSpaces(ctx context.Context, sourceID string, keepKeys []string) (int, error)
- func (db *DB) PruneLocalHistory(ctx context.Context) error
- func (db *DB) PutSavedView(ctx context.Context, v SavedView) error
- func (db *DB) RecordSearch(ctx context.Context, query string, resultCount int, ...) (Search, error)
- func (db *DB) RecordSync(ctx context.Context, sourceID string, r SyncResult) error
- func (db *DB) RecordVisit(ctx context.Context, kind, key string) (Visit, error)
- func (db *DB) ReingestCustom(ctx context.Context, specs []fields.SpecIDs, bodyFieldIDs []string) (int, error)
- func (db *DB) ReplaceFieldUsage(ctx context.Context, rows []FieldUsageRow) error
- func (db *DB) ReplaceSourceQueries(ctx context.Context, sourceID string, queries []SourceQuery) error
- func (db *DB) SavedViews(ctx context.Context) ([]SavedView, error)
- func (db *DB) ScanFieldFill(ctx context.Context, ...) error
- func (db *DB) SchemaVersion() int
- func (db *DB) Search(ctx context.Context, query string, limit int) (SearchResult, error)
- func (db *DB) SetFavorite(ctx context.Context, key string, on bool) error
- func (db *DB) SetLastNotifiedAt(ctx context.Context, sourceID, at string) error
- func (db *DB) SetSearchOpened(ctx context.Context, id int64, kind, key string) (Search, error)
- func (db *DB) SetSpaceWatermark(ctx context.Context, sourceID, key, watermark string) error
- func (db *DB) SetWatch(ctx context.Context, key string, on bool) error
- func (db *DB) SourceQueries(ctx context.Context, sourceID string) ([]SourceQuery, error)
- func (db *DB) SyncRuns(ctx context.Context, sourceID string, limit int) ([]SyncRun, error)
- func (db *DB) SyncState(ctx context.Context, sourceID string) (SyncState, error)
- func (db *DB) TableCount(ctx context.Context, table string) (int, error)
- func (db *DB) UpsertIssues(ctx context.Context, b Batch) (int, error)
- func (db *DB) UpsertPages(ctx context.Context, records []PageRecord) (int, error)
- func (db *DB) UpsertSource(ctx context.Context, s Source) error
- func (db *DB) UpsertSpaces(ctx context.Context, sourceID string, rows []SpaceRow) error
- func (db *DB) Watches(ctx context.Context) ([]string, error)
- type DeriveInput
- type Derived
- type Detail
- type DetailAttachment
- type DetailChange
- type DetailComment
- type DetailLink
- type FeedFocus
- type FeedIdentity
- type FeedItem
- type FeedOpts
- type FeedResult
- type FeedUnreadCounts
- type FieldUsageRow
- type HistoryItem
- type HistoryOpts
- type HistoryPage
- type Issue
- type IssueLite
- type IssueRecord
- type Item
- type ItemRef
- type Link
- type MarkFeedReadOpts
- type MarkFeedReadResult
- type Page
- type PageComment
- type PageDetail
- type PageLite
- type PageRecord
- type PageStamp
- type SavedView
- type Search
- type SearchMatch
- type SearchResult
- type Source
- type SourceQuery
- type SpaceRow
- type SyncResult
- type SyncRun
- type SyncState
- type Visit
Constants ¶
const ( FeedWindowDays = 30 FeedDefaultLimit = 80 FeedMaxLimit = 200 )
Feed window and default page size match the client contract (web/src/lib/api.ts getFeed / FeedFocus).
const ( VisitKindIssue = "issue" VisitKindPage = "page" )
VisitKind is visits.kind / searches.opened_kind: "issue" or "page".
const ( CommentsByAuthorDefaultLimit = 50 CommentsByAuthorMaxLimit = 200 )
People-axis comment list defaults (GET people/{author_id}/comments/).
const CategoryDone = "done"
CategoryDone is the only status category with meaning to the derived rules. Every rule keys on the category, never on a status name: the internal tool this was extracted from matched "Reopened" / "다시 열림" and broke on any other site or account language (contracts/sync.md, "Localization hazard").
const LocalRetention = 180 * 24 * time.Hour
LocalRetention is how long raw visit/search events are kept. Counts older than this window are not rolled up — derive from whatever is still here.
Variables ¶
var ErrNotFound = errors.New("not found")
ErrNotFound means a single-row lookup (Detail, etc.) found no matching key. Callers map it to 404 / "not in the mirror"; store code still uses sql.ErrNoRows internally and wraps at the package boundary.
Functions ¶
func EnsureLocal ¶ added in v0.13.0
EnsureLocal creates local.db next to the mirror if needed and migrates it. Failures are returned; callers of Open log them and still open the mirror.
func Now ¶
func Now() string
Now is the timestamp format every column the store itself writes uses. The server hands the same value to clients as the `delta` cursor, so it must come from here. Milliseconds are not decoration: a whole-second cursor would drop a row written in the same second the cursor was taken.
Types ¶
type APIUsageDay ¶
type APIUsageDay struct {
Day string `json:"day"`
Requests int64 `json:"requests"`
Throttled int64 `json:"throttled"`
ServerErrors int64 `json:"server_errors"`
Retries int64 `json:"retries"`
WaitMS int64 `json:"wait_ms"`
LastThrottledAt *string `json:"last_throttled_at,omitempty"`
}
APIUsageDay is one row of daily accumulated outbound API volume.
type APIUsageDelta ¶
type APIUsageDelta struct {
Requests int64
Throttled int64
ServerErrors int64
Retries int64
WaitMS int64
LastThrottledAt string // RFC3339 UTC, empty if unknown this flush
}
APIUsageDelta is a non-negative increment to accumulate into one UTC day. Numbers only — this package must not import jira (layering).
type APIUsageSum ¶
type APIUsageSum struct {
Requests int64 `json:"requests"`
Throttled int64 `json:"throttled"`
ServerErrors int64 `json:"server_errors"`
Retries int64 `json:"retries"`
WaitMS int64 `json:"wait_ms"`
LastThrottledAt *string `json:"last_throttled_at,omitempty"`
}
APIUsageSum is a multi-day total (no day key).
type APIUsageSummary ¶
type APIUsageSummary struct {
Today APIUsageDay `json:"today"`
Last7Days APIUsageSum `json:"last_7_days"`
}
APIUsageSummary is the shape status --json and GET settings/ runtime expose: today's row plus a 7-day rollup of our outbound call volume.
type Attachment ¶
type Attachment struct {
ID string
ExternalID string
Filename string
MimeType string
Size int64
Author string
AuthorID string
CreatedAt string
}
Attachment is metadata only. Bytes are proxied on demand, never mirrored.
type AuthorComment ¶
type AuthorComment struct {
Key string `json:"key"`
Kind string `json:"kind"` // "issue" | "page"
Title string `json:"title"`
Snippet string `json:"snippet"`
CreatedAt string `json:"created_at"`
}
AuthorComment is one row in GET people/{author_id}/comments/.
type Batch ¶
type Batch struct {
Categories map[string]string // status id -> status category
Priorities []string // priority display names, most urgent first
Records []IssueRecord
// Force rewrites rows whose updated_at is unchanged. Off, an unchanged row
// is skipped entirely, which is what keeps an incremental re-run from
// bumping sync_state.version.
Force bool
}
Batch is one page of sync output. Categories and Priorities come from the site's own metadata endpoints (which are not localized) and feed the derived field rules.
type ChangeEntry ¶
type ChangeEntry struct {
ID string
At string
Author string
AuthorID string
Field string // "status", "assignee", "priority", ...
FromValue string
FromID string
ToValue string
ToID string
}
ChangeEntry is one field change. Status entries carry ids because display names are localized per account.
type Comment ¶
type Comment struct {
ID string // "<source_id>:<comment_id>"
ExternalID string
Author string
AuthorID string
BodyADF json.RawMessage
BodyText string
CreatedAt string
UpdatedAt string
}
Comment is stored flat: the source API exposes no thread parent.
type CommentsByAuthorResult ¶
type CommentsByAuthorResult struct {
// Author is the display name from the newest matching comment ("" when none).
Author string `json:"author"`
Total int `json:"total"`
Comments []AuthorComment `json:"comments"`
}
CommentsByAuthorResult is the response body for comments by author.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB is a handle on the mirror. Safe for concurrent use; writes are serialized.
func Open ¶
Open opens or creates the mirror at path and migrates it forward. A database written by a newer gadak is refused rather than used.
The data directory is created (and existing dirs tightened) to 0700; the DB file and any -wal/-shm sidecars are set to 0600. Chmod failures are logged and ignored so unsupported filesystems (or Windows) still work.
func (*DB) APIUsage ¶
APIUsage returns up to the most recent days rows, newest day first. days <= 0 means all rows.
func (*DB) APIUsageSummary ¶
func (db *DB) APIUsageSummary(ctx context.Context) (APIUsageSummary, error)
APIUsageSummary builds today + last 7 days from the mirror. Missing days are zeros. last_throttled_at on the rollup is the latest timestamp in the window.
func (*DB) AddAPIUsage ¶
AddAPIUsage UPSERTs delta into the given UTC day (YYYY-MM-DD), adding to any existing counters. last_throttled_at keeps the latest non-empty timestamp.
func (*DB) AppendSyncRun ¶
AppendSyncRun stores one run and prunes the history to the newest 100.
func (*DB) AttachmentBelongs ¶ added in v0.14.1
AttachmentBelongs reports whether the mirror lists attachmentID on issueKey. The id is the Jira external id when that column is set, otherwise the store row id — the same rule handleDetail uses when it builds content URLs. One EXISTS query: serving bytes must not pay for Detail's comments/history/links/refs.
func (*DB) CommentsByAuthor ¶
func (db *DB) CommentsByAuthor(ctx context.Context, authorID string, limit int) (CommentsByAuthorResult, error)
CommentsByAuthor returns comments for an exact author_id match, newest first. Missing author_id yields total 0 and an empty list (not an error). limit defaults to 50 and is capped at 200.
func (*DB) ComputeFieldUsage ¶
ComputeFieldUsage builds field_usage rows from issues.custom for the given aliases. total is the issue count per project; filled is how many issues have a non-empty value for that alias.
func (*DB) ConfluenceSpaceWatermarks ¶ added in v0.13.0
func (db *DB) ConfluenceSpaceWatermarks(ctx context.Context, sourceID string) (map[string]string, error)
ConfluenceSpaceWatermarks returns each space key's incremental floor for sourceID. A missing or NULL watermark is "". Callers treat empty as "not yet backfilled" and run a full fetch for that space.
func (*DB) DeleteItems ¶
DeleteItems removes items whose keys have left the source's scope and records a tombstone so `delta` can report the deletion to a client that missed it.
func (*DB) DeletedKeysSince ¶
DeletedKeysSince lists keys tombstoned after the cursor. `delta` must report these: a missed deletion leaves a tombstone visible in the client forever.
func (*DB) Detail ¶
Detail assembles one issue. An unknown key returns ErrNotFound so the handler can answer 404 without importing database/sql.
func (*DB) DistinctCount ¶
DistinctCount returns COUNT(DISTINCT col) for a fixed known table.column used by doctor. Empty/NULL values are excluded.
func (*DB) EnrichmentsByKind ¶
func (db *DB) EnrichmentsByKind(ctx context.Context, kind string) (map[string]json.RawMessage, error)
EnrichmentsByKind returns every payload of one kind, keyed by issue key. The list and delta responses merge it in.
func (*DB) EnrichmentsFor ¶
EnrichmentsFor returns every kind attached to one issue, for the detail view.
func (*DB) FieldUsage ¶
func (db *DB) FieldUsage(ctx context.Context) ([]FieldUsageRow, error)
FieldUsage returns every field_usage row.
func (*DB) FreshenSyncClock ¶
FreshenSyncClock stamps every sync timestamp as now. It exists for throwaway fixtures — `gadak demo` and the demo recordings — where the snapshot's real age would surface as a stale-sync warning about data that is deliberately frozen. Never call this on a mirror that syncs for real: it would hide a stalled sync.
func (*DB) HasCustomFieldKeysInRaw ¶
HasCustomFieldKeysInRaw reports whether any stored raw document contains a customfield_ key under fields. Used by `gadak fields --apply` to refuse when the mirror was never synced with custom fields.
func (*DB) History ¶ added in v0.13.0
func (db *DB) History(ctx context.Context, opts HistoryOpts) (HistoryPage, error)
History returns visits and searches newest-first. Cursor is opaque.
func (*DB) IssueLites ¶
IssueLites returns the whole mirror, which is what `bootstrap` sends.
func (*DB) IssueLitesByKeys ¶ added in v0.14.1
IssueLitesByKeys returns the IssueLite rows for keys, in the order asked. Missing and empty keys are skipped. Duplicate keys yield duplicate rows. The SQL IN-list is de-duplicated; callers that need FTS rank pass the ranked key list and get that order back.
func (*DB) IssueLitesSince ¶
IssueLitesSince returns rows written at or after the given cursor. Only a row whose content actually changed has a newer synced_at, so an idle poll returns none. The bound is inclusive on purpose: re-sending a row the client already has is a harmless upsert, while missing one leaves it stale forever.
func (*DB) MarkFeedRead ¶
func (db *DB) MarkFeedRead(ctx context.Context, opts MarkFeedReadOpts) (MarkFeedReadResult, error)
MarkFeedRead upserts read receipts and returns the refreshed unread counts.
func (*DB) PageDetail ¶
PageDetail assembles one page. An unknown key returns (nil, nil).
func (*DB) PageStamps ¶ added in v0.14.2
func (db *DB) PageStamps(ctx context.Context, sourceID, spaceKey string) (map[string]PageStamp, error)
PageStamps returns the version stamp of every mirrored page in one space, keyed by the source's own page id (items.external_id). It exists so an incremental sync can answer "do I already hold this page?" from the mirror instead of spending a body fetch to find out — a search hit already carries version.number and version.when.
The mirror is a disposable cache, so a missing or malformed row simply means "fetch it": callers must treat an absent key as unknown, never as unchanged.
func (*DB) PruneConfluenceSpaces ¶ added in v0.13.0
func (db *DB) PruneConfluenceSpaces(ctx context.Context, sourceID string, keepKeys []string) (int, error)
PruneConfluenceSpaces deletes mirrored wiki pages (and their items, FTS rows, and cascaded children) whose space_key is not in keepKeys, then drops the matching spaces rows. keepKeys empty is a no-op so a zero-scope call cannot wipe the mirror. The returned count is the number of pages removed (space-row-only cleanup is not counted).
func (*DB) PruneLocalHistory ¶ added in v0.13.0
PruneLocalHistory deletes visit/search rows older than LocalRetention. Called from the same pass as tombstone expiry (DeleteItems).
func (*DB) PutSavedView ¶
PutSavedView inserts or replaces a view. The caller owns id generation.
func (*DB) RecordSearch ¶ added in v0.13.0
func (db *DB) RecordSearch(ctx context.Context, query string, resultCount int, openedKind, openedKey string) (Search, error)
RecordSearch appends one search. openedKind/openedKey may both be empty, or both set to the item opened from this search.
func (*DB) RecordSync ¶
RecordSync stores the run's bookkeeping. It does not bump `version`: a run that changed no rows must leave the ETag alone. A successful run advances first_sync_at (once) and sync_count.
func (*DB) RecordVisit ¶ added in v0.13.0
RecordVisit appends one view. Same (kind, key) twice is two rows.
func (*DB) ReingestCustom ¶
func (db *DB) ReingestCustom(ctx context.Context, specs []fields.SpecIDs, bodyFieldIDs []string) (int, error)
ReingestCustom recomputes issues.custom and the FTS body from the stored raw JSON — no network. Returns the number of issues rewritten.
func (*DB) ReplaceFieldUsage ¶
func (db *DB) ReplaceFieldUsage(ctx context.Context, rows []FieldUsageRow) error
ReplaceFieldUsage replaces the entire field_usage table.
func (*DB) ReplaceSourceQueries ¶ added in v0.13.0
func (db *DB) ReplaceSourceQueries(ctx context.Context, sourceID string, queries []SourceQuery) error
ReplaceSourceQueries swaps one source's named queries in a single write. An empty list clears that source (the account deleted every filter).
func (*DB) ScanFieldFill ¶
func (db *DB) ScanFieldFill(ctx context.Context, fn func(projectKey string, fieldVals map[string]json.RawMessage) error) error
ScanFieldFill streams every issue's project key and the fields object from stored raw JSON. Rows with empty or unparseable raw are skipped (older rows). fn receives field id → raw value for custom and system fields present in raw.
func (*DB) SchemaVersion ¶
SchemaVersion is the migration level this binary applied.
func (*DB) Search ¶
Search runs an FTS5 query over titles, bodies and comment text and returns matching issues and pages, best match first. Bare terms are rewritten as quoted prefix queries (see ftsPrefixQuery). A query FTS5 cannot parse is retried as a literal phrase rather than surfaced as an error, because this is fed raw user input. limit applies to the combined FTS result set.
func (*DB) SetFavorite ¶
SetFavorite adds or removes a favorite issue key.
func (*DB) SetLastNotifiedAt ¶
SetLastNotifiedAt advances the OS-notification watermark. Never touches feed_reads.
func (*DB) SetSearchOpened ¶ added in v0.13.0
SetSearchOpened stamps the item opened from an existing search row.
func (*DB) SetSpaceWatermark ¶ added in v0.13.0
SetSpaceWatermark writes one space's incremental floor. An unknown key is inserted so a just-scoped space can be stamped after its first successful pass without a separate UpsertSpaces.
func (*DB) SourceQueries ¶ added in v0.13.0
SourceQueries returns one source's named queries, starred first then by name.
func (*DB) SyncState ¶
SyncState reads the state for one source. A source that has never synced returns a zero state, not an error.
func (*DB) TableCount ¶
TableCount returns SELECT COUNT(*) for a fixed known table name used by status and doctor. Only whitelisted table names are accepted.
func (*DB) UpsertIssues ¶
UpsertIssues writes one page of sync output in a single transaction and returns how many items it actually changed. Derived fields are recomputed from the batch's changelog, never carried over (contracts/sync.md invariant 5).
An item whose updated_at is unchanged is skipped whole — children, FTS and the version counter included — unless Batch.Force is set. That is what makes an incremental re-run over the watermark overlap window a no-op.
func (*DB) UpsertPages ¶
UpsertPages writes document records in a single transaction and returns how many items it actually changed. A page whose stored body, meta and comments match the incoming record is skipped (no version bump). Comment-only edits still bump because comments are part of the compare — that is the comments- only trap the old always-rewrite path existed to close.
func (*DB) UpsertSource ¶
UpsertSource records a connector instance. Credentials never come near it (Constitution Article 8).
func (*DB) UpsertSpaces ¶
UpsertSpaces writes wiki space rows (key → name/kind/homepage_id) for a source. Empty name/kind/homepage_id on conflict keeps the previous value so page-hit upserts (name only) do not wipe kind or homepage filled by a full space listing or per-space GET.
type DeriveInput ¶
type DeriveInput struct {
Changelog []ChangeEntry
Categories map[string]string // status id -> new | inprogress | done
CurrentCategory string // the issue's category right now
Priority string
Priorities []string // site priority names, most urgent first
Comments []Comment
Links []Link
}
DeriveInput is everything the derived-field rules need. Changelog entries carry only status ids, so the id -> category map has to come from the site's status list, which the connector supplies per batch.
type Derived ¶
type Derived struct {
StatusChangedAt *string
ResolvedAt *string
ReopenCount int
ReopenedAt *string
ReopenReason string
AssigneeChangedAt *string
CommentCount int
PriorityRank int
ClonedFrom string
}
Derived holds the columns gadak computes because the source does not provide them. Rules are documented in data-model.md, "Derived field rules".
func Derive ¶
func Derive(in DeriveInput) Derived
Derive computes every derived field in one pass over the issue's changelog. A status id missing from the category map counts as not-done, which can only ever miss a reopen — never invent one.
type Detail ¶
type Detail struct {
IssueKey string `json:"issue_key"`
DescriptionADF json.RawMessage `json:"description_adf"`
Comments []DetailComment `json:"comments"`
Attachments []DetailAttachment `json:"attachments"`
History []DetailChange `json:"history"`
LinkedIssues []DetailLink `json:"linked_issues"`
// RefPages are wiki pages this issue's body/comments mention (item_refs,
// target_kind=page). Only pages present in the mirror; empty omitted.
RefPages []PageLite `json:"ref_pages,omitempty"`
// BacklinkPages are wiki pages that mention this issue key. Empty omitted.
BacklinkPages []PageLite `json:"backlink_pages,omitempty"`
// Custom is the issue's full alias→value map. List rows strip body-role
// values (they can be document-sized); detail is where they surface.
Custom map[string]any `json:"-"`
}
Detail is everything the on-demand detail view needs, assembled from the mirror with no call to the source.
type DetailAttachment ¶
type DetailAttachment struct {
ID string `json:"id"`
ExternalID string `json:"external_id"`
Filename string `json:"filename"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
Author string `json:"author,omitempty"`
AuthorID string `json:"author_id,omitempty"`
CreatedAt string `json:"created_at"`
}
DetailAttachment is metadata only; the handler turns ExternalID into the content proxy path.
type DetailChange ¶
type DetailChange struct {
At string `json:"at"`
Author string `json:"author"`
AuthorID string `json:"author_id,omitempty"`
Field string `json:"field"`
FromValue string `json:"from_value"`
FromID string `json:"from_id"`
ToValue string `json:"to_value"`
ToID string `json:"to_id"`
}
DetailChange is one history row. The ids come along because display values are localized: a caller that wants the `from_category` / `to_category` the detail contract allows has to resolve them from an id, never from a name.
type DetailComment ¶
type DetailComment struct {
ID string `json:"id"`
ExternalID string `json:"external_id"`
Author string `json:"author"`
AuthorID string `json:"author_id"`
BodyADF json.RawMessage `json:"body_adf"`
Body string `json:"body"` // flattened; the client's fallback when ADF will not render
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
DetailComment is one comment as the detail panel renders it.
type DetailLink ¶
type DetailLink struct {
Key string `json:"key"`
Type string `json:"type"`
Direction string `json:"direction"`
Summary string `json:"summary"`
StatusCategory string `json:"status_category"`
}
DetailLink is an edge plus whatever the mirror knows about the far side. A target outside the mirror keeps an empty summary and category.
type FeedFocus ¶
type FeedFocus string
FeedFocus is the focus tab the client sends: all | assignee | reporter | mention. "watched" is a reason, not a focus filter.
type FeedIdentity ¶
type FeedIdentity struct {
AccountID string
Email string
DisplayName string // TokenOwner / display name for author matching
}
FeedIdentity is the local user, used for relevance and self-action exclusion. AccountID comes from config (Jira /myself); Email/DisplayName fall back when the mirror still only has display names on some rows.
type FeedItem ¶
type FeedItem struct {
ID int `json:"id"`
EventID string `json:"event_id"`
IssueKey string `json:"issue_key"`
Summary string `json:"summary"`
CurrentStatus string `json:"current_status"`
EventType string `json:"event_type"`
OccurredAt *string `json:"occurred_at"`
ActorName string `json:"actor_name"`
Payload map[string]any `json:"payload"`
Reasons []string `json:"reasons"`
ReadAt *string `json:"read_at"`
}
FeedItem is one activity row the client renders (web/src/lib/types.ts FeedItem).
type FeedOpts ¶
type FeedOpts struct {
Focus FeedFocus
Limit int
Me FeedIdentity
// Now, when set, freezes the 30-day window for tests.
Now time.Time
}
FeedOpts configures a personal-feed query.
type FeedResult ¶
type FeedResult struct {
Items []FeedItem `json:"items"`
UnreadCounts FeedUnreadCounts `json:"unread_counts"`
}
FeedResult is GET feed/ response body (without the outer wrapper keys).
type FeedUnreadCounts ¶
type FeedUnreadCounts struct {
All int `json:"all"`
Assignee int `json:"assignee"`
Reporter int `json:"reporter"`
Mention int `json:"mention"`
}
FeedUnreadCounts is the badge counts per focus tab.
type FieldUsageRow ¶
FieldUsageRow is one (project, alias) fill statistic from field_usage.
type HistoryItem ¶ added in v0.13.0
type HistoryItem struct {
Type string `json:"type"` // visit | search
ID int64 `json:"id"`
Kind string `json:"kind,omitempty"`
Key string `json:"key,omitempty"`
Query string `json:"query,omitempty"`
ResultCount *int `json:"result_count,omitempty"`
OpenedKind *string `json:"opened_kind,omitempty"`
OpenedKey *string `json:"opened_key,omitempty"`
At string `json:"at"`
}
HistoryItem is one row of the mixed timeline (newest first).
type HistoryOpts ¶ added in v0.13.0
type HistoryOpts struct {
// Kind is "issue", "page", or "search". Empty means all three.
Kind string
Limit int
Cursor string
}
HistoryOpts filters the mixed visit+search timeline.
type HistoryPage ¶ added in v0.13.0
type HistoryPage struct {
Items []HistoryItem `json:"items"`
NextCursor string `json:"next_cursor,omitempty"`
}
HistoryPage is one cursor page of History.
type Issue ¶
type Issue struct {
ProjectKey string
IssueType string
IssueTypeID string
Status string
StatusID string
StatusCategory string // new | inprogress | done
Priority string
Assignee string
AssigneeID string
AssigneeEmail string
Reporter string
ReporterID string
ReporterEmail string
ParentKey string
// HierarchyLevel is the source-neutral tree rank of this issue's type
// (e.g. epic=1, standard=0, sub-task=-1). Used to derive EpicKey.
HierarchyLevel int
Labels []string
Components []string
FixVersions []string
AffectsVersions []string
EnvironmentText string
Duedate string
Resolution string
DescriptionADF json.RawMessage
Custom map[string]any // mapped custom fields, keyed by config alias
Raw json.RawMessage
}
Issue is the tracker projection. `item_id`, `key`, `created_at`, `updated_at` are taken from the record's Item and every derived field is computed by the store, so a connector fills none of them.
type IssueLite ¶
type IssueLite struct {
IssueKey string `json:"issue_key"`
Summary string `json:"summary"`
ProjectKey string `json:"project_key"`
IssueType string `json:"issue_type"`
IssueTypeID string `json:"issue_type_id"`
Status string `json:"status"`
StatusID string `json:"status_id"`
StatusCategory string `json:"status_category"`
Priority *string `json:"priority"`
// PriorityID is the stable Jira priority id. Empty until a later schema
// column is filled — the field is on the wire so clients can match id-first
// the same way they do for status_id / issue_type_id. There is no
// issues.priority_id column today (only name + rank).
PriorityID string `json:"priority_id"`
PriorityRank int `json:"priority_rank"`
Assignee *string `json:"assignee"`
AssigneeID *string `json:"assignee_id"`
AssigneeEmail *string `json:"assignee_email"`
Reporter *string `json:"reporter"`
ReporterID *string `json:"reporter_id"`
ReporterEmail *string `json:"reporter_email"`
// EpicKey is the nearest hierarchy_level==1 ancestor (derived). Nil when
// none. Distinct from ParentKey, which is the direct parent only.
EpicKey *string `json:"epic_key"`
// ParentKey is the direct parent issue key (source field), not the epic.
ParentKey *string `json:"parent_key"`
Labels []string `json:"labels"`
Components []string `json:"components"`
FixVersions []string `json:"fix_versions"`
Duedate *string `json:"duedate"`
Resolution *string `json:"resolution"`
CreatedAt *string `json:"created_at"`
UpdatedAt *string `json:"updated_at"`
StatusChangedAt *string `json:"status_changed_at"`
ResolvedAt *string `json:"resolved_at"`
ReopenCount int `json:"reopen_count"`
ReopenedAt *string `json:"reopened_at"`
ReopenReason *string `json:"reopen_reason"`
ClonedFrom *string `json:"cloned_from"`
// SourceProject is ClonedFrom's project prefix, precomputed because the
// list filter groups by it.
SourceProject *string `json:"source_project"`
CommentCount int `json:"comment_count"`
// Custom holds the configured field aliases from issues.custom. The server
// spreads them into the response as top-level keys, which is where the client
// reads severity and friends.
Custom map[string]any `json:"custom,omitempty"`
}
IssueLite is the row shape the read API hydrates the client with. Field names are the ones web/src/lib/types.ts already parses (contracts/api.md, "IssueLite"): adding is safe, renaming is not.
type IssueRecord ¶
type IssueRecord struct {
Item Item
Issue Issue
Comments []Comment
Attachments []Attachment
Changelog []ChangeEntry
Links []Link
}
IssueRecord is one item and everything hanging off it. Child lists are replaced wholesale on upsert, so a partial list would delete rows.
type Item ¶
type Item struct {
ID string // "<source_id>:<external_id>"
SourceID string
Kind string // "issue" in v0.1
ExternalID string
Key string // human-facing key, unique per source
Title string
BodyText string // flattened body, what FTS indexes
Author string
AuthorID string
URL string
CreatedAt string
UpdatedAt string
}
Item is the neutral spine row: anything with a title, body, author and timestamp fits it.
type ItemRef ¶
type ItemRef struct {
TargetKind string // "issue" | "page"
TargetKey string // issue key or page id (numeric string)
Via string // "url" | "text"
}
ItemRef is one outgoing reference from an item. Pure extraction returns these; the write path persists them into item_refs.
func ExtractIssueRefsFromPage ¶
ExtractIssueRefsFromPage finds issue keys in a page's ADF (URL paths) and plain body_text (bare keys filtered by knownProjects). Same key from both sources yields one row with via=url. knownProjects may be empty (no text hits).
func ExtractPageRefsFromIssue ¶
ExtractPageRefsFromIssue finds Confluence page IDs in an issue description (raw ADF + flattened text) and its comment bodies. Scanning ADF is what picks up link marks and inlineCards that PlainText drops — the same raw scan ExtractIssueRefsFromPage already does in the other direction. Both URL shapes use via=url. Duplicates collapse to one.
type MarkFeedReadOpts ¶
type MarkFeedReadOpts struct {
EventIDs []string
IssueKeys []string
All bool
Me FeedIdentity
Now time.Time
}
MarkFeedReadOpts is POST feed/read/ body semantics.
type MarkFeedReadResult ¶
type MarkFeedReadResult struct {
Updated int `json:"updated"`
UnreadCounts FeedUnreadCounts `json:"unread_counts"`
}
MarkFeedReadResult is POST feed/read/ response.
type Page ¶
type Page struct {
SpaceKey string
ParentID string
Version int
Status string
// Labels are source-neutral tag names (JSON array column). Empty slice is
// stored as "[]", never NULL. Sync sorts alphabetically for determinism.
Labels []string
// BodyADF is the raw Atlas Document Format body for rendering. Empty when
// the mirror only has flattened body_text (pre-v10 rows).
BodyADF json.RawMessage
}
Page is the document projection (one row in the pages table). Field names match the projection columns, not any source API (same status as Issue).
type PageComment ¶
type PageComment struct {
Author string `json:"author"`
CreatedAt string `json:"created_at"`
BodyADF json.RawMessage `json:"body_adf"`
BodyText string `json:"body_text"`
}
PageComment is one comment on a page detail response.
type PageDetail ¶
type PageDetail struct {
PageLite
BodyADF json.RawMessage `json:"body_adf"`
Comments []PageComment `json:"comments"`
// RefIssueKeys are issue keys this page's body mentions (item_refs). Only
// issues present in the mirror, sorted ascending. Empty omitted.
RefIssueKeys []string `json:"ref_issue_keys,omitempty"`
// BacklinkIssueKeys are issue keys that mention this page. Empty omitted.
BacklinkIssueKeys []string `json:"backlink_issue_keys,omitempty"`
}
PageDetail is PageLite plus the raw ADF body and comments.
type PageLite ¶
type PageLite struct {
Key string `json:"key"`
Title string `json:"title"`
SpaceKey string `json:"space_key"`
// SpaceName is the human-readable space title from the spaces table join.
// Empty when no spaces row is mirrored for this key (never omitted in JSON).
SpaceName string `json:"space_name"`
// SpaceHomepageID is the content id of the space root page (spaces.homepage_id).
// Empty when the space row is missing or homepage has not been learned yet.
SpaceHomepageID string `json:"space_homepage_id"`
ParentID string `json:"parent_id"`
Author string `json:"author"`
// AuthorID is items.author_id. Empty on legacy rows that only have a
// display name — clients group by this when present and fall back to Author.
AuthorID string `json:"author_id"`
UpdatedAt string `json:"updated_at"`
Version int `json:"version"`
URL string `json:"url"`
// Excerpt is a one-line body preview derived from body_adf (schema v15):
// whitespace-normalized, at most 200 runes. Empty when the body is empty.
Excerpt string `json:"excerpt"`
Labels []string `json:"labels"`
}
PageLite is the list/search row for a mirrored wiki page. Field names are snake_case JSON to match IssueLite and the read API contract.
type PageRecord ¶
PageRecord is one document item plus its projection and comments. Comments are replaced wholesale on upsert, matching IssueRecord.
type PageStamp ¶ added in v0.14.2
PageStamp is the mirror's record of one page's upstream identity: the source's version number and the lastModified the row was written from. Both together, never the number alone — a rollback can reuse a number.
type SavedView ¶
type SavedView struct {
ID string `json:"id"`
Name string `json:"name"`
Config json.RawMessage `json:"config"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
SavedView is a user's stored filter set. Personal state is the only thing in this database a user would miss, so it is also the only thing `gadak export` has to dump (Constitution Article 1).
type Search ¶ added in v0.13.0
type Search struct {
ID int64 `json:"id"`
Query string `json:"query"`
SearchedAt string `json:"searched_at"`
ResultCount int `json:"result_count"`
OpenedKind *string `json:"opened_kind"`
OpenedKey *string `json:"opened_key"`
}
Search is one append-only search execution. OpenedKind/OpenedKey name the item opened from that search, when one was.
type SearchMatch ¶
SearchMatch says which FTS column matched and shows a plain-text snippet. Field is "title" | "body" | "comment". Snippet has no HTML or highlight markers — the client highlights against its own query string.
type SearchResult ¶
type SearchResult struct {
Keys []string `json:"keys"`
Pages []PageLite `json:"pages"`
Total int `json:"total"`
Matches map[string]SearchMatch `json:"matches"`
}
SearchResult is a kind-aware FTS hit list. Keys are issue keys (best match first among issues in the ranked window); Pages are page hits in the same ranked window. Total is the number of hits returned (keys + pages), matching the pre-R2 meaning of total = len(results after limit). Matches maps each returned issue or page key to the winning column match.
type SourceQuery ¶ added in v0.13.0
type SourceQuery struct {
ID string `json:"id"`
SourceID string `json:"source_id"`
ExternalID string `json:"external_id"`
Name string `json:"name"`
QueryText string `json:"query_text"`
Config json.RawMessage `json:"config"`
Favourite bool `json:"favourite"`
Owner string `json:"owner"`
Applied []string `json:"applied"`
Unsupported []string `json:"unsupported"`
UpdatedAt string `json:"updated_at"`
}
SourceQuery is a named query mirrored from a connector (Jira saved filter). Jira is the record: ReplaceSourceQueries rewrites one source's rows.
type SpaceRow ¶
SpaceRow is one wiki space (key + human name + kind). Source-neutral: a connector maps its space listing or page-embedded space ref onto this. Kind is the source type string (e.g. "global", "personal"); empty is allowed when only a name is known from a page hit. HomepageID is the content id of the space root page when known; empty on page-hit upserts so they do not wipe a value filled by a space listing.
type SyncResult ¶
type SyncResult struct {
Watermark string // ignored when empty or not greater than the stored one
FullSync bool // stamps last_full_sync_at
Err error // recorded as last_error; nil clears it
}
SyncResult is what a finished sync run reports.
type SyncRun ¶
type SyncRun struct {
Kind string `json:"kind"` // full | incremental (+reconcile)
StartedAt string `json:"started_at"`
FinishedAt string `json:"finished_at"`
Fetched int `json:"fetched"`
Changed int `json:"changed"`
Deleted int `json:"deleted"`
Error string `json:"error,omitempty"`
}
SyncRun is one recorded sync pass. Only meaningful runs are stored: ones that changed something, were a full pass, or failed — the watch loop's no-op incrementals would otherwise bury the history in noise.
type SyncState ¶
type SyncState struct {
SourceID string `json:"source_id"`
Watermark string `json:"watermark"`
// SyncedAt is the last run that finished without an error — the only field
// here that means "the mirror is fresh". A watermark stalls on its own
// whenever the project is simply quiet.
SyncedAt *string `json:"synced_at"`
Version int64 `json:"version"`
LastFullSyncAt *string `json:"last_full_sync_at"`
LastError *string `json:"last_error"`
SchemaVersion int `json:"schema_version"`
// FirstSyncAt is the first successful sync for this source (retention).
FirstSyncAt *string `json:"first_sync_at,omitempty"`
// SyncCount is the number of successful sync runs (retention).
SyncCount int64 `json:"sync_count"`
// LastNotifiedAt is the OS-notification watermark. Independent of
// feed_reads: delivering a desktop alert must not mark the feed read.
LastNotifiedAt *string `json:"last_notified_at,omitempty"`
}
SyncState is the per-source sync bookkeeping.