postgres

package
v0.26.15 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const (
	EventTypeScoreChange    = "score_change"
	EventTypeNewContributor = "new_contributor"
)

Watchlist notification event types. Exported so other packages writing notification events use the same canonical strings.

Variables

This section is empty.

Functions

This section is empty.

Types

type ActiveTenant

type ActiveTenant struct {
	ID   string
	Plan string
}

ActiveTenant is a minimal tenant projection used by background jobs.

type BehavioralSignals deprecated

type BehavioralSignals = model.Behavior

BehavioralSignals holds derived behavioral metrics for a contributor.

Deprecated: Use model.Behavior directly. Kept as an alias for backward compatibility.

type DailyActivityCount

type DailyActivityCount = DailyCount

DailyActivityCount holds a single day's total activity count.

type DailyCount

type DailyCount struct {
	Day   time.Time
	Count int
}

DailyCount holds a single day's count for chart rendering.

type DailyScoringCount

type DailyScoringCount = DailyCount

DailyScoringCount holds one day's scoring count.

type DigestTarget

type DigestTarget struct {
	TenantID string
	Email    string
	Username string
	Plan     string
}

DigestTarget holds tenant info for sending a digest email.

type HourlyCount

type HourlyCount = DailyCount

HourlyCount is an alias for DailyCount used for hourly chart rendering.

type HourlySummary

type HourlySummary struct {
	Username      string
	Provider      string
	Hour          time.Time
	PRsOpened     int
	PRsMerged     int
	PRsClosed     int
	ReviewsGiven  int
	IssueComments int
	IssuesOpened  int
	IssuesClosed  int
	DistinctRepos int
	Repos         []string
}

HourlySummary represents one hour of aggregated contributor activity from GH Archive.

type NotificationEvent

type NotificationEvent struct {
	ID        int64
	EventType string
	Username  string
	Target    string // joined from watchlist
	Details   map[string]any
	CreatedAt time.Time
	SentAt    *time.Time
}

NotificationEvent represents a detected watchlist event for display.

func (NotificationEvent) DetailSummary

func (e NotificationEvent) DetailSummary() string

DetailSummary returns a human-readable verb phrase. Never returns "" for known event types; falls back to "first activity" when no counts are present.

func (NotificationEvent) RepoSummary

func (e NotificationEvent) RepoSummary() string

RepoSummary returns the Repo column display string. Empty when no repos are recorded. For a single repo, returns the bare repo name (org prefix stripped). For multiple repos, returns "first +N" where N is the count of remaining repos.

func (NotificationEvent) RepoTooltip

func (e NotificationEvent) RepoTooltip() string

RepoTooltip returns the full comma-joined list of repos for the HTML title attribute. Returns "" when no repos are recorded.

type NotificationEventFilter

type NotificationEventFilter struct {
	Contributor string
	Org         string
	Repo        string
	Since       time.Time
}

NotificationEventFilter narrows GetNotificationEvents results by column. Empty fields are ignored. Contributor matches ne.username (substring, case-insensitive). Org matches w.target (the watched org/repo target). Repo matches any repo in ne.details->'repos'. Since restricts to events with created_at >= the given time.

type PREventRow

type PREventRow struct {
	Provider string
	Repo     string
	Number   int
	Action   string
	Author   string
	OccurAt  time.Time
}

PREventRow is the per-archive-hour observation written to the merge-graph table. Action is one of "opened", "merged", "closed". For the opened path Author is populated; for merged/closed it's empty (the actor is typically a CI bot we don't credit).

type PREventsStats

type PREventsStats struct {
	TotalRows            int
	Last24hRows          int
	AuthorAttributionPct float64
	BotOpenedRows        int
	MissingOpenRows      int
	IngestAttributionPct float64
	LastEventAt          time.Time
}

PREventsStats holds operational metrics for the merge-graph table.

IngestAttributionPct is the load-bearing pipeline-health number: of rows where we observed an opened event by a human (i.e. could have attributed authorship), what share did we actually attribute. Denominator = withAuthor + missingOpen — excludes bot-opened rows (intentional NULL author per aggregator policy) so it isolates true ingest gaps from structural NULLs. Near 100% on a healthy pipeline.

