forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

Compare changes

Choose any two refs to compare.

+3 -2
appview/db/artifact.go
···
"github.com/go-git/go-git/v5/plumbing"
"github.com/ipfs/go-cid"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func AddArtifact(e Execer, artifact models.Artifact) error {
···
return err
}
-
func GetArtifact(e Execer, filters ...filter) ([]models.Artifact, error) {
+
func GetArtifact(e Execer, filters ...orm.Filter) ([]models.Artifact, error) {
var artifacts []models.Artifact
var conditions []string
···
return artifacts, nil
}
-
func DeleteArtifact(e Execer, filters ...filter) error {
+
func DeleteArtifact(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
+4 -3
appview/db/collaborators.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func AddCollaborator(e Execer, c models.Collaborator) error {
···
return err
}
-
func DeleteCollaborator(e Execer, filters ...filter) error {
+
func DeleteCollaborator(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return nil, nil
}
-
return GetRepos(e, 0, FilterIn("at_uri", repoAts))
+
return GetRepos(e, 0, orm.FilterIn("at_uri", repoAts))
}
-
func GetCollaborators(e Execer, filters ...filter) ([]models.Collaborator, error) {
+
func GetCollaborators(e Execer, filters ...orm.Filter) ([]models.Collaborator, error) {
var collaborators []models.Collaborator
var conditions []string
var args []any
+24 -137
appview/db/db.go
···
import (
"context"
"database/sql"
-
"fmt"
"log/slog"
-
"reflect"
"strings"
_ "github.com/mattn/go-sqlite3"
"tangled.org/core/log"
+
"tangled.org/core/orm"
)
type DB struct {
···
}
// run migrations
-
runMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-description-to-repos", func(tx *sql.Tx) error {
tx.Exec(`
alter table repos add column description text check (length(description) <= 200);
`)
return nil
})
-
runMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-rkey-to-pubkeys", func(tx *sql.Tx) error {
// add unconstrained column
_, err := tx.Exec(`
alter table public_keys
···
return nil
})
-
runMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-rkey-to-comments", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table comments drop column comment_at;
alter table comments add column rkey text;
···
return err
})
-
runMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-deleted-and-edited-to-issue-comments", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table comments add column deleted text; -- timestamp
alter table comments add column edited text; -- timestamp
···
return err
})
-
runMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-source-info-to-pulls-and-submissions", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table pulls add column source_branch text;
alter table pulls add column source_repo_at text;
···
return err
})
-
runMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-source-to-repos", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table repos add column source text;
`)
···
//
// [0]: https://sqlite.org/pragma.html#pragma_foreign_keys
conn.ExecContext(ctx, "pragma foreign_keys = off;")
-
runMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "recreate-pulls-column-for-stacking-support", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table pulls_new (
-- identifiers
···
})
conn.ExecContext(ctx, "pragma foreign_keys = on;")
-
runMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-spindle-to-repos", func(tx *sql.Tx) error {
tx.Exec(`
alter table repos add column spindle text;
`)
···
// drop all knot secrets, add unique constraint to knots
//
// knots will henceforth use service auth for signed requests
-
runMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "no-more-secrets", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table registrations_new (
id integer primary key autoincrement,
···
})
// recreate and add rkey + created columns with default constraint
-
runMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "rework-collaborators-table", func(tx *sql.Tx) error {
// create new table
// - repo_at instead of repo integer
// - rkey field
···
return err
})
-
runMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-rkey-to-issues", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table issues add column rkey text not null default '';
···
})
// repurpose the read-only column to "needs-upgrade"
-
runMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table registrations rename column read_only to needs_upgrade;
`)
···
})
// require all knots to upgrade after the release of total xrpc
-
runMigration(conn, logger, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "migrate-knots-to-total-xrpc", func(tx *sql.Tx) error {
_, err := tx.Exec(`
update registrations set needs_upgrade = 1;
`)
···
})
// require all knots to upgrade after the release of total xrpc
-
runMigration(conn, logger, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "migrate-spindles-to-xrpc-owner", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table spindles add column needs_upgrade integer not null default 0;
`)
···
//
// disable foreign-keys for the next migration
conn.ExecContext(ctx, "pragma foreign_keys = off;")
-
runMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "remove-issue-at-from-issues", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists issues_new (
-- identifiers
···
// - new columns
// * column "reply_to" which can be any other comment
// * column "at-uri" which is a generated column
-
runMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "rework-issue-comments", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists issue_comments (
-- identifiers
···
//
// disable foreign-keys for the next migration
conn.ExecContext(ctx, "pragma foreign_keys = off;")
-
runMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-at-uri-to-pulls", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists pulls_new (
-- identifiers
···
//
// disable foreign-keys for the next migration
conn.ExecContext(ctx, "pragma foreign_keys = off;")
-
runMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "remove-repo-at-pull-id-from-pull-submissions", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table if not exists pull_submissions_new (
-- identifiers
···
// knots may report the combined patch for a comparison, we can store that on the appview side
// (but not on the pds record), because calculating the combined patch requires a git index
-
runMigration(conn, logger, "add-combined-column-submissions", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-combined-column-submissions", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table pull_submissions add column combined text;
`)
return err
})
-
runMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-pronouns-profile", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table profile add column pronouns text;
`)
return err
})
-
runMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-meta-column-repos", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table repos add column website text;
alter table repos add column topics text;
···
return err
})
-
runMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "add-usermentioned-preference", func(tx *sql.Tx) error {
_, err := tx.Exec(`
alter table notification_preferences add column user_mentioned integer not null default 1;
`)
···
})
// remove the foreign key constraints from stars.
-
runMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error {
+
orm.RunMigration(conn, logger, "generalize-stars-subject", func(tx *sql.Tx) error {
_, err := tx.Exec(`
create table stars_new (
id integer primary key autoincrement,
···
}, nil
-
type migrationFn = func(*sql.Tx) error
-
-
func runMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error {
-
logger = logger.With("migration", name)
-
-
tx, err := c.BeginTx(context.Background(), nil)
-
if err != nil {
-
return err
-
}
-
defer tx.Rollback()
-
-
var exists bool
-
err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
-
if err != nil {
-
return err
-
}
-
-
if !exists {
-
// run migration
-
err = migrationFn(tx)
-
if err != nil {
-
logger.Error("failed to run migration", "err", err)
-
return err
-
}
-
-
// mark migration as complete
-
_, err = tx.Exec("insert into migrations (name) values (?)", name)
-
if err != nil {
-
logger.Error("failed to mark migration as complete", "err", err)
-
return err
-
}
-
-
// commit the transaction
-
if err := tx.Commit(); err != nil {
-
return err
-
}
-
-
logger.Info("migration applied successfully")
-
} else {
-
logger.Warn("skipped migration, already applied")
-
}
-
-
return nil
-
}
-
func (d *DB) Close() error {
return d.DB.Close()
-
-
type filter struct {
-
key string
-
arg any
-
cmp string
-
}
-
-
func newFilter(key, cmp string, arg any) filter {
-
return filter{
-
key: key,
-
arg: arg,
-
cmp: cmp,
-
}
-
}
-
-
func FilterEq(key string, arg any) filter { return newFilter(key, "=", arg) }
-
func FilterNotEq(key string, arg any) filter { return newFilter(key, "<>", arg) }
-
func FilterGte(key string, arg any) filter { return newFilter(key, ">=", arg) }
-
func FilterLte(key string, arg any) filter { return newFilter(key, "<=", arg) }
-
func FilterIs(key string, arg any) filter { return newFilter(key, "is", arg) }
-
func FilterIsNot(key string, arg any) filter { return newFilter(key, "is not", arg) }
-
func FilterIn(key string, arg any) filter { return newFilter(key, "in", arg) }
-
func FilterLike(key string, arg any) filter { return newFilter(key, "like", arg) }
-
func FilterNotLike(key string, arg any) filter { return newFilter(key, "not like", arg) }
-
func FilterContains(key string, arg any) filter {
-
return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg))
-
}
-
-
func (f filter) Condition() string {
-
rv := reflect.ValueOf(f.arg)
-
kind := rv.Kind()
-
-
// if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)`
-
if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
-
if rv.Len() == 0 {
-
// always false
-
return "1 = 0"
-
}
-
-
placeholders := make([]string, rv.Len())
-
for i := range placeholders {
-
placeholders[i] = "?"
-
}
-
-
return fmt.Sprintf("%s %s (%s)", f.key, f.cmp, strings.Join(placeholders, ", "))
-
}
-
-
return fmt.Sprintf("%s %s ?", f.key, f.cmp)
-
}
-
-
func (f filter) Arg() []any {
-
rv := reflect.ValueOf(f.arg)
-
kind := rv.Kind()
-
if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
-
if rv.Len() == 0 {
-
return nil
-
}
-
-
out := make([]any, rv.Len())
-
for i := range rv.Len() {
-
out[i] = rv.Index(i).Interface()
-
}
-
return out
-
}
-
-
return []any{f.arg}
-
}
+4 -3
appview/db/follow.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func AddFollow(e Execer, follow *models.Follow) error {
···
return result, nil
}
-
func GetFollows(e Execer, limit int, filters ...filter) ([]models.Follow, error) {
+
func GetFollows(e Execer, limit int, filters ...orm.Filter) ([]models.Follow, error) {
var follows []models.Follow
var conditions []string
···
}
func GetFollowers(e Execer, did string) ([]models.Follow, error) {
-
return GetFollows(e, 0, FilterEq("subject_did", did))
+
return GetFollows(e, 0, orm.FilterEq("subject_did", did))
}
func GetFollowing(e Execer, did string) ([]models.Follow, error) {
-
return GetFollows(e, 0, FilterEq("user_did", did))
+
return GetFollows(e, 0, orm.FilterEq("user_did", did))
}
func getFollowStatuses(e Execer, userDid string, subjectDids []string) (map[string]models.FollowStatus, error) {
+21 -20
appview/db/issues.go
···
"tangled.org/core/api/tangled"
"tangled.org/core/appview/models"
"tangled.org/core/appview/pagination"
+
"tangled.org/core/orm"
)
func PutIssue(tx *sql.Tx, issue *models.Issue) error {
···
issues, err := GetIssues(
tx,
-
FilterEq("did", issue.Did),
-
FilterEq("rkey", issue.Rkey),
+
orm.FilterEq("did", issue.Did),
+
orm.FilterEq("rkey", issue.Rkey),
)
switch {
case err != nil:
···
return nil
}
-
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...filter) ([]models.Issue, error) {
+
func GetIssuesPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Issue, error) {
issueMap := make(map[string]*models.Issue) // at-uri -> issue
var conditions []string
···
whereClause = " where " + strings.Join(conditions, " and ")
}
-
pLower := FilterGte("row_num", page.Offset+1)
-
pUpper := FilterLte("row_num", page.Offset+page.Limit)
+
pLower := orm.FilterGte("row_num", page.Offset+1)
+
pUpper := orm.FilterLte("row_num", page.Offset+page.Limit)
pageClause := ""
if page.Limit > 0 {
···
repoAts = append(repoAts, string(issue.RepoAt))
}
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", repoAts))
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoAts))
if err != nil {
return nil, fmt.Errorf("failed to build repo mappings: %w", err)
}
···
// collect comments
issueAts := slices.Collect(maps.Keys(issueMap))
-
comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts))
+
comments, err := GetIssueComments(e, orm.FilterIn("issue_at", issueAts))
if err != nil {
return nil, fmt.Errorf("failed to query comments: %w", err)
}
···
}
// collect allLabels for each issue
-
allLabels, err := GetLabels(e, FilterIn("subject", issueAts))
+
allLabels, err := GetLabels(e, orm.FilterIn("subject", issueAts))
if err != nil {
return nil, fmt.Errorf("failed to query labels: %w", err)
}
···
}
// collect references for each issue
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", issueAts))
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", issueAts))
if err != nil {
return nil, fmt.Errorf("failed to query reference_links: %w", err)
}
···
issues, err := GetIssuesPaginated(
e,
pagination.Page{},
-
FilterEq("repo_at", repoAt),
-
FilterEq("issue_id", issueId),
+
orm.FilterEq("repo_at", repoAt),
+
orm.FilterEq("issue_id", issueId),
)
if err != nil {
return nil, err
···
return &issues[0], nil
}
-
func GetIssues(e Execer, filters ...filter) ([]models.Issue, error) {
+
func GetIssues(e Execer, filters ...orm.Filter) ([]models.Issue, error) {
return GetIssuesPaginated(e, pagination.Page{}, filters...)
}
···
func GetIssueIDs(e Execer, opts models.IssueSearchOptions) ([]int64, error) {
var ids []int64
-
var filters []filter
+
var filters []orm.Filter
openValue := 0
if opts.IsOpen {
openValue = 1
}
-
filters = append(filters, FilterEq("open", openValue))
+
filters = append(filters, orm.FilterEq("open", openValue))
if opts.RepoAt != "" {
-
filters = append(filters, FilterEq("repo_at", opts.RepoAt))
+
filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt))
}
var conditions []string
···
return id, nil
}
-
func DeleteIssueComments(e Execer, filters ...filter) error {
+
func DeleteIssueComments(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func GetIssueComments(e Execer, filters ...filter) ([]models.IssueComment, error) {
+
func GetIssueComments(e Execer, filters ...orm.Filter) ([]models.IssueComment, error) {
commentMap := make(map[string]*models.IssueComment)
var conditions []string
···
// collect references for each comments
commentAts := slices.Collect(maps.Keys(commentMap))
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
if err != nil {
return nil, fmt.Errorf("failed to query reference_links: %w", err)
}
···
return nil
}
-
func CloseIssues(e Execer, filters ...filter) error {
+
func CloseIssues(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func ReopenIssues(e Execer, filters ...filter) error {
+
func ReopenIssues(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
+8 -7
appview/db/label.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
// no updating type for now
···
return id, nil
}
-
func DeleteLabelDefinition(e Execer, filters ...filter) error {
+
func DeleteLabelDefinition(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func GetLabelDefinitions(e Execer, filters ...filter) ([]models.LabelDefinition, error) {
+
func GetLabelDefinitions(e Execer, filters ...orm.Filter) ([]models.LabelDefinition, error) {
var labelDefinitions []models.LabelDefinition
var conditions []string
var args []any
···
}
// helper to get exactly one label def
-
func GetLabelDefinition(e Execer, filters ...filter) (*models.LabelDefinition, error) {
+
func GetLabelDefinition(e Execer, filters ...orm.Filter) (*models.LabelDefinition, error) {
labels, err := GetLabelDefinitions(e, filters...)
if err != nil {
return nil, err
···
return id, nil
}
-
func GetLabelOps(e Execer, filters ...filter) ([]models.LabelOp, error) {
+
func GetLabelOps(e Execer, filters ...orm.Filter) ([]models.LabelOp, error) {
var labelOps []models.LabelOp
var conditions []string
var args []any
···
}
// get labels for a given list of subject URIs
-
func GetLabels(e Execer, filters ...filter) (map[syntax.ATURI]models.LabelState, error) {
+
func GetLabels(e Execer, filters ...orm.Filter) (map[syntax.ATURI]models.LabelState, error) {
ops, err := GetLabelOps(e, filters...)
if err != nil {
return nil, err
···
}
labelAts := slices.Collect(maps.Keys(labelAtSet))
-
actx, err := NewLabelApplicationCtx(e, FilterIn("at_uri", labelAts))
+
actx, err := NewLabelApplicationCtx(e, orm.FilterIn("at_uri", labelAts))
if err != nil {
return nil, err
}
···
return results, nil
}
-
func NewLabelApplicationCtx(e Execer, filters ...filter) (*models.LabelApplicationCtx, error) {
+
func NewLabelApplicationCtx(e Execer, filters ...orm.Filter) (*models.LabelApplicationCtx, error) {
labels, err := GetLabelDefinitions(e, filters...)
if err != nil {
return nil, err
+5 -4
appview/db/language.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
-
func GetRepoLanguages(e Execer, filters ...filter) ([]models.RepoLanguage, error) {
+
func GetRepoLanguages(e Execer, filters ...orm.Filter) ([]models.RepoLanguage, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
return nil
}
-
func DeleteRepoLanguages(e Execer, filters ...filter) error {
+
func DeleteRepoLanguages(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
func UpdateRepoLanguages(tx *sql.Tx, repoAt syntax.ATURI, ref string, langs []models.RepoLanguage) error {
err := DeleteRepoLanguages(
tx,
-
FilterEq("repo_at", repoAt),
-
FilterEq("ref", ref),
+
orm.FilterEq("repo_at", repoAt),
+
orm.FilterEq("ref", ref),
)
if err != nil {
return fmt.Errorf("failed to delete existing languages: %w", err)
+14 -13
appview/db/notifications.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
"tangled.org/core/appview/pagination"
+
"tangled.org/core/orm"
)
func CreateNotification(e Execer, notification *models.Notification) error {
···
}
// GetNotificationsPaginated retrieves notifications with filters and pagination
-
func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...filter) ([]*models.Notification, error) {
+
func GetNotificationsPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.Notification, error) {
var conditions []string
var args []any
···
}
// GetNotificationsWithEntities retrieves notifications with their related entities
-
func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...filter) ([]*models.NotificationWithEntity, error) {
+
func GetNotificationsWithEntities(e Execer, page pagination.Page, filters ...orm.Filter) ([]*models.NotificationWithEntity, error) {
var conditions []string
var args []any
···
}
// GetNotifications retrieves notifications with filters
-
func GetNotifications(e Execer, filters ...filter) ([]*models.Notification, error) {
+
func GetNotifications(e Execer, filters ...orm.Filter) ([]*models.Notification, error) {
return GetNotificationsPaginated(e, pagination.FirstPage(), filters...)
}
-
func CountNotifications(e Execer, filters ...filter) (int64, error) {
+
func CountNotifications(e Execer, filters ...orm.Filter) (int64, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
}
func MarkNotificationRead(e Execer, notificationID int64, userDID string) error {
-
idFilter := FilterEq("id", notificationID)
-
recipientFilter := FilterEq("recipient_did", userDID)
+
idFilter := orm.FilterEq("id", notificationID)
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
query := fmt.Sprintf(`
UPDATE notifications
···
}
func MarkAllNotificationsRead(e Execer, userDID string) error {
-
recipientFilter := FilterEq("recipient_did", userDID)
-
readFilter := FilterEq("read", 0)
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
+
readFilter := orm.FilterEq("read", 0)
query := fmt.Sprintf(`
UPDATE notifications
···
}
func DeleteNotification(e Execer, notificationID int64, userDID string) error {
-
idFilter := FilterEq("id", notificationID)
-
recipientFilter := FilterEq("recipient_did", userDID)
+
idFilter := orm.FilterEq("id", notificationID)
+
recipientFilter := orm.FilterEq("recipient_did", userDID)
query := fmt.Sprintf(`
DELETE FROM notifications
···
}
func GetNotificationPreference(e Execer, userDid string) (*models.NotificationPreferences, error) {
-
prefs, err := GetNotificationPreferences(e, FilterEq("user_did", userDid))
+
prefs, err := GetNotificationPreferences(e, orm.FilterEq("user_did", userDid))
if err != nil {
return nil, err
}
···
return p, nil
}
-
func GetNotificationPreferences(e Execer, filters ...filter) (map[syntax.DID]*models.NotificationPreferences, error) {
+
func GetNotificationPreferences(e Execer, filters ...orm.Filter) (map[syntax.DID]*models.NotificationPreferences, error) {
prefsMap := make(map[syntax.DID]*models.NotificationPreferences)
var conditions []string
···
func (d *DB) ClearOldNotifications(ctx context.Context, olderThan time.Duration) error {
cutoff := time.Now().Add(-olderThan)
-
createdFilter := FilterLte("created", cutoff)
+
createdFilter := orm.FilterLte("created", cutoff)
query := fmt.Sprintf(`
DELETE FROM notifications
+6 -5
appview/db/pipeline.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
-
func GetPipelines(e Execer, filters ...filter) ([]models.Pipeline, error) {
+
func GetPipelines(e Execer, filters ...orm.Filter) ([]models.Pipeline, error) {
var pipelines []models.Pipeline
var conditions []string
···
// this is a mega query, but the most useful one:
// get N pipelines, for each one get the latest status of its N workflows
-
func GetPipelineStatuses(e Execer, limit int, filters ...filter) ([]models.Pipeline, error) {
+
func GetPipelineStatuses(e Execer, limit int, filters ...orm.Filter) ([]models.Pipeline, error) {
var conditions []string
var args []any
for _, filter := range filters {
-
filter.key = "p." + filter.key // the table is aliased in the query to `p`
+
filter.Key = "p." + filter.Key // the table is aliased in the query to `p`
conditions = append(conditions, filter.Condition())
args = append(args, filter.Arg()...)
}
···
conditions = nil
args = nil
for _, p := range pipelines {
-
knotFilter := FilterEq("pipeline_knot", p.Knot)
-
rkeyFilter := FilterEq("pipeline_rkey", p.Rkey)
+
knotFilter := orm.FilterEq("pipeline_knot", p.Knot)
+
rkeyFilter := orm.FilterEq("pipeline_rkey", p.Rkey)
conditions = append(conditions, fmt.Sprintf("(%s and %s)", knotFilter.Condition(), rkeyFilter.Condition()))
args = append(args, p.Knot)
args = append(args, p.Rkey)
+6 -5
appview/db/profile.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
const TimeframeMonths = 7
···
issues, err := GetIssues(
e,
-
FilterEq("did", forDid),
-
FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)),
+
orm.FilterEq("did", forDid),
+
orm.FilterGte("created", time.Now().AddDate(0, -TimeframeMonths, 0)),
)
if err != nil {
return nil, fmt.Errorf("error getting issues by owner did: %w", err)
···
*items = append(*items, &issue)
}
-
repos, err := GetRepos(e, 0, FilterEq("did", forDid))
+
repos, err := GetRepos(e, 0, orm.FilterEq("did", forDid))
if err != nil {
return nil, fmt.Errorf("error getting all repos by did: %w", err)
}
···
return tx.Commit()
}
-
func GetProfiles(e Execer, filters ...filter) (map[string]*models.Profile, error) {
+
func GetProfiles(e Execer, filters ...orm.Filter) (map[string]*models.Profile, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
}
// ensure all pinned repos are either own repos or collaborating repos
-
repos, err := GetRepos(e, 0, FilterEq("did", profile.Did))
+
repos, err := GetRepos(e, 0, orm.FilterEq("did", profile.Did))
if err != nil {
log.Printf("getting repos for %s: %s", profile.Did, err)
}
+21 -20
appview/db/pulls.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func NewPull(tx *sql.Tx, pull *models.Pull) error {
···
return pullId - 1, err
}
-
func GetPullsWithLimit(e Execer, limit int, filters ...filter) ([]*models.Pull, error) {
+
func GetPullsWithLimit(e Execer, limit int, filters ...orm.Filter) ([]*models.Pull, error) {
pulls := make(map[syntax.ATURI]*models.Pull)
var conditions []string
···
for _, p := range pulls {
pullAts = append(pullAts, p.AtUri())
}
-
submissionsMap, err := GetPullSubmissions(e, FilterIn("pull_at", pullAts))
+
submissionsMap, err := GetPullSubmissions(e, orm.FilterIn("pull_at", pullAts))
if err != nil {
return nil, fmt.Errorf("failed to get submissions: %w", err)
}
···
}
// collect allLabels for each issue
-
allLabels, err := GetLabels(e, FilterIn("subject", pullAts))
+
allLabels, err := GetLabels(e, orm.FilterIn("subject", pullAts))
if err != nil {
return nil, fmt.Errorf("failed to query labels: %w", err)
}
···
sourceAts = append(sourceAts, *p.PullSource.RepoAt)
}
}
-
sourceRepos, err := GetRepos(e, 0, FilterIn("at_uri", sourceAts))
+
sourceRepos, err := GetRepos(e, 0, orm.FilterIn("at_uri", sourceAts))
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("failed to get source repos: %w", err)
}
···
}
}
-
allReferences, err := GetReferencesAll(e, FilterIn("from_at", pullAts))
+
allReferences, err := GetReferencesAll(e, orm.FilterIn("from_at", pullAts))
if err != nil {
return nil, fmt.Errorf("failed to query reference_links: %w", err)
}
···
return orderedByPullId, nil
}
-
func GetPulls(e Execer, filters ...filter) ([]*models.Pull, error) {
+
func GetPulls(e Execer, filters ...orm.Filter) ([]*models.Pull, error) {
return GetPullsWithLimit(e, 0, filters...)
}
func GetPullIDs(e Execer, opts models.PullSearchOptions) ([]int64, error) {
var ids []int64
-
var filters []filter
-
filters = append(filters, FilterEq("state", opts.State))
+
var filters []orm.Filter
+
filters = append(filters, orm.FilterEq("state", opts.State))
if opts.RepoAt != "" {
-
filters = append(filters, FilterEq("repo_at", opts.RepoAt))
+
filters = append(filters, orm.FilterEq("repo_at", opts.RepoAt))
}
var conditions []string
···
}
func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*models.Pull, error) {
-
pulls, err := GetPullsWithLimit(e, 1, FilterEq("repo_at", repoAt), FilterEq("pull_id", pullId))
+
pulls, err := GetPullsWithLimit(e, 1, orm.FilterEq("repo_at", repoAt), orm.FilterEq("pull_id", pullId))
if err != nil {
return nil, err
}
···
}
// mapping from pull -> pull submissions
-
func GetPullSubmissions(e Execer, filters ...filter) (map[syntax.ATURI][]*models.PullSubmission, error) {
+
func GetPullSubmissions(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]*models.PullSubmission, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
// Get comments for all submissions using GetPullComments
submissionIds := slices.Collect(maps.Keys(submissionMap))
-
comments, err := GetPullComments(e, FilterIn("submission_id", submissionIds))
+
comments, err := GetPullComments(e, orm.FilterIn("submission_id", submissionIds))
if err != nil {
return nil, fmt.Errorf("failed to get pull comments: %w", err)
}
···
return m, nil
}
-
func GetPullComments(e Execer, filters ...filter) ([]models.PullComment, error) {
+
func GetPullComments(e Execer, filters ...orm.Filter) ([]models.PullComment, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
// collect references for each comments
commentAts := slices.Collect(maps.Keys(commentMap))
-
allReferencs, err := GetReferencesAll(e, FilterIn("from_at", commentAts))
+
allReferencs, err := GetReferencesAll(e, orm.FilterIn("from_at", commentAts))
if err != nil {
return nil, fmt.Errorf("failed to query reference_links: %w", err)
}
···
return err
}
-
func SetPullParentChangeId(e Execer, parentChangeId string, filters ...filter) error {
+
func SetPullParentChangeId(e Execer, parentChangeId string, filters ...orm.Filter) error {
var conditions []string
var args []any
···
// Only used when stacking to update contents in the event of a rebase (the interdiff should be empty).
// otherwise submissions are immutable
-
func UpdatePull(e Execer, newPatch, sourceRev string, filters ...filter) error {
+
func UpdatePull(e Execer, newPatch, sourceRev string, filters ...orm.Filter) error {
var conditions []string
var args []any
···
func GetStack(e Execer, stackId string) (models.Stack, error) {
unorderedPulls, err := GetPulls(
e,
-
FilterEq("stack_id", stackId),
-
FilterNotEq("state", models.PullDeleted),
+
orm.FilterEq("stack_id", stackId),
+
orm.FilterNotEq("state", models.PullDeleted),
)
if err != nil {
return nil, err
···
func GetAbandonedPulls(e Execer, stackId string) ([]*models.Pull, error) {
pulls, err := GetPulls(
e,
-
FilterEq("stack_id", stackId),
-
FilterEq("state", models.PullDeleted),
+
orm.FilterEq("stack_id", stackId),
+
orm.FilterEq("state", models.PullDeleted),
)
if err != nil {
return nil, err
+2 -1
appview/db/punchcard.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
// this adds to the existing count
···
return err
}
-
func MakePunchcard(e Execer, filters ...filter) (*models.Punchcard, error) {
+
func MakePunchcard(e Execer, filters ...orm.Filter) (*models.Punchcard, error) {
punchcard := &models.Punchcard{}
now := time.Now()
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
+4 -3
appview/db/reference.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/api/tangled"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
// ValidateReferenceLinks resolves refLinks to Issue/PR/IssueComment/PullComment ATURIs.
···
return err
}
-
func GetReferencesAll(e Execer, filters ...filter) (map[syntax.ATURI][]syntax.ATURI, error) {
+
func GetReferencesAll(e Execer, filters ...orm.Filter) (map[syntax.ATURI][]syntax.ATURI, error) {
var (
conditions []string
args []any
···
if len(aturis) == 0 {
return nil, nil
}
-
filter := FilterIn("c.at_uri", aturis)
+
filter := orm.FilterIn("c.at_uri", aturis)
rows, err := e.Query(
fmt.Sprintf(
`select r.did, r.name, i.issue_id, c.id, i.title, i.open
···
if len(aturis) == 0 {
return nil, nil
}
-
filter := FilterIn("c.comment_at", aturis)
+
filter := orm.FilterIn("c.comment_at", aturis)
rows, err := e.Query(
fmt.Sprintf(
`select r.did, r.name, p.pull_id, c.id, p.title, p.state
+4 -3
appview/db/registration.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
-
func GetRegistrations(e Execer, filters ...filter) ([]models.Registration, error) {
+
func GetRegistrations(e Execer, filters ...orm.Filter) ([]models.Registration, error) {
var registrations []models.Registration
var conditions []string
···
return registrations, nil
}
-
func MarkRegistered(e Execer, filters ...filter) error {
+
func MarkRegistered(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func DeleteKnot(e Execer, filters ...filter) error {
+
func DeleteKnot(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
+6 -5
appview/db/repos.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
-
func GetRepos(e Execer, limit int, filters ...filter) ([]models.Repo, error) {
+
func GetRepos(e Execer, limit int, filters ...orm.Filter) ([]models.Repo, error) {
repoMap := make(map[syntax.ATURI]*models.Repo)
var conditions []string
···
}
// helper to get exactly one repo
-
func GetRepo(e Execer, filters ...filter) (*models.Repo, error) {
+
func GetRepo(e Execer, filters ...orm.Filter) (*models.Repo, error) {
repos, err := GetRepos(e, 0, filters...)
if err != nil {
return nil, err
···
return &repos[0], nil
}
-
func CountRepos(e Execer, filters ...filter) (int64, error) {
+
func CountRepos(e Execer, filters ...orm.Filter) (int64, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func UnsubscribeLabel(e Execer, filters ...filter) error {
+
func UnsubscribeLabel(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func GetRepoLabels(e Execer, filters ...filter) ([]models.RepoLabel, error) {
+
func GetRepoLabels(e Execer, filters ...orm.Filter) ([]models.RepoLabel, error) {
var conditions []string
var args []any
for _, filter := range filters {
+6 -5
appview/db/spindle.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
-
func GetSpindles(e Execer, filters ...filter) ([]models.Spindle, error) {
+
func GetSpindles(e Execer, filters ...orm.Filter) ([]models.Spindle, error) {
var spindles []models.Spindle
var conditions []string
···
return err
}
-
func VerifySpindle(e Execer, filters ...filter) (int64, error) {
+
func VerifySpindle(e Execer, filters ...orm.Filter) (int64, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
return res.RowsAffected()
}
-
func DeleteSpindle(e Execer, filters ...filter) error {
+
func DeleteSpindle(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func RemoveSpindleMember(e Execer, filters ...filter) error {
+
func RemoveSpindleMember(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
···
return err
}
-
func GetSpindleMembers(e Execer, filters ...filter) ([]models.SpindleMember, error) {
+
func GetSpindleMembers(e Execer, filters ...orm.Filter) ([]models.SpindleMember, error) {
var members []models.SpindleMember
var conditions []string
+5 -4
appview/db/star.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func AddStar(e Execer, star *models.Star) error {
···
// GetRepoStars return a list of stars each holding target repository.
// If there isn't known repo with starred at-uri, those stars will be ignored.
-
func GetRepoStars(e Execer, limit int, filters ...filter) ([]models.RepoStar, error) {
+
func GetRepoStars(e Execer, limit int, filters ...orm.Filter) ([]models.RepoStar, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
return nil, nil
}
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", args))
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", args))
if err != nil {
return nil, err
}
···
return repoStars, nil
}
-
func CountStars(e Execer, filters ...filter) (int64, error) {
+
func CountStars(e Execer, filters ...orm.Filter) (int64, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
}
// get full repo data
-
repos, err := GetRepos(e, 0, FilterIn("at_uri", repoUris))
+
repos, err := GetRepos(e, 0, orm.FilterIn("at_uri", repoUris))
if err != nil {
return nil, err
}
+4 -3
appview/db/strings.go
···
"time"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func AddString(e Execer, s models.String) error {
···
return err
}
-
func GetStrings(e Execer, limit int, filters ...filter) ([]models.String, error) {
+
func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, error) {
var all []models.String
var conditions []string
···
return all, nil
}
-
func CountStrings(e Execer, filters ...filter) (int64, error) {
+
func CountStrings(e Execer, filters ...orm.Filter) (int64, error) {
var conditions []string
var args []any
for _, filter := range filters {
···
return count, nil
}
-
func DeleteString(e Execer, filters ...filter) error {
+
func DeleteString(e Execer, filters ...orm.Filter) error {
var conditions []string
var args []any
for _, filter := range filters {
+9 -8
appview/db/timeline.go
···
"github.com/bluesky-social/indigo/atproto/syntax"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
// TODO: this gathers heterogenous events from different sources and aggregates
···
}
func getTimelineRepos(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
-
filters := make([]filter, 0)
+
filters := make([]orm.Filter, 0)
if userIsFollowing != nil {
-
filters = append(filters, FilterIn("did", userIsFollowing))
+
filters = append(filters, orm.FilterIn("did", userIsFollowing))
}
repos, err := GetRepos(e, limit, filters...)
···
var origRepos []models.Repo
if args != nil {
-
origRepos, err = GetRepos(e, 0, FilterIn("at_uri", args))
+
origRepos, err = GetRepos(e, 0, orm.FilterIn("at_uri", args))
}
if err != nil {
return nil, err
···
}
func getTimelineStars(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
-
filters := make([]filter, 0)
+
filters := make([]orm.Filter, 0)
if userIsFollowing != nil {
-
filters = append(filters, FilterIn("did", userIsFollowing))
+
filters = append(filters, orm.FilterIn("did", userIsFollowing))
}
stars, err := GetRepoStars(e, limit, filters...)
···
}
func getTimelineFollows(e Execer, limit int, loggedInUserDid string, userIsFollowing []string) ([]models.TimelineEvent, error) {
-
filters := make([]filter, 0)
+
filters := make([]orm.Filter, 0)
if userIsFollowing != nil {
-
filters = append(filters, FilterIn("user_did", userIsFollowing))
+
filters = append(filters, orm.FilterIn("user_did", userIsFollowing))
}
follows, err := GetFollows(e, limit, filters...)
···
return nil, nil
}
-
profiles, err := GetProfiles(e, FilterIn("did", subjects))
+
profiles, err := GetProfiles(e, orm.FilterIn("did", subjects))
if err != nil {
return nil, err
}
+25 -24
appview/ingester.go
···
"tangled.org/core/appview/serververify"
"tangled.org/core/appview/validator"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
)
···
err = db.AddArtifact(i.Db, artifact)
case jmodels.CommitOperationDelete:
-
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
+
err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey))
}
if err != nil {
···
err = db.UpsertProfile(tx, &profile)
case jmodels.CommitOperationDelete:
-
err = db.DeleteArtifact(i.Db, db.FilterEq("did", did), db.FilterEq("rkey", e.Commit.RKey))
+
err = db.DeleteArtifact(i.Db, orm.FilterEq("did", did), orm.FilterEq("rkey", e.Commit.RKey))
}
if err != nil {
···
// get record from db first
members, err := db.GetSpindleMembers(
ddb,
-
db.FilterEq("did", did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", did),
+
orm.FilterEq("rkey", rkey),
)
if err != nil || len(members) != 1 {
return fmt.Errorf("failed to get member: %w, len(members) = %d", err, len(members))
···
// remove record by rkey && update enforcer
if err = db.RemoveSpindleMember(
tx,
-
db.FilterEq("did", did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", did),
+
orm.FilterEq("rkey", rkey),
); err != nil {
return fmt.Errorf("failed to remove from db: %w", err)
}
···
// get record from db first
spindles, err := db.GetSpindles(
ddb,
-
db.FilterEq("owner", did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", did),
+
orm.FilterEq("instance", instance),
)
if err != nil || len(spindles) != 1 {
return fmt.Errorf("failed to get spindles: %w, len(spindles) = %d", err, len(spindles))
···
// remove spindle members first
err = db.RemoveSpindleMember(
tx,
-
db.FilterEq("owner", did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", did),
+
orm.FilterEq("instance", instance),
)
if err != nil {
return err
···
err = db.DeleteSpindle(
tx,
-
db.FilterEq("owner", did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", did),
+
orm.FilterEq("instance", instance),
)
if err != nil {
return err
···
case jmodels.CommitOperationDelete:
if err := db.DeleteString(
ddb,
-
db.FilterEq("did", did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", did),
+
orm.FilterEq("rkey", rkey),
); err != nil {
l.Error("failed to delete", "err", err)
return fmt.Errorf("failed to delete string record: %w", err)
···
// get record from db first
registrations, err := db.GetRegistrations(
ddb,
-
db.FilterEq("domain", domain),
-
db.FilterEq("did", did),
+
orm.FilterEq("domain", domain),
+
orm.FilterEq("did", did),
)
if err != nil {
return fmt.Errorf("failed to get registration: %w", err)
···
err = db.DeleteKnot(
tx,
-
db.FilterEq("did", did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
return err
···
case jmodels.CommitOperationDelete:
if err := db.DeleteIssueComments(
ddb,
-
db.FilterEq("did", did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", did),
+
orm.FilterEq("rkey", rkey),
); err != nil {
return fmt.Errorf("failed to delete issue comment record: %w", err)
}
···
case jmodels.CommitOperationDelete:
if err := db.DeleteLabelDefinition(
ddb,
-
db.FilterEq("did", did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", did),
+
orm.FilterEq("rkey", rkey),
); err != nil {
return fmt.Errorf("failed to delete labeldef record: %w", err)
}
···
var repo *models.Repo
switch collection {
case tangled.RepoIssueNSID:
-
i, err := db.GetIssues(ddb, db.FilterEq("at_uri", subject))
+
i, err := db.GetIssues(ddb, orm.FilterEq("at_uri", subject))
if err != nil || len(i) != 1 {
return fmt.Errorf("failed to find subject: %w || subject count %d", err, len(i))
···
return fmt.Errorf("unsupport label subject: %s", collection)
-
actx, err := db.NewLabelApplicationCtx(ddb, db.FilterIn("at_uri", repo.Labels))
+
actx, err := db.NewLabelApplicationCtx(ddb, orm.FilterIn("at_uri", repo.Labels))
if err != nil {
return fmt.Errorf("failed to build label application ctx: %w", err)
+46 -45
appview/issues/issues.go
···
"tangled.org/core/appview/config"
"tangled.org/core/appview/db"
issues_indexer "tangled.org/core/appview/indexer/issues"
+
"tangled.org/core/appview/mentions"
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/pages/repoinfo"
"tangled.org/core/appview/pagination"
-
"tangled.org/core/appview/refresolver"
"tangled.org/core/appview/reporesolver"
"tangled.org/core/appview/validator"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
)
type Issues struct {
-
oauth *oauth.OAuth
-
repoResolver *reporesolver.RepoResolver
-
enforcer *rbac.Enforcer
-
pages *pages.Pages
-
idResolver *idresolver.Resolver
-
refResolver *refresolver.Resolver
-
db *db.DB
-
config *config.Config
-
notifier notify.Notifier
-
logger *slog.Logger
-
validator *validator.Validator
-
indexer *issues_indexer.Indexer
+
oauth *oauth.OAuth
+
repoResolver *reporesolver.RepoResolver
+
enforcer *rbac.Enforcer
+
pages *pages.Pages
+
idResolver *idresolver.Resolver
+
mentionsResolver *mentions.Resolver
+
db *db.DB
+
config *config.Config
+
notifier notify.Notifier
+
logger *slog.Logger
+
validator *validator.Validator
+
indexer *issues_indexer.Indexer
}
func New(
···
enforcer *rbac.Enforcer,
pages *pages.Pages,
idResolver *idresolver.Resolver,
-
refResolver *refresolver.Resolver,
+
mentionsResolver *mentions.Resolver,
db *db.DB,
config *config.Config,
notifier notify.Notifier,
···
logger *slog.Logger,
) *Issues {
return &Issues{
-
oauth: oauth,
-
repoResolver: repoResolver,
-
enforcer: enforcer,
-
pages: pages,
-
idResolver: idResolver,
-
refResolver: refResolver,
-
db: db,
-
config: config,
-
notifier: notifier,
-
logger: logger,
-
validator: validator,
-
indexer: indexer,
+
oauth: oauth,
+
repoResolver: repoResolver,
+
enforcer: enforcer,
+
pages: pages,
+
idResolver: idResolver,
+
mentionsResolver: mentionsResolver,
+
db: db,
+
config: config,
+
notifier: notifier,
+
logger: logger,
+
validator: validator,
+
indexer: indexer,
}
}
···
labelDefs, err := db.GetLabelDefinitions(
rp.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", tangled.RepoIssueNSID),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", tangled.RepoIssueNSID),
)
if err != nil {
l.Error("failed to fetch labels", "err", err)
···
newIssue := issue
newIssue.Title = r.FormValue("title")
newIssue.Body = r.FormValue("body")
-
newIssue.Mentions, newIssue.References = rp.refResolver.Resolve(r.Context(), newIssue.Body)
+
newIssue.Mentions, newIssue.References = rp.mentionsResolver.Resolve(r.Context(), newIssue.Body)
if err := rp.validator.ValidateIssue(newIssue); err != nil {
l.Error("validation error", "err", err)
···
if isIssueOwner || isRepoOwner || isCollaborator {
err = db.CloseIssues(
rp.db,
-
db.FilterEq("id", issue.Id),
+
orm.FilterEq("id", issue.Id),
)
if err != nil {
l.Error("failed to close issue", "err", err)
···
if isCollaborator || isRepoOwner || isIssueOwner {
err := db.ReopenIssues(
rp.db,
-
db.FilterEq("id", issue.Id),
+
orm.FilterEq("id", issue.Id),
)
if err != nil {
l.Error("failed to reopen issue", "err", err)
···
replyTo = &replyToUri
}
-
mentions, references := rp.refResolver.Resolve(r.Context(), body)
+
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
comment := models.IssueComment{
Did: user.Did,
···
commentId := chi.URLParam(r, "commentId")
comments, err := db.GetIssueComments(
rp.db,
-
db.FilterEq("id", commentId),
+
orm.FilterEq("id", commentId),
)
if err != nil {
l.Error("failed to fetch comment", "id", commentId)
···
commentId := chi.URLParam(r, "commentId")
comments, err := db.GetIssueComments(
rp.db,
-
db.FilterEq("id", commentId),
+
orm.FilterEq("id", commentId),
)
if err != nil {
l.Error("failed to fetch comment", "id", commentId)
···
newComment := comment
newComment.Body = newBody
newComment.Edited = &now
-
newComment.Mentions, newComment.References = rp.refResolver.Resolve(r.Context(), newBody)
+
newComment.Mentions, newComment.References = rp.mentionsResolver.Resolve(r.Context(), newBody)
record := newComment.AsRecord()
···
commentId := chi.URLParam(r, "commentId")
comments, err := db.GetIssueComments(
rp.db,
-
db.FilterEq("id", commentId),
+
orm.FilterEq("id", commentId),
)
if err != nil {
l.Error("failed to fetch comment", "id", commentId)
···
commentId := chi.URLParam(r, "commentId")
comments, err := db.GetIssueComments(
rp.db,
-
db.FilterEq("id", commentId),
+
orm.FilterEq("id", commentId),
)
if err != nil {
l.Error("failed to fetch comment", "id", commentId)
···
commentId := chi.URLParam(r, "commentId")
comments, err := db.GetIssueComments(
rp.db,
-
db.FilterEq("id", commentId),
+
orm.FilterEq("id", commentId),
)
if err != nil {
l.Error("failed to fetch comment", "id", commentId)
···
// optimistic deletion
deleted := time.Now()
-
err = db.DeleteIssueComments(rp.db, db.FilterEq("id", comment.Id))
+
err = db.DeleteIssueComments(rp.db, orm.FilterEq("id", comment.Id))
if err != nil {
l.Error("failed to delete comment", "err", err)
rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment")
···
issues, err = db.GetIssues(
rp.db,
-
db.FilterIn("id", res.Hits),
+
orm.FilterIn("id", res.Hits),
)
if err != nil {
l.Error("failed to get issues", "err", err)
···
issues, err = db.GetIssuesPaginated(
rp.db,
page,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("open", openInt),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("open", openInt),
)
if err != nil {
l.Error("failed to get issues", "err", err)
···
labelDefs, err := db.GetLabelDefinitions(
rp.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", tangled.RepoIssueNSID),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", tangled.RepoIssueNSID),
)
if err != nil {
l.Error("failed to fetch labels", "err", err)
···
})
case http.MethodPost:
body := r.FormValue("body")
-
mentions, references := rp.refResolver.Resolve(r.Context(), body)
+
mentions, references := rp.mentionsResolver.Resolve(r.Context(), body)
issue := &models.Issue{
RepoAt: f.RepoAt(),
+19 -18
appview/knots/knots.go
···
"tangled.org/core/appview/xrpcclient"
"tangled.org/core/eventconsumer"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
···
user := k.OAuth.GetUser(r)
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
+
orm.FilterEq("did", user.Did),
)
if err != nil {
k.Logger.Error("failed to fetch knot registrations", "err", err)
···
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
l.Error("failed to get registrations", "err", err)
···
repos, err := db.GetRepos(
k.Db,
0,
-
db.FilterEq("knot", domain),
+
orm.FilterEq("knot", domain),
)
if err != nil {
l.Error("failed to get knot repos", "err", err)
···
// get record from db first
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
l.Error("failed to get registration", "err", err)
···
err = db.DeleteKnot(
tx,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
l.Error("failed to delete registration", "err", err)
···
// get record from db first
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
l.Error("failed to get registration", "err", err)
···
// Get updated registration to show
registrations, err = db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
)
if err != nil {
l.Error("failed to get registration", "err", err)
···
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
-
db.FilterIsNot("registered", "null"),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
+
orm.FilterIsNot("registered", "null"),
)
if err != nil {
l.Error("failed to get registration", "err", err)
···
registrations, err := db.GetRegistrations(
k.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("domain", domain),
-
db.FilterIsNot("registered", "null"),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("domain", domain),
+
orm.FilterIsNot("registered", "null"),
)
if err != nil {
l.Error("failed to get registration", "err", err)
+5 -4
appview/labels/labels.go
···
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/validator"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
···
repoAt := r.Form.Get("repo")
subjectUri := r.Form.Get("subject")
-
repo, err := db.GetRepo(l.db, db.FilterEq("at_uri", repoAt))
+
repo, err := db.GetRepo(l.db, orm.FilterEq("at_uri", repoAt))
if err != nil {
fail("Failed to get repository.", err)
return
}
// find all the labels that this repo subscribes to
-
repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt))
+
repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_at", repoAt))
if err != nil {
fail("Failed to get labels for this repository.", err)
return
···
labelAts = append(labelAts, rl.LabelAt.String())
}
-
actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts))
+
actx, err := db.NewLabelApplicationCtx(l.db, orm.FilterIn("at_uri", labelAts))
if err != nil {
fail("Invalid form data.", err)
return
}
// calculate the start state by applying already known labels
-
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
+
existingOps, err := db.GetLabelOps(l.db, orm.FilterEq("subject", subjectUri))
if err != nil {
fail("Invalid form data.", err)
return
+67
appview/mentions/resolver.go
···
+
package mentions
+
+
import (
+
"context"
+
"log/slog"
+
+
"github.com/bluesky-social/indigo/atproto/syntax"
+
"tangled.org/core/appview/config"
+
"tangled.org/core/appview/db"
+
"tangled.org/core/appview/models"
+
"tangled.org/core/appview/pages/markup"
+
"tangled.org/core/idresolver"
+
)
+
+
type Resolver struct {
+
config *config.Config
+
idResolver *idresolver.Resolver
+
execer db.Execer
+
logger *slog.Logger
+
}
+
+
func New(
+
config *config.Config,
+
idResolver *idresolver.Resolver,
+
execer db.Execer,
+
logger *slog.Logger,
+
) *Resolver {
+
return &Resolver{
+
config,
+
idResolver,
+
execer,
+
logger,
+
}
+
}
+
+
func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) {
+
l := r.logger.With("method", "Resolve")
+
+
rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source)
+
l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs)
+
+
idents := r.idResolver.ResolveIdents(ctx, rawMentions)
+
var mentions []syntax.DID
+
for _, ident := range idents {
+
if ident != nil && !ident.Handle.IsInvalidHandle() {
+
mentions = append(mentions, ident.DID)
+
}
+
}
+
l.Debug("found mentions", "mentions", mentions)
+
+
var resolvedRefs []models.ReferenceLink
+
for _, rawRef := range rawRefs {
+
ident, err := r.idResolver.ResolveIdent(ctx, rawRef.Handle)
+
if err != nil || ident == nil || ident.Handle.IsInvalidHandle() {
+
continue
+
}
+
rawRef.Handle = string(ident.DID)
+
resolvedRefs = append(resolvedRefs, rawRef)
+
}
+
aturiRefs, err := db.ValidateReferenceLinks(r.execer, resolvedRefs)
+
if err != nil {
+
l.Error("failed running query", "err", err)
+
}
+
l.Debug("found references", "refs", aturiRefs)
+
+
return mentions, aturiRefs
+
}
+3 -2
appview/middleware/middleware.go
···
"tangled.org/core/appview/pagination"
"tangled.org/core/appview/reporesolver"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
)
···
repo, err := db.GetRepo(
mw.db,
-
db.FilterEq("did", id.DID.String()),
-
db.FilterEq("name", repoName),
+
orm.FilterEq("did", id.DID.String()),
+
orm.FilterEq("name", repoName),
)
if err != nil {
log.Println("failed to resolve repo", "err", err)
+5 -4
appview/notifications/notifications.go
···
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/pagination"
+
"tangled.org/core/orm"
)
type Notifications struct {
···
total, err := db.CountNotifications(
n.db,
-
db.FilterEq("recipient_did", user.Did),
+
orm.FilterEq("recipient_did", user.Did),
)
if err != nil {
l.Error("failed to get total notifications", "err", err)
···
notifications, err := db.GetNotificationsWithEntities(
n.db,
page,
-
db.FilterEq("recipient_did", user.Did),
+
orm.FilterEq("recipient_did", user.Did),
)
if err != nil {
l.Error("failed to get notifications", "err", err)
···
count, err := db.CountNotifications(
n.db,
-
db.FilterEq("recipient_did", user.Did),
-
db.FilterEq("read", 0),
+
orm.FilterEq("recipient_did", user.Did),
+
orm.FilterEq("read", 0),
)
if err != nil {
http.Error(w, "Failed to get unread count", http.StatusInternalServerError)
+77 -66
appview/notify/db/db.go
···
import (
"context"
"log"
-
"maps"
"slices"
"github.com/bluesky-social/indigo/atproto/syntax"
···
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
+
"tangled.org/core/sets"
)
const (
-
maxMentions = 5
+
maxMentions = 8
)
type databaseNotifier struct {
···
return
}
var err error
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(star.RepoAt)))
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(star.RepoAt)))
if err != nil {
log.Printf("NewStar: failed to get repos: %v", err)
return
}
actorDid := syntax.DID(star.Did)
-
recipients := []syntax.DID{syntax.DID(repo.Did)}
+
recipients := sets.Singleton(syntax.DID(repo.Did))
eventType := models.NotificationTypeRepoStarred
entityType := "repo"
entityId := star.RepoAt.String()
···
}
func (n *databaseNotifier) NewIssue(ctx context.Context, issue *models.Issue, mentions []syntax.DID) {
-
-
// build the recipients list
-
// - owner of the repo
-
// - collaborators in the repo
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
if err != nil {
log.Printf("failed to fetch collaborators: %v", err)
return
}
+
+
// build the recipients list
+
// - owner of the repo
+
// - collaborators in the repo
+
// - remove users already mentioned
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
+
recipients.Insert(c.SubjectDid)
+
}
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(issue.Did)
···
)
n.notifyEvent(
actorDid,
-
mentions,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
func (n *databaseNotifier) NewIssueComment(ctx context.Context, comment *models.IssueComment, mentions []syntax.DID) {
-
issues, err := db.GetIssues(n.db, db.FilterEq("at_uri", comment.IssueAt))
+
issues, err := db.GetIssues(n.db, orm.FilterEq("at_uri", comment.IssueAt))
if err != nil {
log.Printf("NewIssueComment: failed to get issues: %v", err)
return
···
}
issue := issues[0]
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
+
// built the recipients list:
+
// - the owner of the repo
+
// - | if the comment is a reply -> everybody on that thread
+
// | if the comment is a top level -> just the issue owner
+
// - remove mentioned users from the recipients list
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
if comment.IsReply() {
// if this comment is a reply, then notify everybody in that thread
parentAtUri := *comment.ReplyTo
-
allThreads := issue.CommentList()
// find the parent thread, and add all DIDs from here to the recipient list
-
for _, t := range allThreads {
+
for _, t := range issue.CommentList() {
if t.Self.AtUri().String() == parentAtUri {
-
recipients = append(recipients, t.Participants()...)
+
for _, p := range t.Participants() {
+
recipients.Insert(p)
+
}
}
}
} else {
// not a reply, notify just the issue author
-
recipients = append(recipients, syntax.DID(issue.Did))
+
recipients.Insert(syntax.DID(issue.Did))
+
}
+
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(comment.Did)
···
)
n.notifyEvent(
actorDid,
-
mentions,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
func (n *databaseNotifier) NewFollow(ctx context.Context, follow *models.Follow) {
actorDid := syntax.DID(follow.UserDid)
-
recipients := []syntax.DID{syntax.DID(follow.SubjectDid)}
+
recipients := sets.Singleton(syntax.DID(follow.SubjectDid))
eventType := models.NotificationTypeFollowed
entityType := "follow"
entityId := follow.UserDid
···
}
func (n *databaseNotifier) NewPull(ctx context.Context, pull *models.Pull) {
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt)))
if err != nil {
log.Printf("NewPull: failed to get repos: %v", err)
return
}
-
-
// build the recipients list
-
// - owner of the repo
-
// - collaborators in the repo
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(repo.Did))
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt()))
if err != nil {
log.Printf("failed to fetch collaborators: %v", err)
return
}
+
+
// build the recipients list
+
// - owner of the repo
+
// - collaborators in the repo
+
recipients := sets.Singleton(syntax.DID(repo.Did))
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
+
recipients.Insert(c.SubjectDid)
}
actorDid := syntax.DID(pull.OwnerDid)
···
return
}
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", comment.RepoAt))
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", comment.RepoAt))
if err != nil {
log.Printf("NewPullComment: failed to get repos: %v", err)
return
···
// build up the recipients list:
// - repo owner
// - all pull participants
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(repo.Did))
+
// - remove those already mentioned
+
recipients := sets.Singleton(syntax.DID(repo.Did))
for _, p := range pull.Participants() {
-
recipients = append(recipients, syntax.DID(p))
+
recipients.Insert(syntax.DID(p))
+
}
+
for _, m := range mentions {
+
recipients.Remove(m)
}
actorDid := syntax.DID(comment.OwnerDid)
···
)
n.notifyEvent(
actorDid,
-
mentions,
+
sets.Collect(slices.Values(mentions)),
models.NotificationTypeUserMentioned,
entityType,
entityId,
···
}
func (n *databaseNotifier) NewIssueState(ctx context.Context, actor syntax.DID, issue *models.Issue) {
-
// build up the recipients list:
-
// - repo owner
-
// - repo collaborators
-
// - all issue participants
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(issue.Repo.Did))
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", issue.Repo.RepoAt()))
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", issue.Repo.RepoAt()))
if err != nil {
log.Printf("failed to fetch collaborators: %v", err)
return
}
+
+
// build up the recipients list:
+
// - repo owner
+
// - repo collaborators
+
// - all issue participants
+
recipients := sets.Singleton(syntax.DID(issue.Repo.Did))
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
+
recipients.Insert(c.SubjectDid)
}
for _, p := range issue.Participants() {
-
recipients = append(recipients, syntax.DID(p))
+
recipients.Insert(syntax.DID(p))
}
entityType := "pull"
···
func (n *databaseNotifier) NewPullState(ctx context.Context, actor syntax.DID, pull *models.Pull) {
// Get repo details
-
repo, err := db.GetRepo(n.db, db.FilterEq("at_uri", string(pull.RepoAt)))
+
repo, err := db.GetRepo(n.db, orm.FilterEq("at_uri", string(pull.RepoAt)))
if err != nil {
log.Printf("NewPullState: failed to get repos: %v", err)
return
}
-
// build up the recipients list:
-
// - repo owner
-
// - all pull participants
-
var recipients []syntax.DID
-
recipients = append(recipients, syntax.DID(repo.Did))
-
collaborators, err := db.GetCollaborators(n.db, db.FilterEq("repo_at", repo.RepoAt()))
+
collaborators, err := db.GetCollaborators(n.db, orm.FilterEq("repo_at", repo.RepoAt()))
if err != nil {
log.Printf("failed to fetch collaborators: %v", err)
return
}
+
+
// build up the recipients list:
+
// - repo owner
+
// - all pull participants
+
recipients := sets.Singleton(syntax.DID(repo.Did))
for _, c := range collaborators {
-
recipients = append(recipients, c.SubjectDid)
+
recipients.Insert(c.SubjectDid)
}
for _, p := range pull.Participants() {
-
recipients = append(recipients, syntax.DID(p))
+
recipients.Insert(syntax.DID(p))
}
entityType := "pull"
···
func (n *databaseNotifier) notifyEvent(
actorDid syntax.DID,
-
recipients []syntax.DID,
+
recipients sets.Set[syntax.DID],
eventType models.NotificationType,
entityType string,
entityId string,
···
issueId *int64,
pullId *int64,
) {
-
if eventType == models.NotificationTypeUserMentioned && len(recipients) > maxMentions {
-
recipients = recipients[:maxMentions]
+
// if the user is attempting to mention >maxMentions users, this is probably spam, do not mention anybody
+
if eventType == models.NotificationTypeUserMentioned && recipients.Len() > maxMentions {
+
return
}
-
recipientSet := make(map[syntax.DID]struct{})
-
for _, did := range recipients {
-
// everybody except actor themselves
-
if did != actorDid {
-
recipientSet[did] = struct{}{}
-
}
-
}
+
+
recipients.Remove(actorDid)
prefMap, err := db.GetNotificationPreferences(
n.db,
-
db.FilterIn("user_did", slices.Collect(maps.Keys(recipientSet))),
+
orm.FilterIn("user_did", slices.Collect(recipients.All())),
)
if err != nil {
// failed to get prefs for users
···
defer tx.Rollback()
// filter based on preferences
-
for recipientDid := range recipientSet {
+
for recipientDid := range recipients.All() {
prefs, ok := prefMap[recipientDid]
if !ok {
prefs = models.DefaultNotificationPreferences(recipientDid)
+3 -2
appview/oauth/handler.go
···
"tangled.org/core/api/tangled"
"tangled.org/core/appview/db"
"tangled.org/core/consts"
+
"tangled.org/core/orm"
"tangled.org/core/tid"
)
···
// and create an sh.tangled.spindle.member record with that
spindleMembers, err := db.GetSpindleMembers(
o.Db,
-
db.FilterEq("instance", "spindle.tangled.sh"),
-
db.FilterEq("subject", did),
+
orm.FilterEq("instance", "spindle.tangled.sh"),
+
orm.FilterEq("subject", did),
)
if err != nil {
l.Error("failed to get spindle members", "err", err)
-2
appview/pages/markup/markdown.go
···
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/alecthomas/chroma/v2/styles"
-
treeblood "github.com/wyatt915/goldmark-treeblood"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/ast"
···
extension.NewFootnote(
extension.WithFootnoteIDPrefix([]byte("footnote")),
),
-
treeblood.MathML(),
callout.CalloutExtention,
textension.AtExt,
),
+9 -6
appview/pages/templates/user/signup.html
···
page to complete your registration.
</span>
<div class="w-full mt-4 text-center">
-
<div class="cf-turnstile" data-sitekey="{{ .CloudflareSiteKey }}"></div>
+
<div class="cf-turnstile" data-sitekey="{{ .CloudflareSiteKey }}" data-size="flexible"></div>
</div>
<button class="btn text-base w-full my-2 mt-6" type="submit" id="signup-button" tabindex="7" >
<span>join now</span>
</button>
+
<p class="text-sm text-gray-500">
+
Already have an AT Protocol account? <a href="/login" class="underline">Login to Tangled</a>.
+
</p>
+
+
<p id="signup-msg" class="error w-full"></p>
+
<p class="text-sm text-gray-500 pt-4">
+
By signing up, you agree to our <a href="/terms" class="underline">Terms of Service</a> and <a href="/privacy" class="underline">Privacy Policy</a>.
+
</p>
</form>
-
<p class="text-sm text-gray-500">
-
Already have an AT Protocol account? <a href="/login" class="underline">Login to Tangled</a>.
-
</p>
-
-
<p id="signup-msg" class="error w-full"></p>
</main>
</body>
</html>
+12 -11
appview/pipelines/pipelines.go
···
"tangled.org/core/appview/reporesolver"
"tangled.org/core/eventconsumer"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
spindlemodel "tangled.org/core/spindle/models"
···
ps, err := db.GetPipelineStatuses(
p.db,
30,
-
db.FilterEq("repo_owner", f.Did),
-
db.FilterEq("repo_name", f.Name),
-
db.FilterEq("knot", f.Knot),
+
orm.FilterEq("repo_owner", f.Did),
+
orm.FilterEq("repo_name", f.Name),
+
orm.FilterEq("knot", f.Knot),
)
if err != nil {
l.Error("failed to query db", "err", err)
···
ps, err := db.GetPipelineStatuses(
p.db,
1,
-
db.FilterEq("repo_owner", f.Did),
-
db.FilterEq("repo_name", f.Name),
-
db.FilterEq("knot", f.Knot),
-
db.FilterEq("id", pipelineId),
+
orm.FilterEq("repo_owner", f.Did),
+
orm.FilterEq("repo_name", f.Name),
+
orm.FilterEq("knot", f.Knot),
+
orm.FilterEq("id", pipelineId),
)
if err != nil {
l.Error("failed to query db", "err", err)
···
ps, err := db.GetPipelineStatuses(
p.db,
1,
-
db.FilterEq("repo_owner", f.Did),
-
db.FilterEq("repo_name", f.Name),
-
db.FilterEq("knot", f.Knot),
-
db.FilterEq("id", pipelineId),
+
orm.FilterEq("repo_owner", f.Did),
+
orm.FilterEq("repo_name", f.Name),
+
orm.FilterEq("knot", f.Knot),
+
orm.FilterEq("id", pipelineId),
)
if err != nil || len(ps) != 1 {
l.Error("pipeline query failed", "err", err, "count", len(ps))
+2 -1
appview/pulls/opengraph.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/ogcard"
+
"tangled.org/core/orm"
"tangled.org/core/patchutil"
"tangled.org/core/types"
)
···
}
// Get comment count from database
-
comments, err := db.GetPullComments(s.db, db.FilterEq("pull_id", pull.ID))
+
comments, err := db.GetPullComments(s.db, orm.FilterEq("pull_id", pull.ID))
if err != nil {
log.Printf("failed to get pull comments: %v", err)
}
+48 -47
appview/pulls/pulls.go
···
"tangled.org/core/appview/config"
"tangled.org/core/appview/db"
pulls_indexer "tangled.org/core/appview/indexer/pulls"
+
"tangled.org/core/appview/mentions"
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/pages/markup"
"tangled.org/core/appview/pages/repoinfo"
-
"tangled.org/core/appview/refresolver"
"tangled.org/core/appview/reporesolver"
"tangled.org/core/appview/validator"
"tangled.org/core/appview/xrpcclient"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/patchutil"
"tangled.org/core/rbac"
"tangled.org/core/tid"
···
)
type Pulls struct {
-
oauth *oauth.OAuth
-
repoResolver *reporesolver.RepoResolver
-
pages *pages.Pages
-
idResolver *idresolver.Resolver
-
refResolver *refresolver.Resolver
-
db *db.DB
-
config *config.Config
-
notifier notify.Notifier
-
enforcer *rbac.Enforcer
-
logger *slog.Logger
-
validator *validator.Validator
-
indexer *pulls_indexer.Indexer
+
oauth *oauth.OAuth
+
repoResolver *reporesolver.RepoResolver
+
pages *pages.Pages
+
idResolver *idresolver.Resolver
+
mentionsResolver *mentions.Resolver
+
db *db.DB
+
config *config.Config
+
notifier notify.Notifier
+
enforcer *rbac.Enforcer
+
logger *slog.Logger
+
validator *validator.Validator
+
indexer *pulls_indexer.Indexer
}
func New(
···
repoResolver *reporesolver.RepoResolver,
pages *pages.Pages,
resolver *idresolver.Resolver,
-
refResolver *refresolver.Resolver,
+
mentionsResolver *mentions.Resolver,
db *db.DB,
config *config.Config,
notifier notify.Notifier,
···
logger *slog.Logger,
) *Pulls {
return &Pulls{
-
oauth: oauth,
-
repoResolver: repoResolver,
-
pages: pages,
-
idResolver: resolver,
-
refResolver: refResolver,
-
db: db,
-
config: config,
-
notifier: notifier,
-
enforcer: enforcer,
-
logger: logger,
-
validator: validator,
-
indexer: indexer,
+
oauth: oauth,
+
repoResolver: repoResolver,
+
pages: pages,
+
idResolver: resolver,
+
mentionsResolver: mentionsResolver,
+
db: db,
+
config: config,
+
notifier: notifier,
+
enforcer: enforcer,
+
logger: logger,
+
validator: validator,
+
indexer: indexer,
}
}
···
ps, err := db.GetPipelineStatuses(
s.db,
len(shas),
-
db.FilterEq("repo_owner", f.Did),
-
db.FilterEq("repo_name", f.Name),
-
db.FilterEq("knot", f.Knot),
-
db.FilterIn("sha", shas),
+
orm.FilterEq("repo_owner", f.Did),
+
orm.FilterEq("repo_name", f.Name),
+
orm.FilterEq("knot", f.Knot),
+
orm.FilterIn("sha", shas),
)
if err != nil {
log.Printf("failed to fetch pipeline statuses: %s", err)
···
labelDefs, err := db.GetLabelDefinitions(
s.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", tangled.RepoPullNSID),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", tangled.RepoPullNSID),
)
if err != nil {
log.Println("failed to fetch labels", err)
···
pulls, err := db.GetPulls(
s.db,
-
db.FilterIn("id", ids),
+
orm.FilterIn("id", ids),
)
if err != nil {
log.Println("failed to get pulls", err)
···
ps, err := db.GetPipelineStatuses(
s.db,
len(shas),
-
db.FilterEq("repo_owner", f.Did),
-
db.FilterEq("repo_name", f.Name),
-
db.FilterEq("knot", f.Knot),
-
db.FilterIn("sha", shas),
+
orm.FilterEq("repo_owner", f.Did),
+
orm.FilterEq("repo_name", f.Name),
+
orm.FilterEq("knot", f.Knot),
+
orm.FilterIn("sha", shas),
)
if err != nil {
log.Printf("failed to fetch pipeline statuses: %s", err)
···
labelDefs, err := db.GetLabelDefinitions(
s.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", tangled.RepoPullNSID),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", tangled.RepoPullNSID),
)
if err != nil {
log.Println("failed to fetch labels", err)
···
return
}
-
mentions, references := s.refResolver.Resolve(r.Context(), body)
+
mentions, references := s.mentionsResolver.Resolve(r.Context(), body)
// Start a transaction
tx, err := s.db.BeginTx(r.Context(), nil)
···
-
mentions, references := s.refResolver.Resolve(r.Context(), body)
+
mentions, references := s.mentionsResolver.Resolve(r.Context(), body)
rkey := tid.TID()
initialSubmission := models.PullSubmission{
···
// fork repo
repo, err := db.GetRepo(
s.db,
-
db.FilterEq("did", forkOwnerDid),
-
db.FilterEq("name", forkName),
+
orm.FilterEq("did", forkOwnerDid),
+
orm.FilterEq("name", forkName),
if err != nil {
log.Println("failed to get repo", "did", forkOwnerDid, "name", forkName, "err", err)
···
tx,
p.ParentChangeId,
// these should be enough filters to be unique per-stack
-
db.FilterEq("repo_at", p.RepoAt.String()),
-
db.FilterEq("owner_did", p.OwnerDid),
-
db.FilterEq("change_id", p.ChangeId),
+
orm.FilterEq("repo_at", p.RepoAt.String()),
+
orm.FilterEq("owner_did", p.OwnerDid),
+
orm.FilterEq("change_id", p.ChangeId),
if err != nil {
···
body := fp.Body
rkey := tid.TID()
-
mentions, references := s.refResolver.Resolve(ctx, body)
+
mentions, references := s.mentionsResolver.Resolve(ctx, body)
initialSubmission := models.PullSubmission{
Patch: fp.Raw,
-65
appview/refresolver/resolver.go
···
-
package refresolver
-
-
import (
-
"context"
-
"log/slog"
-
-
"github.com/bluesky-social/indigo/atproto/syntax"
-
"tangled.org/core/appview/config"
-
"tangled.org/core/appview/db"
-
"tangled.org/core/appview/models"
-
"tangled.org/core/appview/pages/markup"
-
"tangled.org/core/idresolver"
-
)
-
-
type Resolver struct {
-
config *config.Config
-
idResolver *idresolver.Resolver
-
execer db.Execer
-
logger *slog.Logger
-
}
-
-
func New(
-
config *config.Config,
-
idResolver *idresolver.Resolver,
-
execer db.Execer,
-
logger *slog.Logger,
-
) *Resolver {
-
return &Resolver{
-
config,
-
idResolver,
-
execer,
-
logger,
-
}
-
}
-
-
func (r *Resolver) Resolve(ctx context.Context, source string) ([]syntax.DID, []syntax.ATURI) {
-
l := r.logger.With("method", "Resolve")
-
rawMentions, rawRefs := markup.FindReferences(r.config.Core.AppviewHost, source)
-
l.Debug("found possible references", "mentions", rawMentions, "refs", rawRefs)
-
idents := r.idResolver.ResolveIdents(ctx, rawMentions)
-
var mentions []syntax.DID
-
for _, ident := range idents {
-
if ident != nil && !ident.Handle.IsInvalidHandle() {
-
mentions = append(mentions, ident.DID)
-
}
-
}
-
l.Debug("found mentions", "mentions", mentions)
-
-
var resolvedRefs []models.ReferenceLink
-
for _, rawRef := range rawRefs {
-
ident, err := r.idResolver.ResolveIdent(ctx, rawRef.Handle)
-
if err != nil || ident == nil || ident.Handle.IsInvalidHandle() {
-
continue
-
}
-
rawRef.Handle = string(ident.DID)
-
resolvedRefs = append(resolvedRefs, rawRef)
-
}
-
aturiRefs, err := db.ValidateReferenceLinks(r.execer, resolvedRefs)
-
if err != nil {
-
l.Error("failed running query", "err", err)
-
}
-
l.Debug("found references", "refs", aturiRefs)
-
-
return mentions, aturiRefs
-
}
+10 -9
appview/repo/artifact.go
···
"tangled.org/core/appview/models"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/xrpcclient"
+
"tangled.org/core/orm"
"tangled.org/core/tid"
"tangled.org/core/types"
···
artifacts, err := db.GetArtifact(
rp.db,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("tag", tag.Tag.Hash[:]),
-
db.FilterEq("name", filename),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("tag", tag.Tag.Hash[:]),
+
orm.FilterEq("name", filename),
)
if err != nil {
log.Println("failed to get artifacts", err)
···
artifacts, err := db.GetArtifact(
rp.db,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("tag", tag[:]),
-
db.FilterEq("name", filename),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("tag", tag[:]),
+
orm.FilterEq("name", filename),
)
if err != nil {
log.Println("failed to get artifacts", err)
···
defer tx.Rollback()
err = db.DeleteArtifact(tx,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("tag", artifact.Tag[:]),
-
db.FilterEq("name", filename),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("tag", artifact.Tag[:]),
+
orm.FilterEq("name", filename),
)
if err != nil {
log.Println("failed to remove artifact record from db", err)
+3 -2
appview/repo/feed.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/pagination"
+
"tangled.org/core/orm"
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
···
func (rp *Repo) getRepoFeed(ctx context.Context, repo *models.Repo, ownerSlashRepo string) (*feeds.Feed, error) {
const feedLimitPerType = 100
-
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", repo.RepoAt()))
+
pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, orm.FilterEq("repo_at", repo.RepoAt()))
if err != nil {
return nil, err
}
···
issues, err := db.GetIssuesPaginated(
rp.db,
pagination.Page{Limit: feedLimitPerType},
-
db.FilterEq("repo_at", repo.RepoAt()),
+
orm.FilterEq("repo_at", repo.RepoAt()),
)
if err != nil {
return nil, err
+3 -2
appview/repo/index.go
···
"tangled.org/core/appview/models"
"tangled.org/core/appview/pages"
"tangled.org/core/appview/xrpcclient"
+
"tangled.org/core/orm"
"tangled.org/core/types"
"github.com/go-chi/chi/v5"
···
// first attempt to fetch from db
langs, err := db.GetRepoLanguages(
rp.db,
-
db.FilterEq("repo_at", repo.RepoAt()),
-
db.FilterEq("ref", currentRef),
+
orm.FilterEq("repo_at", repo.RepoAt()),
+
orm.FilterEq("ref", currentRef),
)
if err != nil || langs == nil {
+3 -2
appview/repo/opengraph.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/ogcard"
+
"tangled.org/core/orm"
"tangled.org/core/types"
)
···
var languageStats []types.RepoLanguageDetails
langs, err := db.GetRepoLanguages(
rp.db,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("is_default_ref", 1),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("is_default_ref", 1),
)
if err != nil {
log.Printf("failed to get language stats from db: %v", err)
+17 -16
appview/repo/repo.go
···
xrpcclient "tangled.org/core/appview/xrpcclient"
"tangled.org/core/eventconsumer"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
"tangled.org/core/xrpc/serviceauth"
···
// get form values
labelId := r.FormValue("label-id")
-
label, err := db.GetLabelDefinition(rp.db, db.FilterEq("id", labelId))
+
label, err := db.GetLabelDefinition(rp.db, orm.FilterEq("id", labelId))
if err != nil {
fail("Failed to find label definition.", err)
return
···
err = db.UnsubscribeLabel(
tx,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterEq("label_at", removedAt),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterEq("label_at", removedAt),
)
if err != nil {
fail("Failed to unsubscribe label.", err)
return
}
-
err = db.DeleteLabelDefinition(tx, db.FilterEq("id", label.Id))
+
err = db.DeleteLabelDefinition(tx, orm.FilterEq("id", label.Id))
if err != nil {
fail("Failed to delete label definition.", err)
return
···
}
labelAts := r.Form["label"]
-
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
+
_, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts))
if err != nil {
fail("Failed to subscribe to label.", err)
return
···
}
labelAts := r.Form["label"]
-
_, err = db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", labelAts))
+
_, err = db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", labelAts))
if err != nil {
fail("Failed to unsubscribe to label.", err)
return
···
err = db.UnsubscribeLabel(
rp.db,
-
db.FilterEq("repo_at", f.RepoAt()),
-
db.FilterIn("label_at", labelAts),
+
orm.FilterEq("repo_at", f.RepoAt()),
+
orm.FilterIn("label_at", labelAts),
)
if err != nil {
fail("Failed to unsubscribe label.", err)
···
labelDefs, err := db.GetLabelDefinitions(
rp.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", subject.Collection().String()),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", subject.Collection().String()),
)
if err != nil {
l.Error("failed to fetch label defs", "err", err)
···
defs[l.AtUri().String()] = &l
}
-
states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject))
+
states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject))
if err != nil {
l.Error("failed to build label state", "err", err)
return
···
labelDefs, err := db.GetLabelDefinitions(
rp.db,
-
db.FilterIn("at_uri", f.Labels),
-
db.FilterContains("scope", subject.Collection().String()),
+
orm.FilterIn("at_uri", f.Labels),
+
orm.FilterContains("scope", subject.Collection().String()),
)
if err != nil {
l.Error("failed to fetch labels", "err", err)
···
defs[l.AtUri().String()] = &l
}
-
states, err := db.GetLabels(rp.db, db.FilterEq("subject", subject))
+
states, err := db.GetLabels(rp.db, orm.FilterEq("subject", subject))
if err != nil {
l.Error("failed to build label state", "err", err)
return
···
// in the user's account.
existingRepo, err := db.GetRepo(
rp.db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("name", forkName),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("name", forkName),
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
+5 -4
appview/repo/repo_util.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
"tangled.org/core/types"
)
···
ps, err := db.GetPipelineStatuses(
d,
len(shas),
-
db.FilterEq("repo_owner", repo.Did),
-
db.FilterEq("repo_name", repo.Name),
-
db.FilterEq("knot", repo.Knot),
-
db.FilterIn("sha", shas),
+
orm.FilterEq("repo_owner", repo.Did),
+
orm.FilterEq("repo_name", repo.Name),
+
orm.FilterEq("knot", repo.Knot),
+
orm.FilterIn("sha", shas),
)
if err != nil {
return nil, err
+3 -2
appview/repo/settings.go
···
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
xrpcclient "tangled.org/core/appview/xrpcclient"
+
"tangled.org/core/orm"
"tangled.org/core/types"
comatproto "github.com/bluesky-social/indigo/api/atproto"
···
return
}
-
defaultLabels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs))
+
defaultLabels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", rp.config.Label.DefaultLabelDefs))
if err != nil {
l.Error("failed to fetch labels", "err", err)
rp.pages.Error503(w)
return
}
-
labels, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Labels))
+
labels, err := db.GetLabelDefinitions(rp.db, orm.FilterIn("at_uri", f.Labels))
if err != nil {
l.Error("failed to fetch labels", "err", err)
rp.pages.Error503(w)
+2 -1
appview/repo/tags.go
···
"tangled.org/core/appview/models"
"tangled.org/core/appview/pages"
xrpcclient "tangled.org/core/appview/xrpcclient"
+
"tangled.org/core/orm"
"tangled.org/core/types"
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
···
rp.pages.Error503(w)
return
}
-
artifacts, err := db.GetArtifact(rp.db, db.FilterEq("repo_at", f.RepoAt()))
+
artifacts, err := db.GetArtifact(rp.db, orm.FilterEq("repo_at", f.RepoAt()))
if err != nil {
l.Error("failed grab artifacts", "err", err)
return
+5 -4
appview/serververify/verify.go
···
"tangled.org/core/api/tangled"
"tangled.org/core/appview/db"
"tangled.org/core/appview/xrpcclient"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
)
···
// mark this spindle as verified in the db
rowId, err := db.VerifySpindle(
tx,
-
db.FilterEq("owner", owner),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", owner),
+
orm.FilterEq("instance", instance),
)
if err != nil {
return 0, fmt.Errorf("failed to write to DB: %w", err)
···
// mark as registered
err = db.MarkRegistered(
tx,
-
db.FilterEq("did", owner),
-
db.FilterEq("domain", domain),
+
orm.FilterEq("did", owner),
+
orm.FilterEq("domain", domain),
)
if err != nil {
return fmt.Errorf("failed to register domain: %w", err)
+25 -24
appview/spindles/spindles.go
···
"tangled.org/core/appview/serververify"
"tangled.org/core/appview/xrpcclient"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
···
user := s.OAuth.GetUser(r)
all, err := db.GetSpindles(
s.Db,
-
db.FilterEq("owner", user.Did),
+
orm.FilterEq("owner", user.Did),
)
if err != nil {
s.Logger.Error("failed to fetch spindles", "err", err)
···
spindles, err := db.GetSpindles(
s.Db,
-
db.FilterEq("instance", instance),
-
db.FilterEq("owner", user.Did),
-
db.FilterIsNot("verified", "null"),
+
orm.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterIsNot("verified", "null"),
)
if err != nil || len(spindles) != 1 {
l.Error("failed to get spindle", "err", err, "len(spindles)", len(spindles))
···
repos, err := db.GetRepos(
s.Db,
0,
-
db.FilterEq("spindle", instance),
+
orm.FilterEq("spindle", instance),
)
if err != nil {
l.Error("failed to get spindle repos", "err", err)
···
spindles, err := db.GetSpindles(
s.Db,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil || len(spindles) != 1 {
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
// remove spindle members first
err = db.RemoveSpindleMember(
tx,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil {
l.Error("failed to remove spindle members", "err", err)
···
err = db.DeleteSpindle(
tx,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil {
l.Error("failed to delete spindle", "err", err)
···
spindles, err := db.GetSpindles(
s.Db,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil || len(spindles) != 1 {
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
verifiedSpindle, err := db.GetSpindles(
s.Db,
-
db.FilterEq("id", rowId),
+
orm.FilterEq("id", rowId),
)
if err != nil || len(verifiedSpindle) != 1 {
l.Error("failed get new spindle", "err", err)
···
spindles, err := db.GetSpindles(
s.Db,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil || len(spindles) != 1 {
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
spindles, err := db.GetSpindles(
s.Db,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("instance", instance),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("instance", instance),
)
if err != nil || len(spindles) != 1 {
l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
···
// get the record from the DB first:
members, err := db.GetSpindleMembers(
s.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("instance", instance),
-
db.FilterEq("subject", memberId.DID),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("instance", instance),
+
orm.FilterEq("subject", memberId.DID),
)
if err != nil || len(members) != 1 {
l.Error("failed to get member", "err", err)
···
// remove from db
if err = db.RemoveSpindleMember(
tx,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("instance", instance),
-
db.FilterEq("subject", memberId.DID),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("instance", instance),
+
orm.FilterEq("subject", memberId.DID),
); err != nil {
l.Error("failed to remove spindle member", "err", err)
fail()
+6 -5
appview/state/gfi.go
···
"tangled.org/core/appview/pages"
"tangled.org/core/appview/pagination"
"tangled.org/core/consts"
+
"tangled.org/core/orm"
)
func (s *State) GoodFirstIssues(w http.ResponseWriter, r *http.Request) {
···
goodFirstIssueLabel := s.config.Label.GoodFirstIssue
-
gfiLabelDef, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", goodFirstIssueLabel))
+
gfiLabelDef, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", goodFirstIssueLabel))
if err != nil {
log.Println("failed to get gfi label def", err)
s.pages.Error500(w)
return
}
-
repoLabels, err := db.GetRepoLabels(s.db, db.FilterEq("label_at", goodFirstIssueLabel))
+
repoLabels, err := db.GetRepoLabels(s.db, orm.FilterEq("label_at", goodFirstIssueLabel))
if err != nil {
log.Println("failed to get repo labels", err)
s.pages.Error503(w)
···
pagination.Page{
Limit: 500,
},
-
db.FilterIn("repo_at", repoUris),
-
db.FilterEq("open", 1),
+
orm.FilterIn("repo_at", repoUris),
+
orm.FilterEq("open", 1),
)
if err != nil {
log.Println("failed to get issues", err)
···
}
if len(uriList) > 0 {
-
allLabelDefs, err = db.GetLabelDefinitions(s.db, db.FilterIn("at_uri", uriList))
+
allLabelDefs, err = db.GetLabelDefinitions(s.db, orm.FilterIn("at_uri", uriList))
if err != nil {
log.Println("failed to fetch labels", err)
}
+6 -5
appview/state/knotstream.go
···
ec "tangled.org/core/eventconsumer"
"tangled.org/core/eventconsumer/cursor"
"tangled.org/core/log"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/workflow"
···
knots, err := db.GetRegistrations(
d,
-
db.FilterIsNot("registered", "null"),
+
orm.FilterIsNot("registered", "null"),
)
if err != nil {
return nil, err
···
repos, err := db.GetRepos(
d,
0,
-
db.FilterEq("did", record.RepoDid),
-
db.FilterEq("name", record.RepoName),
+
orm.FilterEq("did", record.RepoDid),
+
orm.FilterEq("name", record.RepoName),
)
if err != nil {
return fmt.Errorf("failed to look for repo in DB (%s/%s): %w", record.RepoDid, record.RepoName, err)
···
repos, err := db.GetRepos(
d,
0,
-
db.FilterEq("did", record.TriggerMetadata.Repo.Did),
-
db.FilterEq("name", record.TriggerMetadata.Repo.Repo),
+
orm.FilterEq("did", record.TriggerMetadata.Repo.Did),
+
orm.FilterEq("name", record.TriggerMetadata.Repo.Repo),
)
if err != nil {
return fmt.Errorf("failed to look for repo in DB: nsid %s, rkey %s, %w", msg.Nsid, msg.Rkey, err)
+13 -12
appview/state/profile.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
"tangled.org/core/appview/pages"
+
"tangled.org/core/orm"
)
func (s *State) Profile(w http.ResponseWriter, r *http.Request) {
···
return nil, fmt.Errorf("failed to get profile: %w", err)
}
-
repoCount, err := db.CountRepos(s.db, db.FilterEq("did", did))
+
repoCount, err := db.CountRepos(s.db, orm.FilterEq("did", did))
if err != nil {
return nil, fmt.Errorf("failed to get repo count: %w", err)
}
-
stringCount, err := db.CountStrings(s.db, db.FilterEq("did", did))
+
stringCount, err := db.CountStrings(s.db, orm.FilterEq("did", did))
if err != nil {
return nil, fmt.Errorf("failed to get string count: %w", err)
}
-
starredCount, err := db.CountStars(s.db, db.FilterEq("did", did))
+
starredCount, err := db.CountStars(s.db, orm.FilterEq("did", did))
if err != nil {
return nil, fmt.Errorf("failed to get starred repo count: %w", err)
}
···
startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC)
punchcard, err := db.MakePunchcard(
s.db,
-
db.FilterEq("did", did),
-
db.FilterGte("date", startOfYear.Format(time.DateOnly)),
-
db.FilterLte("date", now.Format(time.DateOnly)),
+
orm.FilterEq("did", did),
+
orm.FilterGte("date", startOfYear.Format(time.DateOnly)),
+
orm.FilterLte("date", now.Format(time.DateOnly)),
)
if err != nil {
return nil, fmt.Errorf("failed to get punchcard for %s: %w", did, err)
···
repos, err := db.GetRepos(
s.db,
0,
-
db.FilterEq("did", profile.UserDid),
+
orm.FilterEq("did", profile.UserDid),
)
if err != nil {
l.Error("failed to fetch repos", "err", err)
···
repos, err := db.GetRepos(
s.db,
0,
-
db.FilterEq("did", profile.UserDid),
+
orm.FilterEq("did", profile.UserDid),
)
if err != nil {
l.Error("failed to get repos", "err", err)
···
}
l = l.With("profileDid", profile.UserDid)
-
stars, err := db.GetRepoStars(s.db, 0, db.FilterEq("did", profile.UserDid))
+
stars, err := db.GetRepoStars(s.db, 0, orm.FilterEq("did", profile.UserDid))
if err != nil {
l.Error("failed to get stars", "err", err)
s.pages.Error500(w)
···
}
l = l.With("profileDid", profile.UserDid)
-
strings, err := db.GetStrings(s.db, 0, db.FilterEq("did", profile.UserDid))
+
strings, err := db.GetStrings(s.db, 0, orm.FilterEq("did", profile.UserDid))
if err != nil {
l.Error("failed to get strings", "err", err)
s.pages.Error500(w)
···
followDids = append(followDids, extractDid(follow))
}
-
profiles, err := db.GetProfiles(s.db, db.FilterIn("did", followDids))
+
profiles, err := db.GetProfiles(s.db, orm.FilterIn("did", followDids))
if err != nil {
l.Error("failed to get profiles", "followDids", followDids, "err", err)
return &params, err
···
log.Printf("getting profile data for %s: %s", user.Did, err)
}
-
repos, err := db.GetRepos(s.db, 0, db.FilterEq("did", user.Did))
+
repos, err := db.GetRepos(s.db, 0, orm.FilterEq("did", user.Did))
if err != nil {
log.Printf("getting repos for %s: %s", user.Did, err)
}
+2 -2
appview/state/router.go
···
s.enforcer,
s.pages,
s.idResolver,
-
s.refResolver,
+
s.mentionsResolver,
s.db,
s.config,
s.notifier,
···
s.repoResolver,
s.pages,
s.idResolver,
-
s.refResolver,
+
s.mentionsResolver,
s.db,
s.config,
s.notifier,
+2 -1
appview/state/spindlestream.go
···
ec "tangled.org/core/eventconsumer"
"tangled.org/core/eventconsumer/cursor"
"tangled.org/core/log"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
spindle "tangled.org/core/spindle/models"
)
···
spindles, err := db.GetSpindles(
d,
-
db.FilterIsNot("verified", "null"),
+
orm.FilterIsNot("verified", "null"),
)
if err != nil {
return nil, err
+28 -27
appview/state/state.go
···
"tangled.org/core/appview/config"
"tangled.org/core/appview/db"
"tangled.org/core/appview/indexer"
+
"tangled.org/core/appview/mentions"
"tangled.org/core/appview/models"
"tangled.org/core/appview/notify"
dbnotify "tangled.org/core/appview/notify/db"
phnotify "tangled.org/core/appview/notify/posthog"
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
-
"tangled.org/core/appview/refresolver"
"tangled.org/core/appview/reporesolver"
"tangled.org/core/appview/validator"
xrpcclient "tangled.org/core/appview/xrpcclient"
···
"tangled.org/core/jetstream"
"tangled.org/core/log"
tlog "tangled.org/core/log"
+
"tangled.org/core/orm"
"tangled.org/core/rbac"
"tangled.org/core/tid"
···
)
type State struct {
-
db *db.DB
-
notifier notify.Notifier
-
indexer *indexer.Indexer
-
oauth *oauth.OAuth
-
enforcer *rbac.Enforcer
-
pages *pages.Pages
-
idResolver *idresolver.Resolver
-
refResolver *refresolver.Resolver
-
posthog posthog.Client
-
jc *jetstream.JetstreamClient
-
config *config.Config
-
repoResolver *reporesolver.RepoResolver
-
knotstream *eventconsumer.Consumer
-
spindlestream *eventconsumer.Consumer
-
logger *slog.Logger
-
validator *validator.Validator
+
db *db.DB
+
notifier notify.Notifier
+
indexer *indexer.Indexer
+
oauth *oauth.OAuth
+
enforcer *rbac.Enforcer
+
pages *pages.Pages
+
idResolver *idresolver.Resolver
+
mentionsResolver *mentions.Resolver
+
posthog posthog.Client
+
jc *jetstream.JetstreamClient
+
config *config.Config
+
repoResolver *reporesolver.RepoResolver
+
knotstream *eventconsumer.Consumer
+
spindlestream *eventconsumer.Consumer
+
logger *slog.Logger
+
validator *validator.Validator
}
func Make(ctx context.Context, config *config.Config) (*State, error) {
···
repoResolver := reporesolver.New(config, enforcer, d)
-
refResolver := refresolver.New(config, res, d, log.SubLogger(logger, "refResolver"))
+
mentionsResolver := mentions.New(config, res, d, log.SubLogger(logger, "mentionsResolver"))
wrapper := db.DbWrapper{Execer: d}
jc, err := jetstream.NewJetstreamClient(
···
enforcer,
pages,
res,
-
refResolver,
+
mentionsResolver,
posthog,
jc,
config,
···
return
}
-
gfiLabel, err := db.GetLabelDefinition(s.db, db.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
+
gfiLabel, err := db.GetLabelDefinition(s.db, orm.FilterEq("at_uri", s.config.Label.GoodFirstIssue))
if err != nil {
// non-fatal
}
···
regs, err := db.GetRegistrations(
s.db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("needs_upgrade", 1),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("needs_upgrade", 1),
)
if err != nil {
l.Error("non-fatal: failed to get registrations", "err", err)
···
spindles, err := db.GetSpindles(
s.db,
-
db.FilterEq("owner", user.Did),
-
db.FilterEq("needs_upgrade", 1),
+
orm.FilterEq("owner", user.Did),
+
orm.FilterEq("needs_upgrade", 1),
)
if err != nil {
l.Error("non-fatal: failed to get spindles", "err", err)
···
// Check for existing repos
existingRepo, err := db.GetRepo(
s.db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("name", repoName),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("name", repoName),
)
if err == nil && existingRepo != nil {
l.Info("repo exists")
···
}
func BackfillDefaultDefs(e db.Execer, r *idresolver.Resolver, defaults []string) error {
-
defaultLabels, err := db.GetLabelDefinitions(e, db.FilterIn("at_uri", defaults))
+
defaultLabels, err := db.GetLabelDefinitions(e, orm.FilterIn("at_uri", defaults))
if err != nil {
return err
}
+7 -6
appview/strings/strings.go
···
"tangled.org/core/appview/pages"
"tangled.org/core/appview/pages/markup"
"tangled.org/core/idresolver"
+
"tangled.org/core/orm"
"tangled.org/core/tid"
"github.com/bluesky-social/indigo/api/atproto"
···
strings, err := db.GetStrings(
s.Db,
0,
-
db.FilterEq("did", id.DID),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", id.DID),
+
orm.FilterEq("rkey", rkey),
)
if err != nil {
l.Error("failed to fetch string", "err", err)
···
all, err := db.GetStrings(
s.Db,
0,
-
db.FilterEq("did", id.DID),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", id.DID),
+
orm.FilterEq("rkey", rkey),
)
if err != nil {
l.Error("failed to fetch string", "err", err)
···
if err := db.DeleteString(
s.Db,
-
db.FilterEq("did", user.Did),
-
db.FilterEq("rkey", rkey),
+
orm.FilterEq("did", user.Did),
+
orm.FilterEq("rkey", rkey),
); err != nil {
fail("Failed to delete string.", err)
return
+2 -1
appview/validator/issue.go
···
"tangled.org/core/appview/db"
"tangled.org/core/appview/models"
+
"tangled.org/core/orm"
)
func (v *Validator) ValidateIssueComment(comment *models.IssueComment) error {
// if comments have parents, only ingest ones that are 1 level deep
if comment.ReplyTo != nil {
-
parents, err := db.GetIssueComments(v.db, db.FilterEq("at_uri", *comment.ReplyTo))
+
parents, err := db.GetIssueComments(v.db, orm.FilterEq("at_uri", *comment.ReplyTo))
if err != nil {
return fmt.Errorf("failed to fetch parent comment: %w", err)
}
+3 -3
flake.lock
···
},
"nixpkgs": {
"locked": {
-
"lastModified": 1751984180,
-
"narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
+
"lastModified": 1765186076,
+
"narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "nixos",
"repo": "nixpkgs",
-
"rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
+
"rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github"
},
"original": {
-2
flake.nix
···
}).buildGoApplication;
modules = ./nix/gomod2nix.toml;
sqlite-lib = self.callPackage ./nix/pkgs/sqlite-lib.nix {
-
inherit (pkgs) gcc;
inherit sqlite-lib-src;
};
lexgen = self.callPackage ./nix/pkgs/lexgen.nix {inherit indigo;};
···
nativeBuildInputs = [
pkgs.go
pkgs.air
-
pkgs.tilt
pkgs.gopls
pkgs.httpie
pkgs.litecli
+1 -3
go.mod
···
module tangled.org/core
-
go 1.24.4
+
go 1.25.0
require (
github.com/Blank-Xu/sql-adapter v1.1.1
···
github.com/stretchr/testify v1.10.0
github.com/urfave/cli/v3 v3.3.3
github.com/whyrusleeping/cbor-gen v0.3.1
-
github.com/wyatt915/goldmark-treeblood v0.0.1
github.com/yuin/goldmark v1.7.13
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/staticnoise/goldmark-callout v0.0.0-20240609120641-6366b799e4ab
···
github.com/vmihailenco/go-tinylfu v0.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-
github.com/wyatt915/treeblood v0.1.16 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b // indirect
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
-4
go.sum
···
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/cbor-gen v0.3.1 h1:82ioxmhEYut7LBVGhGq8xoRkXPLElVuh5mV67AFfdv0=
github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
-
github.com/wyatt915/goldmark-treeblood v0.0.1 h1:6vLJcjFrHgE4ASu2ga4hqIQmbvQLU37v53jlHZ3pqDs=
-
github.com/wyatt915/goldmark-treeblood v0.0.1/go.mod h1:SmcJp5EBaV17rroNlgNQFydYwy0+fv85CUr/ZaCz208=
-
github.com/wyatt915/treeblood v0.1.16 h1:byxNbWZhnPDxdTp7W5kQhCeaY8RBVmojTFz1tEHgg8Y=
-
github.com/wyatt915/treeblood v0.1.16/go.mod h1:i7+yhhmzdDP17/97pIsOSffw74EK/xk+qJ0029cSXUY=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+81
knotserver/db/db.go
···
+
package db
+
+
import (
+
"context"
+
"database/sql"
+
"log/slog"
+
"strings"
+
+
_ "github.com/mattn/go-sqlite3"
+
"tangled.org/core/log"
+
)
+
+
type DB struct {
+
db *sql.DB
+
logger *slog.Logger
+
}
+
+
func Setup(ctx context.Context, dbPath string) (*DB, error) {
+
// https://github.com/mattn/go-sqlite3#connection-string
+
opts := []string{
+
"_foreign_keys=1",
+
"_journal_mode=WAL",
+
"_synchronous=NORMAL",
+
"_auto_vacuum=incremental",
+
}
+
+
logger := log.FromContext(ctx)
+
logger = log.SubLogger(logger, "db")
+
+
db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
+
if err != nil {
+
return nil, err
+
}
+
+
conn, err := db.Conn(ctx)
+
if err != nil {
+
return nil, err
+
}
+
defer conn.Close()
+
+
_, err = conn.ExecContext(ctx, `
+
create table if not exists known_dids (
+
did text primary key
+
);
+
+
create table if not exists public_keys (
+
id integer primary key autoincrement,
+
did text not null,
+
key text not null,
+
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
+
unique(did, key),
+
foreign key (did) references known_dids(did) on delete cascade
+
);
+
+
create table if not exists _jetstream (
+
id integer primary key autoincrement,
+
last_time_us integer not null
+
);
+
+
create table if not exists events (
+
rkey text not null,
+
nsid text not null,
+
event text not null, -- json
+
created integer not null default (strftime('%s', 'now')),
+
primary key (rkey, nsid)
+
);
+
+
create table if not exists migrations (
+
id integer primary key autoincrement,
+
name text unique
+
);
+
`)
+
if err != nil {
+
return nil, err
+
}
+
+
return &DB{
+
db: db,
+
logger: logger,
+
}, nil
+
}
-64
knotserver/db/init.go
···
-
package db
-
-
import (
-
"database/sql"
-
"strings"
-
-
_ "github.com/mattn/go-sqlite3"
-
)
-
-
type DB struct {
-
db *sql.DB
-
}
-
-
func Setup(dbPath string) (*DB, error) {
-
// https://github.com/mattn/go-sqlite3#connection-string
-
opts := []string{
-
"_foreign_keys=1",
-
"_journal_mode=WAL",
-
"_synchronous=NORMAL",
-
"_auto_vacuum=incremental",
-
}
-
-
db, err := sql.Open("sqlite3", dbPath+"?"+strings.Join(opts, "&"))
-
if err != nil {
-
return nil, err
-
}
-
-
// NOTE: If any other migration is added here, you MUST
-
// copy the pattern in appview: use a single sql.Conn
-
// for every migration.
-
-
_, err = db.Exec(`
-
create table if not exists known_dids (
-
did text primary key
-
);
-
-
create table if not exists public_keys (
-
id integer primary key autoincrement,
-
did text not null,
-
key text not null,
-
created text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
-
unique(did, key),
-
foreign key (did) references known_dids(did) on delete cascade
-
);
-
-
create table if not exists _jetstream (
-
id integer primary key autoincrement,
-
last_time_us integer not null
-
);
-
-
create table if not exists events (
-
rkey text not null,
-
nsid text not null,
-
event text not null, -- json
-
created integer not null default (strftime('%s', 'now')),
-
primary key (rkey, nsid)
-
);
-
`)
-
if err != nil {
-
return nil, err
-
}
-
-
return &DB{db: db}, nil
-
}
+1 -1
knotserver/server.go
···
logger.Info("running in dev mode, signature verification is disabled")
}
-
db, err := db.Setup(c.Server.DBPath)
+
db, err := db.Setup(ctx, c.Server.DBPath)
if err != nil {
return fmt.Errorf("failed to load db: %w", err)
}
-6
nix/gomod2nix.toml
···
[mod."github.com/whyrusleeping/cbor-gen"]
version = "v0.3.1"
hash = "sha256-PAd8M2Z8t6rVRBII+Rg8Bz+QaJIwbW64bfyqsv31kgc="
-
[mod."github.com/wyatt915/goldmark-treeblood"]
-
version = "v0.0.1"
-
hash = "sha256-hAVFaktO02MiiqZFffr8ZlvFEfwxw4Y84OZ2t7e5G7g="
-
[mod."github.com/wyatt915/treeblood"]
-
version = "v0.1.16"
-
hash = "sha256-T68sa+iVx0qY7dDjXEAJvRWQEGXYIpUsf9tcWwO1tIw="
[mod."github.com/xo/terminfo"]
version = "v0.0.0-20220910002029-abceb7e1c41e"
hash = "sha256-GyCDxxMQhXA3Pi/TsWXpA8cX5akEoZV7CFx4RO3rARU="
+7 -5
nix/pkgs/sqlite-lib.nix
···
{
-
gcc,
stdenv,
sqlite-lib-src,
}:
stdenv.mkDerivation {
name = "sqlite-lib";
src = sqlite-lib-src;
-
nativeBuildInputs = [gcc];
+
buildPhase = ''
-
gcc -c sqlite3.c
-
ar rcs libsqlite3.a sqlite3.o
-
ranlib libsqlite3.a
+
$CC -c sqlite3.c
+
$AR rcs libsqlite3.a sqlite3.o
+
$RANLIB libsqlite3.a
+
'';
+
+
installPhase = ''
mkdir -p $out/include $out/lib
cp *.h $out/include
cp libsqlite3.a $out/lib
+122
orm/orm.go
···
+
package orm
+
+
import (
+
"context"
+
"database/sql"
+
"fmt"
+
"log/slog"
+
"reflect"
+
"strings"
+
)
+
+
type migrationFn = func(*sql.Tx) error
+
+
func RunMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error {
+
logger = logger.With("migration", name)
+
+
tx, err := c.BeginTx(context.Background(), nil)
+
if err != nil {
+
return err
+
}
+
defer tx.Rollback()
+
+
var exists bool
+
err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
+
if err != nil {
+
return err
+
}
+
+
if !exists {
+
// run migration
+
err = migrationFn(tx)
+
if err != nil {
+
logger.Error("failed to run migration", "err", err)
+
return err
+
}
+
+
// mark migration as complete
+
_, err = tx.Exec("insert into migrations (name) values (?)", name)
+
if err != nil {
+
logger.Error("failed to mark migration as complete", "err", err)
+
return err
+
}
+
+
// commit the transaction
+
if err := tx.Commit(); err != nil {
+
return err
+
}
+
+
logger.Info("migration applied successfully")
+
} else {
+
logger.Warn("skipped migration, already applied")
+
}
+
+
return nil
+
}
+
+
type Filter struct {
+
Key string
+
arg any
+
Cmp string
+
}
+
+
func newFilter(key, cmp string, arg any) Filter {
+
return Filter{
+
Key: key,
+
arg: arg,
+
Cmp: cmp,
+
}
+
}
+
+
func FilterEq(key string, arg any) Filter { return newFilter(key, "=", arg) }
+
func FilterNotEq(key string, arg any) Filter { return newFilter(key, "<>", arg) }
+
func FilterGte(key string, arg any) Filter { return newFilter(key, ">=", arg) }
+
func FilterLte(key string, arg any) Filter { return newFilter(key, "<=", arg) }
+
func FilterIs(key string, arg any) Filter { return newFilter(key, "is", arg) }
+
func FilterIsNot(key string, arg any) Filter { return newFilter(key, "is not", arg) }
+
func FilterIn(key string, arg any) Filter { return newFilter(key, "in", arg) }
+
func FilterLike(key string, arg any) Filter { return newFilter(key, "like", arg) }
+
func FilterNotLike(key string, arg any) Filter { return newFilter(key, "not like", arg) }
+
func FilterContains(key string, arg any) Filter {
+
return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg))
+
}
+
+
func (f Filter) Condition() string {
+
rv := reflect.ValueOf(f.arg)
+
kind := rv.Kind()
+
+
// if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)`
+
if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
+
if rv.Len() == 0 {
+
// always false
+
return "1 = 0"
+
}
+
+
placeholders := make([]string, rv.Len())
+
for i := range placeholders {
+
placeholders[i] = "?"
+
}
+
+
return fmt.Sprintf("%s %s (%s)", f.Key, f.Cmp, strings.Join(placeholders, ", "))
+
}
+
+
return fmt.Sprintf("%s %s ?", f.Key, f.Cmp)
+
}
+
+
func (f Filter) Arg() []any {
+
rv := reflect.ValueOf(f.arg)
+
kind := rv.Kind()
+
if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
+
if rv.Len() == 0 {
+
return nil
+
}
+
+
out := make([]any, rv.Len())
+
for i := range rv.Len() {
+
out[i] = rv.Index(i).Interface()
+
}
+
return out
+
}
+
+
return []any{f.arg}
+
}
+31
sets/gen.go
···
+
package sets
+
+
import (
+
"math/rand"
+
"reflect"
+
"testing/quick"
+
)
+
+
func (_ Set[T]) Generate(rand *rand.Rand, size int) reflect.Value {
+
s := New[T]()
+
+
var zero T
+
itemType := reflect.TypeOf(zero)
+
+
for {
+
if s.Len() >= size {
+
break
+
}
+
+
item, ok := quick.Value(itemType, rand)
+
if !ok {
+
continue
+
}
+
+
if val, ok := item.Interface().(T); ok {
+
s.Insert(val)
+
}
+
}
+
+
return reflect.ValueOf(s)
+
}
+35
sets/readme.txt
···
+
sets
+
----
+
set datastructure for go with generics and iterators. the
+
api is supposed to mimic rust's std::collections::HashSet api.
+
+
s1 := sets.Collect(slices.Values([]int{1, 2, 3, 4}))
+
s2 := sets.Collect(slices.Values([]int{1, 2, 3, 4, 5, 6}))
+
+
union := sets.Collect(s1.Union(s2))
+
intersect := sets.Collect(s1.Intersection(s2))
+
diff := sets.Collect(s1.Difference(s2))
+
symdiff := sets.Collect(s1.SymmetricDifference(s2))
+
+
s1.Len() // 4
+
s1.Contains(1) // true
+
s1.IsEmpty() // false
+
s1.IsSubset(s2) // true
+
s1.IsSuperset(s2) // false
+
s1.IsDisjoint(s2) // false
+
+
if exists := s1.Insert(1); exists {
+
// already existed in set
+
}
+
+
if existed := s1.Remove(1); existed {
+
// existed in set, now removed
+
}
+
+
+
testing
+
-------
+
includes property-based tests using the wonderful
+
testing/quick module!
+
+
go test -v
+174
sets/set.go
···
+
package sets
+
+
import (
+
"iter"
+
"maps"
+
)
+
+
type Set[T comparable] struct {
+
data map[T]struct{}
+
}
+
+
func New[T comparable]() Set[T] {
+
return Set[T]{
+
data: make(map[T]struct{}),
+
}
+
}
+
+
func (s *Set[T]) Insert(item T) bool {
+
_, exists := s.data[item]
+
s.data[item] = struct{}{}
+
return !exists
+
}
+
+
func Singleton[T comparable](item T) Set[T] {
+
n := New[T]()
+
_ = n.Insert(item)
+
return n
+
}
+
+
func (s *Set[T]) Remove(item T) bool {
+
_, exists := s.data[item]
+
if exists {
+
delete(s.data, item)
+
}
+
return exists
+
}
+
+
func (s Set[T]) Contains(item T) bool {
+
_, exists := s.data[item]
+
return exists
+
}
+
+
func (s Set[T]) Len() int {
+
return len(s.data)
+
}
+
+
func (s Set[T]) IsEmpty() bool {
+
return len(s.data) == 0
+
}
+
+
func (s *Set[T]) Clear() {
+
s.data = make(map[T]struct{})
+
}
+
+
func (s Set[T]) All() iter.Seq[T] {
+
return func(yield func(T) bool) {
+
for item := range s.data {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
}
+
+
func (s Set[T]) Clone() Set[T] {
+
return Set[T]{
+
data: maps.Clone(s.data),
+
}
+
}
+
+
func (s Set[T]) Union(other Set[T]) iter.Seq[T] {
+
if s.Len() >= other.Len() {
+
return chain(s.All(), other.Difference(s))
+
} else {
+
return chain(other.All(), s.Difference(other))
+
}
+
}
+
+
func chain[T any](seqs ...iter.Seq[T]) iter.Seq[T] {
+
return func(yield func(T) bool) {
+
for _, seq := range seqs {
+
for item := range seq {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
}
+
}
+
+
func (s Set[T]) Intersection(other Set[T]) iter.Seq[T] {
+
return func(yield func(T) bool) {
+
for item := range s.data {
+
if other.Contains(item) {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
}
+
}
+
+
func (s Set[T]) Difference(other Set[T]) iter.Seq[T] {
+
return func(yield func(T) bool) {
+
for item := range s.data {
+
if !other.Contains(item) {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
}
+
}
+
+
func (s Set[T]) SymmetricDifference(other Set[T]) iter.Seq[T] {
+
return func(yield func(T) bool) {
+
for item := range s.data {
+
if !other.Contains(item) {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
for item := range other.data {
+
if !s.Contains(item) {
+
if !yield(item) {
+
return
+
}
+
}
+
}
+
}
+
}
+
+
func (s Set[T]) IsSubset(other Set[T]) bool {
+
for item := range s.data {
+
if !other.Contains(item) {
+
return false
+
}
+
}
+
return true
+
}
+
+
func (s Set[T]) IsSuperset(other Set[T]) bool {
+
return other.IsSubset(s)
+
}
+
+
func (s Set[T]) IsDisjoint(other Set[T]) bool {
+
for item := range s.data {
+
if other.Contains(item) {
+
return false
+
}
+
}
+
return true
+
}
+
+
func (s Set[T]) Equal(other Set[T]) bool {
+
if s.Len() != other.Len() {
+
return false
+
}
+
for item := range s.data {
+
if !other.Contains(item) {
+
return false
+
}
+
}
+
return true
+
}
+
+
func Collect[T comparable](seq iter.Seq[T]) Set[T] {
+
result := New[T]()
+
for item := range seq {
+
result.Insert(item)
+
}
+
return result
+
}
+411
sets/set_test.go
···
+
package sets
+
+
import (
+
"slices"
+
"testing"
+
"testing/quick"
+
)
+
+
func TestNew(t *testing.T) {
+
s := New[int]()
+
if s.Len() != 0 {
+
t.Errorf("New set should be empty, got length %d", s.Len())
+
}
+
if !s.IsEmpty() {
+
t.Error("New set should be empty")
+
}
+
}
+
+
func TestFromSlice(t *testing.T) {
+
s := Collect(slices.Values([]int{1, 2, 3, 2, 1}))
+
if s.Len() != 3 {
+
t.Errorf("Expected length 3, got %d", s.Len())
+
}
+
if !s.Contains(1) || !s.Contains(2) || !s.Contains(3) {
+
t.Error("Set should contain all unique elements from slice")
+
}
+
}
+
+
func TestInsert(t *testing.T) {
+
s := New[string]()
+
+
if !s.Insert("hello") {
+
t.Error("First insert should return true")
+
}
+
if s.Insert("hello") {
+
t.Error("Duplicate insert should return false")
+
}
+
if s.Len() != 1 {
+
t.Errorf("Expected length 1, got %d", s.Len())
+
}
+
}
+
+
func TestRemove(t *testing.T) {
+
s := Collect(slices.Values([]int{1, 2, 3}))
+
+
if !s.Remove(2) {
+
t.Error("Remove existing element should return true")
+
}
+
if s.Remove(2) {
+
t.Error("Remove non-existing element should return false")
+
}
+
if s.Contains(2) {
+
t.Error("Element should be removed")
+
}
+
if s.Len() != 2 {
+
t.Errorf("Expected length 2, got %d", s.Len())
+
}
+
}
+
+
func TestContains(t *testing.T) {
+
s := Collect(slices.Values([]int{1, 2, 3}))
+
+
if !s.Contains(1) {
+
t.Error("Should contain 1")
+
}
+
if s.Contains(4) {
+
t.Error("Should not contain 4")
+
}
+
}
+
+
func TestClear(t *testing.T) {
+
s := Collect(slices.Values([]int{1, 2, 3}))
+
s.Clear()
+
+
if !s.IsEmpty() {
+
t.Error("Set should be empty after clear")
+
}
+
if s.Len() != 0 {
+
t.Errorf("Expected length 0, got %d", s.Len())
+
}
+
}
+
+
func TestIterator(t *testing.T) {
+
s := Collect(slices.Values([]int{1, 2, 3}))
+
var items []int
+
+
for item := range s.All() {
+
items = append(items, item)
+
}
+
+
slices.Sort(items)
+
expected := []int{1, 2, 3}
+
if !slices.Equal(items, expected) {
+
t.Errorf("Expected %v, got %v", expected, items)
+
}
+
}
+
+
func TestClone(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := s1.Clone()
+
+
if !s1.Equal(s2) {
+
t.Error("Cloned set should be equal to original")
+
}
+
+
s2.Insert(4)
+
if s1.Contains(4) {
+
t.Error("Modifying clone should not affect original")
+
}
+
}
+
+
func TestUnion(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2}))
+
s2 := Collect(slices.Values([]int{2, 3}))
+
+
result := Collect(s1.Union(s2))
+
expected := Collect(slices.Values([]int{1, 2, 3}))
+
+
if !result.Equal(expected) {
+
t.Errorf("Expected %v, got %v", expected, result)
+
}
+
}
+
+
func TestIntersection(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{2, 3, 4}))
+
+
expected := Collect(slices.Values([]int{2, 3}))
+
result := Collect(s1.Intersection(s2))
+
+
if !result.Equal(expected) {
+
t.Errorf("Expected %v, got %v", expected, result)
+
}
+
}
+
+
func TestDifference(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{2, 3, 4}))
+
+
expected := Collect(slices.Values([]int{1}))
+
result := Collect(s1.Difference(s2))
+
+
if !result.Equal(expected) {
+
t.Errorf("Expected %v, got %v", expected, result)
+
}
+
}
+
+
func TestSymmetricDifference(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{2, 3, 4}))
+
+
expected := Collect(slices.Values([]int{1, 4}))
+
result := Collect(s1.SymmetricDifference(s2))
+
+
if !result.Equal(expected) {
+
t.Errorf("Expected %v, got %v", expected, result)
+
}
+
}
+
+
func TestSymmetricDifferenceCommutativeProperty(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{2, 3, 4}))
+
+
result1 := Collect(s1.SymmetricDifference(s2))
+
result2 := Collect(s2.SymmetricDifference(s1))
+
+
if !result1.Equal(result2) {
+
t.Errorf("Expected %v, got %v", result1, result2)
+
}
+
}
+
+
func TestIsSubset(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2}))
+
s2 := Collect(slices.Values([]int{1, 2, 3}))
+
+
if !s1.IsSubset(s2) {
+
t.Error("s1 should be subset of s2")
+
}
+
if s2.IsSubset(s1) {
+
t.Error("s2 should not be subset of s1")
+
}
+
}
+
+
func TestIsSuperset(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{1, 2}))
+
+
if !s1.IsSuperset(s2) {
+
t.Error("s1 should be superset of s2")
+
}
+
if s2.IsSuperset(s1) {
+
t.Error("s2 should not be superset of s1")
+
}
+
}
+
+
func TestIsDisjoint(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2}))
+
s2 := Collect(slices.Values([]int{3, 4}))
+
s3 := Collect(slices.Values([]int{2, 3}))
+
+
if !s1.IsDisjoint(s2) {
+
t.Error("s1 and s2 should be disjoint")
+
}
+
if s1.IsDisjoint(s3) {
+
t.Error("s1 and s3 should not be disjoint")
+
}
+
}
+
+
func TestEqual(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2, 3}))
+
s2 := Collect(slices.Values([]int{3, 2, 1}))
+
s3 := Collect(slices.Values([]int{1, 2}))
+
+
if !s1.Equal(s2) {
+
t.Error("s1 and s2 should be equal")
+
}
+
if s1.Equal(s3) {
+
t.Error("s1 and s3 should not be equal")
+
}
+
}
+
+
func TestCollect(t *testing.T) {
+
s1 := Collect(slices.Values([]int{1, 2}))
+
s2 := Collect(slices.Values([]int{2, 3}))
+
+
unionSet := Collect(s1.Union(s2))
+
if unionSet.Len() != 3 {
+
t.Errorf("Expected union set length 3, got %d", unionSet.Len())
+
}
+
if !unionSet.Contains(1) || !unionSet.Contains(2) || !unionSet.Contains(3) {
+
t.Error("Union set should contain 1, 2, and 3")
+
}
+
+
diffSet := Collect(s1.Difference(s2))
+
if diffSet.Len() != 1 {
+
t.Errorf("Expected difference set length 1, got %d", diffSet.Len())
+
}
+
if !diffSet.Contains(1) {
+
t.Error("Difference set should contain 1")
+
}
+
}
+
+
func TestPropertySingleonLen(t *testing.T) {
+
f := func(item int) bool {
+
single := Singleton(item)
+
return single.Len() == 1
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyInsertIdempotent(t *testing.T) {
+
f := func(s Set[int], item int) bool {
+
clone := s.Clone()
+
+
clone.Insert(item)
+
firstLen := clone.Len()
+
+
clone.Insert(item)
+
secondLen := clone.Len()
+
+
return firstLen == secondLen
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyUnionCommutative(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
union1 := Collect(s1.Union(s2))
+
union2 := Collect(s2.Union(s1))
+
return union1.Equal(union2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyIntersectionCommutative(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
inter1 := Collect(s1.Intersection(s2))
+
inter2 := Collect(s2.Intersection(s1))
+
return inter1.Equal(inter2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyCloneEquals(t *testing.T) {
+
f := func(s Set[int]) bool {
+
clone := s.Clone()
+
return s.Equal(clone)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyIntersectionIsSubset(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
inter := Collect(s1.Intersection(s2))
+
return inter.IsSubset(s1) && inter.IsSubset(s2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyUnionIsSuperset(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
union := Collect(s1.Union(s2))
+
return union.IsSuperset(s1) && union.IsSuperset(s2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyDifferenceDisjoint(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
diff := Collect(s1.Difference(s2))
+
return diff.IsDisjoint(s2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertySymmetricDifferenceCommutative(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int]) bool {
+
symDiff1 := Collect(s1.SymmetricDifference(s2))
+
symDiff2 := Collect(s2.SymmetricDifference(s1))
+
return symDiff1.Equal(symDiff2)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyRemoveWorks(t *testing.T) {
+
f := func(s Set[int], item int) bool {
+
clone := s.Clone()
+
clone.Insert(item)
+
clone.Remove(item)
+
return !clone.Contains(item)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyClearEmpty(t *testing.T) {
+
f := func(s Set[int]) bool {
+
s.Clear()
+
return s.IsEmpty() && s.Len() == 0
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyIsSubsetReflexive(t *testing.T) {
+
f := func(s Set[int]) bool {
+
return s.IsSubset(s)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+
+
func TestPropertyDeMorganUnion(t *testing.T) {
+
f := func(s1 Set[int], s2 Set[int], universe Set[int]) bool {
+
// create a universe that contains both sets
+
u := universe.Clone()
+
for item := range s1.All() {
+
u.Insert(item)
+
}
+
for item := range s2.All() {
+
u.Insert(item)
+
}
+
+
// (A u B)' = A' n B'
+
union := Collect(s1.Union(s2))
+
complementUnion := Collect(u.Difference(union))
+
+
complementS1 := Collect(u.Difference(s1))
+
complementS2 := Collect(u.Difference(s2))
+
intersectionComplements := Collect(complementS1.Intersection(complementS2))
+
+
return complementUnion.Equal(intersectionComplements)
+
}
+
+
if err := quick.Check(f, nil); err != nil {
+
t.Error(err)
+
}
+
}
+5 -3
spindle/engines/nixery/engine.go
···
workflowEnvs.AddEnv(s.Key, s.Value)
}
-
step := w.Steps[idx].(Step)
+
step := w.Steps[idx]
select {
case <-ctx.Done():
···
}
envs := append(EnvVars(nil), workflowEnvs...)
-
for k, v := range step.environment {
-
envs.AddEnv(k, v)
+
if nixStep, ok := step.(Step); ok {
+
for k, v := range nixStep.environment {
+
envs.AddEnv(k, v)
+
}
}
envs.AddEnv("HOME", homeDir)
+6 -1
types/commit.go
···
func (commit Commit) CoAuthors() []object.Signature {
var coAuthors []object.Signature
-
+
seen := make(map[string]bool)
matches := coAuthorRegex.FindAllStringSubmatch(commit.Message, -1)
for _, match := range matches {
if len(match) >= 3 {
name := strings.TrimSpace(match[1])
email := strings.TrimSpace(match[2])
+
+
if seen[email] {
+
continue
+
}
+
seen[email] = true
coAuthors = append(coAuthors, object.Signature{
Name: name,