domain

package
v0.1.2 Latest Latest
Warning

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

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

Documentation

Overview

Package domain holds Githome's business logic and value objects. It sits between the API surface and the store: handlers call domain services, domain services call the store, and the result is a domain value the presenter renders. The single dependency path is api -> domain -> store; domain never imports api or presenter.

Index

Constants

View Source
const (
	RollupError    = "ERROR"
	RollupFailure  = "FAILURE"
	RollupPending  = "PENDING"
	RollupSuccess  = "SUCCESS"
	RollupExpected = "EXPECTED"
)

Rollup and combined-status states, worst first. A rollup takes the worst state present across every status and check run; the combined status uses the statuses-only subset (error folds into failure there).

View Source
const (
	EventPush              = "push"
	EventIssues            = "issues"
	EventIssueComment      = "issue_comment"
	EventPullRequest       = "pull_request"
	EventPullRequestReview = "pull_request_review"
)

Webhook and Events API event names. These are the GitHub event identifiers the X-GitHub-Event delivery header and the Events API "type" both derive from.

View Source
const (
	JobDeliverEvent   = "deliver_event"
	JobDeliverWebhook = "deliver_webhook"
)

The fan-out job kinds the webhook milestone introduces. deliver_event loads one recorded event, renders its payload, and enqueues a delivery per subscribed hook; deliver_webhook performs one POST and records the result. The handlers live in the webhook package, which may import domain and presenter as a leaf consumer; defining the kinds here keeps the producer and the constants together the same way the push and recompute kinds are.

View Source
const (
	JobReindexSearch           = "reindex_search"
	JobRecomputeMergeability   = "recompute_mergeability"
	JobRecomputeReviewDecision = "recompute_review_decision"
)

The job kinds the push sink enqueues. The workers that consume them land with the milestones that own each kind (webhook fan-out in M7, mergeability in M5, search in a later pass); defining the kinds here, where they are produced, lets the worker package depend on domain rather than the other way around.

View Source
const (
	ReviewPending          = "PENDING"
	ReviewApproved         = "APPROVED"
	ReviewChangesRequested = "CHANGES_REQUESTED"
	ReviewCommented        = "COMMENTED"
	ReviewDismissed        = "DISMISSED"
)

Review states, the values a review row carries and the API renders.

View Source
const (
	EventApprove        = "APPROVE"
	EventRequestChanges = "REQUEST_CHANGES"
	EventComment        = "COMMENT"
)

Review events, the verbs a submit names (the gh pr review flags map onto them).

View Source
const ZeroSHA = "0000000000000000000000000000000000000000"

ZeroSHA is the all-zero object id git uses in a post-receive line to mark a created ref (as the old value) or a deleted ref (as the new value).

Variables

View Source
var (
	// ErrForbidden is returned when the actor may see the repository but not
	// write to it.
	ErrForbidden = errors.New("domain: write access denied")

	// ErrInvalidRef is returned for a ref name that is not a fully qualified,
	// well-formed reference.
	ErrInvalidRef = errors.New("domain: invalid reference name")

	// ErrRefExists is returned by CreateRef when the reference already exists.
	ErrRefExists = errors.New("domain: reference already exists")

	// ErrRefNotFound is returned by UpdateRef when the reference does not exist.
	ErrRefNotFound = errors.New("domain: reference not found")

	// ErrObjectMissing is returned when the target sha is not an object in the
	// repository.
	ErrObjectMissing = errors.New("domain: target object does not exist")

	// ErrNotFastForward is returned when a non-force update would not be a
	// fast-forward.
	ErrNotFastForward = errors.New("domain: update is not a fast-forward")
)

The git-ref write errors the REST layer maps to status: a forbidden write on a visible repository is a 403, an invalid ref name or a missing target object is a 422, an already-existing ref on create and a non-fast-forward update without force are 422 (GitHub reports both as unprocessable).

View Source
var (
	// ErrIssueNotFound is returned when no issue matches the lookup in a visible
	// repository.
	ErrIssueNotFound = errors.New("domain: issue not found")

	// ErrCommentNotFound is returned when no comment matches the lookup.
	ErrCommentNotFound = errors.New("domain: comment not found")

	// ErrLabelNotFound is returned when no label matches the lookup.
	ErrLabelNotFound = errors.New("domain: label not found")

	// ErrMilestoneNotFound is returned when no milestone matches the lookup.
	ErrMilestoneNotFound = errors.New("domain: milestone not found")

	// ErrValidation is returned for an edit that violates a field rule (an empty
	// title, an unknown state, a bad reaction content).
	ErrValidation = errors.New("domain: validation failed")

	// ErrLabelExists is returned by CreateLabel when the name is already taken.
	ErrLabelExists = errors.New("domain: label already exists")
)

The issue service errors the REST and GraphQL layers map to status. An issue in an invisible repository is reported as not found through the repository resolution, so the repo's existence never leaks; a missing issue in a visible repository is ErrIssueNotFound (404). A write the actor may not perform is ErrForbidden (403, reusing the git-write sentinel); a malformed edit is ErrValidation (422).

View Source
var (
	// ErrPullNotFound is returned when no pull request matches the lookup in a
	// visible repository.
	ErrPullNotFound = errors.New("domain: pull request not found")

	// ErrNotMergeable is returned when a pull request cannot be merged: it is
	// closed, already merged, conflicting, or has nothing to merge.
	ErrNotMergeable = errors.New("domain: pull request is not mergeable")

	// ErrHeadMismatch is returned when a merge's expected head sha does not match
	// the pull request's current head.
	ErrHeadMismatch = errors.New("domain: head sha does not match")

	// ErrInvalidMergeMethod is returned for a merge_method other than merge,
	// squash, or rebase.
	ErrInvalidMergeMethod = errors.New("domain: invalid merge method")
)

The pull request service errors the REST and GraphQL layers map to status. A pull request missing in a visible repository is ErrPullNotFound (404); a merge of a pull request that cannot land (closed, already merged, conflicting, or nothing to merge) is ErrNotMergeable (405); a merge whose expected head sha no longer matches is ErrHeadMismatch (409); a bad merge_method is ErrInvalidMergeMethod (422). Forbidden writes and validation reuse the shared sentinels.

View Source
var (
	// ErrRepoNotFound is returned when no repository matches the lookup or the
	// actor is not allowed to see it.
	ErrRepoNotFound = errors.New("domain: repository not found")

	// ErrGitNotFound is returned when a ref, revision, object id, or path does
	// not resolve within a repository.
	ErrGitNotFound = errors.New("domain: git object or path not found")

	// ErrEmptyRepo is returned by head-dependent reads on a repository that has
	// no commits yet.
	ErrEmptyRepo = errors.New("domain: repository is empty")

	// ErrBlobTooLarge is returned when a blob or file read exceeds the server's
	// blob size ceiling. The REST layer maps it to a 403 too_large.
	ErrBlobTooLarge = errors.New("domain: blob exceeds size limit")
)

The repo service errors. The REST layer maps them to status: a repository the actor cannot see is reported as not found rather than forbidden, so a private repo's existence never leaks through a 403. An empty repository is not an error for branch and tag listings (they come back empty) but is a 404 for the commit, tree, blob, and contents reads that need a head commit.

View Source
var (
	// ErrReviewNotFound is returned when no review or review comment matches the
	// lookup in a visible repository.
	ErrReviewNotFound = errors.New("domain: review not found")

	// ErrPendingReviewExists is returned when a user who already holds a pending
	// review draft on a pull request tries to open a second one.
	ErrPendingReviewExists = errors.New("domain: a pending review already exists")
)

The review service errors the REST and GraphQL layers map to status. A review or comment missing in a visible repository is ErrReviewNotFound (404); an anchor that is not part of the pull request's diff, a missing body where one is required, or an attempt to self-approve is ErrValidation (422). Forbidden writes reuse the shared ErrForbidden.

View Source
var ErrCheckNotFound = errors.New("domain: check run not found")

ErrCheckNotFound is returned when no check run matches the lookup in a visible repository.

View Source
var ErrHookNotFound = errors.New("domain: hook not found")

ErrHookNotFound is returned when no webhook or delivery matches the lookup in a repository the actor administers.

View Source
var ErrSearchScopeRequired = errors.New("domain: code search requires a repo, user, or org qualifier")

ErrSearchScopeRequired is returned by the code search when the query names no repository or owner to scope the walk. GitHub rejects an unscoped code search the same way, since it cannot grep every repository on the host; the REST layer maps this to 422.

View Source
var ErrUserNotFound = errors.New("domain: user not found")

ErrUserNotFound is returned when no account matches the lookup.

Functions

This section is empty.

Types

type Batcher added in v0.1.1

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

Batcher provides cross-domain batch loading for the GraphQL dataloaders. It is stateless; per-request memoization is handled by the dataloaders. The methods return domain types so the GraphQL layer never imports the store.