AuthorAttributionPct is the overall withAuthor/total share. It conflates bot-opened and missing-open rows so a low value can mean "lots of Renovate churn" or "ingest dropping opens" — useful as a raw signal but not load-bearing. Prefer IngestAttributionPct.

BotOpenedRows is rows where the opened event WAS observed but the opener was a bot (author stripped at aggregator). Renovate / Dependabot dominate this bucket.

MissingOpenRows is rows with a merge or close event but no opened event observed. Driven by the backfill horizon: PRs opened before our earliest archive hour but merged inside it will permanently lack opened_at. Steady-state share, not a transient.

type PipelineStats

type PipelineStats struct {
	LastIngest      time.Time
	LastScored      time.Time
	TotalActivities int
}

PipelineStats holds pipeline health metrics inferred from DB timestamps.

type PoolConfig

type PoolConfig struct {
	AppName         string
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration
	ConnMaxIdleTime time.Duration
}

func DefaultPoolConfig

func DefaultPoolConfig() PoolConfig

type QueueEntry

type QueueEntry struct {
	Username string
	Provider string
	Priority int
	QueuedAt time.Time
}

QueueEntry represents a pending scoring request.

type ScoreHistoryEntry

type ScoreHistoryEntry struct {
	Score    float64   `json:"score"`
	Grade    string    `json:"grade"`
	ScoredAt time.Time `json:"scored_at"`
}

ScoreHistoryEntry represents a single point on a score trend chart.

type StaleContributor

type StaleContributor struct {
	Username string
	Provider string
	Score    float64
	ScoredAt time.Time
}

StaleContributor represents a contributor whose score is outdated.

type Store

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

func New

func New(ctx context.Context, dsn string, cfg PoolConfig) (*Store, error)

func NewFromEnv

func NewFromEnv(ctx context.Context) (*Store, error)

func (*Store) BatchUpsertActivity

func (s *Store) BatchUpsertActivity(ctx context.Context, summaries []HourlySummary) (int, error)

BatchUpsertActivity upserts hourly summaries into devtrace_contributor_activity. On conflict, counts are added to existing values. Returns the number of rows upserted. All rows are written in a single transaction, so partial progress is impossible; on error the entire batch is rolled back and count 0 is returned.

func (*Store) BatchUpsertPREvents

func (s *Store) BatchUpsertPREvents(ctx context.Context, events []PREventRow) (int, error)

BatchUpsertPREvents writes per-PR observations into the merge graph. Each row stamps only the column corresponding to its action; the COALESCE-based UPDATE preserves earlier values so an opened event in March + a merged event in May land in the same row with both timestamps populated.

Bot-opened PRs (Renovate, Dependabot, etc.) arrive with empty Author by aggregator-side filtering; their opened_at still gets stamped so we can recognize the PR existed, but they won't show up in author-attributed merge counts.

func (*Store) BumpScoredAt

func (s *Store) BumpScoredAt(ctx context.Context, username, provider string) error

BumpScoredAt advances a contributor's scored_at timestamp without changing their score. Used to move permanently-failing contributors (404, 451) out of the stale pool so they aren't retried every 30 seconds.

func (*Store) Close

func (s *Store) Close() error

func (*Store) CompactActivity

func (s *Store) CompactActivity(ctx context.Context, olderThan time.Duration) (int64, error)

CompactActivity aggregates hourly rows older than the given age into weekly buckets (Monday 00:00 UTC), then deletes the originals. Runs in a single transaction so a failure leaves data unchanged. Returns rows deleted.

func (*Store) ContributorCount

func (s *Store) ContributorCount(ctx context.Context) (int, error)

ContributorCount returns the exact number of known contributors.

func (*Store) ContributorExists

func (s *Store) ContributorExists(ctx context.Context, username, provider string) (bool, error)

ContributorExists checks whether a reputation row exists for a contributor.

func (*Store) CreateWatchlist

func (s *Store) CreateWatchlist(ctx context.Context, tenantID, target, source string) error

CreateWatchlist adds a manual watchlist entry. Uses ON CONFLICT DO NOTHING for idempotency.

func (*Store) DB

func (s *Store) DB() *sql.DB

func (*Store) DailyActivityCounts

func (s *Store) DailyActivityCounts(ctx context.Context, days int) ([]DailyCount, error)

