redditraffler/redditraffler

View on GitHub
app/db/models/user.py

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
from datetime import datetime
from flask import current_app

from app.util import crypto_helper, jwt_helper
from app.extensions import db


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(
        db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
    )

    username = db.Column(db.Text, index=True, unique=True, nullable=False)
    refresh_token_enc = db.Column(db.LargeBinary, unique=True, nullable=True)

    raffles = db.relationship("Raffle", backref="creator", lazy=True)

    def __repr__(self):
        return "<User {}>".format(self.username)

    @classmethod
    def find_by_jwt(cls, jwt):
        """
        Given a JWT, queries the database using the user_id in the decoded JWT payload

        Args:
            jwt (obj): The user's JWT

        Returns:
            User: The user identified by the JWT
        """
        user_id = jwt_helper.decode(jwt)["user_id"]
        return cls.query.get(user_id)

    @classmethod
    def find_or_create(cls, username):
        """
        Given a username, try to find the user by username or create the user if
        it does not exist.

        Args:
            username (str): The username to search or create

        Raises:
            ValueError: When the username is empty or with length < 3

        Returns:
            User: The found or created user
        """
        if not username or len(username) < 3:
            raise ValueError("Username must be non-empty")

        user = cls.query.filter_by(username=username).first()
        if user is not None:
            return user

        new_user = cls(username=username)
        db.session.add(new_user)
        db.session.commit()
        current_app.logger.info("Created new user", {"username": username})
        return new_user

    def set_refresh_token(self, refresh_token):
        """
        Given a raw Reddit refresh token, encrypts and saves it for this user.

        Args:
            refresh_token (string): Reddit refresh token
        """
        if not refresh_token:
            raise ValueError("Refresh token cannot be empty")

        encrypted_token_bytes = crypto_helper.encrypt(refresh_token)
        self.refresh_token_enc = encrypted_token_bytes
        db.session.add(self)
        db.session.commit()

    def get_refresh_token(self):
        """
        Returns the user's refresh token as a decrypted string.

        Raises:
            AttributeError: When the user doesn't have a saved refresh token

        Returns:
            str: The decrypted refresh token
        """
        if not self.refresh_token_enc:
            raise AttributeError("User does not have a saved refresh token")

        return crypto_helper.decrypt(self.refresh_token_enc).decode()

    def get_jwt(self):
        """
        Returns a JWT containing the user ID and the username.

        Returns:
            str: Payload encoded as a JWT string
        """
        payload = {"user_id": self.id, "username": self.username}
        jwt_bytes = jwt_helper.encode(payload)
        return jwt_bytes.decode()

    def get_profile(self):
        raffles = self.raffles
        raffle_submission_ids = [raf.submission_id for raf in raffles]

        # Vanity stats
        raffle_count = len(self.raffles)
        num_winners_selected = sum([raf.winner_count for raf in raffles])

        return dict(
            created_at=self.created_at.isoformat(),
            raffle_submission_ids=raffle_submission_ids,
            raffle_count=raffle_count,
            num_winners_selected=num_winners_selected,
        )