func NewBatcher added in v0.1.1

func NewBatcher(s batcherStore) *Batcher

NewBatcher returns a Batcher backed by s.

func (*Batcher) AssigneesByIssues added in v0.1.1

func (b *Batcher) AssigneesByIssues(ctx context.Context, issuePKs []int64) (map[int64][]*User, error)

AssigneesByIssues loads assignees for the given issue PKs. It resolves assignee user PKs in one additional batch load. Returns a map from issue PK to the ordered slice of domain Users.

func (*Batcher) LabelsByIssues added in v0.1.1

func (b *Batcher) LabelsByIssues(ctx context.Context, issuePKs []int64) (map[int64][]*Label, error)

LabelsByIssues loads labels for the given issue PKs and returns a map from issue PK to the ordered slice of domain Labels.

func (*Batcher) Users added in v0.1.1

func (b *Batcher) Users(ctx context.Context, pks []int64) (map[int64]*User, error)

Users loads users by primary key and returns a map from PK to domain User. PKs that have no live row are absent from the map (ghost authors).

type CheckRun

type CheckRun struct {
	PK            int64
	ID            int64
	SuitePK       int64
	RepoPK        int64
	HeadSHA       string
	Name          string
	Status        string
	Conclusion    *string
	DetailsURL    *string
	ExternalID    *string
	OutputTitle   *string
	OutputSummary *string
	OutputText    *string
	StartedAt     *time.Time
	CompletedAt   *time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

CheckRun is one named check against a head sha inside a suite. Status is queued, in_progress, or completed; Conclusion is set once completed.

type CheckRunInput

type CheckRunInput struct {
	Name          string
	HeadSHA       string
	Status        string
	Conclusion    string
	DetailsURL    string
	ExternalID    string
	OutputTitle   string
	OutputSummary string
	OutputText    string
}

CheckRunInput is the create or update payload for a check run.

type CheckSuite

type CheckSuite struct {
	PK         int64
	ID         int64
	RepoPK     int64
	HeadSHA    string
	AppSlug    string
	Status     string
	Conclusion *string
	Runs       []*CheckRun
	CreatedAt  time.Time
	UpdatedAt  time.Time
}

CheckSuite is the per-app container for the check runs reported against a head sha.

type ChecksService

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

ChecksService implements the commit status and check run subsystem. Both are reported against a head sha by a client with write access and read back per sha to form the combined status and the status check rollup. The service leans on the repo service for visibility and authorization and on the git store to resolve a ref to the sha a status or check anchors to.

func NewChecksService

func NewChecksService(st checksStore, repos *RepoService, issues *IssueService, gs *git.Store) *ChecksService

NewChecksService builds a ChecksService over the store, the repo and issue services, and the git store.

func (*ChecksService) CombinedStatus

func (s *ChecksService) CombinedStatus(ctx context.Context, viewerPK int64, owner, name, ref string) (*CombinedStatus, error)

CombinedStatus folds the latest status per context against a ref into one state, the body of the combined-status endpoint.

func (*ChecksService) CreateCheckRun

func (s *ChecksService) CreateCheckRun(ctx context.Context, actorPK int64, owner, name string, in CheckRunInput) (*CheckRun, error)

CreateCheckRun reports a check run against a head sha, resolving or creating its suite first. It needs write access.

func (*ChecksService) CreateStatus

func (s *ChecksService) CreateStatus(ctx context.Context, actorPK int64, owner, name, sha string, in StatusInput) (*CommitStatus, error)

CreateStatus reports a commit status against a sha. It needs write access, validates the state, writes the row, and enqueues a decision recompute for every open pull request whose head is that sha so their rollup refreshes.

func (*ChecksService) GetCheckRun

func (s *ChecksService) GetCheckRun(ctx context.Context, viewerPK int64, owner, name string, runDBID int64) (*CheckRun, error)

GetCheckRun resolves one check run by id for the viewer.

func (*ChecksService) ListCheckRuns

func (s *ChecksService) ListCheckRuns(ctx context.Context, viewerPK int64, owner, name, ref string) ([]*CheckRun, string, error)

ListCheckRuns returns every check run reported against a ref for the viewer.

func (*ChecksService) ListCheckSuites

func (s *ChecksService) ListCheckSuites(ctx context.Context, viewerPK int64, owner, name, ref string) ([]*CheckSuite, string, error)

ListCheckSuites returns the check suites reported against a ref for the viewer, each carrying its check runs.

func (*ChecksService) ListStatuses

func (s *ChecksService) ListStatuses(ctx context.Context, viewerPK int64, owner, name, ref string) ([]*CommitStatus, string, error)

ListStatuses returns every status reported against a ref for the viewer.

func (*ChecksService) Rollup

func (s *ChecksService) Rollup(ctx context.Context, viewerPK int64, owner, name, ref string) (*StatusCheckRollup, error)

Rollup folds a ref's statuses and check runs into the status check rollup for the viewer, the value the pull request and its head commit surface.

func (*ChecksService) RollupForPull

func (s *ChecksService) RollupForPull(ctx context.Context, repo *Repo, headSHA string) (*StatusCheckRollup, error)

RollupForPull builds the rollup at a pull request's recorded head, the path the GraphQL pull request resolver takes without re-resolving a ref.

func (*ChecksService) UpdateCheckRun

func (s *ChecksService) UpdateCheckRun(ctx context.Context, actorPK int64, owner, name string, runDBID int64, in CheckRunInput) (*CheckRun, error)

UpdateCheckRun rolls a check run forward, the transition a run finishing or reporting progress performs. It needs write access.

type CodeResult

type CodeResult struct {
	Repo *Repo
	Path string
	Name string
	SHA  string
}

CodeResult is one matching file from a code search: the repository it lives in, its path within the head tree, and the blob's object id. The presenter renders the GitHub code-search item (name, path, sha, urls, repository) from it.

type CombinedStatus

type CombinedStatus struct {
	State      string // failure | pending | success
	SHA        string
	TotalCount int
	Statuses   []*CommitStatus
	Repo       *Repo
}

CombinedStatus folds the latest status per context into one state, the body of the combined-status endpoint. TotalCount is the number of contributing statuses; Statuses is the latest one per context.

type Comment

type Comment struct {
	ID          int64
	IssuePK     int64
	IssueNumber int64
	User        *User
	Body        string
	Reactions   ReactionRollup
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

Comment is the domain view of an issue comment. IssueNumber is the per-repo number of the issue the comment belongs to, carried so the presenter can build the comment's issue and html URLs without a second lookup.

type CommitStatus

type CommitStatus struct {
	PK          int64
	ID          int64
	RepoPK      int64
	SHA         string
	State       string // error | failure | pending | success
	Context     string
	TargetURL   *string
	Description *string
	Creator     *User
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

CommitStatus is one external report against a sha under a context.

type DeliverEventPayload

type DeliverEventPayload struct {
	EventPK int64        `json:"event_pk"`
	Push    *PushPayload `json:"push,omitempty"`
}

DeliverEventPayload is the body of a deliver_event job: the recorded event to fan out, plus the push detail that has no home in a table and so rides along.

type DeliverWebhookPayload

type DeliverWebhookPayload struct {
	WebhookPK   int64        `json:"webhook_pk"`
	EventPK     int64        `json:"event_pk"`
	Push        *PushPayload `json:"push,omitempty"`
	RedeliverOf int64        `json:"redeliver_of,omitempty"`
}

DeliverWebhookPayload is the body of a deliver_webhook job: the hook to POST to and the event whose body to render and send. Push carries the moved refs a push event has no table to reload from, propagated from the deliver_event job so each hook's body renders the same push. RedeliverOf, when set, replays a recorded delivery instead: the worker re-sends its stored request rather than rendering the event afresh.

type Event

type Event struct {
	ID        int64
	Type      string
	Actor     *User
	Repo      *Repo
	Payload   json.RawMessage
	Public    bool
	CreatedAt time.Time
}

Event is the domain view of one activity-feed entry: the resolved actor and repository, the GitHub event type, and the payload object the fan-out worker rendered and stored. Payload is the raw JSON the feed serves verbatim.

type EventService

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

EventService serves the pull-based activity feed: the global public timeline, a repository's timeline (gated by the same visibility rule as every other repo read), and a user's timeline. It reads the rendered payload the fan-out worker stored on each event, so the feed never re-derives what a delivery already built.

func NewEventService

func NewEventService(st EventStore, repos *RepoService) *EventService

NewEventService builds an EventService over the store and the repo service.

func (*EventService) PublicFeed

func (s *EventService) PublicFeed(ctx context.Context, perPage int) ([]Event, error)

PublicFeed returns the global public timeline: events on public repositories that are themselves public, newest first.

func (*EventService) RepoFeed

func (s *EventService) RepoFeed(ctx context.Context, viewerPK int64, owner, name string, perPage int) ([]Event, error)

RepoFeed returns a repository's timeline. The visibility check is the repo service's: a viewer who cannot see the repository gets ErrRepoNotFound, never a leak, and a viewer who can see a private repository sees its private events.

func (*EventService) UserFeed

func (s *EventService) UserFeed(ctx context.Context, viewerPK int64, login string, perPage int) ([]Event, error)

UserFeed returns the events a user performed. A viewer reading their own feed sees their private activity; any other viewer sees only the public subset.

type EventStore

type EventStore interface {
	ListEvents(ctx context.Context, f store.EventFilter) ([]store.EventRow, error)
	UserByLogin(ctx context.Context, login string) (*store.UserRow, error)
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
}

EventStore is the slice of the store the activity feed reads through: the scoped event list, the login lookup the per-user feed resolves an actor by, and the by-pk lookups that resolve each event's actor and repository into the compact objects the feed embeds.

type GitEndpoint

type GitEndpoint struct {
	Label string
	Ref   string
	SHA   string
	Repo  *Repo
	User  *User
}

GitEndpoint is one side of a pull request, a base or a head. Label is the "owner:branch" form GitHub renders; Ref is the short branch name; SHA is the tip the pull request recorded. Repo and User are the repository the ref lives in and its owner; for a same-repository pull request both sides point at the one repository.

type Hook

type Hook struct {
	ID           int64
	Name         string
	Active       bool
	Events       []string
	Config       HookConfig
	LastResponse *HookResponse
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

Hook is the domain view of a repository webhook the API returns. It never carries the signing secret: HasSecret reports only whether one is set, so the secret cannot leak through a presenter. The delivery worker reads the secret straight from the store, off this path.

type HookConfig

type HookConfig struct {
	URL         string
	ContentType string
	InsecureSSL bool
	HasSecret   bool
}

HookConfig is the transport configuration of a hook minus the secret.

type HookDelivery

type HookDelivery struct {
	ID           int64
	GUID         string
	Event        string
	Action       *string
	StatusCode   int
	Status       string
	Duration     float64
	Redelivery   bool
	RepositoryID *int64
	DeliveredAt  time.Time
	Request      *HookDeliveryHTTP
	Response     *HookDeliveryHTTP
}

HookDelivery is the domain view of one recorded delivery attempt. Request and Response are populated only for the single-delivery detail view; the list view leaves them nil.

type HookDeliveryHTTP

type HookDeliveryHTTP struct {
	Headers map[string]string
	Body    string
}

HookDeliveryHTTP is one half (request or response) of a delivery's full record: the headers and the body.

type HookInput

type HookInput struct {
	Name        string
	URL         string
	ContentType string
	Secret      *string
	InsecureSSL bool
	Active      *bool
	Events      []string
}

HookInput is the create payload: the delivery URL, the content type, an optional signing secret, the TLS-verification setting, the active flag, and the subscribed event names. An empty event list defaults to push, matching GitHub.

type HookPatch

type HookPatch struct {
	URL          *string
	ContentType  *string
	Secret       *string
	InsecureSSL  *bool
	Active       *bool
	Events       *[]string
	AddEvents    []string
	RemoveEvents []string
}

HookPatch is the edit payload. A nil field is left unchanged. AddEvents and RemoveEvents adjust the subscription incrementally the way the REST config endpoint does, applied after a wholesale Events replacement.

type HookResponse

type HookResponse struct {
	Code    *int
	Status  string
	Message *string
}

HookResponse summarizes a hook's most recent delivery, the value the API reports as last_response.

type HookService

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

HookService manages a repository's webhooks. Webhook administration needs the same authority as writing to the repository, so every method authorizes write access first; the secret a hook carries is held but never returned, so the service maps each row to a domain view that omits it.

func NewHookService

func NewHookService(st HookStore, repos *RepoService, enq worker.Enqueuer) *HookService

NewHookService builds a HookService over the store and the repo service.

func (*HookService) CreateHook

func (s *HookService) CreateHook(ctx context.Context, actorPK int64, owner, name string, in HookInput) (*Hook, error)

CreateHook registers a webhook on the repository after authorizing write access and validating the URL and content type.

func (*HookService) DeleteHook

func (s *HookService) DeleteHook(ctx context.Context, actorPK int64, owner, name string, hookID int64) error

DeleteHook removes a webhook and its deliveries.

func (*HookService) GetHook

func (s *HookService) GetHook(ctx context.Context, actorPK int64, owner, name string, hookID int64) (*Hook, error)

GetHook resolves one webhook by its public id, secret omitted.

func (*HookService) GetHookDelivery

func (s *HookService) GetHookDelivery(ctx context.Context, actorPK int64, owner, name string, hookID, deliveryID int64) (*HookDelivery, error)

GetHookDelivery resolves one delivery of a webhook by its public id, with the full request and response record.

func (*HookService) ListHookDeliveries

func (s *HookService) ListHookDeliveries(ctx context.Context, actorPK int64, owner, name string, hookID int64, perPage int) ([]HookDelivery, error)

ListHookDeliveries returns a webhook's recent deliveries, newest first.

func (*HookService) ListHooks

func (s *HookService) ListHooks(ctx context.Context, actorPK int64, owner, name string) ([]Hook, error)

ListHooks returns the repository's webhooks, secrets omitted.

func (*HookService) RedeliverHookDelivery

func (s *HookService) RedeliverHookDelivery(ctx context.Context, actorPK int64, owner, name string, hookID, deliveryID int64) error

RedeliverHookDelivery enqueues a replay of a recorded delivery: the worker re-sends the stored request and records a new delivery marked as a redelivery.

func (*HookService) UpdateHook

func (s *HookService) UpdateHook(ctx context.Context, actorPK int64, owner, name string, hookID int64, p HookPatch) (*Hook, error)

UpdateHook applies a patch to a webhook and returns its domain view.

type HookStore

type HookStore interface {
	InsertWebhook(ctx context.Context, w *store.WebhookRow) error
	GetWebhookForRepo(ctx context.Context, repoPK, dbID int64) (*store.WebhookRow, error)
	ListWebhooks(ctx context.Context, repoPK int64) ([]store.WebhookRow, error)
	UpdateWebhook(ctx context.Context, w *store.WebhookRow) error
	DeleteWebhook(ctx context.Context, repoPK, dbID int64) error
	ListDeliveries(ctx context.Context, webhookPK int64, limit int) ([]store.WebhookDeliveryRow, error)
	GetDeliveryForWebhook(ctx context.Context, webhookPK, dbID int64) (*store.WebhookDeliveryRow, error)
}

HookStore is the slice of the store the hook service writes and reads through: the webhook CRUD, its delivery history, and the queue the redeliver replays through.

type Issue

type Issue struct {
	PK     int64
	ID     int64
	RepoPK int64
	RepoID int64

	Number      int64
	Title       string
	Body        *string
	State       string
	StateReason *string
	Locked      bool

	User          *User
	UserPK        int64 // internal PK for the author; zero for a ghost
	Assignees     []*User
	Labels        []*Label
	Milestone     *Milestone
	ClosedBy      *User
	Reactions     ReactionRollup
	CommentsCount int

	ClosedAt  *time.Time
	CreatedAt time.Time
	UpdatedAt time.Time
	// contains filtered or unexported fields
}

Issue is the domain view of an issue, assembled with its author and the related rows GitHub embeds on the issue resource: labels, assignees, the milestone, and the reaction rollup. ID is the public database id (issues.db_id); PK is the internal primary key the service carries for the write paths and never renders. RepoPK and RepoID locate the repository the presenter builds the issue's URLs from.

type IssueHit

type IssueHit struct {
	Issue *Issue
	Repo  *Repo
}

IssueHit pairs a matched issue with the repository it belongs to, so the presenter can build the issue's URLs (which hang off the owner/name path) without a second lookup. Cross-repository search returns issues from many repositories, none of which is implied by the request path.

type IssueInput

type IssueInput struct {
	Title           string
	Body            *string
	Labels          []string
	AssigneeLogins  []string
	MilestoneNumber *int64
}

IssueInput is the create payload: a title, an optional body, and the labels, assignees, and milestone to attach. Labels and assignees are named (label names, user logins) the way the REST and GraphQL inputs name them; unknown names are skipped, matching GitHub.

type IssuePatch

type IssuePatch struct {
	Title           *string
	Body            *string
	State           *string
	StateReason     *string
	Labels          *[]string
	AssigneeLogins  *[]string
	MilestoneNumber *int64 // a pointer-to-zero clears the milestone
	ClearMilestone  bool
}

IssuePatch is the edit payload. A nil field is left unchanged; a non-nil field is written. State moves through Open/Closed and carries StateReason (completed, not_planned, reopened).

type IssueQuery

type IssueQuery struct {
	State           string
	Labels          []string
	CreatorLogin    string
	AssigneeLogin   string
	MilestoneNumber *int64
	Sort            string
	Direction       string
	Page            int
	PerPage         int
	// Cursor is an opaque token from the previous page's Link header. When set
	// and the sort is "created" DESC (the default), the store uses a keyset
	// seek instead of OFFSET so deep pages are O(1) in depth.
	Cursor string
}

IssueQuery narrows the list endpoint.

type IssueService

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

IssueService implements the issue subsystem over the store, reusing the repo service for repository resolution so the visibility and write rules stay in one place. Writes that touch several rows run inside one transaction; a create also records an `issues` webhook event in the durable queue, delivered when the webhook milestone lands.

func NewIssueService

func NewIssueService(st IssueStore, repos *RepoService) *IssueService

NewIssueService builds an IssueService over the store and the repo service.

func (*IssueService) CreateComment

func (s *IssueService) CreateComment(ctx context.Context, actorPK int64, owner, name string, number int64, body string) (*Comment, error)

CreateComment adds a comment to an issue. Any authenticated viewer who can see the repository may comment; the post-receive write rule does not apply, so a reader of a public repository can join the discussion. The comment bumps the issue's cached count in the same transaction the insert runs in.

func (*IssueService) CreateCommentReaction

func (s *IssueService) CreateCommentReaction(ctx context.Context, actorPK int64, owner, name string, commentID int64, content string) (*Reaction, error)

CreateCommentReaction adds the actor's reaction to an issue comment.

func (*IssueService) CreateIssue

func (s *IssueService) CreateIssue(ctx context.Context, actorPK int64, owner, name string, in IssueInput) (*Issue, error)

CreateIssue opens an issue in the repository after authorizing write access. It allocates the per-repo number, inserts the row, attaches the resolved labels and assignees, bumps the repository's open-issue count, and enqueues the `issues` opened event, all in one transaction plus the durable enqueue.

func (*IssueService) CreateIssueReaction

func (s *IssueService) CreateIssueReaction(ctx context.Context, actorPK int64, owner, name string, number int64, content string) (*Reaction, error)

CreateIssueReaction adds the actor's reaction to an issue. The content must be one of GitHub's eight reaction names. Reacting twice with the same content is idempotent: the existing reaction comes back rather than a duplicate.

func (*IssueService) CreateLabel

func (s *IssueService) CreateLabel(ctx context.Context, actorPK int64, owner, name string, in LabelInput) (*Label, error)

CreateLabel adds a label to a repository. Write access is required. A name that already exists (case-insensitively) is a conflict.

func (*IssueService) CreateMilestone

func (s *IssueService) CreateMilestone(ctx context.Context, actorPK int64, owner, name string, in MilestoneInput) (*Milestone, error)

CreateMilestone adds a milestone to a repository. Write access is required.

func (*IssueService) DeleteComment

func (s *IssueService) DeleteComment(ctx context.Context, actorPK int64, owner, name string, commentID int64) error

DeleteComment removes a comment. The author or a user with write access may delete; the issue's cached count is decremented in the store.

func (*IssueService) DeleteCommentReaction

func (s *IssueService) DeleteCommentReaction(ctx context.Context, actorPK int64, owner, name string, commentID, reactionID int64) error

DeleteCommentReaction removes a reaction from a comment by its public id.

func (*IssueService) DeleteIssueReaction

func (s *IssueService) DeleteIssueReaction(ctx context.Context, actorPK int64, owner, name string, number, reactionID int64) error

DeleteIssueReaction removes a reaction from an issue by its public id.

func (*IssueService) DeleteLabel

func (s *IssueService) DeleteLabel(ctx context.Context, actorPK int64, owner, name, label string) error

DeleteLabel removes a label by name.

func (*IssueService) DeleteMilestone

func (s *IssueService) DeleteMilestone(ctx context.Context, actorPK int64, owner, name string, number int64) error

DeleteMilestone removes a milestone by number; issues pointing at it have their milestone cleared by the store's foreign key.

func (*IssueService) EditComment

func (s *IssueService) EditComment(ctx context.Context, actorPK int64, owner, name string, commentID int64, body string) (*Comment, error)

EditComment changes a comment's body. The author or a user with write access to the repository may edit.

func (*IssueService) EditIssue

func (s *IssueService) EditIssue(ctx context.Context, actorPK int64, owner, name string, number int64, p IssuePatch) (*Issue, error)

EditIssue applies a patch to an issue under the optimistic lock, retrying once on a lost race. It writes the state transition (stamping or clearing closed_at and closed_by), replaces labels and assignees when the patch names them, adjusts the open-issue count on a state change, records a timeline event for the transition, and enqueues the matching `issues` action.

func (*IssueService) GetComment

func (s *IssueService) GetComment(ctx context.Context, viewerPK int64, owner, name string, commentID int64) (*Comment, error)

GetComment resolves a single comment by its public id, gating on the repository the comment's issue belongs to being visible to the viewer.

func (*IssueService) GetIssue

func (s *IssueService) GetIssue(ctx context.Context, viewerPK int64, owner, name string, number int64) (*Issue, error)

GetIssue resolves one issue by number for the viewer.

func (*IssueService) GetLabel

func (s *IssueService) GetLabel(ctx context.Context, viewerPK int64, owner, name, label string) (*Label, error)

GetLabel resolves a single label by name.

func (*IssueService) GetMilestone

func (s *IssueService) GetMilestone(ctx context.Context, viewerPK int64, owner, name string, number int64) (*Milestone, error)

GetMilestone resolves a single milestone by number.

func (*IssueService) IssueForEvent

func (s *IssueService) IssueForEvent(ctx context.Context, repo *Repo, issuePK int64) (*Issue, error)

IssueForEvent assembles an issue by internal pk for the webhook renderer. The repository is already resolved by the caller; no visibility check applies because the event was authorized when it was recorded.

func (*IssueService) IssueRef

func (s *IssueService) IssueRef(ctx context.Context, issueDBID int64) (owner, name string, number int64, err error)

IssueRef resolves an issue's public database id to the owner login, repository name, and per-repo number, the coordinates the write methods take. The GraphQL mutations decode an issue node id to its database id and resolve it here. It does not authorize: the write method the caller invokes next (EditIssue, CreateComment) enforces write access and repository visibility.

func (*IssueService) ListCommentReactions

func (s *IssueService) ListCommentReactions(ctx context.Context, viewerPK int64, owner, name string, commentID int64) ([]*Reaction, error)

ListCommentReactions returns a comment's reactions, oldest first.

func (*IssueService) ListComments

func (s *IssueService) ListComments(ctx context.Context, viewerPK int64, owner, name string, number, page, perPage int64) ([]*Comment, error)

ListComments returns a page of an issue's comments, oldest first.

func (*IssueService) ListIssueReactions

func (s *IssueService) ListIssueReactions(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]*Reaction, error)

ListIssueReactions returns an issue's reactions, oldest first.

func (*IssueService) ListIssues

func (s *IssueService) ListIssues(ctx context.Context, viewerPK int64, owner, name string, q IssueQuery) ([]*Issue, int, error)

ListIssues returns a page of the repository's issues plus the total matching the filter, for the pagination headers.

func (*IssueService) ListIssuesPage added in v0.1.2

func (s *IssueService) ListIssuesPage(ctx context.Context, viewerPK int64, owner, name string, q IssueQuery) ([]*Issue, bool, error)

ListIssuesPage returns a keyset-paginated page of the repository's issues plus whether a further page exists, without the COUNT that ListIssues runs for the page-number Link header. It is the flat read path for cursor walks: deep pages of a several-hundred-thousand-issue repo cost the page, not a full count plus a deep OFFSET scan. The caller routes here only when the cursor decoded, so the filter is keyset-eligible.

func (*IssueService) ListLabels

func (s *IssueService) ListLabels(ctx context.Context, viewerPK int64, owner, name string) ([]*Label, error)

ListLabels returns a repository's labels in name order.

func (*IssueService) ListMilestones

func (s *IssueService) ListMilestones(ctx context.Context, viewerPK int64, owner, name, state string) ([]*Milestone, error)

ListMilestones returns a repository's milestones filtered by state ("open"|"closed"|"all").

func (*IssueService) UpdateLabel

func (s *IssueService) UpdateLabel(ctx context.Context, actorPK int64, owner, name, current string, in LabelInput) (*Label, error)

UpdateLabel edits a label resolved by its current name. A rename onto another existing label's name is a conflict.

func (*IssueService) UpdateMilestone

func (s *IssueService) UpdateMilestone(ctx context.Context, actorPK int64, owner, name string, number int64, in MilestoneInput) (*Milestone, error)

UpdateMilestone edits a milestone resolved by number, including its open and closed state transition.

type IssueStore

type IssueStore interface {
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	UserByLogin(ctx context.Context, login string) (*store.UserRow, error)

	GetIssueByNumber(ctx context.Context, repoPK, number int64) (*store.IssueRow, error)
	GetIssueByPK(ctx context.Context, pk int64) (*store.IssueRow, error)
	GetIssueByDBID(ctx context.Context, dbID int64) (*store.IssueRow, error)
	ListIssues(ctx context.Context, repoPK int64, f store.IssueFilter) ([]store.IssueRow, error)
	ListIssuesPage(ctx context.Context, repoPK int64, f store.IssueFilter) ([]store.IssueRow, bool, error)
	CountIssues(ctx context.Context, repoPK int64, f store.IssueFilter) (int, error)
	LabelsByIssue(ctx context.Context, issuePK int64) ([]store.LabelRow, error)
	ListAssigneePKs(ctx context.Context, issuePK int64) ([]int64, error)
	LabelsByIssuePKs(ctx context.Context, issuePKs []int64) (map[int64][]store.LabelRow, error)
	AssigneesByIssuePKs(ctx context.Context, issuePKs []int64) (map[int64][]int64, error)
	UsersByPKs(ctx context.Context, pks []int64) (map[int64]*store.UserRow, error)
	MilestonesByPKs(ctx context.Context, pks []int64) (map[int64]*store.MilestoneRow, error)
	ReactionRollupsBySubjectPKs(ctx context.Context, subjectType string, subjectPKs []int64) (map[int64]store.ReactionRollup, error)

	ListLabels(ctx context.Context, repoPK int64) ([]store.LabelRow, error)
	GetLabel(ctx context.Context, repoPK int64, name string) (*store.LabelRow, error)
	LabelsByNames(ctx context.Context, repoPK int64, names []string) ([]store.LabelRow, error)
	InsertLabel(ctx context.Context, l *store.LabelRow) error
	UpdateLabel(ctx context.Context, l *store.LabelRow) error
	DeleteLabel(ctx context.Context, pk int64) error

	ListMilestones(ctx context.Context, repoPK int64, state string) ([]store.MilestoneRow, error)
	GetMilestoneByNumber(ctx context.Context, repoPK, number int64) (*store.MilestoneRow, error)
	GetMilestoneByPK(ctx context.Context, pk int64) (*store.MilestoneRow, error)
	InsertMilestone(ctx context.Context, m *store.MilestoneRow) error
	UpdateMilestone(ctx context.Context, m *store.MilestoneRow) error
	DeleteMilestone(ctx context.Context, pk int64) error
	MilestoneIssueCounts(ctx context.Context, milestonePK int64) (open, closed int, err error)

	ListIssueComments(ctx context.Context, issuePK int64, limit, offset int) ([]store.CommentRow, error)
	GetComment(ctx context.Context, dbID int64) (*store.CommentRow, error)
	UpdateComment(ctx context.Context, c *store.CommentRow) error
	DeleteComment(ctx context.Context, pk int64) error

	ReactionRollupFor(ctx context.Context, subjectType string, subjectPK int64) (store.ReactionRollup, error)
	ListReactions(ctx context.Context, subjectType string, subjectPK int64) ([]store.ReactionRow, error)
	InsertReaction(ctx context.Context, r *store.ReactionRow) (bool, error)
	DeleteReaction(ctx context.Context, subjectType string, subjectPK, dbID int64) error

	WithTx(ctx context.Context, fn func(*store.Tx) error) error
	EnqueueJob(ctx context.Context, j *store.JobRow) (bool, error)
	InsertEvent(ctx context.Context, e *store.EventRow) error
}

IssueStore is the slice of the store the issue service needs: the issue, comment, label, milestone, and reaction reads and writes, the transaction entry point the multi-statement writes run through, the user and repository lookups it resolves participants and locations with, and the job enqueue it records the webhook event through.

type Label

type Label struct {
	ID          int64
	Name        string
	Color       string
	Description *string
	Default     bool
}

Label is the domain view of a repository label.

type LabelInput

type LabelInput struct {
	Name        string
	Color       string
	Description *string
}

LabelInput is the create/update payload for a label.

type MergeInput

type MergeInput struct {
	Method        git.MergeMethod
	CommitTitle   string
	CommitMessage string
	ExpectedHead  string
}

MergeInput is the merge payload: the strategy, the optional commit title and message overriding the defaults, and the optional expected head sha that guards against merging a head that moved out from under the caller.

type MergeResult

type MergeResult struct {
	SHA     string
	Merged  bool
	Message string
}

MergeResult is the outcome of a successful merge: the new commit on the base branch and the message it carries.

type MergeState

type MergeState struct {
	Mergeable    *bool
	State        string
	Rebaseable   *bool
	Additions    int
	Deletions    int
	ChangedFiles int
	Commits      int
}

MergeState is the worker-derived merge state of a pull request: the tri-state mergeable, the GitHub mergeable_state string, the rebaseable flag, the diff stats, and the commit count. The worker computes it and SetMergeability persists it; nil Mergeable is the unknown state.

type Milestone

type Milestone struct {
	ID           int64
	Number       int64
	Title        string
	Description  *string
	State        string
	Creator      *User
	OpenIssues   int
	ClosedIssues int
	DueOn        *time.Time
	ClosedAt     *time.Time
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

Milestone is the domain view of a milestone, including the open and closed issue counts computed on read.

type MilestoneInput

type MilestoneInput struct {
	Title       *string
	Description *string
	State       *string
	DueOn       *time.Time
	ClearDueOn  bool
}

MilestoneInput is the create/update payload for a milestone. A nil field on update leaves the value unchanged; State moves between open and closed.

type PRInput

type PRInput struct {
	Title               string
	Body                *string
	Base                string
	Head                string
	Draft               bool
	MaintainerCanModify bool
}

PRInput is the create payload: the title and optional body, the base and head branch names, and the draft and maintainer flags. For M5 the head is a branch in the same repository; cross-repository forks arrive with their milestone.

type PRQuery

type PRQuery struct {
	State   string
	Page    int
	PerPage int
	Cursor  string
}

PRQuery narrows the list endpoint to a state (open, closed, all) and a page. Cursor, when set, is the opaque keyset token from the previous page's Link header, which switches the list to a number seek instead of OFFSET.

type PRService

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

PRService implements the pull request subsystem. It leans on the repo service for visibility and write authorization and on the issue service for the issue half of a pull request, so those rules live in one place. The git store backs the merge surface: the test merge behind mergeability, the three merge strategies, and the synthetic refs/pull/<n>/head and /merge refs.

func NewPRService

func NewPRService(st PullStore, repos *RepoService, issues *IssueService, gs *git.Store) *PRService

NewPRService builds a PRService over the store, the repo and issue services, and the git store.

func (*PRService) Commits

func (s *PRService) Commits(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]git.Commit, error)

Commits returns a pull request's own commits, oldest first.

func (*PRService) CreatePR

func (s *PRService) CreatePR(ctx context.Context, actorPK int64, owner, name string, in PRInput) (*PullRequest, error)

CreatePR opens a pull request after authorizing write access. It validates the base and head branches resolve and differ, allocates the shared issue number, writes the issue row (is_pull) and the pull extension in one transaction, publishes the synthetic refs/pull/<n>/head ref at the head tip, and enqueues the mergeability recompute so mergeable transitions from null to a value.

func (*PRService) Diff

func (s *PRService) Diff(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]byte, error)

Diff returns the pull request's unified diff, the .diff media body and what gh pr diff prints.

func (*PRService) Files

func (s *PRService) Files(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]git.FileChange, error)

Files returns the per-file diff of a pull request over the three-dot range from the base branch tip to the head, the body of the files endpoint.

func (*PRService) GetPR

func (s *PRService) GetPR(ctx context.Context, viewerPK int64, owner, name string, number int64) (*PullRequest, error)

GetPR resolves one pull request by number for the viewer.

func (*PRService) GetPRByID

func (s *PRService) GetPRByID(ctx context.Context, viewerPK, dbID int64) (*PullRequest, error)

GetPRByID resolves a pull request by its public database id for the viewer, the path a PullRequest node id decodes to. A pull request in a repository the viewer cannot see is ErrPullNotFound, never leaked.

func (*PRService) ListPRs

func (s *PRService) ListPRs(ctx context.Context, viewerPK int64, owner, name string, q PRQuery) ([]*PullRequest, int, error)

ListPRs returns a page of the repository's pull requests plus the total matching the state filter, for the pagination headers.

func (*PRService) ListPRsPage added in v0.1.2

func (s *PRService) ListPRsPage(ctx context.Context, viewerPK int64, owner, name string, q PRQuery) ([]*PullRequest, bool, error)

ListPRsPage returns a keyset-paginated page of the repository's pull requests plus whether a further page exists, without the COUNT that ListPRs runs for the page-number Link header. It is the flat read path for cursor walks: a malformed cursor decodes to nil and starts from the newest, matching the issue list's degrade-to-first-page behavior.

func (*PRService) Merge

func (s *PRService) Merge(ctx context.Context, actorPK int64, owner, name string, number int64, in MergeInput) (*MergeResult, error)

Merge lands a pull request by the given method after authorizing write access. It guards the expected head sha, refuses a closed, merged, conflicting, or empty merge, writes the merge commit, advances the base branch to it, closes the issue, and records the merge, all so a re-read reports merged true.

func (*PRService) OnHeadPush

func (s *PRService) OnHeadPush(ctx context.Context, repoPK int64, branch, newSHA string) error

OnHeadPush refreshes the pull requests a push to a branch touches. A push to a head branch repoints that pull request's recorded head and its refs/pull/<n>/head ref; a push to a base branch leaves the head alone. Either way every affected open pull request is re-checked for mergeability.

func (*PRService) Patch

func (s *PRService) Patch(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]byte, error)