DailyActivityCounts returns per-day total activity counts for the last N days.

func (*Store) DailyScoringCounts

func (s *Store) DailyScoringCounts(ctx context.Context, days int) ([]DailyCount, error)

DailyScoringCounts returns per-day scoring counts for the last N days.

func (*Store) DeleteWatchlist

func (s *Store) DeleteWatchlist(ctx context.Context, watchlistID, tenantID string) error

DeleteWatchlist removes a manual watchlist entry. The tenant_id check ensures a tenant can only delete their own watchlists.

func (*Store) DequeueForScoring

func (s *Store) DequeueForScoring(ctx context.Context, limit int) ([]QueueEntry, error)

DequeueForScoring atomically removes and returns the highest-priority entries from the scoring queue. Priority 1 is highest. Uses DELETE ... RETURNING to ensure entries are exclusively claimed even under concurrent consumers.

func (*Store) DisableWatchlistEmails

func (s *Store) DisableWatchlistEmails(ctx context.Context, tenantID string) error

DisableWatchlistEmails sets notify_email = false for all watchlists belonging to a tenant. Used by the one-click unsubscribe handler.

func (*Store) EnqueueForScoring

func (s *Store) EnqueueForScoring(ctx context.Context, username, provider string, priority int) error

EnqueueForScoring adds a contributor to the scoring queue. On conflict, keeps the highest priority (lowest number) and updates queued_at only when priority is upgraded.

func (*Store) EnsureImplicitWatchlist

func (s *Store) EnsureImplicitWatchlist(ctx context.Context, tenantID, targetLogin string) error

EnsureImplicitWatchlist creates an implicit watchlist entry for a GitHub App installation. Idempotent: does nothing if the entry already exists.

func (*Store) GetAllWatchlistTargets

func (s *Store) GetAllWatchlistTargets(ctx context.Context) (map[string][]WatchlistEntry, error)

GetAllWatchlistTargets returns all active watchlist targets grouped by target. Each target maps to a slice of watchlist entries so the ingest pipeline can match contributor repos against watchlists.

func (*Store) GetAuthoredMergedPRCount

func (s *Store) GetAuthoredMergedPRCount(ctx context.Context, provider, username string) (int, error)

GetAuthoredMergedPRCount returns the number of PRs authored by the given user that have been merged. The query relies on the partial index idx_devtrace_pr_events_author_merged so it stays cheap even as the table grows.

Returns 0 (not nil) for users with no observations, so callers don't need to special-case "never seen". An error is returned only on a real query failure.

func (*Store) GetBehavioralSignals

func (s *Store) GetBehavioralSignals(ctx context.Context, username, provider string) (*BehavioralSignals, error)

GetBehavioralSignals computes behavioral metrics from the last 90 days of devtrace_contributor_activity. Returns nil when no data exists for the contributor.

func (*Store) GetCachedSignals

func (s *Store) GetCachedSignals(ctx context.Context, username, provider string) (*score.InputSignals, error)

GetCachedSignals returns the previously stored signals for a contributor. Returns (nil, nil) when no reputation record exists.

func (*Store) GetCrossVCS

func (s *Store) GetCrossVCS(ctx context.Context, provider, username string) (*model.CrossVCS, time.Time, error)

GetCrossVCS returns the cached cross-VCS match summary for a user. Returns (nil, zero-time, nil) when no row exists.

A row with TotalMatched=0 and an empty Matches slice is the sentinel for "we looked, no shared keys found"; the row keeps a non-zero fetched_at so the service-layer TTL check suppresses re-asking against forges the contributor doesn't use. The UI render path treats len(Matches)==0 as "no cross-VCS data" and omits the section.

func (*Store) GetCurrentGrade

func (s *Store) GetCurrentGrade(ctx context.Context, username, provider string) (string, error)

GetCurrentGrade returns the current letter grade for a contributor. Returns "" if no reputation record exists.

func (*Store) GetLifetimeActivity

func (s *Store) GetLifetimeActivity(ctx context.Context, username, provider string) (*model.LifetimeActivity, error)

GetLifetimeActivity returns aggregate counts across the entire activity history for the contributor. Returns nil when no data exists.

func (*Store) GetNotificationEvents

