Source

classes/filters.ts

import { UserType } from "../types/basicUser";
import IFilters from "../types/filters";
import { RequestContext } from "./requestContext";

/**
 * Class for handling filters
 */
class Filters {
    sortBy?: "hot" | "new" | "topRated" | "topViewed";
    category?: string;
    from?: Date;
    author?: string;
    skip?: number;
    limit?: number;
    query?: string;
    clientID?: string;

    private variables: string[] = [];
    private queryParts: {
        select: string[],
        from: string[],
        where: string[],
        groupBy: string[],
        orderBy: string[],
        limit: (string|number)[]
    } = {
        select: ["animations.*"],
        from: ["animations"],
        where: [],
        groupBy: [],
        orderBy: [],
        limit: []
    };

    /**
     * Instantiates the class
     * @param {IFilters} data
     */
    constructor(data: IFilters, public ctx: RequestContext) {
        this.sortBy = data.sortBy;
        this.category = data.category;
        this.from = data.from;
        this.author = data.author;
        this.query = data.query;
    }

    /**
     * Builds the query to get the count
     * @return {{ query: string, variables: string[]}}
     */
    buildCountQuery(): { query: string, variables: string[] } {
        this.variables = [];
        this.prepareQueryOrderByPart();

        this.buildQueryWherePart();
        this.buildQueryLimitPart();

        var query =
        "SELECT COUNT(*) as count FROM (SELECT " + this.queryParts.select.join(", ") +
        " FROM " + this.queryParts.from.join(", ") +
        (this.queryParts.where.length ? " WHERE " + this.queryParts.where.join(" AND ") : "") +
        (this.queryParts.groupBy.length ? " GROUP BY " + this.queryParts.groupBy.join(", ") : "") +
        ") as anims";

        return {
            query,
            variables: this.variables
        };
    }

    /**
     * Builds the query
     * @return {{ query: string, variables: string[] }}
     */
    buildQuery(): { query: string, variables: string[] } {
        this.variables = [];
        this.prepareQueryOrderByPart();

        this.buildQueryWherePart();
        this.buildQueryLimitPart();

        var query =
        `SELECT ${this.queryParts.select.join(", ")} FROM ${this.queryParts.from.join(", ")}` +
        (this.queryParts.where.length ? " WHERE " + this.queryParts.where.join(" AND ") : "") +
        (this.queryParts.groupBy.length ? " GROUP BY " + this.queryParts.groupBy.join(", ") : "") +
        (this.queryParts.orderBy.length ? " ORDER BY " + this.queryParts.orderBy.join(", ") : "") +
        (this.queryParts.limit.length ? " LIMIT " + this.queryParts.limit.join(",") : "");

        return {
            query,
            variables: this.variables
        };
    }

    /**
     * Builds the limit part
     */
    private buildQueryLimitPart() {
        if(this.skip) this.queryParts.limit.push(this.skip.toString());
        if(this.limit) this.queryParts.limit.push(this.limit.toString());
    }

    /**
     * Prepares the orderBy part
     */
    private prepareQueryOrderByPart() {
        switch(this.sortBy) {
            case "topRated":
                this.queryParts.select.push(
                    "SUM(ratings.stars) as num_stars", "COUNT(ratings.snowflake) as rating_count"
                );
                this.queryParts.from.push("ratings");
                this.queryParts.where.push("ratings.animation = animations.snowflake");
                this.queryParts.groupBy.push("animations.snowflake");
                this.queryParts.orderBy.push(
                    "(SUM(ratings.stars) * COUNT(ratings.snowflake)) DESC"
                );
                break;
            case "hot":
                this.queryParts.select.push(
                    "SUM(ratings.stars) as num_stars", "COUNT(ratings.snowflake) as rating_count"
                );
                this.queryParts.from.push("ratings");
                this.queryParts.where.push("ratings.animation = animations.snowflake");
                this.queryParts.groupBy.push("animations.snowflake");
                this.queryParts.orderBy.push(
                    "( (SUM(ratings.stars) * COUNT(ratings.snowflake)) / animations.snowflake) DESC"
                );
                break;
            case "topViewed":
                this.queryParts.orderBy.push("views DESC");
                break;
            case "new":
                this.queryParts.orderBy.push("snowflake DESC");
                break;
        }
    }

    /**
     * Builds the where part of queries
     */
    private buildQueryWherePart() {
        if(this.category) {
            this.queryParts.where.push("category = ?");
            this.variables.push(this.category);
        }
        if(this.from) {
            var snowflake = BigInt(this.from.valueOf()) << BigInt(22);
            this.queryParts.where.push("snowflake > ?");
            this.variables.push(snowflake.toString());
        }
        if(this.author) {
            this.queryParts.where.push("author = ?");
            this.variables.push(this.author);
        }
        if(this.query) {
            this.queryParts.where.push("MATCH (name, description) AGAINST (? IN BOOLEAN MODE)");
            let query = this.query;
            query = `(${query.split(" ")
                .map((t) => t + "*").join(" ")}) ("${query.replace(/"/g, "")}")`;
            this.variables.push(query);
        }
        if(this.ctx.user && this.ctx.user.type < UserType.MODERATOR) {
            this.queryParts.where.push(
                "(published IS NOT NULL OR author = ?)",
                "category >= 0"
            );
            this.variables.push(this.ctx.user.id.toString());
        } else if(!this.ctx.user) {
            this.queryParts.where.push(
                "published IS NOT NULL",
                "category >= 0"
            );
        }
        // blocked doesn't exist. User relations does - fix that later
        // this.queryParts.where
        // .push("NOT EXISTS (SELECT * FROM blocked WHERE blocked.blocked = animations.author)");
    }
};

export default Filters;