Help us shape the future of Go! Take the 2026 Go Developer Survey

githubsync

package
v0.2.5 Latest Latest
Warning

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

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Overview

Package githubsync owns the GitHub integration's application rules: which accounts may follow which repositories, webhook deduplication, coalesced refresh intent and reconciliation. It holds no transport or SQL.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrForbidden    = errors.New("GitHub repository is not accessible to this account")
	ErrNotConnected = errors.New("GitHub is not connected")
	ErrReconnect    = errors.New("GitHub authorization expired or was revoked; connect again")
	ErrSignature    = errors.New("invalid webhook signature")
)
View Source
var ErrInvalidResult = errors.New("fetch result does not match the leased pull request")

ErrInvalidResult reports a fetcher snapshot that does not match its lease.

View Source
var ErrLeaseLost = errors.New("fetch lease lost")
View Source
var ErrNotFound = errors.New("not found")

ErrNotFound reports an absent connection, follow or OAuth state.

Functions

This section is empty.

Types

type Admission

type Admission struct {
	OwnerID      string
	Request      ingestion.Request
	Notification *Notification
}

Admission is one account's submission of a fetched snapshot. A non-nil Notification is push intent linked to the admitted ingestion job.

type Connection

type Connection struct {
	OwnerID          string
	UserID           int64
	Login            string
	AccessToken      []byte
	AccessExpiresAt  int64
	RefreshToken     []byte
	RefreshExpiresAt int64
	Status           string // active, revoked or expired
	ConnectedAt      int64
}

Connection is one account's GitHub user authorization. Tokens are sealed; storage never sees plaintext.

type Follow

type Follow struct {
	OwnerID        string
	RepositoryID   int64
	InstallationID int64
	FullName       string
	Status         string // active or lost
	VerifiedAt     int64
	CreatedAt      int64
	// Muted follows still receive snapshots but no push notifications. Reads
	// report it; PutFollow leaves it unchanged.
	Muted bool
}

Follow is an account's subscription to a repository's pull requests. Lost follows stay listed but receive nothing until access is verified again.

type GitHub

type GitHub interface {
	AppSlug(ctx context.Context) (string, error)
	VerifyWebhook(body []byte, signature256 string) bool
	AuthorizeURL(state, redirectURI string) string
	Exchange(ctx context.Context, code, redirectURI string) (github.UserToken, error)
	Refresh(ctx context.Context, refreshToken string) (github.UserToken, error)
	User(ctx context.Context, accessToken string) (github.Account, error)
	UserRepositories(ctx context.Context, accessToken string) ([]github.Repository, error)
	OpenPullRequests(ctx context.Context, installationID int64, fullName string) ([]github.PullRequest, error)
	PullRequest(ctx context.Context, installationID int64, fullName string, number int) (github.PullRequest, error)
	InstallationToken(ctx context.Context, installationID int64, repositoryIDs []int64, permissions map[string]string) (github.Token, error)
}

GitHub is the adapter subset this service needs.

type Lease

type Lease struct {
	Ref          PullRequestRef
	Token        string
	Generation   int64
	RetentionRun string
	Attempt      int
}

Lease is one claimed refresh of a pull request at a generation.

type Notification added in v0.2.2

type Notification struct {
	Title string `json:"title"`
	Body  string `json:"body"`
}

Notification is the text of a pull request push notification. Delivery adds the link once the follower's snapshot is published.

type Options

type Options struct {
	PublicURL string
	TokenKey  []byte
	Now       func() time.Time
}

type PullRequestRef

type PullRequestRef struct {
	RepositoryID   int64
	InstallationID int64
	FullName       string
	Number         int
}

PullRequestRef addresses one pull request through the installation that can read it.

type Repository

type Repository struct {
	ID       int64  `json:"id"`
	FullName string `json:"fullName"`
	Private  bool   `json:"private"`
	Followed bool   `json:"followed"`
	// Lost marks a followed repository the account can no longer read.
	Lost bool `json:"lost"`
	// Notify reports whether new pull request diffs send push notifications:
	// followed and not muted.
	Notify bool `json:"notify"`
}

Repository is a repository the account can follow or already follows.

type Service

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

func New

func New(store Store, client GitHub, options Options) (*Service, error)

func (*Service) Complete

func (s *Service) Complete(ctx context.Context, ownerID, state, code string) error

Complete exchanges the callback code. A state issued to another account is refused.

func (*Service) Connect

func (s *Service) Connect(ctx context.Context, ownerID string) (string, error)

Connect starts authorization bound to the account; only that account's session can complete it.

func (*Service) Disconnect

func (s *Service) Disconnect(ctx context.Context, ownerID string) error

func (*Service) Finish

func (s *Service) Finish(ctx context.Context, result fetchprotocol.Result) error

Finish applies a fetcher result. A snapshot is published to every account with verified access in one transaction with the refresh; a failure retries with backoff. ErrLeaseLost means another lease superseded this one. When the snapshot announces new work (see announcement), the same transaction records a push notification for every admitted follower that has not muted the repository.

func (*Service) Follow

func (s *Service) Follow(ctx context.Context, ownerID string, repositoryID int64) error

Follow subscribes the account after verifying access, then requests a refresh of every open pull request so existing work appears immediately.

func (*Service) Followers

func (s *Service) Followers(ctx context.Context, repositoryID int64) ([]string, error)

Followers returns accounts whose access to the repository is verified within verificationAge, rechecking stale follows first. Accounts that lost access are excluded; their follows stay lost until verified again.