func (s *Store) GetNotificationEvents(ctx context.Context, tenantID string, f NotificationEventFilter, limit, offset int) ([]NotificationEvent, int, error)

GetNotificationEvents returns paginated notification events for a tenant, optionally filtered by per-column predicates. Events are ordered by created_at DESC. Returns the events and the total count of the filtered set.

func (*Store) GetOSSFScorecard

func (s *Store) GetOSSFScorecard(ctx context.Context, provider, owner, repo string) (*model.OSSFScorecard, time.Time, error)

GetOSSFScorecard returns the cached OSSF Scorecard for the given repo. Returns (nil, zero-time, nil) when no row exists. The returned fetched_at lets callers compare against config.OSSFTTL.

A non-nil zero-score Scorecard with empty Checks represents the "fetched but upstream had no scorecard" sentinel, recorded so we don't hammer the OSSF API for repos it has no record of. Callers should treat the zero-score sentinel as "no data" — the UI render path is gated on len(Checks) > 0.

func (*Store) GetPublisherProfile

func (s *Store) GetPublisherProfile(ctx context.Context, provider, username, registry string) (*model.RegistryProfile, time.Time, error)

GetPublisherProfile returns the cached publisher profile for the given user on the given registry. Returns (nil, zero-time, nil) when no row exists. The fetched_at timestamp lets callers decide whether to refresh against config.PublisherTTL.

A zero-PackageCount profile is still returned (not collapsed to nil) so callers can distinguish "we looked, found nothing" from "never looked"; the UI render path treats zero-count as "no publisher data" but the service layer's TTL check uses the non-nil row to suppress repeated re-fetching.

func (*Store) GetRepoSummary

func (s *Store) GetRepoSummary(ctx context.Context, username, provider string) (*model.OwnedRepos, time.Time, error)

GetRepoSummary returns the cached summary for the contributor along with its fetched_at timestamp. Returns (nil, zero-time, nil) when no row exists. Callers compare fetched_at against config.RepoSummaryTTL to decide whether to refresh.

func (*Store) GetScoreHistory

func (s *Store) GetScoreHistory(ctx context.Context, username, provider string, limit int) ([]ScoreHistoryEntry, error)

GetScoreHistory returns up to limit history entries for a contributor, ordered oldest-first (suitable for left-to-right chart rendering).

func (*Store) GetSecurityCredits

func (s *Store) GetSecurityCredits(ctx context.Context, username, provider string) (*model.SecurityCredits, time.Time, error)

GetSecurityCredits returns the aggregate plus the most-recent N advisory credits for the contributor. Returns (nil, zero-time, nil) when no rows exist. The fetched_at returned is the latest fetch timestamp across all rows for this contributor — callers compare it to config.SecurityCreditTTL to decide whether to refresh.

func (*Store) GetStackOverflowProfile

func (s *Store) GetStackOverflowProfile(ctx context.Context, provider, username string) (*model.StackOverflow, time.Time, error)

GetStackOverflowProfile returns the cached SO profile for a user. Returns (nil, zero-time, nil) when no row exists.

A zero-rep / zero-user-id row is the sentinel for "we looked, no SO link declared" (or "the link pointed to a missing user"); the row keeps a non-zero fetched_at so the service-layer TTL check suppresses re-asking. The UI render path treats user_id==0 as "no SO data" and omits the section.

func (*Store) GetStaleContributors

func (s *Store) GetStaleContributors(ctx context.Context, lowDays, highDays, limit int) ([]StaleContributor, error)

GetStaleContributors returns contributors whose scores are older than configured thresholds. Low scores (<0.5): stale after lowDays. High scores (>=0.5): stale after highDays.

func (*Store) GetSyncState

func (s *Store) GetSyncState(ctx context.Context, key string) (time.Time, error)

GetSyncState returns the time stored under the given key in sync_state. Returns the zero time if the key does not exist.

func (*Store) GetTenantRepos

func (s *Store) GetTenantRepos(ctx context.Context) (map[string]bool, error)

GetTenantRepos returns the set of org/user logins with active GitHub App installations. The ingest job uses this to determine if a repo owner is a tenant.

func (*Store) GetTenantsWithUnsentEvents

func (s *Store) GetTenantsWithUnsentEvents(ctx context.Context) ([]DigestTarget, error)

