import string import logging from random import SystemRandom from datetime import datetime from peewee import * from peewee import create_model_tables from app import app logger = logging.getLogger(__name__) db = app.config['DB_DRIVER'](app.config['DB_NAME'], **app.config['DB_CONNECTION_ARGS']) def connect_db(): logger.debug('Connectin to database.') db.connect() def close_db(exc): if not db.is_closed(): logger.debug('Disconnecting from database.') db.close() app.before_request(connect_db) app.teardown_request(close_db) class BaseModel(Model): class Meta: database = db class User(BaseModel): username = CharField(unique=True, index=True) password_hash = CharField(null=True) email = CharField(unique=True, index=True) verified = BooleanField(default=False) stripe_id = CharField(index=True, null=True) class LoginService(BaseModel): name = CharField(unique=True, index=True) class FederatedLogin(BaseModel): user = ForeignKeyField(User, index=True) service = ForeignKeyField(LoginService, index=True) service_ident = CharField() class Meta: database = db indexes = ( # create a unique index on service and the local service id (('service', 'service_ident'), True), # a user may only have one federated login per service (('service', 'user'), True), ) class Visibility(BaseModel): name = CharField(index=True) class Repository(BaseModel): namespace = CharField() name = CharField() visibility = ForeignKeyField(Visibility) description = TextField(null=True) class Meta: database = db indexes = ( # create a unique index on namespace and name (('namespace', 'name'), True), ) class Role(BaseModel): name = CharField(index=True) class RepositoryPermission(BaseModel): user = ForeignKeyField(User, index=True) repository = ForeignKeyField(Repository, index=True) role = ForeignKeyField(Role) class Meta: database = db indexes = ( (('user', 'repository'), True), ) def random_string_generator(length=16): def random_string(): random = SystemRandom() return ''.join([random.choice(string.ascii_uppercase + string.digits) for x in range(length)]) return random_string class AccessToken(BaseModel): code = CharField(default=random_string_generator(), unique=True, index=True) user = ForeignKeyField(User, null=True) repository = ForeignKeyField(Repository) created = DateTimeField(default=datetime.now) class EmailConfirmation(BaseModel): code = CharField(default=random_string_generator(), unique=True, index=True) user = ForeignKeyField(User) pw_reset = BooleanField(default=False) email_confirm = BooleanField(default=False) created = DateTimeField(default=datetime.now) class Image(BaseModel): # This class is intentionally denormalized. Even though images are supposed # to be globally unique we can't treat them as such for permissions and # security reasons. So rather than Repository <-> Image being many to many # each image now belongs to exactly one repository. docker_image_id = CharField() checksum = CharField(null=True) created = DateTimeField(null=True) comment = TextField(null=True) repository = ForeignKeyField(Repository) # '/' separated list of ancestory ids, e.g. /1/2/6/7/10/ ancestors = CharField(index=True, default='/', max_length=64535) class Meta: database = db indexes = ( # we don't really want duplicates (('repository', 'docker_image_id'), True), ) class RepositoryTag(BaseModel): name = CharField() image = ForeignKeyField(Image) repository = ForeignKeyField(Repository) class Meta: database = db indexes = ( (('repository', 'name'), True), ) def initialize_db(): create_model_tables([User, Repository, Image, AccessToken, Role, RepositoryPermission, Visibility, RepositoryTag, EmailConfirmation, FederatedLogin, LoginService]) Role.create(name='admin') Role.create(name='write') Role.create(name='read') Visibility.create(name='public') Visibility.create(name='private') LoginService.create(name='github')