~edwargix/git.sr.ht

42c00081083d9f30e4bb801e72b67b64275e578f — Adnan Maolood 2 years ago aaa1f6f
api/loaders: Add RepositoriesByOwnerIDRepoName loader
M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +1 -2
@@ 1280,8 1280,7 @@ func (r *treeResolver) Entries(ctx context.Context, obj *model.Tree, cursor *cor
}

func (r *userResolver) Repository(ctx context.Context, obj *model.User, name string) (*model.Repository, error) {
	// TODO: Load repository with user ID instead of username. Needs a new loader.
	return loaders.ForContext(ctx).RepositoriesByOwnerRepoName.Load(loaders.OwnerRepoName{obj.Username, name})
	return loaders.ForContext(ctx).RepositoriesByOwnerIDRepoName.Load(loaders.OwnerIDRepoName{obj.ID, name})
}

func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor *coremodel.Cursor, filter *coremodel.Filter) (*model.RepositoryCursor, error) {

M api/loaders/generate.go => api/loaders/generate.go +1 -0
@@ 5,5 5,6 @@ package loaders

//go:generate ./gen RepositoriesByIDLoader int api/graph/model.Repository
//go:generate ./gen RepositoriesByOwnerRepoNameLoader OwnerRepoName api/graph/model.Repository
//go:generate ./gen RepositoriesByOwnerIDRepoNameLoader OwnerIDRepoName api/graph/model.Repository
//go:generate ./gen UsersByIDLoader int api/graph/model.User
//go:generate ./gen UsersByNameLoader string api/graph/model.User

M api/loaders/middleware.go => api/loaders/middleware.go +77 -4
@@ 24,10 24,11 @@ type contextKey struct {
}

type Loaders struct {
	UsersByID                   UsersByIDLoader
	UsersByName                 UsersByNameLoader
	RepositoriesByID            RepositoriesByIDLoader
	RepositoriesByOwnerRepoName RepositoriesByOwnerRepoNameLoader
	UsersByID                     UsersByIDLoader
	UsersByName                   UsersByNameLoader
	RepositoriesByID              RepositoriesByIDLoader
	RepositoriesByOwnerRepoName   RepositoriesByOwnerRepoNameLoader
	RepositoriesByOwnerIDRepoName RepositoriesByOwnerIDRepoNameLoader
}

func fetchUsersByID(ctx context.Context) func(ids []int) ([]*model.User, []error) {


@@ 236,6 237,73 @@ func fetchRepositoriesByOwnerRepoName(ctx context.Context) func([]OwnerRepoName)
	}
}

type OwnerIDRepoName struct {
	OwnerID  int
	RepoName string
}

func (or OwnerIDRepoName) Value() (driver.Value, error) {
	return fmt.Sprintf("(%d,%q)", or.OwnerID, or.RepoName), nil
}

func fetchRepositoriesByOwnerIDRepoName(ctx context.Context) func([]OwnerIDRepoName) ([]*model.Repository, []error) {
	return func(ownerIDRepoNames []OwnerIDRepoName) ([]*model.Repository, []error) {
		repos := make([]*model.Repository, len(ownerIDRepoNames))
		if err := database.WithTx(ctx, &sql.TxOptions{
			Isolation: 0,
			ReadOnly:  true,
		}, func(tx *sql.Tx) error {
			var (
				err  error
				rows *sql.Rows
			)
			query := database.
				Select(ctx).
				Prefix(`WITH owner_id_repo_names AS (
					SELECT owner_id, repo_name
					FROM unnest(?::owner_id_repo_name[]))`, pq.GenericArray{ownerIDRepoNames}).
				Columns(database.Columns(ctx, (&model.Repository{}).As(`repo`))...).
				Columns(`o.owner_id`).
				Distinct().
				From(`owner_id_repo_names o`).
				Join(`repository repo ON o.repo_name = repo.name
					AND o.owner_id = repo.owner_id`).
				LeftJoin(`access ON repo.id = access.repo_id`).
				Where(sq.Or{
					sq.Expr(`? IN (access.user_id, repo.owner_id)`,
						auth.ForContext(ctx).UserID),
					sq.Expr(`repo.visibility != 'private'`),
				})
			if rows, err = query.RunWith(tx).QueryContext(ctx); err != nil {
				panic(err)
			}
			defer rows.Close()

			reposByOwnerIDRepoName := map[OwnerIDRepoName]*model.Repository{}
			for rows.Next() {
				var ownerID int
				repo := model.Repository{}
				if err := rows.Scan(append(
					database.Scan(ctx, &repo), &ownerID)...); err != nil {
					panic(err)
				}
				reposByOwnerIDRepoName[OwnerIDRepoName{ownerID, repo.Name}] = &repo
			}
			if err = rows.Err(); err != nil {
				panic(err)
			}

			for i, or := range ownerIDRepoNames {
				repos[i] = reposByOwnerIDRepoName[or]
			}
			return nil
		}); err != nil {
			panic(err)
		}
		return repos, nil
	}
}

func Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := context.WithValue(r.Context(), loadersCtxKey, &Loaders{


@@ 259,6 327,11 @@ func Middleware(next http.Handler) http.Handler {
				wait:     1 * time.Millisecond,
				fetch:    fetchRepositoriesByOwnerRepoName(r.Context()),
			},
			RepositoriesByOwnerIDRepoName: RepositoriesByOwnerIDRepoNameLoader{
				maxBatch: 100,
				wait:     1 * time.Millisecond,
				fetch:    fetchRepositoriesByOwnerIDRepoName(r.Context()),
			},
		})
		r = r.WithContext(ctx)
		next.ServeHTTP(w, r)

A gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py => gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py +29 -0
@@ 0,0 1,29 @@
"""Add owner_id_repo_name custom type

Revision ID: 38952f52f32d
Revises: 822baa9910cd
Create Date: 2022-02-16 10:57:33.542300

"""

# revision identifiers, used by Alembic.
revision = '38952f52f32d'
down_revision = '822baa9910cd'

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.execute("""
    CREATE TYPE owner_id_repo_name AS (
        owner_id integer,
        repo_name text
    );
    """)


def downgrade():
    op.execute("""
    DROP TYPE owner_id_repo_name;
    """)