sync

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

View Source
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.

View Source
const ConfluenceSourceID = "confluence"

ConfluenceSourceID is the slug the wiki connector owns in sources / sync_state.

View Source
const SourceID = "jira"

SourceID is the slug the Jira connector owns in `sources` and `sync_state`.

Variables

View Source
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

func SyncPage(ctx context.Context, cfg *config.Config, db *store.DB, id string) error

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

func Watch(ctx context.Context, cfg *config.Config, db *store.DB, opts Options) error

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

type Notifier interface {
	Notify(title, body string) error
}

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

type Result struct {
	Full      bool
	Fetched   int
	Changed   int
	Deleted   int
	Watermark string
}

func Run

func Run(ctx context.Context, cfg *config.Config, db *store.DB, opts Options) (Result, error)

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, delete reconcile, and changelog are out of scope for R1.

A failure leaves already-committed batches in place and does not advance the watermark past the last successful batch.

Jump to

Keyboard shortcuts

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