class Message(db.Model, Base): __tablename__ = 'message' id = db.Column(db.Integer, primary_key=True) from_user_id = db.Column(db.Integer, db.ForeignKey('user.id')) to_user_id = db.Column(db.Integer, db.ForeignKey('user.id')) to_player_id = db.Column(db.Integer, db.ForeignKey('player.id')) sent_at = db.Column(db.DateTime, default=datetime.utcnow) seen_at = db.Column(db.DateTime) notified_at = db.Column(db.DateTime) body = db.Column(db.Text()) body_html = db.Column(db.Text()) user_ip = db.Column(db.String(15)) deleted = db.Column(db.Boolean, default=False) from_user = db.relationship('User', foreign_keys='Message.from_user_id', backref=db.backref('messages_sent')) to_user = db.relationship('User', foreign_keys='Message.to_user_id', backref=db.backref('messages_received')) to_player = db.relationship('Player', foreign_keys='Message.to_player_id', backref=db.backref('messages_received')) def save(self, commit=True): from standardweb.lib import forums self.body_html = forums.convert_bbcode(self.body) for pat, path in forums.emoticon_map: self.body_html = pat.sub(path, self.body_html) return super(Message, self).save(commit) def to_dict(self): result = { 'id': self.id, 'sent_at': self.sent_at.replace(tzinfo=pytz.UTC).isoformat(), 'seen_at': self.seen_at.replace( tzinfo=pytz.UTC).isoformat() if self.seen_at else None, 'from_user': self.from_user.to_dict(), 'body_html': self.body_html } if self.to_user: result['to_user'] = self.to_user.to_dict() return result
class ForumPostVote(db.Model, Base): __tablename__ = 'forum_post_vote' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) post_id = db.Column(db.Integer, db.ForeignKey('forum_post.id')) vote = db.Column(db.Integer(), default=0) user_ip = db.Column(db.String(15)) computed_weight = db.Column(db.Numeric()) created = db.Column(db.DateTime, default=datetime.utcnow) updated = db.Column(db.DateTime, default=None) post = db.relationship('ForumPost', backref=db.backref('votes')) user = db.relationship('User', backref=db.backref('votes'))
class GroupInvite(db.Model, Base): __tablename__ = 'group_invite' group_id = db.Column(db.Integer, db.ForeignKey('group.id'), primary_key=True) invite = db.Column(db.String(30), primary_key=True) group = db.relationship('Group', backref=db.backref('invites'))
class Title(db.Model, Base): __tablename__ = 'title' id = db.Column(db.Integer, primary_key=True) created = db.Column(db.DateTime, default=datetime.utcnow) name = db.Column(db.String(20)) displayname = db.Column(db.String(40)) broadcast = db.Column(db.Boolean, default=False) players = db.relationship('Player', secondary=player_title, backref=db.backref('titles'))
class ForumBan(db.Model, Base): __tablename__ = 'forum_ban' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) ban_start = db.Column(db.DateTime, default=datetime.utcnow) reason = db.Column(db.Text()) by_user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship('User', foreign_keys='ForumBan.user_id', backref=db.backref('forum_ban', uselist=False)) by_user = db.relationship('User', foreign_keys='ForumBan.by_user_id')
class ForumAttachment(db.Model, Base): __tablename__ = 'forum_attachment' id = db.Column(db.Integer, primary_key=True) post_id = db.Column(db.Integer, db.ForeignKey('forum_post.id')) size = db.Column(db.Integer()) content_type = db.Column(db.String(255)) path = db.Column(db.String(255)) name = db.Column(db.Text()) hash = db.Column(db.String(40)) post = db.relationship('ForumPost', backref=db.backref('attachments')) @classmethod def create_attachment(cls, post_id, image, commit=True): try: content_type = image.headers.get('Content-Type') image_content = image.content size = len(image_content) path = str(post_id) attachment = cls(post_id=post_id, size=size, content_type=content_type, path=path, name=image.filename) attachment.save(commit=commit) with open(attachment.file_path, 'w') as f: f.write(image_content) return attachment except: return None def save(self, commit=True): import hashlib self.hash = hashlib.sha1(self.path + app.config['SECRET_KEY']).hexdigest() return super(ForumAttachment, self).save(commit) @property def url(self): return url_for('forum_attachment', hash=self.hash) @property def file_path(self): return os.path.join(app.root_path, 'attachments', self.path)
class ForumPostTracking(db.Model, Base): __tablename__ = 'forum_posttracking' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) topics = db.Column(db.Text(), default=None) last_read = db.Column(db.DateTime, default=None) user = db.relationship('User', backref=db.backref('posttracking', uselist=False)) def get_topics(self): try: return json.loads(self.topics) if self.topics else None except ValueError: return None def set_topics(self, topics): self.topics = json.dumps(topics)
class ForumProfile(db.Model, Base): __tablename__ = 'forum_profile' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) post_count = db.Column(db.Integer, default=0) signature = db.Column(db.Text()) signature_html = db.Column(db.Text()) user = db.relationship('User', backref=db.backref('forum_profile', uselist=False)) @property def last_post(self): post = ForumPost.query.filter( ForumPost.deleted == False, ForumPost.user_id == self.user_id).order_by( ForumPost.created.desc()).limit(1).first() return post
class User(db.Model, Base): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(30)) player_id = db.Column(db.Integer, db.ForeignKey('player.id')) uuid = db.Column(db.String(32)) full_name = db.Column(db.String(50)) email = db.Column(db.String(75)) password = db.Column(db.String(128)) admin = db.Column(db.Boolean, default=False) moderator = db.Column(db.Boolean, default=False) mfa_login = db.Column(db.Boolean, default=False) mfa_secret = db.Column(db.String(20)) score = db.Column(db.Numeric(), default=0) last_login = db.Column(db.DateTime, default=None) date_joined = db.Column(db.DateTime, default=datetime.utcnow) player = db.relationship('Player', backref=db.backref('user', uselist=False)) @property def admin_or_moderator(self): return self.admin or self.moderator @classmethod def create(cls, player, plaintext_password, email): user = cls(player=player, uuid=player.uuid) user.set_password(plaintext_password) user.last_login = datetime.utcnow() user.email = email user.save(commit=False) forum_profile = ForumProfile(user=user) forum_profile.save(commit=True) # make sure messages/notifications received to this player are properly # associated with the newly created user Message.query.filter_by(to_player=player, to_user=None).update({'to_user_id': user.id}) Notification.query.filter_by(player=player, user=None).update({'user_id': user.id}) db.session.commit() return user def to_dict(self): result = {'username': self.username} if self.player_id: result['player'] = self.player.to_dict() return result def check_password(self, plaintext_password): algorithm, iterations, salt, hash_val = self.password.split('$', 3) expected = User._make_password(plaintext_password, salt=salt, iterations=int(iterations)) return h.safe_str_cmp(self.password, expected) def set_password(self, plaintext_password, commit=True): password = User._make_password(plaintext_password) self.password = password self.save(commit=commit) def get_username(self): if self.player_id: return self.player.username return self.username def get_unread_notification_count(self): return len( Notification.query.with_entities(Notification.id).filter_by( user=self, seen_at=None).all()) def get_unread_message_count(self): return len( Message.query.with_entities(Message.id).filter_by( to_user=self, seen_at=None, deleted=False).all()) def get_notification_preferences(self, create=True, can_commit=True): from standardweb.lib import notifications preferences = NotificationPreference.query.filter_by(user=self).all() if create: active_preference_names = set() for preference in preferences: active_preference_names.add(preference.name) missing_preference_names = notifications.NOTIFICATION_NAMES - active_preference_names for name in missing_preference_names: preference = NotificationPreference(user=self, name=name) preference.save(commit=False) preferences.append(preference) if can_commit: db.session.commit() return sorted(preferences, key=attrgetter('name')) def get_notification_preference(self, type, create=True, can_commit=True): preference = NotificationPreference.query.filter_by(user=self, name=type).first() if not preference and create: preference = NotificationPreference(user=self, name=type) preference.save(commit=can_commit) return preference @property def has_excellent_score(self): return self.score > app.config['EXCELLENT_SCORE_THRESHOLD'] @property def has_great_score(self): return self.score > app.config[ 'GREAT_SCORE_THRESHOLD'] and not self.has_excellent_score @property def has_good_score(self): return self.score > app.config[ 'GOOD_SCORE_THRESHOLD'] and not self.has_great_score @property def has_bad_score(self): return self.score < app.config[ 'BAD_SCORE_THRESHOLD'] and not self.has_terrible_score @property def has_terrible_score(self): return self.score < app.config[ 'TERRIBLE_SCORE_THRESHOLD'] and not self.has_abysmal_score @property def has_abysmal_score(self): return self.score < app.config['ABYSMAL_SCORE_THRESHOLD'] @classmethod def _make_password(cls, password, salt=None, iterations=None): if not salt: salt = binascii.b2a_hex(os.urandom(15)) if not iterations: iterations = 10000 hash_val = pbkdf2_bin(password.encode('utf-8'), salt, iterations, keylen=32, hashfunc=hashlib.sha256) hash_val = hash_val.encode('base64').strip() return '%s$%s$%s$%s' % ('pbkdf2_sha256', iterations, salt, hash_val)
class Notification(db.Model, Base): __tablename__ = 'notification' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) player_id = db.Column(db.Integer, db.ForeignKey('player.id')) timestamp = db.Column(db.DateTime, default=datetime.utcnow) type = db.Column(db.String(30)) seen_at = db.Column(db.DateTime) data = db.Column(JsonEncodedDict, default={}) user = db.relationship('User', foreign_keys='Notification.user_id', backref=db.backref('notifications')) player = db.relationship('Player', foreign_keys='Notification.player_id', backref=db.backref('notifications')) _definition = None @classmethod def create(cls, type, data=None, user_id=None, player_id=None, send_email=True, **kw): from standardweb.lib import notifier if data is None: data = {} data.update(**kw) notification = cls(type=type, user_id=user_id, player_id=player_id, data=data) notification.definition notification.save(commit=True) notifier.notification_notify(notification, send_email=send_email) return notification @property def definition(self): from standardweb.lib import notifications if not self._definition: self._definition = notifications.validate_notification( self.type, self.data) return self._definition @property def description(self): return self.definition.get_html_description(self.data) @property def can_notify_ingame(self): return self.definition.can_notify_ingame