From 42c00081083d9f30e4bb801e72b67b64275e578f Mon Sep 17 00:00:00 2001 From: Adnan Maolood Date: Wed, 16 Feb 2022 11:12:53 -0500 Subject: [PATCH] api/loaders: Add RepositoriesByOwnerIDRepoName loader --- api/graph/schema.resolvers.go | 3 +- api/loaders/generate.go | 1 + api/loaders/middleware.go | 81 ++++++++++++++++++- ...f32d_add_owner_id_repo_name_custom_type.py | 29 +++++++ 4 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 3085ed1..3c8f402 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -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) { diff --git a/api/loaders/generate.go b/api/loaders/generate.go index 6efb1ae..f45ef6f 100644 --- a/api/loaders/generate.go +++ b/api/loaders/generate.go @@ -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 diff --git a/api/loaders/middleware.go b/api/loaders/middleware.go index 1ae4f32..79d0fb8 100644 --- a/api/loaders/middleware.go +++ b/api/loaders/middleware.go @@ -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) diff --git a/gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py b/gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py new file mode 100644 index 0000000..ea61b88 --- /dev/null +++ b/gitsrht/alembic/versions/38952f52f32d_add_owner_id_repo_name_custom_type.py @@ -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; + """) -- 2.38.4