GetTenantsWithUnsentEvents returns tenants that have unsent notification events and have email-enabled watchlists.

func (*Store) GetTokenQuotaSamples

func (s *Store) GetTokenQuotaSamples(ctx context.Context, since time.Time) ([]TokenQuotaSample, error)

GetTokenQuotaSamples returns all quota samples since the given time.

func (*Store) GetTopContributedRepos

func (s *Store) GetTopContributedRepos(ctx context.Context, username, provider string, limit int) ([]model.RepoContribution, error)

GetTopContributedRepos returns the contributor's top repos ranked by distinct active-hour count, limited to `limit` entries. Empty slice when the contributor has no activity data. Uses the activity-table PK prefix (username, provider) — no additional index required.

func (*Store) GetUnsentEventsForDigest

func (s *Store) GetUnsentEventsForDigest(ctx context.Context, tenantID string, limit int) ([]NotificationEvent, error)

GetUnsentEventsForDigest returns unsent notification events for a tenant, capped at limit.

func (*Store) GetWatchlistsForContributor

func (s *Store) GetWatchlistsForContributor(ctx context.Context, username, provider string) ([]WatchlistEntry, error)

GetWatchlistsForContributor returns watchlist entries matching a contributor's recent activity repos. Joins activity repos against watchlist targets.

func (*Store) Go

func (s *Store) Go(fn func())

Go spawns fn in a tracked goroutine. Use this for fire-and-forget DB writes that must outlive the request context (e.g., persisting a score after the API response has been sent). Pair with WaitBackground in the shutdown path so writes drain before Close runs.

func (*Store) HourlyActivityCounts

func (s *Store) HourlyActivityCounts(ctx context.Context, hours int) ([]HourlyCount, error)

HourlyActivityCounts returns per-hour total activity counts for the last N hours.

func (*Store) HourlyScoringCounts

func (s *Store) HourlyScoringCounts(ctx context.Context, hours int) ([]HourlyCount, error)

HourlyScoringCounts returns per-hour scoring counts for the last N hours.

func (*Store) InsertNotificationEvent

func (s *Store) InsertNotificationEvent(ctx context.Context, watchlistID, eventType, username string, details map[string]any) error

InsertNotificationEvent records a new watchlist notification event.

func (*Store) LastDigestSentAt

func (s *Store) LastDigestSentAt(ctx context.Context, tenantID string) (*time.Time, error)

LastDigestSentAt returns the most recent sent_at time for a tenant's digest events. Returns nil if no digest has been sent yet.

func (*Store) ListActiveTenants

func (s *Store) ListActiveTenants(ctx context.Context) ([]ActiveTenant, error)

ListActiveTenants returns all tenants with status = 'active'.

func (*Store) ListWatchlists

func (s *Store) ListWatchlists(ctx context.Context, tenantID string) ([]Watchlist, error)

ListWatchlists returns all watchlists for a tenant.

func (*Store) MarkAllEventsSent

func (s *Store) MarkAllEventsSent(ctx context.Context, tenantID string) error

MarkAllEventsSent marks all unsent notification events for a tenant as sent. Called after a digest email is sent — the email contains the top N events but all pending events are cleared so they don't queue up for the next digest.

func (*Store) Migrate

func (s *Store) Migrate(ctx context.Context) error

func (*Store) PREventsStats

func (s *Store) PREventsStats(ctx context.Context) (*PREventsStats, error)

PREventsStats returns aggregate state of the merge-graph table for the admin dashboard. All counts come from a single FILTER-based scan to keep the dashboard query cheap.

func (*Store) PipelineStats

func (s *Store) PipelineStats(ctx context.Context) (*PipelineStats, error)

PipelineStats returns pipeline health indicators from existing tables.

func (*Store) PruneActivity

func (s *Store) PruneActivity(ctx context.Context, retention time.Duration) (int64, error)

PruneActivity deletes all activity rows older than the given retention window. Returns rows deleted. Run after CompactActivity to remove both hourly and compacted rows beyond the retention limit.

func (*Store) PruneNotificationEvents

func (s *Store) PruneNotificationEvents(ctx context.Context, tenantID string, retentionDays, maxEvents int) (int, error)