func (*Service) Lease

func (s *Service) Lease(ctx context.Context) (*fetchprotocol.Job, error)

Lease claims the next pending pull request refresh for the fetcher. It resolves refreshes that need no fetch (deleted pull requests, repositories without followers) itself. Nil means nothing is pending.

func (*Service) Reconcile

func (s *Service) Reconcile(ctx context.Context) error

Reconcile recovers deliveries GitHub never retries: it rechecks every connected account's access, then requests refreshes for every open or previously open pull request of each actively followed repository.

func (*Service) Repositories

func (s *Service) Repositories(ctx context.Context, ownerID string) ([]Repository, error)

Repositories lists what both the GitHub user and the app can read, plus followed repositories that lost access.

func (*Service) Resync

func (s *Service) Resync(ctx context.Context, ownerID string) error

Resync rechecks the account's access and refreshes every followed repository's pull requests. Its run identity makes each resulting submission fresh, extending retention of pull requests idle since their last change.

func (*Service) SetNotify added in v0.2.2

func (s *Service) SetNotify(ctx context.Context, ownerID string, repositoryID int64, notify bool) error

SetNotify changes whether a followed repository's new pull request diffs send push notifications to the account. ErrNotFound when not followed.

func (*Service) Status

func (s *Service) Status(ctx context.Context, ownerID string) (Status, error)

func (*Service) Unfollow

func (s *Service) Unfollow(ctx context.Context, ownerID string, repositoryID int64) error

func (*Service) Webhook

func (s *Service) Webhook(ctx context.Context, event, delivery, signature string, body []byte) error

Webhook verifies and applies one delivery. Duplicate delivery IDs are acknowledged without effect; a delivery is recorded only after it applies, so a failed one can be redelivered. Every effect is idempotent.

type Status

type Status struct {
	Connected  bool   `json:"connected"`
	Login      string `json:"login,omitempty"`
	State      string `json:"state,omitempty"`
	InstallURL string `json:"installUrl"`
}

Status reports the account's connection and the app installation page.

type Store

type Store interface {
	PutOAuthState(ctx context.Context, hash, ownerID string, expiresAt int64) error
	// TakeOAuthState consumes an unexpired state exactly once.
	TakeOAuthState(ctx context.Context, hash string, now int64) (string, error)
	PutConnection(ctx context.Context, connection Connection) error
	Connection(ctx context.Context, ownerID string) (Connection, error)
	Connections(ctx context.Context) ([]Connection, error)
	// DeleteConnection removes the authorization and every follow of the account.
	DeleteConnection(ctx context.Context, ownerID string) error
	// RevokeUser marks every connection of the GitHub user revoked and their follows lost.
	RevokeUser(ctx context.Context, userID int64) error
	PutFollow(ctx context.Context, follow Follow) error
	DeleteFollow(ctx context.Context, ownerID string, repositoryID int64) error
	Follows(ctx context.Context, ownerID string) ([]Follow, error)
	RepositoryFollows(ctx context.Context, repositoryID int64) ([]Follow, error)
	// LoseInstallation marks follows lost for the installation, or only its listed repositories.
	LoseInstallation(ctx context.Context, installationID int64, repositoryIDs []int64) error
	// SeenDelivery and RecordDelivery deduplicate webhook deliveries by GUID.
	SeenDelivery(ctx context.Context, id string) (bool, error)
	RecordDelivery(ctx context.Context, id, event string, now int64) error
	// SetMuted changes whether a follow sends push notifications. ErrNotFound
	// when the account does not follow the repository.
	SetMuted(ctx context.Context, ownerID string, repositoryID int64, muted bool) error
	// RequestRefresh coalesces refresh intent per pull request by advancing its
	// generation. A nonempty retention run makes the next submission fresh.
	// Opened records, in the same transaction, that a webhook announced the
	// pull request, so its first snapshot notifies followers.
	RequestRefresh(ctx context.Context, ref PullRequestRef, retentionRun string, opened bool, now int64) error
	// PullRequestHead returns the last captured head SHA ("" before the first
	// snapshot) and whether a webhook announced the pull request.
	PullRequestHead(ctx context.Context, repositoryID int64, number int) (head string, opened bool, err error)
	// KnownOpen lists pull request numbers last observed open.
	KnownOpen(ctx context.Context, repositoryID int64) ([]int, error)

	// ClaimRefresh leases the oldest pending, available pull request until
	// leaseUntil, fencing earlier leases. ErrNotFound means none is pending.
	ClaimRefresh(ctx context.Context, token string, now, leaseUntil int64) (Lease, error)
	// RecordLease stores what the lease will publish. ErrLeaseLost when fenced.
	RecordLease(ctx context.Context, lease Lease, leased []byte, now int64) error
	// Leased returns the recorded lease for a current token. ErrLeaseLost when fenced or expired.
	Leased(ctx context.Context, repositoryID int64, number int, token string, now int64) (Lease, []byte, error)
	// ReleaseRefresh ends a failed lease; the pull request stays pending until retryAt.
	ReleaseRefresh(ctx context.Context, lease Lease, retryAt int64, detail string) error
	// CompleteRefresh atomically admits every submission with its notification
	// intent, marks the leased generation refreshed and records the observed
	// state and, when nonempty, the captured head. A generation requested
	// during the lease stays pending.
	CompleteRefresh(ctx context.Context, lease Lease, state, head string, admissions []Admission, now int64) error
}

Store persists integration state atomically per operation.

Jump to

Keyboard shortcuts

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