Patch returns the pull request's commits as an mbox patch series, the .patch media body.

func (*PRService) PullForEvent

func (s *PRService) PullForEvent(ctx context.Context, repo *Repo, issuePK int64) (*PullRequest, error)

PullForEvent assembles a pull request by its issue pk for the webhook renderer. No visibility check applies: the event was authorized when it was recorded.

func (*PRService) RecomputeMergeability

func (s *PRService) RecomputeMergeability(ctx context.Context, issuePK int64) error

RecomputeMergeability resolves and persists a pull request's merge state: it test-merges the current base tip against the head, records mergeable, the mergeable_state string, the rebaseable flag, the diff stats, and the commit count, and on a clean merge publishes a real refs/pull/<n>/merge commit. It is the body of the recompute_mergeability worker; a merged pull request is left untouched.

type PullRequest

type PullRequest struct {
	PK      int64
	ID      int64
	IssueID int64
	Number  int64
	RepoPK  int64
	Repo    *Repo

	Title         string
	Body          *string
	State         string // open | closed
	Locked        bool
	User          *User
	Assignees     []*User
	Labels        []*Label
	Milestone     *Milestone
	CommentsCount int

	Base GitEndpoint
	Head GitEndpoint

	Draft               bool
	MaintainerCanModify bool

	Merged         bool
	MergedAt       *time.Time
	MergedBy       *User
	MergeCommitSHA *string
	Mergeable      *bool
	MergeableState string
	Rebaseable     *bool
	Additions      int
	Deletions      int
	ChangedFiles   int
	CommitsCount   int

	ClosedAt  *time.Time
	CreatedAt time.Time
	UpdatedAt time.Time
}

