initial work on adding models for starring repos.
I'm sick of using `git stash`.
This commit is contained in:
parent
f3259c862b
commit
eb956e5b7d
5 changed files with 197 additions and 16 deletions
|
@ -4,7 +4,9 @@ import uuid
|
||||||
|
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from peewee import *
|
from peewee import (Proxy, MySQLDatabase, SqliteDatabase, PostgresqlDatabase, fn, CharField,
|
||||||
|
BooleanField, IntegerField, DateTimeField, ForeignKeyField, TextField,
|
||||||
|
BigIntegerField)
|
||||||
from data.read_slave import ReadSlaveModel
|
from data.read_slave import ReadSlaveModel
|
||||||
from sqlalchemy.engine.url import make_url
|
from sqlalchemy.engine.url import make_url
|
||||||
from util.names import urn_generator
|
from util.names import urn_generator
|
||||||
|
@ -265,6 +267,20 @@ class Repository(BaseModel):
|
||||||
super(Repository, self).delete_instance(recursive=False, delete_nullable=False)
|
super(Repository, self).delete_instance(recursive=False, delete_nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Star(BaseModel):
|
||||||
|
user = ForeignKeyField(User, index=True, related_name="stars")
|
||||||
|
repository = ForeignKeyField(Repository, index=True, related_name="stargazers")
|
||||||
|
created = DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
read_slaves = (read_slave,)
|
||||||
|
indexes = (
|
||||||
|
# create a unique index on user and repository
|
||||||
|
(('user', 'repository'), True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Role(BaseModel):
|
class Role(BaseModel):
|
||||||
name = CharField(index=True, unique=True)
|
name = CharField(index=True, unique=True)
|
||||||
|
|
||||||
|
@ -550,4 +566,4 @@ all_models = [User, Repository, Image, AccessToken, Role, RepositoryPermission,
|
||||||
Notification, ImageStorageLocation, ImageStoragePlacement,
|
Notification, ImageStorageLocation, ImageStoragePlacement,
|
||||||
ExternalNotificationEvent, ExternalNotificationMethod, RepositoryNotification,
|
ExternalNotificationEvent, ExternalNotificationMethod, RepositoryNotification,
|
||||||
RepositoryAuthorizedEmail, ImageStorageTransformation, DerivedImageStorage,
|
RepositoryAuthorizedEmail, ImageStorageTransformation, DerivedImageStorage,
|
||||||
TeamMemberInvite]
|
TeamMemberInvite, Star]
|
||||||
|
|
42
data/migrations/versions/3b668be15dc0_add_stars.py
Normal file
42
data/migrations/versions/3b668be15dc0_add_stars.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""add stars
|
||||||
|
|
||||||
|
Revision ID: 3b668be15dc0
|
||||||
|
Revises: 204abf14783d
|
||||||
|
Create Date: 2014-11-14 14:11:18.687340
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '3b668be15dc0'
|
||||||
|
down_revision = '204abf14783d'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
def upgrade(tables):
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('star',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('repository_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('created', sa.DateTime(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['repository_id'], ['repository.id'], name=op.f('fk_star_repository_id_repository')),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], name=op.f('fk_star_user_id_user')),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_star'))
|
||||||
|
)
|
||||||
|
op.create_index('star_repository_id', 'star', ['repository_id'], unique=False)
|
||||||
|
op.create_index('star_user_id', 'star', ['user_id'], unique=False)
|
||||||
|
op.create_index('star_user_id_repository_id', 'star', ['user_id', 'repository_id'], unique=True)
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(tables):
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint('fk_star_repository_id_repository', 'star', type_='foreignkey')
|
||||||
|
op.drop_constraint('fk_star_user_id_user', 'star', type_='foreignkey')
|
||||||
|
op.drop_index('star_user_id_repository_id', table_name='star')
|
||||||
|
op.drop_index('star_user_id', table_name='star')
|
||||||
|
op.drop_index('star_repository_id', table_name='star')
|
||||||
|
op.drop_table('star')
|
||||||
|
### end Alembic commands ###
|
|
@ -2385,3 +2385,54 @@ def archivable_buildlogs_query():
|
||||||
.where((RepositoryBuild.phase == BUILD_PHASE.COMPLETE) |
|
.where((RepositoryBuild.phase == BUILD_PHASE.COMPLETE) |
|
||||||
(RepositoryBuild.phase == BUILD_PHASE.ERROR) |
|
(RepositoryBuild.phase == BUILD_PHASE.ERROR) |
|
||||||
(RepositoryBuild.started < presumed_dead_date), RepositoryBuild.logs_archived == False))
|
(RepositoryBuild.started < presumed_dead_date), RepositoryBuild.logs_archived == False))
|
||||||
|
|
||||||
|
|
||||||
|
def star_repository(user, repository):
|
||||||
|
""" Stars a repository. """
|
||||||
|
star = Star.create(user=user.id, repository=repository.id)
|
||||||
|
star.save()
|
||||||
|
|
||||||
|
|
||||||
|
def unstar_repository(user, repository):
|
||||||
|
""" Unstars a repository. """
|
||||||
|
try:
|
||||||
|
star = (Star
|
||||||
|
.select()
|
||||||
|
.join(Repository)
|
||||||
|
.switch(Star)
|
||||||
|
.join(User)
|
||||||
|
.where(Repository.id == repository.id, User.id == user.id)
|
||||||
|
.get())
|
||||||
|
except Star.DoesNotExist:
|
||||||
|
raise DataModelException('Star not found.')
|
||||||
|
|
||||||
|
star.delete_instance()
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_starred_repositories(user, limit=None, page=None):
|
||||||
|
""" Retrieves all of the repositories a user has starred. """
|
||||||
|
query = (Repository
|
||||||
|
.select()
|
||||||
|
.join(Star)
|
||||||
|
.join(User)
|
||||||
|
.where(User.id == user.id)
|
||||||
|
.order_by(Star.created))
|
||||||
|
|
||||||
|
if page and limit:
|
||||||
|
query = query.paginate(page, limit)
|
||||||
|
elif limit:
|
||||||
|
query = query.limit(limit)
|
||||||
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def repository_is_starred(user, repository):
|
||||||
|
""" Determines whether a user has starred a repository or not. """
|
||||||
|
try:
|
||||||
|
(Star
|
||||||
|
.select()
|
||||||
|
.where(Star.repository == repository.id, Star.user == user.id)
|
||||||
|
.get())
|
||||||
|
return True
|
||||||
|
except Star.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
|
@ -102,21 +102,13 @@ class RepositoryList(ApiResource):
|
||||||
@query_param('limit', 'Limit on the number of results (int)', type=int)
|
@query_param('limit', 'Limit on the number of results (int)', type=int)
|
||||||
@query_param('namespace', 'Namespace to use when querying for org repositories.', type=str)
|
@query_param('namespace', 'Namespace to use when querying for org repositories.', type=str)
|
||||||
@query_param('public', 'Whether to include public repositories.', type=truthy_bool, default=True)
|
@query_param('public', 'Whether to include public repositories.', type=truthy_bool, default=True)
|
||||||
@query_param('private', 'Whether to inlcude private repositories.', type=truthy_bool,
|
@query_param('private', 'Whether to include private repositories.', type=truthy_bool,
|
||||||
default=True)
|
default=True)
|
||||||
@query_param('sort', 'Whether to sort the results.', type=truthy_bool, default=False)
|
@query_param('sort', 'Whether to sort the results.', type=truthy_bool, default=False)
|
||||||
@query_param('count', 'Whether to include a count of the total number of results available.',
|
@query_param('count', 'Whether to include a count of the total number of results available.',
|
||||||
type=truthy_bool, default=False)
|
type=truthy_bool, default=False)
|
||||||
def get(self, args):
|
def get(self, args):
|
||||||
"""Fetch the list of repositories under a variety of situations."""
|
"""Fetch the list of repositories under a variety of situations."""
|
||||||
def repo_view(repo_obj):
|
|
||||||
return {
|
|
||||||
'namespace': repo_obj.namespace_user.username,
|
|
||||||
'name': repo_obj.name,
|
|
||||||
'description': repo_obj.description,
|
|
||||||
'is_public': repo_obj.visibility.name == 'public',
|
|
||||||
}
|
|
||||||
|
|
||||||
username = None
|
username = None
|
||||||
if get_authenticated_user() and args['private']:
|
if get_authenticated_user() and args['private']:
|
||||||
username = get_authenticated_user().username
|
username = get_authenticated_user().username
|
||||||
|
@ -141,6 +133,15 @@ class RepositoryList(ApiResource):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def repo_view(repo_obj):
|
||||||
|
return {
|
||||||
|
'namespace': repo_obj.namespace_user.username,
|
||||||
|
'name': repo_obj.name,
|
||||||
|
'description': repo_obj.description,
|
||||||
|
'is_public': repo_obj.visibility.name == 'public',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<repopath:repository>')
|
@resource('/v1/repository/<repopath:repository>')
|
||||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||||
class Repository(RepositoryParamResource):
|
class Repository(RepositoryParamResource):
|
||||||
|
@ -271,6 +272,4 @@ class RepositoryVisibility(RepositoryParamResource):
|
||||||
log_action('change_repo_visibility', namespace,
|
log_action('change_repo_visibility', namespace,
|
||||||
{'repo': repository, 'visibility': values['visibility']},
|
{'repo': repository, 'visibility': values['visibility']},
|
||||||
repo=repo)
|
repo=repo)
|
||||||
return {
|
return {'success': True}
|
||||||
'success': True
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,10 +9,12 @@ from app import app, billing as stripe, authentication, avatar
|
||||||
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
|
||||||
log_action, internal_only, NotFound, require_user_admin, parse_args,
|
log_action, internal_only, NotFound, require_user_admin, parse_args,
|
||||||
query_param, InvalidToken, require_scope, format_date, hide_if, show_if,
|
query_param, InvalidToken, require_scope, format_date, hide_if, show_if,
|
||||||
license_error, require_fresh_login, path_param, define_json_response)
|
license_error, require_fresh_login, path_param, define_json_response,
|
||||||
|
RepositoryParamResource)
|
||||||
from endpoints.api.subscribe import subscribe
|
from endpoints.api.subscribe import subscribe
|
||||||
from endpoints.common import common_login
|
from endpoints.common import common_login
|
||||||
from endpoints.api.team import try_accept_invite
|
from endpoints.api.team import try_accept_invite
|
||||||
|
from endpoints.api.repository import repo_view
|
||||||
|
|
||||||
from data import model
|
from data import model
|
||||||
from data.billing import get_plan
|
from data.billing import get_plan
|
||||||
|
@ -247,7 +249,6 @@ class User(ApiResource):
|
||||||
raise request_error(message='Username is already in use')
|
raise request_error(message='Username is already in use')
|
||||||
|
|
||||||
model.change_username(user, new_username)
|
model.change_username(user, new_username)
|
||||||
|
|
||||||
except model.InvalidPasswordException, ex:
|
except model.InvalidPasswordException, ex:
|
||||||
raise request_error(exception=ex)
|
raise request_error(exception=ex)
|
||||||
|
|
||||||
|
@ -663,3 +664,75 @@ class UserAuthorization(ApiResource):
|
||||||
|
|
||||||
access_token.delete_instance(recursive=True, delete_nullable=True)
|
access_token.delete_instance(recursive=True, delete_nullable=True)
|
||||||
return 'Deleted', 204
|
return 'Deleted', 204
|
||||||
|
|
||||||
|
@resource('/v1/user/starred')
|
||||||
|
class StarredRepositoryList(ApiResource):
|
||||||
|
""" Operations for creating and listing starred repositories. """
|
||||||
|
schemas = {
|
||||||
|
'NewStarredRepository': {
|
||||||
|
'id': 'NewStarredRepository',
|
||||||
|
'type': 'object',
|
||||||
|
'required': [
|
||||||
|
'namespace',
|
||||||
|
'repository',
|
||||||
|
],
|
||||||
|
'properties': {
|
||||||
|
'namespace': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'Namespace in which the repository belongs',
|
||||||
|
},
|
||||||
|
'repository': {
|
||||||
|
'type': 'string',
|
||||||
|
'description': 'Repository name'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@require_scope(scopes.READ_REPO)
|
||||||
|
@nickname('listStarredRepos')
|
||||||
|
@parse_args
|
||||||
|
@query_param('page', 'Offset page number. (int)', type=int)
|
||||||
|
@query_param('limit', 'Limit on the number of results (int)', type=int)
|
||||||
|
def get(self):
|
||||||
|
""" List all starred repositories. """
|
||||||
|
page = args['page']
|
||||||
|
limit = args['limit']
|
||||||
|
starred_repos = list(get_user_starred_repositories(get_authenticated_user(), page=page, limit=limit))
|
||||||
|
return {'repositories': [repo_view(repo) for repo in starred_repos]}
|
||||||
|
|
||||||
|
@require_scope(scopes.READ_REPO)
|
||||||
|
@nickname('createStar')
|
||||||
|
@validate_json_request('NewStarredRepository')
|
||||||
|
def post(self):
|
||||||
|
""" Star a repository. """
|
||||||
|
user = get_authenticated_user()
|
||||||
|
req = request.get_json()
|
||||||
|
namespace = req['namespace']
|
||||||
|
repository = req['repository']
|
||||||
|
repo = model.get_repository(namespace, repository)
|
||||||
|
if repo:
|
||||||
|
model.star_repository(user, repo)
|
||||||
|
log_action('star_repository', user.username, namespace,
|
||||||
|
{'repo': repository, 'namespace': namespace})
|
||||||
|
return {
|
||||||
|
'namespace': namespace,
|
||||||
|
'repository': repository,
|
||||||
|
}, 201
|
||||||
|
|
||||||
|
raise NotFound()
|
||||||
|
|
||||||
|
@resource('/v1/user/starred/<repopath:repository>')
|
||||||
|
class StarredRepository(RepositoryParamResource):
|
||||||
|
""" Operations for managing a specific starred repository. """
|
||||||
|
@nickname('deleteStar')
|
||||||
|
def delete(self, namespace, repository):
|
||||||
|
user = get_authenticated_user()
|
||||||
|
repo = model.get_repository(namespace, repository)
|
||||||
|
if repo:
|
||||||
|
model.unstar_repository(user, repo)
|
||||||
|
log_action('unstar_repository', user.username, namespace,
|
||||||
|
{'repo': repository, 'namespace': namespace})
|
||||||
|
return 'Deleted', 204
|
||||||
|
|
||||||
|
raise NotFound()
|
||||||
|
|
Reference in a new issue