discussion

package
v0.0.0-...-ec3ab7f Latest Latest
Warning

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

Go to latest
Published: Mar 2, 2026 License: BSD-3-Clause Imports: 23 Imported by: 0

Documentation

Overview

Package discussion implements a sync mechanism to mirror GitHub discussions state into a storage.DB. All the functionality is provided by the Client, created by New.

This package stores the following key schemas in the database:

["discussion.SyncProject", Project] => JSON of [projectSync] structure
["discussion.Event", Project, Discussion, API, ID] => [DBTime, Raw(JSON)]
["discussion.EventByTime", DBTime, Project, Discussion, API, ID] => []

To reconstruct the history of a given discussion, scan for keys from ["discussion.Event", Project, Discussion] to ["discussion.Event", Project, Discussion, ordered.Inf].

The API field is "/discussions", or "/discussions/comments", so the first key-value pair is the discussion with its body text and metadata.

The IDs are GitHub's and appear to be ordered by creation time within an API, so that the comments are time-ordered and the discussions are time-ordered, but comments and discussions are not ordered with respect to each other. To order them fully, fetch all the events and sort by the time in the JSON.

The JSON is the raw JSON served from GitHub describing the event. Storing the raw JSON avoids having to re-download everything if we decide another field is of interest to us.

EventByTime is an index of Events by DBTime, which is the time when the record was added to the database, which is not necessarily related to the time the event occurred. Code that processes new events can record which DBTime it has most recently processed and then scan forward in the index to learn about new events.

Index

Constants

View Source
const (
	DiscussionAPI string = "/discussions"
	CommentAPI    string = "/discussions/comments" // both comments and replies
)

The recognized event kinds. The events are fetched from the GrapQL API, which uses queries instead of API endpoints, so these "endpoints" are merely for identification purposes. We use the term API for consistency with the github.Event.API field.

View Source
const DocWatcherID = "discussiondocs"

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

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

Client is a client for making requests to the GitHub GraphQL API and syncing discussion state with a storage.DB.

func New

func New(ctx context.Context, lg *slog.Logger, sdb secret.DB, db storage.DB) *Client

New creates a new client for making requests to the GitHub GraphQL API.

The secret database is expected to have a secret named "api.github.com" of the form "user:pass" where user is a user-name (ignored by GitHub) and pass is an API token ("ghp_...").

func (*Client) Add

func (c *Client) Add(project string) error

Add adds a GitHub project of the form "owner/repo" (for example "golang/go") to the database. It only adds the project sync metadata. The initial data fetch does not happen until [Sync] or [SyncProject] is called. If the project is already present, Add does nothing and returns nil.

func (*Client) DocWatcher

func (c *Client) DocWatcher() *timed.Watcher[*Event]

DocWatcher returns the page watcher with name "discussiondocs". Implements docs.Source.DocWatcher.

func (*Client) EventWatcher

func (c *Client) EventWatcher(name string) *timed.Watcher[*Event]

EventWatcher returns a new timed.Watcher with the given name. It picks up where any previous Watcher of the same name left off.

func (*Client) Events

func (c *Client) Events(project string, discMin, discMax int64) iter.Seq[*Event]

Events returns an iterator over discussion events for the given project, limited to discussions in the range discMin ≤ discussion ≤ discMax. If discMax < 0, there is no upper limit. The events are iterated over in (Project, Discussion, Kind, ID) order, so "/discussions" events come first, then "/discussions/comments" events. Within an event kind, the events are ordered by increasing ID, which corresponds to increasing event time on GitHub.

func (*Client) EventsAfter

func (c *Client) EventsAfter(t timed.DBTime, project string) iter.Seq[*Event]

EventsAfter returns an iterator over discussion events in the given project after DBTime t, which should be e.DBTime from the most recent processed event. The events are iterated over in DBTime order, so the DBTime of the last successfully processed event can be used in a future call to EventsAfter. If project is the empty string, then events from all projects are returned.

func (*Client) Sync

func (c *Client) Sync(ctx context.Context) error

Sync syncs all projects.

func (*Client) SyncProject