PullRequest is the domain view of a pull request: the issue it shares its number, title, body, and state with, plus the git coordinates and merge state the pull_requests extension carries. ID is the pull request's own public database id (pull_requests.db_id), the value a PullRequest node id decodes to; IssueID is the underlying issue row's db_id, which the REST id field renders, matching GitHub where a pull request and its issue share one id space.

Mergeable, Rebaseable, MergedBy, MergedAt, MergeCommitSHA, and ClosedAt are pointers because a pull request acquires them only over its lifetime; a nil Mergeable is the not-yet-computed state the API surfaces as null, the null-then-value contract the mergeability worker resolves.

type PullStore

type PullStore interface {
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	UserByLogin(ctx context.Context, login string) (*store.UserRow, error)
	RepoByPK(ctx context.Context, pk int64) (*store.RepoRow, error)

	GetIssueByNumber(ctx context.Context, repoPK, number int64) (*store.IssueRow, error)
	GetIssueByPK(ctx context.Context, pk int64) (*store.IssueRow, error)
	IssuesByPKs(ctx context.Context, pks []int64) (map[int64]*store.IssueRow, error)
	LabelsByIssue(ctx context.Context, issuePK int64) ([]store.LabelRow, error)
	ListAssigneePKs(ctx context.Context, issuePK int64) ([]int64, error)
	GetMilestoneByPK(ctx context.Context, pk int64) (*store.MilestoneRow, error)
	LabelsByIssuePKs(ctx context.Context, issuePKs []int64) (map[int64][]store.LabelRow, error)
	AssigneesByIssuePKs(ctx context.Context, issuePKs []int64) (map[int64][]int64, error)
	UsersByPKs(ctx context.Context, pks []int64) (map[int64]*store.UserRow, error)
	MilestonesByPKs(ctx context.Context, pks []int64) (map[int64]*store.MilestoneRow, error)

	GetPullByIssuePK(ctx context.Context, issuePK int64) (*store.PullRow, error)
	GetPullByDBID(ctx context.Context, dbID int64) (*store.PullRow, error)
	ListPulls(ctx context.Context, repoPK int64, state string, limit, offset int) ([]store.PullRow, error)
	ListPullsPage(ctx context.Context, repoPK int64, state string, cursor *store.PullCursor, limit int) ([]store.PullRow, bool, error)
	CountPulls(ctx context.Context, repoPK int64, state string) (int, error)
	OpenPullsByHeadRef(ctx context.Context, repoPK int64, headRef string) ([]store.PullRow, error)
	OpenPullsByBaseRef(ctx context.Context, repoPK int64, baseRef string) ([]store.PullRow, error)
	SetMergeability(ctx context.Context, issuePK int64, mergeable *bool, state string, rebaseable *bool, additions, deletions, changedFiles, commits int, checkedAt time.Time) error

	WithTx(ctx context.Context, fn func(*store.Tx) error) error
	EnqueueJob(ctx context.Context, j *store.JobRow) (bool, error)
	InsertEvent(ctx context.Context, e *store.EventRow) error
}

