gitsrht: Store visibility as enum instead of varchar
Add a 'visibility' enum type to the database and use it for the repository.visibility column. This required changes to scm.sr.ht code. Instead of updating scm.sr.ht, most of the scm.sr.ht code that git.sr.ht uses was moved to git.sr.ht.
This commit is contained in:
parent
cd8225a534
commit
ad42bf4448
|
@ -3,7 +3,6 @@ package model
|
|||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -23,28 +22,15 @@ type Repository struct {
|
|||
Description *string `json:"description"`
|
||||
Readme *string `json:"readme"`
|
||||
|
||||
Path string
|
||||
OwnerID int
|
||||
RawVisibility string
|
||||
Path string
|
||||
OwnerID int
|
||||
Visibility Visibility
|
||||
|
||||
alias string
|
||||
repo *RepoWrapper
|
||||
fields *database.ModelFields
|
||||
}
|
||||
|
||||
func (r *Repository) Visibility() Visibility {
|
||||
visMap := map[string]Visibility{
|
||||
"public": VisibilityPublic,
|
||||
"unlisted": VisibilityUnlisted,
|
||||
"private": VisibilityPrivate,
|
||||
}
|
||||
vis, ok := visMap[r.RawVisibility]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("Invalid repo visibility %s", r.RawVisibility)) // Invariant
|
||||
}
|
||||
return vis
|
||||
}
|
||||
|
||||
func (r *Repository) Repo() *RepoWrapper {
|
||||
if r.repo != nil {
|
||||
return r.repo
|
||||
|
@ -94,7 +80,7 @@ func (r *Repository) Fields() *database.ModelFields {
|
|||
{"updated", "updated", &r.Updated},
|
||||
{"name", "name", &r.Name},
|
||||
{"description", "description", &r.Description},
|
||||
{"visibility", "visibility", &r.RawVisibility},
|
||||
{"visibility", "visibility", &r.Visibility},
|
||||
{"readme", "readme", &r.Readme},
|
||||
|
||||
// Always fetch:
|
||||
|
|
|
@ -115,19 +115,6 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
|
|||
}()
|
||||
|
||||
if err := database.WithTx(ctx, nil, func(tx *sql.Tx) error {
|
||||
vismap := map[model.Visibility]string{
|
||||
model.VisibilityPublic: "public",
|
||||
model.VisibilityUnlisted: "unlisted",
|
||||
model.VisibilityPrivate: "private",
|
||||
}
|
||||
var (
|
||||
dvis string
|
||||
ok bool
|
||||
)
|
||||
if dvis, ok = vismap[visibility]; !ok {
|
||||
panic(fmt.Errorf("Unknown visibility %s", visibility)) // Invariant
|
||||
}
|
||||
|
||||
cloneStatus := CloneNone
|
||||
if cloneURL != nil {
|
||||
cloneStatus = CloneInProgress
|
||||
|
@ -144,9 +131,9 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
|
|||
) RETURNING
|
||||
id, created, updated, name, description, visibility,
|
||||
path, owner_id;
|
||||
`, name, description, repoPath, dvis, user.UserID, cloneStatus)
|
||||
`, name, description, repoPath, visibility, user.UserID, cloneStatus)
|
||||
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated, &repo.Name,
|
||||
&repo.Description, &repo.RawVisibility,
|
||||
&repo.Description, &repo.Visibility,
|
||||
&repo.Path, &repo.OwnerID); err != nil {
|
||||
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||
return valid.Errorf(ctx, "name", "A repository with this name already exists.")
|
||||
|
@ -184,7 +171,7 @@ func (r *mutationResolver) CreateRepository(ctx context.Context, name string, vi
|
|||
}
|
||||
|
||||
export := path.Join(repoPath, "git-daemon-export-ok")
|
||||
if repo.Visibility() != model.VisibilityPrivate {
|
||||
if repo.Visibility != model.VisibilityPrivate {
|
||||
_, err := os.Create(export)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -361,7 +348,7 @@ func (r *mutationResolver) UpdateRepository(ctx context.Context, id int, input m
|
|||
valid.OptionalString("visibility", func(vis string) {
|
||||
valid.Expect(model.Visibility(vis).IsValid(),
|
||||
"Invalid visibility '%s'", vis)
|
||||
query = query.Set(`visibility`, strings.ToLower(vis)) // TODO: Can we use uppercase here?
|
||||
query = query.Set(`visibility`, vis)
|
||||
})
|
||||
|
||||
valid.NullableString("readme", func(readme *string) {
|
||||
|
@ -386,7 +373,7 @@ func (r *mutationResolver) UpdateRepository(ctx context.Context, id int, input m
|
|||
|
||||
row := query.RunWith(tx).QueryRowContext(ctx)
|
||||
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated,
|
||||
&repo.Name, &repo.Description, &repo.RawVisibility,
|
||||
&repo.Name, &repo.Description, &repo.Visibility,
|
||||
&repo.Path, &repo.OwnerID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return fmt.Errorf("No repository by ID %d found for this user", id)
|
||||
|
@ -418,7 +405,7 @@ func (r *mutationResolver) UpdateRepository(ctx context.Context, id int, input m
|
|||
}
|
||||
|
||||
export := path.Join(repo.Path, "git-daemon-export-ok")
|
||||
if repo.Visibility() == model.VisibilityPrivate {
|
||||
if repo.Visibility == model.VisibilityPrivate {
|
||||
err := os.Remove(export)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
|
@ -476,7 +463,7 @@ func (r *mutationResolver) DeleteRepository(ctx context.Context, id int) (*model
|
|||
`, id, auth.ForContext(ctx).UserID)
|
||||
|
||||
if err := row.Scan(&repo.ID, &repo.Created, &repo.Updated,
|
||||
&repo.Name, &repo.Description, &repo.RawVisibility,
|
||||
&repo.Name, &repo.Description, &repo.Visibility,
|
||||
&repo.Path, &repo.OwnerID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return fmt.Errorf("No repository by ID %d found for this user", id)
|
||||
|
@ -1302,7 +1289,7 @@ func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor
|
|||
sq.Or{
|
||||
sq.Expr(`? IN (access.user_id, repo.owner_id)`,
|
||||
auth.ForContext(ctx).UserID),
|
||||
sq.Expr(`repo.visibility = 'public'`),
|
||||
sq.Expr(`repo.visibility = 'PUBLIC'`),
|
||||
},
|
||||
sq.Expr(`repo.owner_id = ?`, obj.ID),
|
||||
})
|
||||
|
|
|
@ -138,7 +138,7 @@ func fetchRepositoriesByID(ctx context.Context) func(ids []int) ([]*model.Reposi
|
|||
sq.Expr(`repo.id = ANY(?)`, pq.Array(ids)),
|
||||
sq.Or{
|
||||
sq.Expr(`? IN (access.user_id, repo.owner_id)`, auser.UserID),
|
||||
sq.Expr(`repo.visibility != 'private'`),
|
||||
sq.Expr(`repo.visibility != 'PRIVATE'`),
|
||||
},
|
||||
})
|
||||
if rows, err = query.RunWith(tx).QueryContext(ctx); err != nil {
|
||||
|
@ -205,7 +205,7 @@ func fetchRepositoriesByOwnerRepoName(ctx context.Context) func([]OwnerRepoName)
|
|||
Where(sq.Or{
|
||||
sq.Expr(`? IN (access.user_id, repo.owner_id)`,
|
||||
auth.ForContext(ctx).UserID),
|
||||
sq.Expr(`repo.visibility != 'private'`),
|
||||
sq.Expr(`repo.visibility != 'PRIVATE'`),
|
||||
})
|
||||
if rows, err = query.RunWith(tx).QueryContext(ctx); err != nil {
|
||||
panic(err)
|
||||
|
@ -272,7 +272,7 @@ func fetchRepositoriesByOwnerIDRepoName(ctx context.Context) func([]OwnerIDRepoN
|
|||
Where(sq.Or{
|
||||
sq.Expr(`? IN (access.user_id, repo.owner_id)`,
|
||||
auth.ForContext(ctx).UserID),
|
||||
sq.Expr(`repo.visibility != 'private'`),
|
||||
sq.Expr(`repo.visibility != 'PRIVATE'`),
|
||||
})
|
||||
if rows, err = query.RunWith(tx).QueryContext(ctx); err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~sircmpwn/core-go/auth"
|
||||
|
@ -35,7 +36,7 @@ func DeliverLegacyRepoCreate(ctx context.Context, repo *model.Repository) {
|
|||
Updated: repo.Created,
|
||||
Name: repo.Name,
|
||||
Description: repo.Description,
|
||||
Visibility: repo.RawVisibility,
|
||||
Visibility: strings.ToLower(repo.Visibility.String()),
|
||||
}
|
||||
|
||||
// TODO: User groups
|
||||
|
@ -69,7 +70,7 @@ func DeliverLegacyRepoUpdate(ctx context.Context, repo *model.Repository) {
|
|||
Updated: repo.Created,
|
||||
Name: repo.Name,
|
||||
Description: repo.Description,
|
||||
Visibility: repo.RawVisibility,
|
||||
Visibility: strings.ToLower(repo.Visibility.String()),
|
||||
}
|
||||
|
||||
// TODO: User groups
|
||||
|
|
|
@ -11,13 +11,12 @@ from prometheus_client import CollectorRegistry, Gauge
|
|||
from prometheus_client.context_managers import Timer
|
||||
from srht.config import cfg
|
||||
from srht.database import DbSession
|
||||
from gitsrht.types import Artifact, User, Repository, RepoVisibility
|
||||
from gitsrht.types import Artifact, User, Repository
|
||||
from minio import Minio
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
db = DbSession(cfg("git.sr.ht", "connection-string"))
|
||||
db.init()
|
||||
repo_api = gr.GitRepoApi()
|
||||
|
||||
registry = CollectorRegistry()
|
||||
tg = Gauge("gitsrht_periodic_time",
|
||||
|
|
|
@ -152,7 +152,7 @@ func main() {
|
|||
|
||||
// Fetch the necessary info from SQL. This first query fetches:
|
||||
//
|
||||
// 1. Repository information, such as visibility (public|unlisted|private)
|
||||
// 1. Repository information, such as visibility (PUBLIC|UNLISTED|PRIVATE)
|
||||
// 2. Information about the repository owner's account
|
||||
// 3. Information about the pusher's account
|
||||
// 4. Any access control policies for that repo that apply to the pusher
|
||||
|
@ -266,7 +266,7 @@ func main() {
|
|||
|
||||
repoOwnerId = pusherId
|
||||
repoOwnerName = pusherName
|
||||
repoVisibility = "private"
|
||||
repoVisibility = "PRIVATE"
|
||||
|
||||
query := client.GraphQLQuery{
|
||||
Query: `
|
||||
|
@ -339,11 +339,11 @@ func main() {
|
|||
} else {
|
||||
if accessGrant == nil {
|
||||
switch repoVisibility {
|
||||
case "public":
|
||||
case "PUBLIC":
|
||||
fallthrough
|
||||
case "unlisted":
|
||||
case "UNLISTED":
|
||||
hasAccess = ACCESS_READ
|
||||
case "private":
|
||||
case "PRIVATE":
|
||||
fallthrough
|
||||
default:
|
||||
hasAccess = ACCESS_NONE
|
||||
|
|
|
@ -232,7 +232,7 @@ func (submitter GitBuildSubmitter) GetCommitNote() string {
|
|||
}
|
||||
|
||||
func (submitter GitBuildSubmitter) GetCloneUrl() string {
|
||||
if submitter.Visibility == "private" {
|
||||
if submitter.Visibility == "PRIVATE" {
|
||||
origin := strings.ReplaceAll(submitter.GitOrigin, "http://", "")
|
||||
origin = strings.ReplaceAll(origin, "https://", "")
|
||||
// Use SSH URL
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
from datetime import datetime
|
||||
from enum import IntFlag
|
||||
from flask import abort, current_app, request, redirect, url_for
|
||||
from gitsrht.types import Access, AccessMode, Repository, Redirect, User, RepoVisibility
|
||||
from srht.database import db
|
||||
from srht.oauth import current_user
|
||||
import sqlalchemy as sa
|
||||
import sqlalchemy_utils as sau
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from enum import Enum
|
||||
|
||||
class UserAccess(IntFlag):
|
||||
none = 0
|
||||
read = 1
|
||||
write = 2
|
||||
manage = 4
|
||||
|
||||
def get_repo(owner_name, repo_name):
|
||||
if owner_name[0] == "~":
|
||||
user = User.query.filter(User.username == owner_name[1:]).first()
|
||||
if user:
|
||||
repo = Repository.query.filter(Repository.owner_id == user.id)\
|
||||
.filter(Repository.name == repo_name).first()
|
||||
else:
|
||||
repo = None
|
||||
if user and not repo:
|
||||
repo = (Redirect.query
|
||||
.filter(Redirect.owner_id == user.id)
|
||||
.filter(Redirect.name == repo_name)
|
||||
).first()
|
||||
return user, repo
|
||||
else:
|
||||
# TODO: organizations
|
||||
return None, None
|
||||
|
||||
def get_repo_or_redir(owner, repo):
|
||||
owner, repo = get_repo(owner, repo)
|
||||
if not repo:
|
||||
abort(404)
|
||||
if not has_access(repo, UserAccess.read):
|
||||
abort(401)
|
||||
if isinstance(repo, Redirect):
|
||||
view_args = request.view_args
|
||||
if not "repo" in view_args or not "owner" in view_args:
|
||||
return redirect(url_for(".summary",
|
||||
owner=repo.new_repo.owner.canonical_name,
|
||||
repo=repo.new_repo.name))
|
||||
view_args["owner"] = repo.new_repo.owner.canonical_name
|
||||
view_args["repo"] = repo.new_repo.name
|
||||
abort(redirect(url_for(request.endpoint, **view_args)))
|
||||
return owner, repo
|
||||
|
||||
def get_access(repo, user=None):
|
||||
# Note: when updating push access logic, also update git.sr.ht/gitsrht-shell
|
||||
if not user:
|
||||
user = current_user
|
||||
if not repo:
|
||||
return UserAccess.none
|
||||
if isinstance(repo, Redirect):
|
||||
# Just pretend they have full access for long enough to do the redirect
|
||||
return UserAccess.read | UserAccess.write | UserAccess.manage
|
||||
if not user:
|
||||
if repo.visibility == RepoVisibility.PUBLIC or \
|
||||
repo.visibility == RepoVisibility.UNLISTED:
|
||||
return UserAccess.read
|
||||
return UserAccess.none
|
||||
if repo.owner_id == user.id:
|
||||
return UserAccess.read | UserAccess.write | UserAccess.manage
|
||||
acl = Access.query.filter(
|
||||
Access.user_id == user.id,
|
||||
Access.repo_id == repo.id).first()
|
||||
if acl:
|
||||
acl.updated = datetime.utcnow()
|
||||
db.session.commit()
|
||||
if acl.mode == AccessMode.ro:
|
||||
return UserAccess.read
|
||||
else:
|
||||
return UserAccess.read | UserAccess.write
|
||||
if repo.visibility == RepoVisibility.PRIVATE:
|
||||
return UserAccess.none
|
||||
return UserAccess.read
|
||||
|
||||
def has_access(repo, access, user=None):
|
||||
return access in get_access(repo, user)
|
||||
|
||||
def check_access(owner_name, repo_name, access):
|
||||
owner, repo = get_repo(owner_name, repo_name)
|
||||
if not owner or not repo:
|
||||
abort(404)
|
||||
a = get_access(repo)
|
||||
if not access in a:
|
||||
abort(403)
|
||||
return owner, repo
|
|
@ -0,0 +1,37 @@
|
|||
"""Add visibility enum
|
||||
|
||||
Revision ID: 64fcd80183c8
|
||||
Revises: 38952f52f32d
|
||||
Create Date: 2022-02-24 12:29:23.314019
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '64fcd80183c8'
|
||||
down_revision = '38952f52f32d'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("""
|
||||
UPDATE repository SET visibility = 'private' WHERE visibility = 'autocreated';
|
||||
|
||||
CREATE TYPE visibility AS ENUM (
|
||||
'PUBLIC',
|
||||
'PRIVATE',
|
||||
'UNLISTED'
|
||||
);
|
||||
|
||||
ALTER TABLE repository
|
||||
ALTER COLUMN visibility TYPE visibility USING upper(visibility)::visibility;
|
||||
""")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("""
|
||||
ALTER TABLE repository
|
||||
ALTER COLUMN visibility TYPE varchar USING lower(visibility::varchar);
|
||||
DROP TYPE visibility;
|
||||
""")
|
|
@ -4,25 +4,24 @@ import stat
|
|||
from functools import lru_cache
|
||||
from gitsrht import urls
|
||||
from gitsrht.git import commit_time, commit_links, trim_commit, signature_time
|
||||
from gitsrht.repos import GitRepoApi
|
||||
from gitsrht.service import oauth_service, webhooks_notify
|
||||
from gitsrht.types import Access, Redirect, Repository, User
|
||||
from scmsrht.flask import ScmSrhtFlask
|
||||
from gitsrht.types import User
|
||||
from srht.config import cfg
|
||||
from srht.database import DbSession
|
||||
from srht.flask import session
|
||||
from srht.database import db, DbSession
|
||||
from srht.flask import SrhtFlask, session
|
||||
from jinja2 import FileSystemLoader, ChoiceLoader
|
||||
from werkzeug.urls import url_quote
|
||||
|
||||
db = DbSession(cfg("git.sr.ht", "connection-string"))
|
||||
db.init()
|
||||
|
||||
class GitApp(ScmSrhtFlask):
|
||||
class GitApp(SrhtFlask):
|
||||
def __init__(self):
|
||||
super().__init__("git.sr.ht", __name__,
|
||||
access_class=Access, redirect_class=Redirect,
|
||||
repository_class=Repository, user_class=User,
|
||||
repo_api=GitRepoApi(), oauth_service=oauth_service)
|
||||
oauth_service=oauth_service)
|
||||
|
||||
from gitsrht.blueprints.auth import auth
|
||||
from gitsrht.blueprints.public import public
|
||||
from gitsrht.blueprints.api import register_api
|
||||
from gitsrht.blueprints.api.plumbing import plumbing
|
||||
from gitsrht.blueprints.api.porcelain import porcelain
|
||||
|
@ -32,6 +31,9 @@ class GitApp(ScmSrhtFlask):
|
|||
from gitsrht.blueprints.repo import repo
|
||||
from srht.graphql import gql_blueprint
|
||||
|
||||
self.register_blueprint(auth)
|
||||
self.register_blueprint(public)
|
||||
|
||||
register_api(self)
|
||||
self.register_blueprint(plumbing)
|
||||
self.register_blueprint(porcelain)
|
||||
|
@ -68,6 +70,16 @@ class GitApp(ScmSrhtFlask):
|
|||
"path_join": os.path.join,
|
||||
"stat": stat,
|
||||
"trim_commit": trim_commit,
|
||||
"lookup_user": self.lookup_user
|
||||
}
|
||||
|
||||
choices = [self.jinja_loader, FileSystemLoader(os.path.join(
|
||||
os.path.dirname(__file__), "templates"))]
|
||||
self.jinja_loader = ChoiceLoader(choices)
|
||||
|
||||
self.url_map.strict_slashes = False
|
||||
|
||||
def lookup_user(self, email):
|
||||
return User.query.filter(User.email == email).one_or_none()
|
||||
|
||||
app = GitApp()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pkg_resources
|
||||
from flask import abort
|
||||
from scmsrht.access import UserAccess, get_access
|
||||
from scmsrht.types import Repository, User
|
||||
from gitsrht.access import UserAccess, get_access
|
||||
from gitsrht.types import Repository, User
|
||||
from srht.flask import csrf_bypass
|
||||
from srht.oauth import current_token, oauth
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from flask import Blueprint, Response, current_app, request
|
||||
from scmsrht.access import UserAccess
|
||||
from scmsrht.repos import RepoVisibility
|
||||
from scmsrht.types import Access, Repository, User
|
||||
import gitsrht.repos as repos
|
||||
from gitsrht.access import UserAccess
|
||||
from gitsrht.types import Access, Repository, User, RepoVisibility
|
||||
from gitsrht.webhooks import UserWebhook
|
||||
from gitsrht.blueprints.api import get_user, get_repo
|
||||
from srht.api import paginated_response
|
||||
|
@ -22,12 +22,12 @@ def repos_by_user_GET(username):
|
|||
.filter(Repository.owner_id == user.id))
|
||||
if user.id != current_token.user_id:
|
||||
repos = (repos
|
||||
.outerjoin(Access._get_current_object(),
|
||||
.outerjoin(Access,
|
||||
Access.repo_id == Repository.id)
|
||||
.filter(or_(
|
||||
Access.user_id == current_token.user_id,
|
||||
and_(
|
||||
Repository.visibility == RepoVisibility.public,
|
||||
Repository.visibility == RepoVisibility.PUBLIC,
|
||||
Access.id.is_(None))
|
||||
)))
|
||||
return paginated_response(Repository.id, repos)
|
||||
|
@ -37,7 +37,7 @@ def repos_by_user_GET(username):
|
|||
def repos_POST():
|
||||
valid = Validation(request)
|
||||
user = current_token.user
|
||||
resp = current_app.repo_api.create_repo(valid, user)
|
||||
resp = repos.create_repo(valid, user)
|
||||
if not valid.ok:
|
||||
return valid.response
|
||||
# Convert visibility back to lowercase
|
||||
|
@ -103,7 +103,7 @@ def repos_by_name_DELETE(reponame):
|
|||
user = current_token.user
|
||||
repo = get_repo(user, reponame, needs=UserAccess.manage)
|
||||
repo_id = repo.id
|
||||
current_app.repo_api.delete_repo(repo, user)
|
||||
repos.delete_repo(repo, user)
|
||||
return {}, 204
|
||||
|
||||
@info.route("/api/repos/<reponame>/readme", defaults={"username": None})
|
||||
|
|
|
@ -10,7 +10,7 @@ from gitsrht.repos import upload_artifact
|
|||
from gitsrht.webhooks import RepoWebhook
|
||||
from io import BytesIO
|
||||
from itertools import groupby
|
||||
from scmsrht.access import UserAccess
|
||||
from gitsrht.access import UserAccess
|
||||
from gitsrht.blueprints.api import get_user, get_repo
|
||||
from srht.api import paginated_response
|
||||
from srht.database import db
|
||||
|
|
|
@ -7,7 +7,7 @@ from gitsrht.git import Repository as GitRepository, strip_pgp_signature
|
|||
from gitsrht.repos import delete_artifact, upload_artifact
|
||||
from gitsrht.types import Artifact
|
||||
from minio import Minio
|
||||
from scmsrht.access import check_access, get_repo_or_redir, UserAccess
|
||||
from gitsrht.access import check_access, UserAccess
|
||||
from srht.config import cfg
|
||||
from srht.database import db
|
||||
from srht.oauth import loginrequired
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import os
|
||||
from flask import Blueprint, request
|
||||
from gitsrht.access import get_repo, has_access, UserAccess
|
||||
from urllib.parse import urlparse, unquote
|
||||
|
||||
auth = Blueprint("auth", __name__)
|
||||
|
||||
@auth.route("/authorize")
|
||||
def authorize_http_access():
|
||||
original_uri = request.headers.get("X-Original-URI")
|
||||
original_uri = urlparse(original_uri)
|
||||
path = unquote(original_uri.path)
|
||||
original_path = os.path.normpath(path).split('/')
|
||||
if len(original_path) < 3:
|
||||
return "authorized", 200
|
||||
owner, repo = original_path[1], original_path[2]
|
||||
owner, repo = get_repo(owner, repo)
|
||||
if not repo:
|
||||
return "unauthorized", 403
|
||||
if not has_access(repo, UserAccess.read):
|
||||
return "unauthorized", 403
|
||||
return "authorized", 200
|
|
@ -13,7 +13,7 @@ from flask import Blueprint, render_template, abort, request, url_for, session
|
|||
from flask import redirect
|
||||
from gitsrht.git import Repository as GitRepository, commit_time, diffstat
|
||||
from gitsrht.git import get_log
|
||||
from scmsrht.access import get_repo_or_redir
|
||||
from gitsrht.access import get_repo_or_redir
|
||||
from srht.config import cfg, cfgi, cfgb
|
||||
from srht.oauth import loginrequired, current_user
|
||||
from srht.validation import Validation
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import pygit2
|
||||
from flask import Blueprint, current_app, request, render_template, abort
|
||||
from flask import redirect, url_for
|
||||
import gitsrht.repos as repos
|
||||
from gitsrht.git import Repository as GitRepository
|
||||
from srht.config import cfg
|
||||
from srht.database import db
|
||||
|
@ -8,11 +9,8 @@ from srht.flask import session
|
|||
from srht.graphql import exec_gql, GraphQLError
|
||||
from srht.oauth import current_user, loginrequired, UserType
|
||||
from srht.validation import Validation
|
||||
from scmsrht.access import check_access, UserAccess
|
||||
from scmsrht.repos.access import AccessMode
|
||||
from scmsrht.repos.redirect import BaseRedirectMixin
|
||||
from scmsrht.repos.repository import RepoVisibility
|
||||
from scmsrht.types import Access, User
|
||||
from gitsrht.access import check_access, UserAccess, AccessMode
|
||||
from gitsrht.types import Access, User, Redirect
|
||||
import shutil
|
||||
import os
|
||||
|
||||
|
@ -28,10 +26,8 @@ def create_GET():
|
|||
@manage.route("/create", methods=["POST"])
|
||||
@loginrequired
|
||||
def create_POST():
|
||||
if not current_app.repo_api:
|
||||
abort(501)
|
||||
valid = Validation(request)
|
||||
resp = current_app.repo_api.create_repo(valid)
|
||||
resp = repos.create_repo(valid)
|
||||
if not valid.ok:
|
||||
return render_template("create.html", **valid.kwargs)
|
||||
|
||||
|
@ -51,10 +47,8 @@ def clone():
|
|||
@manage.route("/clone", methods=["POST"])
|
||||
@loginrequired
|
||||
def clone_POST():
|
||||
if not current_app.repo_api:
|
||||
abort(501)
|
||||
valid = Validation(request)
|
||||
resp = current_app.repo_api.clone_repo(valid)
|
||||
resp = repos.clone_repo(valid)
|
||||
if not valid.ok:
|
||||
return render_template("clone.html", **valid.kwargs)
|
||||
return redirect(url_for("repo.summary",
|
||||
|
@ -64,7 +58,7 @@ def clone_POST():
|
|||
@loginrequired
|
||||
def settings_info(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
return redirect(url_for(".settings_info",
|
||||
owner_name=owner_name, repo_name=repo.new_repo.name))
|
||||
return render_template("settings_info.html", owner=owner, repo=repo)
|
||||
|
@ -73,7 +67,7 @@ def settings_info(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_info_POST(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
repo = repo.new_repo
|
||||
|
||||
valid = Validation(request)
|
||||
|
@ -101,7 +95,7 @@ def settings_info_POST(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_rename(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
return redirect(url_for(".settings_rename",
|
||||
owner_name=owner_name, repo_name=repo.new_repo.name))
|
||||
return render_template("settings_rename.html", owner=owner, repo=repo)
|
||||
|
@ -110,7 +104,7 @@ def settings_rename(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_rename_POST(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
repo = repo.new_repo
|
||||
|
||||
valid = Validation(request)
|
||||
|
@ -138,7 +132,7 @@ def settings_rename_POST(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_access(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
return redirect(url_for(".settings_manage",
|
||||
owner_name=owner_name, repo_name=repo.new_repo.name))
|
||||
return render_template("settings_access.html", owner=owner, repo=repo)
|
||||
|
@ -147,7 +141,7 @@ def settings_access(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_access_POST(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
repo = repo.new_repo
|
||||
valid = Validation(request)
|
||||
username = valid.require("user", friendly_name="User")
|
||||
|
@ -190,7 +184,7 @@ def settings_access_POST(owner_name, repo_name):
|
|||
@loginrequired
|
||||
def settings_access_revoke_POST(owner_name, repo_name, grant_id):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
repo = repo.new_repo
|
||||
grant = (Access.query
|
||||
.filter(Access.repo_id == repo.id, Access.id == grant_id)
|
||||
|
@ -206,7 +200,7 @@ def settings_access_revoke_POST(owner_name, repo_name, grant_id):
|
|||
@loginrequired
|
||||
def settings_delete(owner_name, repo_name):
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
return redirect(url_for(".settings_delete",
|
||||
owner_name=owner_name, repo_name=repo.new_repo.name))
|
||||
return render_template("settings_delete.html", owner=owner, repo=repo)
|
||||
|
@ -214,14 +208,12 @@ def settings_delete(owner_name, repo_name):
|
|||
@manage.route("/<owner_name>/<repo_name>/settings/delete", methods=["POST"])
|
||||
@loginrequired
|
||||
def settings_delete_POST(owner_name, repo_name):
|
||||
if not current_app.repo_api:
|
||||
abort(501)
|
||||
owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
|
||||
if isinstance(repo, BaseRedirectMixin):
|
||||
if isinstance(repo, Redirect):
|
||||
# Normally we'd redirect but we don't want to fuck up some other repo
|
||||
abort(404)
|
||||
repo_id = repo.id
|
||||
current_app.repo_api.delete_repo(repo)
|
||||
repos.delete_repo(repo)
|
||||
session["notice"] = "{}/{} was deleted.".format(
|
||||
owner.canonical_name, repo.name)
|
||||
return redirect("/" + owner.canonical_name)
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
from flask import Blueprint, current_app, request
|
||||
from flask import render_template, abort
|
||||
from srht.config import cfg
|
||||
from srht.flask import paginate_query
|
||||
from srht.oauth import current_user
|
||||
from srht.search import search_by
|
||||
from gitsrht.types import Access, Repository, User, RepoVisibility
|
||||
from sqlalchemy import and_, or_
|
||||
|
||||
public = Blueprint('public', __name__)
|
||||
|
||||
@public.route("/")
|
||||
def index():
|
||||
if current_user:
|
||||
repos = (Repository.query
|
||||
.filter(Repository.owner_id == current_user.id)
|
||||
.order_by(Repository.updated.desc())
|
||||
.limit(10)).all()
|
||||
return render_template("dashboard.html", repos=repos)
|
||||
return render_template("index.html")
|
||||
|
||||
@public.route("/~<username>")
|
||||
@public.route("/~<username>/")
|
||||
def user_index(username):
|
||||
user = User.query.filter(User.username == username).first()
|
||||
if not user:
|
||||
abort(404)
|
||||
terms = request.args.get("search")
|
||||
repos = (Repository.query
|
||||
.filter(Repository.owner_id == user.id))
|
||||
if current_user and current_user.id != user.id:
|
||||
repos = (repos
|
||||
.outerjoin(Access,
|
||||
Access.repo_id == Repository.id)
|
||||
.filter(or_(
|
||||
Access.user_id == current_user.id,
|
||||
Repository.visibility == RepoVisibility.PUBLIC,
|
||||
)))
|
||||
elif not current_user:
|
||||
repos = repos.filter(Repository.visibility == RepoVisibility.PUBLIC)
|
||||
|
||||
search_error = None
|
||||
try:
|
||||
repos = search_by(repos, terms,
|
||||
[Repository.name, Repository.description])
|
||||
except ValueError as ex:
|
||||
search_error = str(ex)
|
||||
|
||||
repos = repos.order_by(Repository.updated.desc())
|
||||
repos, pagination = paginate_query(repos)
|
||||
|
||||
return render_template("user.html",
|
||||
user=user, repos=repos,
|
||||
search=terms, search_error=search_error, **pagination)
|
|
@ -19,7 +19,7 @@ from jinja2.utils import url_quote, escape
|
|||
from pygments import highlight
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import guess_lexer, guess_lexer_for_filename, TextLexer
|
||||
from scmsrht.access import get_repo, get_repo_or_redir
|
||||
from gitsrht.access import get_repo, get_repo_or_redir
|
||||
from scmsrht.formatting import get_formatted_readme, get_highlighted_file
|
||||
from scmsrht.urls import get_clone_urls
|
||||
from srht.config import cfg, get_origin
|
||||
|
|
138
gitsrht/repos.py
138
gitsrht/repos.py
|
@ -6,7 +6,6 @@ import shutil
|
|||
import subprocess
|
||||
from gitsrht.types import Artifact, Repository, Redirect
|
||||
from minio import Minio
|
||||
from scmsrht.repos.repository import RepoVisibility
|
||||
from srht.config import cfg
|
||||
from srht.database import db
|
||||
from srht.graphql import exec_gql, GraphQLError
|
||||
|
@ -78,85 +77,82 @@ def upload_artifact(valid, repo, commit, f, filename):
|
|||
db.session.add(artifact)
|
||||
return artifact
|
||||
|
||||
# TODO: Remove repo API wrapper class
|
||||
def get_repo_path(owner, repo_name):
|
||||
return os.path.join(repos_path, "~" + owner.username, repo_name)
|
||||
|
||||
class GitRepoApi():
|
||||
def get_repo_path(self, owner, repo_name):
|
||||
return os.path.join(repos_path, "~" + owner.username, repo_name)
|
||||
def create_repo(valid, user=None):
|
||||
repo_name = valid.require("name", friendly_name="Name")
|
||||
description = valid.optional("description")
|
||||
visibility = valid.optional("visibility")
|
||||
if not valid.ok:
|
||||
return None
|
||||
|
||||
def create_repo(self, valid, user=None):
|
||||
repo_name = valid.require("name", friendly_name="Name")
|
||||
description = valid.optional("description")
|
||||
visibility = valid.optional("visibility")
|
||||
if not valid.ok:
|
||||
return None
|
||||
# Convert the visibility to uppercase. This is needed for the REST API
|
||||
# TODO: Remove this when the REST API is phased out
|
||||
if visibility is not None:
|
||||
visibility = visibility.upper()
|
||||
|
||||
# Convert the visibility to uppercase. This is needed for the REST API
|
||||
# TODO: Remove this when the REST API is phased out
|
||||
if visibility is not None:
|
||||
visibility = visibility.upper()
|
||||
|
||||
resp = exec_gql("git.sr.ht", """
|
||||
mutation CreateRepository(
|
||||
$name: String!,
|
||||
$visibility: Visibility = PUBLIC,
|
||||
$description: String) {
|
||||
createRepository(
|
||||
name: $name,
|
||||
visibility: $visibility,
|
||||
description: $description) {
|
||||
id
|
||||
created
|
||||
updated
|
||||
name
|
||||
owner {
|
||||
canonicalName
|
||||
... on User {
|
||||
name: username
|
||||
}
|
||||
resp = exec_gql("git.sr.ht", """
|
||||
mutation CreateRepository(
|
||||
$name: String!,
|
||||
$visibility: Visibility = PUBLIC,
|
||||
$description: String) {
|
||||
createRepository(
|
||||
name: $name,
|
||||
visibility: $visibility,
|
||||
description: $description) {
|
||||
id
|
||||
created
|
||||
updated
|
||||
name
|
||||
owner {
|
||||
canonicalName
|
||||
... on User {
|
||||
name: username
|
||||
}
|
||||
description
|
||||
visibility
|
||||
}
|
||||
description
|
||||
visibility
|
||||
}
|
||||
""", valid=valid, user=user, name=repo_name,
|
||||
description=description, visibility=visibility)
|
||||
}
|
||||
""", valid=valid, user=user, name=repo_name,
|
||||
description=description, visibility=visibility)
|
||||
|
||||
if not valid.ok:
|
||||
return None
|
||||
return resp["createRepository"]
|
||||
if not valid.ok:
|
||||
return None
|
||||
return resp["createRepository"]
|
||||
|
||||
def clone_repo(self, valid):
|
||||
cloneUrl = valid.require("cloneUrl", friendly_name="Clone URL")
|
||||
name = valid.require("name", friendly_name="Name")
|
||||
description = valid.optional("description")
|
||||
visibility = valid.optional("visibility")
|
||||
if not valid.ok:
|
||||
return None
|
||||
def clone_repo(valid):
|
||||
cloneUrl = valid.require("cloneUrl", friendly_name="Clone URL")
|
||||
name = valid.require("name", friendly_name="Name")
|
||||
description = valid.optional("description")
|
||||
visibility = valid.optional("visibility")
|
||||
if not valid.ok:
|
||||
return None
|
||||
|
||||
resp = exec_gql("git.sr.ht", """
|
||||
mutation CreateRepository(
|
||||
$name: String!,
|
||||
$visibility: Visibility = UNLISTED,
|
||||
$description: String,
|
||||
$cloneUrl: String) {
|
||||
createRepository(name: $name,
|
||||
visibility: $visibility,
|
||||
description: $description,
|
||||
cloneUrl: $cloneUrl) {
|
||||
name
|
||||
}
|
||||
resp = exec_gql("git.sr.ht", """
|
||||
mutation CreateRepository(
|
||||
$name: String!,
|
||||
$visibility: Visibility = UNLISTED,
|
||||
$description: String,
|
||||
$cloneUrl: String) {
|
||||
createRepository(name: $name,
|
||||
visibility: $visibility,
|
||||
description: $description,
|
||||
cloneUrl: $cloneUrl) {
|
||||
name
|
||||
}
|
||||
""", valid=valid, name=name, visibility=visibility,
|
||||
description=description, cloneUrl=cloneUrl)
|
||||
}
|
||||
""", valid=valid, name=name, visibility=visibility,
|
||||
description=description, cloneUrl=cloneUrl)
|
||||
|
||||
if not valid.ok:
|
||||
return None
|
||||
return resp["createRepository"]
|
||||
if not valid.ok:
|
||||
return None
|
||||
return resp["createRepository"]
|
||||
|
||||
def delete_repo(self, repo, user=None):
|
||||
exec_gql("git.sr.ht", """
|
||||
mutation DeleteRepository($id: Int!) {
|
||||
deleteRepository(id: $id) { id }
|
||||
}
|
||||
""", user=user, id=repo.id)
|
||||
def delete_repo(repo, user=None):
|
||||
exec_gql("git.sr.ht", """
|
||||
mutation DeleteRepository($id: Int!) {
|
||||
deleteRepository(id: $id) { id }
|
||||
}
|
||||
""", user=user, id=repo.id)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block body %}
|
||||
{% block content %}{% endblock %}
|
||||
{% endblock %}
|
|
@ -30,9 +30,9 @@
|
|||
owner=current_user.canonical_name,
|
||||
repo=repo.name)}}"
|
||||
>~{{current_user.username}}/{{repo.name}}</a>
|
||||
{% if repo.visibility.value != 'public' %}
|
||||
{% if repo.visibility.value != 'PUBLIC' %}
|
||||
<small class="pull-right">
|
||||
{{ repo.visibility.value }}
|
||||
{{ repo.visibility.value.lower() }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</h4>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% block head %}
|
||||
{% block repohead %}
|
||||
{% endblock %}
|
||||
{% if repo.visibility.value =='unlisted' %}
|
||||
{% if repo.visibility.value == 'UNLISTED' %}
|
||||
<meta name="robots" content="noindex">
|
||||
{% endif %}
|
||||
{# VCS meta tags #}
|
||||
|
@ -44,18 +44,18 @@
|
|||
>{{owner.canonical_name}}</a>/<wbr>{{repo.name}}
|
||||
</h2>
|
||||
<ul class="nav nav-tabs">
|
||||
{% if repo.visibility.value != "public" %}
|
||||
{% if repo.visibility.value != "PUBLIC" %}
|
||||
<li
|
||||
class="nav-item nav-text vis-{{repo.visibility.value.lower()}}"
|
||||
{% if repo.visibility.value == "unlisted" %}
|
||||
{% if repo.visibility.value == "UNLISTED" %}
|
||||
title="This repository is only visible to those who know the URL."
|
||||
{% elif repo.visibility.value == "private" %}
|
||||
{% elif repo.visibility.value == "PRIVATE" %}
|
||||
title="This repository is only visible to those who were invited to view it."
|
||||
{% endif %}
|
||||
>
|
||||
{% if repo.visibility.value == "unlisted" %}
|
||||
{% if repo.visibility.value == "UNLISTED" %}
|
||||
Unlisted
|
||||
{% elif repo.visibility.value == "private" %}
|
||||
{% elif repo.visibility.value == "PRIVATE" %}
|
||||
Private
|
||||
{% endif %}
|
||||
</li>
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
type="radio"
|
||||
name="visibility"
|
||||
value="PUBLIC"
|
||||
{{ "checked" if repo.visibility.value == "public" else "" }}
|
||||
{{ "checked" if repo.visibility.value == "PUBLIC" else "" }}
|
||||
> Public
|
||||
</label>
|
||||
</div>
|
||||
|
@ -57,7 +57,7 @@
|
|||
type="radio"
|
||||
name="visibility"
|
||||
value="UNLISTED"
|
||||
{{ "checked" if repo.visibility.value == "unlisted" else "" }}
|
||||
{{ "checked" if repo.visibility.value == "UNLISTED" else "" }}
|
||||
> Unlisted
|
||||
</label>
|
||||
</div>
|
||||
|
@ -71,7 +71,7 @@
|
|||
type="radio"
|
||||
name="visibility"
|
||||
value="PRIVATE"
|
||||
{{ "checked" if repo.visibility.value == "private" else "" }}
|
||||
{{ "checked" if repo.visibility.value == "PRIVATE" else "" }}
|
||||
> Private
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
<input type="hidden" name="cloneUrl" value="{{(repo|clone_urls)[0]}}" />
|
||||
<input type="hidden" name="name" value="{{repo.name}}" />
|
||||
<input type="hidden" name="description" value="Clone of {{(repo|clone_urls)[0]}}" />
|
||||
<input type="hidden" name="visibility" value="{% if repo.visibility.value == 'private' %}PRIVATE{% else %}UNLISTED{% endif %}" />
|
||||
<input type="hidden" name="visibility" value="{% if repo.visibility.value == 'PRIVATE' %}PRIVATE{% else %}UNLISTED{% endif %}" />
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
Clone repo to your account {{icon('caret-right')}}
|
||||
</button>
|
||||
|
@ -121,7 +121,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% if current_user == repo.owner and not license
|
||||
and repo.visibility.value == 'public' %}
|
||||
and repo.visibility.value == 'PUBLIC' %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Heads up!</strong> We couldn't find a license file for your
|
||||
repository, which means that it may not be possible for others to use this
|
||||
|
|
|
@ -62,9 +62,9 @@
|
|||
<a href="/~{{user.username}}/{{repo.name}}">
|
||||
~{{user.username}}/{{repo.name}}
|
||||
</a>
|
||||
{% if repo.visibility.value != 'public' %}
|
||||
{% if repo.visibility.value != 'PUBLIC' %}
|
||||
<small class="pull-right">
|
||||
{{ repo.visibility.value }}
|
||||
{{ repo.visibility.value.lower() }}
|
||||
</small>
|
||||
{% endif %}
|
||||
</h4>
|
||||
|
|
|
@ -7,8 +7,7 @@ from sqlalchemy.dialects import postgresql
|
|||
from srht.database import Base
|
||||
from srht.oauth import ExternalUserMixin, ExternalOAuthTokenMixin
|
||||
from gitsrht.git import Repository as GitRepository
|
||||
from scmsrht.repos import BaseAccessMixin, BaseRedirectMixin
|
||||
from scmsrht.repos import RepoVisibility
|
||||
from enum import Enum
|
||||
|
||||
class User(Base, ExternalUserMixin):
|
||||
pass
|
||||
|
@ -16,11 +15,86 @@ class User(Base, ExternalUserMixin):
|
|||
class OAuthToken(Base, ExternalOAuthTokenMixin):
|
||||
pass
|
||||
|
||||
class Access(Base, BaseAccessMixin):
|
||||
pass
|
||||
class AccessMode(Enum):
|
||||
ro = 'ro'
|
||||
rw = 'rw'
|
||||
|
||||
class Redirect(Base, BaseRedirectMixin):
|
||||
pass
|
||||
class Access(Base):
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return "access"
|
||||
|
||||
@declared_attr
|
||||
def __table_args__(cls):
|
||||
return (
|
||||
sa.UniqueConstraint('user_id', 'repo_id',
|
||||
name="uq_access_user_id_repo_id"),
|
||||
)
|
||||
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
updated = sa.Column(sa.DateTime, nullable=False)
|
||||
mode = sa.Column(sau.ChoiceType(AccessMode, impl=sa.String()),
|
||||
nullable=False, default=AccessMode.ro)
|
||||
|
||||
@declared_attr
|
||||
def user_id(cls):
|
||||
return sa.Column(sa.Integer, sa.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def user(cls):
|
||||
return sa.orm.relationship('User', backref='access_grants')
|
||||
|
||||
@declared_attr
|
||||
def repo_id(cls):
|
||||
return sa.Column(sa.Integer,
|
||||
sa.ForeignKey('repository.id', ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def repo(cls):
|
||||
return sa.orm.relationship('Repository',
|
||||
backref=sa.orm.backref('access_grants', cascade="all, delete"))
|
||||
|
||||
def __repr__(self):
|
||||
return '<Access {} {}->{}:{}>'.format(
|
||||
self.id, self.user_id, self.repo_id, self.mode)
|
||||
|
||||
class Redirect(Base):
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return "redirect"
|
||||
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
created = sa.Column(sa.DateTime, nullable=False)
|
||||
name = sa.Column(sa.Unicode(256), nullable=False)
|
||||
path = sa.Column(sa.Unicode(1024))
|
||||
|
||||
@declared_attr
|
||||
def owner_id(cls):
|
||||
return sa.Column(sa.Integer, sa.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def owner(cls):
|
||||
return sa.orm.relationship('User')
|
||||
|
||||
@declared_attr
|
||||
def new_repo_id(cls):
|
||||
return sa.Column(
|
||||
sa.Integer,
|
||||
sa.ForeignKey('repository.id', ondelete="CASCADE"),
|
||||
nullable=False)
|
||||
|
||||
@declared_attr
|
||||
def new_repo(cls):
|
||||
return sa.orm.relationship('Repository')
|
||||
|
||||
class RepoVisibility(Enum):
|
||||
# NOTE: SQLAlchemy uses the enum member names, not the values.
|
||||
# The values are used by templates. Therfore, we capitalize both.
|
||||
PUBLIC = 'PUBLIC'
|
||||
PRIVATE = 'PRIVATE'
|
||||
UNLISTED = 'UNLISTED'
|
||||
|
||||
class Repository(Base):
|
||||
@declared_attr
|
||||
|
@ -41,10 +115,7 @@ class Repository(Base):
|
|||
name = sa.Column(sa.Unicode(256), nullable=False)
|
||||
description = sa.Column(sa.Unicode(1024))
|
||||
path = sa.Column(sa.Unicode(1024))
|
||||
visibility = sa.Column(
|
||||
sau.ChoiceType(RepoVisibility, impl=sa.String()),
|
||||
nullable=False,
|
||||
default=RepoVisibility.public)
|
||||
visibility = sa.Column(postgresql.ENUM(RepoVisibility), nullable=False)
|
||||
readme = sa.Column(sa.Unicode)
|
||||
clone_status = sa.Column(postgresql.ENUM(
|
||||
'NONE', 'IN_PROGRESS', 'COMPLETE', 'ERROR'), nullable=False)
|
||||
|
@ -58,6 +129,8 @@ class Repository(Base):
|
|||
def owner(cls):
|
||||
return sa.orm.relationship('User', backref=sa.orm.backref('repos'))
|
||||
|
||||
# This is only used by the REST API
|
||||
# TODO: Remove this when the REST API is phased out
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
|
@ -66,7 +139,7 @@ class Repository(Base):
|
|||
"name": self.name,
|
||||
"owner": self.owner.to_dict(short=True),
|
||||
"description": self.description,
|
||||
"visibility": self.visibility,
|
||||
"visibility": self.visibility.value.lower(),
|
||||
}
|
||||
|
||||
@property
|
||||
|
|
Loading…
Reference in New Issue