func (c *Client) SyncProject(ctx context.Context, project string) error

SyncProject syncs a single project.

func (*Client) Testing

func (c *Client) Testing() *TestingClient

Testing returns a TestingClient, which provides access to Client functionality intended for testing. Testing only returns a non-nil TestingClient in testing mode, which is active if the current program is a test binary (that is, testing.Testing returns true) or if [Client.EnableTesting] has been called. Otherwise, Testing returns nil.

Each Client has only one TestingClient associated with it. Every call to Testing returns the same TestingClient.

func (*Client) ToDocs

func (*Client) ToDocs(e *Event) (iter.Seq[*docs.Doc], bool)

ToDocs converts an event containing a discussion to an embeddable document (wrapped as an iterator). It returns (nil, false) if the event is not a discussion. Implements docs.Source.ToDocs.

type Comment

type Comment struct {
	// URL of this comment.
	URL string `json:"url"`
	// URL of the discussion this is a comment on
	DiscussionURL string `json:"discussion_url"`
	// URL of the comment this is a reply to, if applicable
	ReplyToURL string      `json:"reply_to_url,omitempty"`
	Author     github.User `json:"author"`
	CreatedAt  string      `json:"created_at"`
	UpdatedAt  string      `json:"updated_at"`
	Body       string      `json:"body"`
}

Comment represents a GitHub discussion comment and its metadata.

func (*Comment) ID

func (c *Comment) ID() int64

ID returns the numerical ID of a comment (the last part of its URL), or 0 if its URL is not valid.

type Discussion

type Discussion struct {
	URL              string         `json:"url"`
	Number           int64          `json:"number"`
	Author           github.User    `json:"author"`
	Title            string         `json:"title"`
	CreatedAt        string         `json:"created_at"`
	UpdatedAt        string         `json:"updated_at"`
	LastEditedAt     string         `json:"last_edited_at,omitempty"`
	ClosedAt         string         `json:"closed_at"`
	Body             string         `json:"body"`
	UpvoteCount      int            `json:"upvote_count"`
	Locked           bool           `json:"locked"`
	ActiveLockReason string         `json:"active_lock_reason,omitempty"`
	Labels           []github.Label `json:"labels"`
}

Discussion represents a GitHub discussion and its metadata.

type Event

type Event struct {
	DBTime     timed.DBTime // when event was last written
	Project    string       // project (e.g. "golang/go")
	Discussion int64        // discussion number
	API        string       // the event kind ("API" for consistency with the [github.Event.API] field)
	ID         int64        // ID of event; each API has a different ID space. (Project, Discussion, API, ID) is assumed unique
	JSON       []byte       // JSON for the event data
	Typed      any          // Typed unmarshaling of the event data, of type [*Discussion], [*Comment]
	Updated    time.Time    // when the event was last updated (according to GitHub)
}

An Event is a single GitHub discussion event stored in the database.

func (*Event) LastWritten

func (e *Event) LastWritten() timed.DBTime

LastWritten implements docs.Entry.LastWritten.

type TestingClient

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

A TestingClient provides access to Client functionality intended for testing.

See Client.Testing for a description of testing mode.

func (*TestingClient) AddComment

func (tc *TestingClient) AddComment(project string, disc int64, comment *Comment) int64

AddIssueComment adds the given issue comment to the identified project issue, assigning it a new comment ID starting at 10¹⁰. AddIssueComment creates a new entry in the associated Client's underlying database, so other Client's using the same database will see the issue comment too.

NOTE: Only one TestingClient should be adding issues, since they do not coordinate in the database about ID assignment. Perhaps they should, but normally there is just one Client.

func (*TestingClient) AddDiscussion

func (tc *TestingClient) AddDiscussion(project string, d *Discussion) int64

AddDiscussion adds the given discussion to the identified project, assigning it a new issue number starting at 10⁹. AddDiscussion creates a new entry in the associated Client's underlying database, so other Client's using the same database will see the issue too.

NOTE: Only one TestingClient should be adding issues, since they do not coordinate in the database about ID assignment. Perhaps they should, but normally there is just one Client.

Jump to

Keyboard shortcuts

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