PullStore is the slice of the store the pull request service needs: the pull extension reads and writes, the issue, user, repository, label, and milestone lookups it assembles a pull request from, the transaction entry point the create and merge paths run through, and the job enqueue the mergeability recompute and the webhook event ride on.

type PushBatch

type PushBatch struct {
	RepoPK     int64
	PusherPK   int64
	Protocol   string // "http" | "ssh"
	Updates    []RefUpdate
	ReceivedAt time.Time
}

PushBatch is the parsed post-receive batch the transport hands to OnPush: the repository, the authenticated pusher, the transport, and the moved refs.

type PushPayload

type PushPayload struct {
	RepoPK   int64       `json:"repo_pk"`
	PusherPK int64       `json:"pusher_pk"`
	Protocol string      `json:"protocol"`
	Updates  []RefUpdate `json:"updates"`
}

PushPayload is the parsed push a deliver_event job carries so the renderer can build the push webhook body and the PushEvent feed entry from the moved refs.

type Reaction

type Reaction struct {
	ID        int64
	User      *User
	Content   string
	CreatedAt time.Time
}

Reaction is the domain view of a single reaction.

type ReactionRollup

type ReactionRollup struct {
	TotalCount int
	Counts     map[string]int
}

ReactionRollup is the per-content reaction count GitHub embeds on reactable objects. Counts is keyed by reaction content (+1, heart, ...); TotalCount is their sum.

