示例#1
0
class FinalFeedback(db.Model, CRUDMixin):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)

    motivation = db.Column(db.Enum(MotivationFeedback), nullable=False)

    user_uuid = db.Column(db.String(255), db.ForeignKey('user.uuid'))
    user = relationship("User", back_populates="feedback")
示例#2
0
class Badge(db.Model, CRUDMixin):
    """ Badges that can be earned """
    __tablename__ = "badge"

    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(255), unique=True, nullable=False)
    src_filename = db.Column(db.String(255), nullable=False)
    description = db.Column(db.Text, nullable=False)
    condition = db.Column(db.Enum(BadgeConditions))

    # Relations
    users = relationship("User",
                         secondary=badges_user_association_table,
                         back_populates="badges")

    def __repr__(self):
        return self.name

    def to_dict(self) -> dict:
        return dict(id=self.id,
                    name=self.name,
                    src_filename=self.src_filename,
                    description=self.description)

    @classmethod
    def active_badges(cls) -> typing.Iterable:
        return cls.query.filter(cls.condition != None).all()

    @classmethod
    def json_list(cls):
        return dict(
            list(
                map(lambda badge: (badge.name, badge.to_dict()),
                    cls.query.all())))

    @classmethod
    def earned_through_action(cls, user: User, command: str) -> typing.Set:
        return {
            badge
            for badge in cls.active_badges()
            if badge.condition.is_solved(user, command)
        }
示例#3
0
class ActionLog(db.Model, BaseModel):
    __tablename__ = "action_logs"

    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user = db.relationship("User", foreign_keys=[user_id], lazy='selectin')

    action_date = db.Column(db.DateTime,
                            default=datetime.utcnow,
                            nullable=False)
    action = db.Column(db.String(), nullable=False)
    screen = db.Column(db.Enum(Screen), nullable=False)

    def to_json(self):
        return {
            "action_date": str(self.action_date),
            "user_id": self.user_id,
            "full_name": str(self.user.full_name),
            "action": self.action,
            "screen": self.screen.value
        }
示例#4
0
class User(db.Model, CRUDMixin):
    __tablename__ = "user"

    uuid = db.Column(db.String(255), primary_key=True)
    first_seen = db.Column(db.DateTime,
                           nullable=False,
                           default=datetime.datetime.now)
    mode = db.Column(db.Enum(GameModes), nullable=False)

    # demographics
    age = db.Column(db.String(255))
    gender = db.Column(db.String(255))
    english_skills = db.Column(db.String(255))
    bash_experience = db.Column(db.String(255))

    # Relations
    user_identifiers = relationship("UserIdentifier",
                                    secondary=user_identifier_association,
                                    back_populates="users")

    commands = relationship("SubmittedCommand", back_populates="user")
    badges = relationship("Badge",
                          secondary=badges_user_association_table,
                          back_populates="users")

    solved_challenges = relationship("SolvedChallenges", back_populates="user")

    feedback = relationship("FinalFeedback", back_populates="user")

    def __repr__(self):
        return f"<User: {self.uuid}>"

    @property
    def correct_command_count(self):
        return SubmittedCommand.query.filter(
            SubmittedCommand.user_uuid == self.uuid,
            SubmittedCommand.solved_challenge == True).count()

    @property
    def wrong_command_count(self):
        return SubmittedCommand.query.filter(
            SubmittedCommand.user_uuid == self.uuid,
            SubmittedCommand.solved_challenge == False).count()

    @property
    def last_seen(self):
        try:
            return SubmittedCommand.query.filter(
                SubmittedCommand.user_uuid == self.uuid).order_by(
                    SubmittedCommand.time_submitted.desc()).first(
                    ).time_submitted
        except AttributeError:
            return self.first_seen

    @classmethod
    def create_user(cls):
        return cls.create(uuid=str(uuid4()), mode=choice(list(GameModes)))

    def set_identifier(self, ip_addr: str, user_agent: str):
        # does it exist?
        identifier = UserIdentifier.query.filter(
            UserIdentifier.ip_addr == ip_addr,
            UserIdentifier.user_agent == user_agent).first()

        if identifier:
            if identifier not in self.user_identifiers:
                # update list of user agent of user if the current identifier is not associated with user
                self.user_identifiers.append(identifier)
        else:
            # user agent does not exist -> Create it and link to user
            self.user_identifiers.append(
                UserIdentifier.create(ip_addr=ip_addr, user_agent=user_agent))
        # finally commit changes
        self.save()

    def add_new_badges(self, applicable_badges: typing.Set):
        new_badges = applicable_badges - set(self.badges)
        if new_badges:
            [self.badges.append(badge) for badge in new_badges]
            self.save()
            logger.debug(f"User earned new badges: {new_badges}")

    def to_dict(self):
        return dict(uuid=self.uuid,
                    first_seen=self.first_seen,
                    last_seen=self.last_seen,
                    mode=self.mode.value)

    def add_solved_challenge(self, challenge):
        try:
            a = SolvedChallenges(challenge_id=challenge.identifier)
            self.solved_challenges.append(a)
            self.save()
        except FlushError:
            pass

    def add_command(self, command_string: str, challenge_id: str,
                    solved: bool):
        SubmittedCommand.create(command_string=command_string,
                                challenge_id=challenge_id,
                                solved_challenge=solved,
                                user=self)
        if solved:
            challenge = Challenge.query.get(challenge_id)
            self.add_solved_challenge(challenge)