PruneNotificationEvents deletes notification events for a tenant in two passes: first by age (events older than retentionDays), then by cap (keep newest maxEvents). retentionDays <= 0 skips the age pass. maxEvents <= 0 skips the cap pass. Returns total rows deleted.

func (*Store) PrunePREvents

func (s *Store) PrunePREvents(ctx context.Context, retention time.Duration) (int64, error)

PrunePREvents drops PR-event rows whose last_event_at is older than the retention window. Mirrors PruneActivity's cadence so the merge-graph table doesn't outgrow the activity history that scoring is anchored to.

func (*Store) PruneScoreHistory

func (s *Store) PruneScoreHistory(ctx context.Context, retention time.Duration) (int64, error)

PruneScoreHistory deletes reputation history rows older than the given retention window. Returns rows deleted.

func (*Store) PruneSecurityCredits

func (s *Store) PruneSecurityCredits(ctx context.Context, retention time.Duration) (int64, error)

PruneSecurityCredits drops sentinel rows older than the given window. Real credit rows are kept until the next save replaces them. Returns rows deleted. Currently called from the same digest-sweep cadence as other prune routines.

func (*Store) PurgeNonTenantQueue

func (s *Store) PurgeNonTenantQueue(ctx context.Context) (int64, error)

PurgeNonTenantQueue removes queued entries that were enqueued with priority 2 (non-tenant contributors). Returns the number of rows deleted.

func (*Store) PurgeTokenQuotaSamples

func (s *Store) PurgeTokenQuotaSamples(ctx context.Context, before time.Time) (int64, error)

PurgeTokenQuotaSamples deletes samples older than the given time.

func (*Store) QueueDepth

func (s *Store) QueueDepth(ctx context.Context) (int, error)

QueueDepth returns the number of entries in the scoring queue.

func (*Store) RecordTokenQuotaSample

func (s *Store) RecordTokenQuotaSample(ctx context.Context, installationID int64, login string,
	coreLimit, coreUsed, searchLimit, searchUsed, graphqlLimit, graphqlUsed int) error

RecordTokenQuotaSample writes a single quota snapshot to the time-series table. Pass per-family (limit, used) pairs for core (REST), search, and graphql. Callers that only know about core can pass 0 for the others; existing pre-cutover rows behave the same way.

func (*Store) RemoveFromQueue

func (s *Store) RemoveFromQueue(ctx context.Context, username, provider string) error

RemoveFromQueue deletes a contributor from the scoring queue after processing.

func (*Store) SaveCrossVCS

func (s *Store) SaveCrossVCS(ctx context.Context, provider, username string, summary *model.CrossVCS) error

SaveCrossVCS upserts the cross-VCS row. Pass a non-nil summary with TotalMatched=0 and an empty Matches slice to record a "no shared keys" sentinel — same TTL semantics as a real match, suppressing repeated re-fetching for users without a cross-VCS presence.

func (*Store) SaveOSSFScorecard

func (s *Store) SaveOSSFScorecard(ctx context.Context, provider, owner, repo string, card *model.OSSFScorecard) error

SaveOSSFScorecard upserts the scorecard row for a repo. Pass a non-nil card with Score=0 and no checks to record a "fetch attempted, upstream had nothing" sentinel — same TTL semantics as real data, suppressing repeated re-fetching of repos OSSF doesn't have a scorecard for.

func (*Store) SavePublisherProfile

func (s *Store) SavePublisherProfile(ctx context.Context, provider, username, registry string, profile *model.RegistryProfile) error

SavePublisherProfile upserts the publisher row. Pass a non-nil profile with PackageCount=0 and empty Top to record a "fetched but no packages" sentinel — same TTL semantics as real data, suppressing re-fetching for users with no publisher account.

func (*Store) SaveRepoSummary

func (s *Store) SaveRepoSummary(ctx context.Context, username, provider string, summary *model.OwnedRepos) error