type RefUpdate

type RefUpdate struct {
	Ref    string // fully qualified, e.g. "refs/heads/main"
	OldSHA string // ZeroSHA on create
	NewSHA string // ZeroSHA on delete
}

RefUpdate is one moved reference from a push.

func (RefUpdate) Created

func (u RefUpdate) Created() bool

Created reports whether the update brought a new ref into existence.

func (RefUpdate) Deleted

func (u RefUpdate) Deleted() bool

Deleted reports whether the update removed a ref.

type Repo

type Repo struct {
	PK      int64
	OwnerPK int64
	ID      int64
	Owner   *User

	Name          string
	Description   *string
	Homepage      *string
	Private       bool
	Fork          bool
	DefaultBranch string

	HasIssues    bool
	HasProjects  bool
	HasWiki      bool
	HasDownloads bool
	Archived     bool
	Disabled     bool
	IsTemplate   bool

	OpenIssuesCount int
	PushedAt        *time.Time
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Repo is the domain view of a repository. It is the presenter's input for the Repository wire model and the handle the git-data reads work through.

ID is the public database id (repositories.db_id), the value rendered as the REST "id". PK is the internal primary key; the git store shards bare repositories by it, so the service carries it for git access. The presenter never reads PK. OwnerPK is the owning user's internal pk, which the REST handler compares against the actor to decide the permissions block; it is also not rendered. Owner is the resolved owning account, the presenter's input for the embedded SimpleUser.

func (*Repo) FullName

func (r *Repo) FullName() string

FullName is the owner/name pair GitHub renders as full_name and uses to build the repository's URLs.

type RepoService

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

RepoService resolves repositories and reads their git data. It pairs the metadata store with the git object store: GetRepo authorizes and assembles the domain Repo, and the git-data methods open the repository's bare git store through the internal pk GetRepo carried.

func NewRepoService

func NewRepoService(st RepoStore, gs *git.Store) *RepoService

NewRepoService builds a RepoService over the metadata store and the git store. The push sink submits its jobs through a store-backed enqueuer built from the same store, so a push records its events in the durable queue.

func (*RepoService) AuthorizeWrite

func (s *RepoService) AuthorizeWrite(ctx context.Context, actorPK int64, owner, name string) (*Repo, error)

AuthorizeWrite resolves the repository for the actor and checks write access. Visibility is enforced by GetRepo, so the not-found-vs-forbidden distinction matches GitHub: invisible -> 404 (ErrRepoNotFound), visible-but-no-write -> 403 (ErrForbidden). The git transport calls it to gate receive-pack.

func (*RepoService) Contents

func (s *RepoService) Contents(repo *Repo, path, ref string) (git.PathResult, error)

Contents resolves a path at a ref. An empty ref reads the head commit. A blob yields a file result with content; a tree yields a directory listing.

func (*RepoService) CreateRef

func (s *RepoService) CreateRef(ctx context.Context, actorPK int64, owner, name, ref, sha string) (git.Ref, error)

CreateRef creates a fully qualified reference (refs/heads/x, refs/tags/x) at sha after authorizing write access. The repository is resolved through the visibility rule first, so a private repository the actor cannot see is ErrRepoNotFound (404) rather than ErrForbidden (403).

func (*RepoService) DefaultBranchRef

func (s *RepoService) DefaultBranchRef(repo *Repo) (git.Branch, error)

DefaultBranchRef resolves the repository's head branch. It returns ErrEmptyRepo when the repository has no commits, which the caller renders as a null default_branch ref rather than an error.

func (*RepoService) GetBlob

func (s *RepoService) GetBlob(repo *Repo, sha string) (git.Blob, error)

GetBlob loads a blob by its object id.

func (*RepoService) GetBranch

func (s *RepoService) GetBranch(repo *Repo, name string) (git.Branch, error)

GetBranch resolves a single branch by short name.

func (*RepoService) GetCommit

func (s *RepoService) GetCommit(repo *Repo, rev string) (git.Commit, error)

GetCommit loads a single commit by any revision (a sha, a branch or tag name, HEAD, or an expression like HEAD~2).

func (*RepoService) GetRef

func (s *RepoService) GetRef(repo *Repo, name string) (git.Ref, error)

GetRef resolves a single reference. The name carries the suffix the REST API uses (heads/main, tags/v1.0) or is fully qualified.

func (*RepoService) GetRepo

func (s *RepoService) GetRepo(ctx context.Context, viewerPK int64, owner, name string) (*Repo, error)

GetRepo resolves a repository by owner login and name for the given viewer (the authenticated user's internal pk, or 0 when anonymous). A private repository the viewer does not own is reported as ErrRepoNotFound, the same as a repository that does not exist, so a private repo never leaks through the status code.

func (*RepoService) GetRepoByID

func (s *RepoService) GetRepoByID(ctx context.Context, viewerPK, dbID int64) (*Repo, error)

GetRepoByID resolves a repository by its public database id for the viewer, applying the same visibility rule as GetRepo: a private repository the viewer cannot see is ErrRepoNotFound, never leaked. The GraphQL mutations decode a repository node id to this database id, then act through the owner-login and name path the rest of the domain uses.

func (*RepoService) GetTree

func (s *RepoService) GetTree(repo *Repo, rev string, recursive bool) (git.Tree, error)

GetTree loads a tree by any revision, optionally walking the whole subtree.

func (*RepoService) ListBranches

func (s *RepoService) ListBranches(repo *Repo) ([]git.Branch, error)

ListBranches lists the repository's branches in name order. An empty or uninitialized repository yields an empty slice, not an error.

func (*RepoService) ListCommits

func (s *RepoService) ListCommits(repo *Repo, opts git.LogOpts) ([]git.Commit, error)

ListCommits walks commit history from opts.From (defaulting to the head branch), optionally filtered to a path.

func (*RepoService) ListRefs

func (s *RepoService) ListRefs(repo *Repo) ([]git.Ref, error)

ListRefs lists every branch and tag ref, fully qualified, in name order.

func (*RepoService) ListTags

func (s *RepoService) ListTags(repo *Repo) ([]git.Tag, error)

ListTags lists the repository's tags in name order. An empty or uninitialized repository yields an empty slice, not an error.

func (*RepoService) OnPush

func (s *RepoService) OnPush(ctx context.Context, b PushBatch) error

OnPush is the single database-sync entry point after a push. It advances the repository's pushed_at, records a push event for webhook delivery, and enqueues a search reindex when the default branch moved. The mergeability recompute per affected pull request lands with the pull-request milestone that introduces the pull_requests table; the dedupe key it will use is reserved by the enqueue helper below. The git objects and refs are already written by the time OnPush runs, so a failure here is a metadata-lag bug, not data loss.

func (*RepoService) RepoForEvent

func (s *RepoService) RepoForEvent(ctx context.Context, repoPK int64) (*Repo, error)

RepoForEvent assembles a repository by internal pk with no visibility check, the system path the webhook renderer loads an event's repository through. The event was already authorized when it was recorded, so the renderer does not re-gate it.

func (*RepoService) UpdateRef

func (s *RepoService) UpdateRef(ctx context.Context, actorPK int64, owner, name, ref, sha string, force bool) (git.Ref, error)

UpdateRef moves an existing reference to sha after authorizing write access. Unless force is set the move must be a fast-forward.

type RepoStore

type RepoStore interface {
	RepoByOwnerName(ctx context.Context, owner, name string) (*store.RepoRow, error)
	RepoByPK(ctx context.Context, pk int64) (*store.RepoRow, error)
	RepoByDBID(ctx context.Context, dbID int64) (*store.RepoRow, error)
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	TouchRepoPushedAt(ctx context.Context, pk int64, at time.Time) error
	EnqueueJob(ctx context.Context, j *store.JobRow) (bool, error)
	InsertEvent(ctx context.Context, e *store.EventRow) error
}

RepoStore is the slice of the store the repo service needs. The write path (the post-receive sink) adds the repo-by-pk lookup, the pushed_at touch, and the job enqueue; enqueuing through the store keeps the domain on its single store dependency rather than importing the worker package.

type Review

type Review struct {
	PK               int64
	ID               int64
	PullPK           int64
	PullNumber       int64
	RepoPK           int64
	User             *User
	State            string
	Body             string
	CommitID         string
	DismissedMessage *string
	Comments         []*ReviewComment
	SubmittedAt      *time.Time
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

Review is the domain view of one review. ID is the review's own public database id (the value a PullRequestReview node id decodes to). State is one of the Review* constants; SubmittedAt is nil while the review is still a pending draft.

type ReviewComment

type ReviewComment struct {
	PK               int64
	ID               int64
	ReviewPK         int64
	ReviewID         int64
	PullPK           int64
	PullNumber       int64
	RepoPK           int64
	User             *User
	Path             string
	Side             string
	Line             *int64
	StartLine        *int64
	StartSide        *string
	Position         *int64
	OriginalPosition *int64
	CommitID         string
	OriginalCommitID string
	InReplyTo        *int64
	DiffHunk         string
	SubjectType      string
	Body             string
	Resolved         bool
	CreatedAt        time.Time
	UpdatedAt        time.Time
}

ReviewComment is the domain view of one inline comment. Line and StartLine are file line numbers in the line/side model; Position is the legacy 1-based diff offset. Both are filled in: the service resolves whichever the caller omitted from the pull request's diff. InReplyTo is the root comment's id when this is a reply.

type ReviewCommentInput

type ReviewCommentInput struct {
	Path      string
	Body      string
	Side      string
	Line      *int64
	StartSide string
	StartLine *int64
	Position  *int64
}

ReviewCommentInput is one inline comment in a review batch or a standalone comment. A caller gives either the line/side anchor or the legacy position; the service resolves the other from the diff.

type ReviewInput

type ReviewInput struct {
	Event    string
	Body     string
	CommitID string
	Comments []ReviewCommentInput
}

ReviewInput is the submit payload: the event (empty opens a pending draft), the review body, an optional commit the review pins to, and the batch of inline comments to attach.

type ReviewService

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

ReviewService implements the code review subsystem. It leans on the repo service for visibility and write authorization, on the pull request service to resolve and load a pull request, and on the issue service for user assembly. It reads the pull request's diff through the git store so it can resolve a comment anchor between the line/side and legacy position models and reject one that is not in the diff.

func NewReviewService

func NewReviewService(st reviewStore, repos *RepoService, prs *PRService, issues *IssueService, gs *git.Store) *ReviewService

NewReviewService builds a ReviewService over the store, the repo, pull request, and issue services, and the git store.

func (*ReviewService) CreateComment

func (s *ReviewService) CreateComment(ctx context.Context, actorPK int64, owner, name string, number int64, in ReviewCommentInput) (*ReviewComment, error)

CreateComment adds a standalone inline comment, the POST pulls/{n}/comments path. A standalone comment rides its own submitted, bodyless COMMENTED review, so every comment still belongs to a review.

func (*ReviewService) CreateReview

func (s *ReviewService) CreateReview(ctx context.Context, actorPK int64, owner, name string, number int64, in ReviewInput) (*Review, error)

CreateReview opens a review on a pull request. With an event it submits immediately (approved, changes requested, or commented); without one it opens the author's single pending draft. It authorizes read access, forbids self-approval, requires a body where the event needs one, resolves every inline anchor against the diff, writes the review and its comments in one transaction, and enqueues the decision recompute.

func (*ReviewService) DismissReview

func (s *ReviewService) DismissReview(ctx context.Context, actorPK int64, owner, name string, number, reviewDBID int64, message string) (*Review, error)

DismissReview drops a submitted review's approval or change request, recording a reason. It needs write access, the permission to override a reviewer.

func (*ReviewService) GetComment

func (s *ReviewService) GetComment(ctx context.Context, viewerPK int64, owner, name string, commentDBID int64) (*ReviewComment, error)

GetComment resolves one inline comment by id for the viewer. The standalone pulls/comments/{id} route carries no pull number, so the owning pull request's number is resolved from the comment to build its urls.

func (*ReviewService) GetReview

func (s *ReviewService) GetReview(ctx context.Context, viewerPK int64, owner, name string, number, reviewDBID int64) (*Review, error)

GetReview resolves one review by id for the viewer.

func (*ReviewService) ListComments

func (s *ReviewService) ListComments(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]*ReviewComment, error)

ListComments returns every inline comment on a pull request for the viewer.

func (*ReviewService) ListReviews

func (s *ReviewService) ListReviews(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]*Review, error)

ListReviews returns a pull request's submitted reviews for the viewer.

func (*ReviewService) RecomputeReviewDecision

func (s *ReviewService) RecomputeReviewDecision(ctx context.Context, issuePK int64) error

RecomputeReviewDecision resolves and caches a pull request's review decision and status check rollup, the body of the recompute_review_decision worker. A missing pull request is a no-op, the same tolerance the mergeability recompute has for a deleted row.

func (*ReviewService) ReplyComment

func (s *ReviewService) ReplyComment(ctx context.Context, actorPK int64, owner, name string, number, inReplyToDBID int64, body string) (*ReviewComment, error)

ReplyComment adds a reply under an existing comment's thread, the POST pulls/{n}/comments/{id}/replies path. The reply inherits the root's anchor.

func (*ReviewService) ResolveThread

func (s *ReviewService) ResolveThread(ctx context.Context, actorPK int64, owner, name string, number, rootDBID int64, resolved bool) (*ReviewThread, error)

ResolveThread resolves or unresolves a review thread by its root comment id. It needs write access, the permission to settle a conversation.

func (*ReviewService) ReviewDecision

func (s *ReviewService) ReviewDecision(ctx context.Context, viewerPK int64, owner, name string, number int64) (*string, error)

ReviewDecision returns a pull request's derived review decision for the viewer, the value the GraphQL field and the pull request view surface.

func (*ReviewService) ReviewThreads

func (s *ReviewService) ReviewThreads(ctx context.Context, viewerPK int64, owner, name string, number int64) ([]*ReviewThread, error)

ReviewThreads folds a pull request's inline comments into threads (a root and its replies) and marks each resolved and outdated, the shape the GraphQL reviewThreads connection renders. viewerPK gates repository visibility.

func (*ReviewService) SubmitReview

func (s *ReviewService) SubmitReview(ctx context.Context, actorPK int64, owner, name string, number, reviewDBID int64, event, body string) (*Review, error)

SubmitReview submits a previously opened pending review under an event, the path the reviews/{id}/events endpoint and gh pr review on a draft take.

func (*ReviewService) ThreadRef

func (s *ReviewService) ThreadRef(ctx context.Context, viewerPK, rootDBID int64) (owner, name string, number int64, err error)

ThreadRef decodes a review thread's root comment id into the owner, repo, and pull number the resolve and unresolve mutations address it by. It enforces the viewer's visibility through GetRepo, so a thread in a repository the viewer cannot see resolves as not found rather than leaking its existence.

type ReviewThread

type ReviewThread struct {
	RootPK     int64
	ID         int64
	PullPK     int64
	Path       string
	Line       *int64
	IsResolved bool
	IsOutdated bool
	Comments   []*ReviewComment
}

ReviewThread is a conversation: a root comment and the replies chained under it. ID is the root comment's thread node id. IsResolved follows the root's resolved flag; IsOutdated is true when the anchored line is no longer present in the pull request's current diff.

type SearchService

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

SearchService runs the three search surfaces over the store and git. Issue and repository search are filtered scans; code search walks the head tree of each in-scope repository. It reuses the repo and issue services to assemble the domain values it returns, so visibility and rendering stay defined in one place. The store scans already gate visibility by viewer, so a private repository never appears in another viewer's results.

func NewSearchService

func NewSearchService(st SearchStore, repos *RepoService, issues *IssueService, gs *git.Store) *SearchService

NewSearchService builds a SearchService over the store, the repo and issue services, and the git store code search reads blobs from.

func (*SearchService) SearchCode

func (s *SearchService) SearchCode(ctx context.Context, viewerPK int64, raw string, page, perPage int) ([]CodeResult, int, bool, error)

SearchCode walks the head tree of each repository the query scopes to and returns the files whose path or content matches the free-text terms. It requires a repo:, user:, or org: qualifier, since an unindexed walk cannot span every repository. The bool result reports whether the walk stopped at the scan limit before finishing, which becomes the envelope's incomplete_results.

func (*SearchService) SearchIssues

func (s *SearchService) SearchIssues(ctx context.Context, viewerPK int64, raw, sort, order string, page, perPage int) ([]IssueHit, int, error)

SearchIssues runs an issue/pull-request search for the viewer and returns the page of assembled issues plus the total match count for the envelope.

func (*SearchService) SearchRepositories

func (s *SearchService) SearchRepositories(ctx context.Context, viewerPK int64, raw, sort, order string, page, perPage int) ([]*Repo, int, error)

SearchRepositories runs a repository search for the viewer and returns the page of assembled repositories plus the total match count.

type SearchStore

type SearchStore interface {
	SearchIssues(ctx context.Context, q store.IssueSearch) ([]store.IssueRow, error)
	CountSearchIssues(ctx context.Context, q store.IssueSearch) (int, error)
	SearchRepositories(ctx context.Context, q store.RepoSearch) ([]store.RepoRow, error)
	CountSearchRepositories(ctx context.Context, q store.RepoSearch) (int, error)
	VisibleRepoPKs(ctx context.Context, viewerPK int64, ownerPKs []int64) ([]int64, error)
	UserByLogin(ctx context.Context, login string) (*store.UserRow, error)
	RepoByOwnerName(ctx context.Context, owner, name string) (*store.RepoRow, error)
}

SearchStore is the slice of the store the search service needs: the cross-repository issue and repository scans, the visible-repository lookup code search walks, and the login and owner/name resolution that turns qualifiers into the internal pks the scans filter on.

type StatusCheckRollup

type StatusCheckRollup struct {
	State      string // one of the Rollup* constants
	SHA        string
	Statuses   []*CommitStatus
	CheckRuns  []*CheckRun
	TotalCount int
}

StatusCheckRollup is the combined verdict across every status and check run on a head sha, the value the pull request and its head commit surface.

type StatusInput

type StatusInput struct {
	State       string
	Context     string
	TargetURL   string
	Description string
}

StatusInput is the create payload for a commit status.

type User

type User struct {
	ID              int64
	Login           string
	Type            string
	SiteAdmin       bool
	Name            *string
	Company         *string
	Blog            string
	Location        *string
	Email           *string
	Hireable        *bool
	Bio             *string
	TwitterUsername *string
	PublicRepos     int
	PublicGists     int
	Followers       int
	Following       int
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

User is the domain view of an account. It is the presenter's input for both the SimpleUser and full User wire models. ID is the public database id (users.db_id), not the internal primary key.

type UserService

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

UserService resolves accounts into domain users. The REST layer holds it and passes the authenticated actor's user id to Viewer for GET /user.

func NewUserService

func NewUserService(st UserStore) *UserService

NewUserService builds a UserService over the store.

func (*UserService) ByLogin

func (s *UserService) ByLogin(ctx context.Context, login string) (*User, error)

ByLogin resolves a public profile by login.

func (*UserService) Viewer

func (s *UserService) Viewer(ctx context.Context, userPK int64) (*User, error)

Viewer resolves the authenticated user behind GET /user. The caller has already established that the actor is a user and passes its internal pk.

type UserStore

type UserStore interface {
	UserByPK(ctx context.Context, pk int64) (*store.UserRow, error)
	UserByLogin(ctx context.Context, login string) (*store.UserRow, error)
}

UserStore is the slice of the store the user service needs.

Jump to

Keyboard shortcuts

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