Documentation
¶
Overview ¶
Package sync fills the mirror from Jira. It only ever writes to the mirror, never to Jira, and the rules it implements are in specs/000-product/contracts/sync.md.
Index ¶
- Constants
- Variables
- func FeedIdentity(cfg *config.Config) store.FeedIdentity
- func SyncIssue(ctx context.Context, cfg *config.Config, db *store.DB, key string, ...) error
- func SyncPage(ctx context.Context, cfg *config.Config, db *store.DB, id string) error
- func Watch(ctx context.Context, cfg *config.Config, db *store.DB, opts Options) error
- type Notifier
- type OSNotifier
- type Options
- type Result
Constants ¶
const ( PhaseIssues = "issues" PhaseDocuments = "documents" PhaseIdle = "" )
Phase source names for Options.Phase. Callers report what the mirror is fetching; Progress carries how much. Keep these string values stable — the web client keys UI copy on them.
const ConfluenceSourceID = "confluence"
ConfluenceSourceID is the slug the wiki connector owns in sources / sync_state.
const SourceID = "jira"
SourceID is the slug the Jira connector owns in `sources` and `sync_state`.
Variables ¶
var ErrNotFound = errors.New("sync: issue not found upstream")
ErrNotFound means the upstream answered but no issue/page with that key came back: it is outside the credential's permissions, or it was just deleted.
Functions ¶
func FeedIdentity ¶
func FeedIdentity(cfg *config.Config) store.FeedIdentity
FeedIdentity builds the personal-feed identity the same way the HTTP server does.
func SyncIssue ¶
func SyncIssue(ctx context.Context, cfg *config.Config, db *store.DB, key string, opts Options) error
SyncIssue re-reads one issue and writes it to the mirror. Every write-through endpoint ends here, which is what makes a row that came back from a write identical to one a scheduled sync produced — same field mapping, same derived fields, no second code path to keep in step.
func SyncPage ¶
SyncPage re-reads one Confluence page and writes it to the mirror. Used when the desktop app closes an in-app browser tab after editing a wiki page: the rewrite moves synced_at and bumps sync_state.version so the client's next pages list / detail fetch sees the edit.
Unlike a full Confluence pass (commitBatch in confluence.go), this must not call RecordSync. A single page's lastModified must not become the source watermark — that would jump incremental sync into the future and skip every page modified in between. UpsertPages alone is enough: it rewrites the row and bumps version without advancing the watermark.
func Watch ¶
Watch runs incremental sync on an interval and reconcile on a longer one. A transport failure is logged and retried on the next tick; a rejected credential ends the loop, because every further request would only burn rate budget. After each successful cycle, new personal-feed events may produce one OS desktop notification (see notifyAfterSync); notification failures never stop the loop.
When opts.Reload is set, each cycle re-reads config so a settings edit (projects, Confluence, intervals) takes effect without restarting the process.
Types ¶
type Notifier ¶
Notifier delivers one OS desktop notification. Implementations must never panic; callers treat every error as non-fatal and continue the sync loop.
type OSNotifier ¶
type OSNotifier struct{}
OSNotifier uses the platform's desktop-notification command. darwin: osascript display notification; linux: notify-send; windows: no-op.
func (OSNotifier) Notify ¶
func (OSNotifier) Notify(title, body string) error
Notify implements Notifier.
type Options ¶
type Options struct {
Full bool
Reconcile bool
// Log, when set, receives one line per committed page and per pass.
Log func(string)
// Progress, when set, is called once per committed page with the running
// totals. It exists so a caller can report progress without parsing Log.
Progress func(fetched, changed int)
// Phase, when set, is called with the connector a pass is about to run
// ("issues", "documents") and with "" when the cycle has nothing in flight.
// It exists so a caller can report *what* is being fetched; Progress carries
// how much. Watch calls it around each source; one-shot callers set it too.
Phase func(source string)
// Client is for tests and for a server that wants to share one; nil builds
// one from cfg.
Client *jira.Client
// ConfluenceClient is for tests; nil builds one from cfg when Confluence is configured.
ConfluenceClient *confluence.Client
// Notifier delivers OS desktop alerts for new personal-feed events after
// each successful Watch cycle. Nil uses OSNotifier. Never aborts the loop.
Notifier Notifier
// Reload re-reads the config at the top of each watch cycle. Nil keeps the
// config Watch was called with. A reload error is logged and the previous
// config stays in use: a momentarily unreadable file must not stop the mirror.
Reload func() (*config.Config, error)
}
type Result ¶
func Run ¶
Run does one sync pass: full or incremental, plus a reconcile pass when asked for or after a full sync.
A failure leaves the pages already committed in place and does not advance the watermark, so the next run re-reads from the last known-good point (contracts/sync.md invariants 2 and 3).
An empty cfg.Projects means no project filter: the account's full visible issue set is the scope (one Search, not one per project).
func RunConfluence ¶
func RunConfluence(ctx context.Context, cfg *config.Config, db *store.DB, opts Options) (Result, error)
RunConfluence does one Confluence mirror pass: full or incremental. Attachments and changelog are out of scope for R1. Every successful pass prunes pages whose space is outside the current config/listing scope. Incremental floors are per-space (spaces.watermark); a failed space does not move its own watermark or any other space's.
A failure leaves already-committed batches in place.