Add initial version of nolabs website
diff --git a/app/models.py b/app/models.py
new file mode 100644
index 0000000..532b120
--- /dev/null
+++ b/app/models.py
@@ -0,0 +1,258 @@
+import base64
+from datetime import datetime, timedelta
+import os
+import enum
+
+from werkzeug.security import generate_password_hash, check_password_hash
+from flask_login import UserMixin
+
+from app import db, login
+
+class UserStatusTypes(enum.Enum):
+ pending = 0
+ active = 1
+ disabled = 2
+
+class BookingStatusTypes(enum.Enum):
+ pending = 0
+ deploying = 1
+ active = 2
+ failed = 3
+ deleted = 4
+
+@login.user_loader
+def load_user(id):
+ return User.query.get(int(id))
+
+class PaginatedAPIMixin(object):
+ @staticmethod
+ def to_collection_dict(query, page, per_page, endpoint, **kwargs):
+ resources = query.paginate(page, per_page, False)
+ data = {
+ 'items': [item.to_dict() for item in resources.items],
+ '_meta': {
+ 'page': page,
+ 'per_page': per_page,
+ 'total_pages': resources.pages,
+ 'total_items': resources.total
+ },
+ }
+ return data
+
+class UserRole(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ role_name = db.Column(db.String(32), index=True, unique=True, default='regular')
+ role_description = db.Column(db.String(128), default='Regular user with access to only base stacks')
+
+ users = db.relationship('User', backref='user_role', lazy='dynamic')
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tRole name: {}\n'
+ '\tRole description: {}\n'
+ .format(self.id, self.role_name, self.role_description))
+
+class UserOrganization(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ org_name = db.Column(db.String(32), index=True, unique=True, default='nordix')
+ org_description = db.Column(db.String(128), default='Nordix Community users')
+
+ users = db.relationship('User', backref='user_organization', lazy='dynamic')
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tOrganization name: {}\n'
+ '\tOrganization description: {}\n'
+ .format(self.id, self.org_name, self.org_description))
+
+class User(PaginatedAPIMixin, UserMixin, db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ role_id = db.Column(db.Integer, db.ForeignKey('user_role.id'))
+ org_id = db.Column(db.Integer, db.ForeignKey('user_organization.id'))
+ username = db.Column(db.String(64), index=True, unique=True)
+ email = db.Column(db.String(128), index=True, unique=True)
+ fullname = db.Column(db.String(128), index=True, unique=True)
+ password_hash = db.Column(db.String(128))
+ ssh_public_key = db.Column(db.String(1024))
+ registered_on = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+ confirmed = db.Column(db.Integer, default=0)
+ last_logged_in = db.Column(db.DateTime, index=True)
+ confirmed_on = db.Column(db.DateTime, index=True)
+ updated_on = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+ token = db.Column(db.String(32), index=True, unique=True)
+ token_expiration = db.Column(db.DateTime)
+
+ bookings = db.relationship('Booking', backref='user', lazy='dynamic')
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tUsername: {}\n'
+ '\tFullname: {}\n'
+ '\tEmail: {}\n'
+ '\tStatus: {}\n'
+ '\tPassword Hash: {}\n'
+ '\tSSH Public Key: {}\n'
+ '\tRegistered On: {}\n'
+ '\tConfirmed: {}\n'
+ '\tConfirmed On: {}\n'
+ '\tUpdated On: {}\n'
+ '\tRole: {}\n'
+ .format(self.id, self.username, self.role, self.fullname, self.email, self.status,
+ self.password_hash, self.ssh_public_key, self.registered_on, self.confirmed,
+ self.confirmed_on, self.updated_on))
+
+ def set_password(self, password):
+ self.password_hash = generate_password_hash(password)
+
+ def check_password(self, password):
+ return check_password_hash(self.password_hash, password)
+
+ def to_dict(self):
+ data = {
+ 'id': self.id,
+ 'role_id': self.role_id,
+ 'role_name': UserRole.query.filter_by(id=self.role_id).first().role_name,
+ 'org_id': self.org_id,
+ 'org_name': UserOrganization.query.filter_by(id=self.org_id).first().org_name,
+ 'username': self.username,
+ 'email': self.email,
+ 'ssh_public_key': self.ssh_public_key,
+ 'registered_on': self.registered_on.isoformat(),
+ 'confirmed': self.confirmed
+ }
+ return data
+
+ def from_dict(self, data, new_user=False):
+ for field in ['confirmed', 'org_id', 'role_id']:
+ if field in data:
+ setattr(self, field, data[field])
+ if new_user and 'password' in data:
+ self.set_password(data['password'])
+
+ def get_token(self, expires_in=3600):
+ now = datetime.utcnow()
+ if self.token and self.token_expiration > now + timedelta(seconds=60):
+ return self.token
+ self.token = base64.b64encode(os.urandom(24)).decode('utf-8')
+ self.token_expiration = now + timedelta(seconds=expires_in)
+ db.session.add(self)
+ return self.token
+
+ def revoke_token(self):
+ self.token_expiration = datetime.utcnow() - timedelta(seconds=1)
+
+ @staticmethod
+ def check_token(token):
+ user = User.query.filter_by(token=token).first()
+ if user is None or user.token_expiration < datetime.utcnow():
+ return None
+ return user
+
+ @staticmethod
+ def check_none_regular_user(token):
+ user = User.query.filter_by(token=token).first()
+ current_user_role_id = user.role_id
+ regular_user_role_id = UserRole.query.filter_by(role_name='regular').first().id
+ if current_user_role_id == regular_user_role_id:
+ return None
+ return user
+
+class Booking(PaginatedAPIMixin, db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
+ status_id = db.Column(db.Integer, db.ForeignKey('booking_status.id'))
+ booking_length = db.Column(db.Integer)
+ created_on = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+ updated_on = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+ expires_on = db.Column(db.DateTime, index=True, default=datetime.utcnow)
+ heat_stack_name = db.Column(db.String(64))
+ floating_ip = db.Column(db.String(64))
+ engine_version = db.Column(db.String(64))
+ stack = db.Column(db.String(64))
+ stack_version = db.Column(db.String(64))
+ scenario = db.Column(db.String(64))
+ scenario_version = db.Column(db.String(64))
+ scenario_deploy_log_url = db.Column(db.String(256))
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tUser ID: {}\n'
+ '\tStatus: {}\n'
+ '\tCreated: {}\n'
+ '\tUpdated: {}\n'
+ '\tExpires: {}\n'
+ '\tHeat Stack Name: {}\n'
+ '\tFloating IP: {}\n'
+ '\tStack Name: {}\n'
+ '\tStack Version: {}\n'
+ '\tScenario: {}\n'
+ '\tScenario Version: {}\n'
+ .format(self.id, self.user_id, self.status, self.created,
+ self.updated, self.expires, self.heat_stack_name, self.floating_ip,
+ self.engine_version, self.stack, self.stack_version, self.scenario, self.scenario_version))
+
+ def to_dict(self):
+ data = {
+ 'id': self.id,
+ 'user_id': self.user_id,
+ 'booking_length': self.booking_length,
+ 'heat_stack_name': self.heat_stack_name,
+ 'scenario_deploy_log_url': self.scenario_deploy_log_url,
+ 'engine_version': self.engine_version,
+ 'scenario_version': self.scenario_version,
+ 'floating_ip': self.floating_ip,
+ 'created_on': self.created_on.isoformat(),
+ 'stack': self.stack,
+ 'status_text': BookingStatus.query.filter_by(id=self.status_id).first().status_text,
+ 'status_id': self.status_id,
+ 'scenario': self.scenario
+ }
+ return data
+
+ def from_dict(self, data, new_booking=False):
+ for field in ['status_text', 'floating_ip', 'heat_stack_name', \
+ 'scenario_deploy_log_url', 'engine_version', \
+ 'scenario_version']:
+ if field in data:
+ setattr(self, field, data[field])
+
+class BookingStatus(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ status_text = db.Column(db.String(64), default='new')
+ status_description = db.Column(db.String(128), default='New booking request')
+
+ bookings = db.relationship('Booking', backref='booking_status', lazy='dynamic')
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tStatus: {}\n'
+ .format(self.id, self.status))
+
+class Stack(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ stack_name = db.Column(db.String(64), index=True, unique=True)
+ stack_description = db.Column(db.String(128), unique=True)
+ is_sandbox_enabled = db.Column(db.Boolean(8), default=False)
+
+ scenarios = db.relationship('Scenario', backref='stack', lazy='dynamic')
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tStack name: {}\n'
+ '\tStack description: {}\n'
+ '\tIs sandbox enabled: {}\n'
+ .format(self.id, self.stack_name, self.stack_description, self.is_sandbox_enabled))
+
+class Scenario(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ stack_id = db.Column(db.Integer, db.ForeignKey('stack.id'))
+ scenario_name = db.Column(db.String(64), index=True, unique=True)
+ scenario_description = db.Column(db.String(128), unique=True)
+ is_sandbox_enabled = db.Column(db.Boolean(8), default=False)
+
+ def __repr__(self):
+ return ('\tID: {}\n'
+ '\tScenario name: {}\n'
+ '\tScenario description: {}\n'
+ '\tIs sandbox enabled: {}\n'
+ .format(self.id, self.scenario_name, self.scenario_description, self.is_sandbox_enabled))