SaveRepoSummary upserts the summary row with fetched_at = NOW(). A nil summary clears optional sub-fields but still records the fetch attempt (so a contributor with zero owned repos isn't refetched every request).

func (*Store) SaveScoreHistory

func (s *Store) SaveScoreHistory(ctx context.Context, username, provider string, score float64, grade string, deep bool) error

SaveScoreHistory inserts a new reputation history entry. The contributor row must already exist (FK constraint).

func (*Store) SaveSecurityCredits

func (s *Store) SaveSecurityCredits(ctx context.Context, username, provider string, credits []model.SecurityCredit) error

SaveSecurityCredits replaces the contributor's credit rows in a single transaction: deletes existing rows, inserts the new set, stamps fetched_at = NOW() on each. An empty `credits` slice still records the fetch attempt by inserting a single sentinel row with advisory_id = "", so the next refresh respects the TTL instead of re-hitting GitHub on every score request for users with no credits.

func (*Store) SaveStackOverflowProfile

func (s *Store) SaveStackOverflowProfile(ctx context.Context, provider, username string, profile *model.StackOverflow) error

SaveStackOverflowProfile upserts the SO profile row. Pass a non-nil profile with UserID=0 and Reputation=0 to record a "no SO link declared" sentinel; the row keeps fetched_at fresh so the service layer's TTL check suppresses repeated re-checks.

func (*Store) SaveSyncState

func (s *Store) SaveSyncState(ctx context.Context, key string, val time.Time) error

SaveSyncState upserts the given time value under key in sync_state.

func (*Store) ScoredCount

func (s *Store) ScoredCount(ctx context.Context) (int, error)

ScoredCount returns the number of contributors with a reputation score.

func (*Store) StaleCount

func (s *Store) StaleCount(ctx context.Context, lowDays, highDays int) (int, error)

StaleCount returns the number of contributors whose scores are stale.

func (*Store) ToggleWatchlistEmail

func (s *Store) ToggleWatchlistEmail(ctx context.Context, watchlistID, tenantID string) error

ToggleWatchlistEmail flips the notify_email flag for a watchlist entry.

func (*Store) UnsentEventCount

func (s *Store) UnsentEventCount(ctx context.Context, tenantID string) (int, error)

UnsentEventCount returns the total number of unsent events for a tenant.

func (*Store) UpdateReputation

func (s *Store) UpdateReputation(ctx context.Context, username, provider string, value float64, grade, version string, deep bool, signals *score.InputSignals) error

UpdateReputation upserts a contributor's reputation score and cached signals.

func (*Store) UpsertContributor

func (s *Store) UpsertContributor(ctx context.Context, username, provider string) error

UpsertContributor ensures a contributor row exists for the given identity. Uses ON CONFLICT DO NOTHING so it is idempotent.

func (*Store) WaitBackground

func (s *Store) WaitBackground(ctx context.Context) error

WaitBackground blocks until all tracked goroutines exit or ctx is canceled. Returns ctx.Err() on timeout so callers can log the leak.

func (*Store) WatchlistManualCount

func (s *Store) WatchlistManualCount(ctx context.Context, tenantID string) (int, error)

WatchlistManualCount returns the number of manual watchlist entries for a tenant.

type TokenQuotaSample

type TokenQuotaSample struct {
	SampledAt      time.Time `json:"sampled_at"`
	InstallationID int64     `json:"installation_id"`
	Login          string    `json:"login"`
	QuotaLimit     int       `json:"quota_limit"`
	QuotaUsed      int       `json:"quota_used"`
	SearchLimit    int       `json:"search_limit"`
	SearchUsed     int       `json:"search_used"`
	GraphQLLimit   int       `json:"graphql_limit"`
	GraphQLUsed    int       `json:"graphql_used"`
}

TokenQuotaSample is a single rate-limit snapshot for one installation. Core REST, Search, and GraphQL families are sampled together so the admin chart can show which family is closest to its ceiling. Pre-cutover rows have search/graphql limits of 0; consumers treat a zero limit as "no data" for that family rather than 100% utilization.

type Watchlist

type Watchlist struct {
	ID          string
	TenantID    string
	Target      string
	Source      string
	NotifyEmail bool
	CreatedAt   time.Time
}

Watchlist represents a full watchlist row for display in settings.

type WatchlistEntry

type WatchlistEntry struct {
	ID       string
	TenantID string
	Target   string
	Source   string
	Plan     string // tenant plan, populated by bulk queries
}

WatchlistEntry represents a single watchlist target with its metadata.

Jump to

Keyboard shortcuts

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