153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
// Copyright (c) 2021 MillenniumEarl
 | 
						|
//
 | 
						|
// This software is released under the MIT License.
 | 
						|
// https://opensource.org/licenses/MIT
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
// Public modules from npm
 | 
						|
import { ArrayMaxSize, IsInt, Min, validateSync } from "class-validator";
 | 
						|
 | 
						|
// Modules from file
 | 
						|
import { urls } from "../../constants/url.js";
 | 
						|
import PrefixParser from "../prefix-parser.js";
 | 
						|
import { IQuery, TCategory, TQueryInterface } from "../../interfaces.js";
 | 
						|
import { fetchGETResponse } from "../../network-helper.js";
 | 
						|
import { AxiosResponse } from "axios";
 | 
						|
import { GenericAxiosError } from "../errors.js";
 | 
						|
import { Result } from "../result.js";
 | 
						|
 | 
						|
// Type definitions
 | 
						|
export type TLatestOrder = "date" | "likes" | "views" | "title" | "rating";
 | 
						|
type TDate = 365 | 180 | 90 | 30 | 14 | 7 | 3 | 1;
 | 
						|
 | 
						|
/**
 | 
						|
 * Query used to search handiwork in the "Latest" tab.
 | 
						|
 */
 | 
						|
export default class LatestSearchQuery implements IQuery {
 | 
						|
  //#region Private fields
 | 
						|
 | 
						|
  private static MAX_TAGS = 5;
 | 
						|
  private static MIN_PAGE = 1;
 | 
						|
 | 
						|
  //#endregion Private fields
 | 
						|
 | 
						|
  //#region Properties
 | 
						|
 | 
						|
  public category: TCategory = "games";
 | 
						|
  /**
 | 
						|
   * Ordering type.
 | 
						|
   *
 | 
						|
   * Default: `date`.
 | 
						|
   */
 | 
						|
  public order: TLatestOrder = "date";
 | 
						|
  /**
 | 
						|
   * Date limit in days, to be understood as "less than".
 | 
						|
   * Use `1` to indicate "today" or `null` to indicate "anytime".
 | 
						|
   *
 | 
						|
   * Default: `null`
 | 
						|
   */
 | 
						|
  public date: TDate = null;
 | 
						|
 | 
						|
  @ArrayMaxSize(LatestSearchQuery.MAX_TAGS, {
 | 
						|
    message: "Too many tags: $value instead of $constraint1"
 | 
						|
  })
 | 
						|
  public includedTags: string[] = [];
 | 
						|
  public includedPrefixes: string[] = [];
 | 
						|
 | 
						|
  @IsInt({
 | 
						|
    message: "$property expect an integer, received $value"
 | 
						|
  })
 | 
						|
  @Min(LatestSearchQuery.MIN_PAGE, {
 | 
						|
    message: "The minimum $property value must be $constraint1, received $value"
 | 
						|
  })
 | 
						|
  public page = LatestSearchQuery.MIN_PAGE;
 | 
						|
  itype: TQueryInterface = "LatestSearchQuery";
 | 
						|
 | 
						|
  //#endregion Properties
 | 
						|
 | 
						|
  //#region Public methods
 | 
						|
 | 
						|
  public validate(): boolean {
 | 
						|
    return validateSync(this).length === 0;
 | 
						|
  }
 | 
						|
 | 
						|
  public async execute(): Promise<Result<GenericAxiosError, AxiosResponse<any>>> {
 | 
						|
    // Check if the query is valid
 | 
						|
    if (!this.validate()) {
 | 
						|
      throw new Error(`Invalid query: ${validateSync(this).join("\n")}`);
 | 
						|
    }
 | 
						|
 | 
						|
    // Prepare the URL
 | 
						|
    const url = this.prepareGETurl();
 | 
						|
    const decoded = decodeURIComponent(url.toString());
 | 
						|
 | 
						|
    // Fetch the result
 | 
						|
    return fetchGETResponse(decoded);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the value (in days) acceptable in the query starting from a generic date.
 | 
						|
   */
 | 
						|
  public findNearestDate(d: Date): TDate {
 | 
						|
    // Find the difference between today and the passed date
 | 
						|
    const diff = this.dateDiffInDays(new Date(), d);
 | 
						|
 | 
						|
    // Find the closest valid value in the array
 | 
						|
    const closest = [365, 180, 90, 30, 14, 7, 3, 1].reduce(function (prev, curr) {
 | 
						|
      return Math.abs(curr - diff) < Math.abs(prev - diff) ? curr : prev;
 | 
						|
    });
 | 
						|
 | 
						|
    return closest as TDate;
 | 
						|
  }
 | 
						|
 | 
						|
  //#endregion Public methods
 | 
						|
 | 
						|
  //#region Private methods
 | 
						|
 | 
						|
  /**
 | 
						|
   * Prepare the URL by filling out the GET parameters with the data in the query.
 | 
						|
   */
 | 
						|
  private prepareGETurl(): URL {
 | 
						|
    // Create the URL
 | 
						|
    const url = new URL(urls.LATEST_PHP);
 | 
						|
    url.searchParams.set("cmd", "list");
 | 
						|
 | 
						|
    // Set the category
 | 
						|
    const cat: TCategory = this.category === "mods" ? "games" : this.category;
 | 
						|
    url.searchParams.set("cat", cat);
 | 
						|
 | 
						|
    // Add tags and prefixes
 | 
						|
    const parser = new PrefixParser();
 | 
						|
    for (const tag of parser.prefixesToIDs(this.includedTags)) {
 | 
						|
      url.searchParams.append("tags[]", tag.toString());
 | 
						|
    }
 | 
						|
 | 
						|
    for (const p of parser.prefixesToIDs(this.includedPrefixes)) {
 | 
						|
      url.searchParams.append("prefixes[]", p.toString());
 | 
						|
    }
 | 
						|
 | 
						|
    // Set the other values
 | 
						|
    url.searchParams.set("sort", this.order.toString());
 | 
						|
    url.searchParams.set("page", this.page.toString());
 | 
						|
    if (this.date) url.searchParams.set("date", this.date.toString());
 | 
						|
 | 
						|
    return url;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   *
 | 
						|
   */
 | 
						|
  private dateDiffInDays(a: Date, b: Date) {
 | 
						|
    const MS_PER_DAY = 1000 * 60 * 60 * 24;
 | 
						|
 | 
						|
    // Discard the time and time-zone information.
 | 
						|
    const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
 | 
						|
    const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
 | 
						|
 | 
						|
    return Math.floor((utc2 - utc1) / MS_PER_DAY);
 | 
						|
  }
 | 
						|
 | 
						|
  //#endregion Private methodss
 | 
						|
}
 |