class User(UserMixin, ResourceMixin, db.Model):
    ROLE = OrderedDict([('admin', 'Admin'), ('client', 'Client'),
                        ('staff', 'Staff'), ('public', 'Public')])

    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)

    # Relationships
    client = db.relationship(Client,
                             backref=db.backref('user', uselist=False),
                             uselist=False,
                             cascade="all, delete-orphan")
    staff = db.relationship(Staff,
                            backref=db.backref('user', uselist=False),
                            uselist=False,
                            cascade="all, delete-orphan")
    comments = db.relationship(Comment,
                               backref=db.backref('author', uselist=False),
                               lazy='dynamic')
    appointments = db.relationship(Appointment, backref='user', lazy='dynamic')

    # Properties.
    role = db.Column(db.Enum(*ROLE, name='role_types', native_enum=False),
                     index=True,
                     nullable=False,
                     server_default='public')
    active = db.Column('is_active',
                       db.Boolean(),
                       nullable=False,
                       server_default='1')
    username = db.Column(db.String(24),
                         unique=True,
                         index=True,
                         nullable=False)
    email = db.Column(db.String(255),
                      unique=True,
                      index=True,
                      nullable=False,
                      server_default='')
    password = db.Column(db.String(128), nullable=False, server_default='')
    first_name = db.Column(db.String(256), nullable=False)
    middle_name = db.Column(db.String(256))
    last_name = db.Column(db.String(256), index=True, nullable=False)
    unit_number = db.Column(db.String(128))
    street_address = db.Column(db.String(128))
    suburb = db.Column(db.String(128))
    postcode = db.Column(db.String(4))
    state = db.Column(db.String(128))
    country = db.Column(db.String(128))
    phone_number = db.Column(db.String(128), nullable=False)
    photo = db.Column(db.String(256))

    # Activity tracking.
    sign_in_count = db.Column(db.Integer, nullable=False, default=0)
    current_sign_in_on = db.Column(AwareDateTime())
    current_sign_in_ip = db.Column(db.String(45))
    last_sign_in_on = db.Column(AwareDateTime())
    last_sign_in_ip = db.Column(db.String(45))

    # Additional settings.
    locale = db.Column(db.String(5), nullable=False, server_default='en')

    def __init__(self, **kwargs):
        # Call Flask-SQLAlchemy's constructor.
        super(User, self).__init__(**kwargs)

        self.password = User.encrypt_password(kwargs.get('password', ''))

    @hybrid_property
    def full_name(self):
        if self.middle_name:
            return self.last_name.upper(
            ) + ', ' + self.first_name + ' ' + self.middle_name
        else:
            return self.last_name.upper() + ', ' + self.first_name

    @hybrid_property
    def first_last_name(self):
        return self.first_name + ' ' + self.last_name

    @hybrid_property
    def full_address(self):
        street_address = self.unit_number + "/" + self.street_address if self.unit_number and self.street_address else self.street_address
        address_elements = [
            street_address, self.suburb, self.postcode, self.state,
            self.country
        ]
        return ", ".join(
            address_elements) if None not in address_elements else None

    @classmethod
    def find_by_identity(cls, identity):
        """
        Find user by e-mail or username.

        :param identity: Email or username
        :type identity: str
        :return: User instance
        """
        return User.query.filter((User.email == identity)
                                 | (User.username == identity)).first()

    @classmethod
    def encrypt_password(cls, plaintext_password):
        """
        Hash password using Bcrypt.

        :param plaintext_password: Password in plain text
        :type plaintext_password: str
        :return: str
        """
        if plaintext_password:
            return hashpw(plaintext_password, gensalt())

        return None

    @classmethod
    def deserialize_token(cls, token):
        """
        Obtain user from de-serializing token.

        :param token: Signed token.
        :type token: str
        :return: User instance or None
        """
        private_key = TimedJSONWebSignatureSerializer(
            current_app.config['SECRET_KEY'])
        try:
            decoded_payload = private_key.loads(token)

            return User.find_by_identity(decoded_payload.get('user_email'))
        except Exception:
            return None

    @classmethod
    def initialize_password_reset(cls, identity):
        """
        Generate token to reset the password for a specific user.

        :param identity: User e-mail address or username
        :type identity: str
        :return: User instance
        """
        u = User.find_by_identity(identity)
        reset_token = u.serialize_token()

        # This prevents circular imports.
        from server.blueprints.user.tasks import (deliver_password_reset_email)
        deliver_password_reset_email.delay(u.id, reset_token)

        return u

    @classmethod
    def search(cls, query):
        """
        Search using ILIKE (case-insensitive) expression.

        :param query: Search query
        :type query: str
        :return: SQLAlchemy filter
        """
        if not query:
            return ''

        search_query = '%{0}%'.format(query)
        search_chain = (User.email.ilike(search_query),
                        User.username.ilike(search_query))

        return or_(*search_chain)

    @classmethod
    def is_last_admin(cls, user, new_role, new_active):
        """
        Return whether user is last admin account.

        :param user: User being tested
        :type user: User
        :param new_role: New role being set
        :type new_role: str
        :param new_active: New active status being set
        :type new_active: bool
        :return: bool
        """
        is_changing_roles = user.role == 'admin' and new_role != 'admin'
        is_changing_active = user.active is True and new_active is None

        if is_changing_roles or is_changing_active:
            admin_count = User.query.filter(User.role == 'admin').count()
            active_count = User.query.filter(User.is_active is True).count()

            if admin_count == 1 or active_count == 1:
                return True

        return False

    def is_active(self):
        """
        Return whether user account is active (overrides Flask-Login default).

        :return: bool
        """
        return self.active

    def get_auth_token(self):
        """
        Return user's auth token.

        :return: str
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = URLSafeTimedSerializer(private_key)
        data = [str(self.id), md5(self.password.encode('utf-8')).hexdigest()]

        return serializer.dumps(data)

    def authenticated(self, with_password=True, password=''):
        """
        Ensure user authenticated.

        :param with_password: Optionally check password
        :type with_password: bool
        :param password: Password to verify
        :type password: str
        :return: bool
        """
        if with_password:
            return checkpw(password.encode('utf-8'),
                           self.password.encode('utf-8'))

        return True

    def serialize_token(self, expiration=3600):
        """
        Serialize token for resetting passwords, etc.

        :param expiration: Seconds until it expires, defaults to 1 hour
        :type expiration: int
        :return: JSON
        """
        private_key = current_app.config['SECRET_KEY']

        serializer = TimedJSONWebSignatureSerializer(private_key, expiration)
        return serializer.dumps({'user_email': self.email}).decode('utf-8')

    def update_activity_tracking(self, ip_address):
        """
        Update user's meta data.

        :param ip_address: IP address
        :type ip_address: str
        :return: SQLAlchemy commit results
        """
        self.sign_in_count += 1
        self.last_sign_in_on = self.current_sign_in_on
        self.last_sign_in_ip = self.current_sign_in_ip
        self.current_sign_in_on = datetime.datetime.now(pytz.utc)
        self.current_sign_in_ip = ip_address

        return self.save()

    def to_json(self):
        active = "Active" if self.active == True else "Disabled"
        json_data = {
            'id': self.id,
            'created': self.created_on,
            'updated': self.updated_on,
            'active': active,
            'firstLastName': self.first_last_name,
            'firstName': self.first_name,
            'middleName': self.middle_name,
            'lastName': self.last_name.upper(),
            'fullName': self.full_name,
            'username': self.username,
            'email': self.email,
            'phoneNumber': self.phone_number,
            'unitNumber': self.unit_number,
            'streetAddress': self.street_address,
            'suburb': self.suburb,
            'postcode': self.postcode,
            'state': self.state,
            'country': self.country,
            'fullAddress': self.full_address,
            'role': self.role,
            'photo': self.photo,
        }

        return json_data
示例#6
0
class Comment(ResourceMixin, db.Model):
    __tablename__ = 'comments'

    TOPICS = OrderedDict([('tech', 'Science & Technology'),
                          ('politics', 'Politics'),
                          ('health', 'Health & Food'),
                          ('entertainment', 'Movies, Music & more')])

    id = db.Column(db.Integer, primary_key=True)
    version = db.Column(db.Integer, server_default='1')
    topic = db.Column(db.Enum(*TOPICS.keys(), name='topic_tags'),
                      index=True,
                      nullable=False,
                      server_default='tech')
    text = db.Column(db.String(255),
                     index=True,
                     nullable=False,
                     server_default='')
    user_id = db.Column(db.Integer,
                        db.ForeignKey('users.id',
                                      onupdate='CASCADE',
                                      ondelete='CASCADE'),
                        index=True,
                        nullable=False)

    def __init__(self, **kwargs):
        # Call Flask-SQLAlchemy's constructor.
        super(Comment, self).__init__(**kwargs)

    def serialize(self, lite=False):
        """
        Return JSON fields to render the comment.

        :return: dict
        """
        from server.blueprints.user.models import User
        user = User.query.get(self.user_id)
        username = user.username if user else ""
        params = {
            'id': self.id,
            'version': self.version,
            'topic': self.topic,
            'text': self.text,
            'created_by': username
        }
        if lite:
            del params['created_by']

        return params

    @classmethod
    def find(cls, _id=None, username=None, topic=None):
        """
        Find comments.

        :param id: Comment id to find
        :type id: str
        :param username: Username of person who created comment
        :type username: str
        :param topic: Comment topic to find
        :type topic: str
        :return: Comments
        """
        comments = Comment.query
        if not topic and not id and not username:
            return comments.all()
        if _id:
            return [comments.get(_id)]

        if topic:
            formatted_topic = topic.lower()
            comments = comments.filter(Comment.topic == formatted_topic)
        if username:
            from server.blueprints.user.models import User
            user = User.query.filter(User.username == username).first()
            if user:
                comments = comments.filter(Comment.user_id == user.id)
        return comments