This commit is contained in:
Lee
2024-10-25 16:56:37 +01:00
parent b911072a47
commit 2edb5c04c9
15 changed files with 509 additions and 125 deletions

View File

@ -0,0 +1,73 @@
import { NotFoundError } from "backend/src/error/not-found-error";
import { Metadata } from "./types/metadata";
export class Pagination<T> {
/**
* The amount of items per page.
* @private
*/
private itemsPerPage: number = 0;
/**
* The amount of items in total.
* @private
*/
private totalItems: number = 0;
/**
* The items to paginate.
* @private
*/
private items: T[] = [];
/**
* Sets the number of items per page.
*
* @param itemsPerPage - The number of items per page.
* @returns the pagination
*/
setItemsPerPage(itemsPerPage: number): Pagination<T> {
this.itemsPerPage = itemsPerPage;
return this;
}
/**
* Sets the items to paginate.
*
* @param items the items to paginate
* @returns the pagination
*/
setItems(items: T[]): Pagination<T> {
this.items = items;
this.totalItems = items.length;
return this;
}
/**
* Gets a page of items.
*
* @param page the page number to retrieve.
* @returns the page of items.
* @throws throws an error if the page number is invalid.
*/
getPage(page: number): Page<T> {
const totalPages = Math.ceil(this.totalItems / this.itemsPerPage);
if (page < 1 || page > totalPages) {
throw new NotFoundError("Invalid page number");
}
const items = this.items.slice((page - 1) * this.itemsPerPage, page * this.itemsPerPage);
return new Page<T>(items, new Metadata(totalPages, this.totalItems, page, this.itemsPerPage));
}
}
class Page<T> {
readonly items: T[];
readonly metadata: Metadata;
constructor(items: T[], metadata: Metadata) {
this.items = items;
this.metadata = metadata;
}
}

View File

@ -168,6 +168,7 @@ class ScoreSaberService extends Service {
* @param playerId the ID of the player to look up
* @param sort the sort to use
* @param page the page to get scores for
* @param limit the amount of scores to fetch
* @param search
* @returns the scores of the player, or undefined
*/
@ -175,11 +176,13 @@ class ScoreSaberService extends Service {
playerId,
sort,
page,
limit = 8,
search,
}: {
playerId: string;
sort: ScoreSort;
page: number;
limit?: number;
search?: string;
useProxy?: boolean;
}): Promise<ScoreSaberPlayerScoresPageToken | undefined> {
@ -189,7 +192,7 @@ class ScoreSaberService extends Service {
);
const response = await this.fetch<ScoreSaberPlayerScoresPageToken>(
LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId)
.replace(":limit", 8 + "")
.replace(":limit", limit + "")
.replace(":sort", sort)
.replace(":page", page + "") + (search ? `&search=${search}` : "")
);

View File

@ -0,0 +1,69 @@
import { ScoreSorter } from "../score-sorter";
import { ScoreSaberScore } from "../../model/score/impl/scoresaber-score";
import { ScoreSortType } from "../sort-type";
import { SortDirection } from "../sort-direction";
export class ScoreSaberScoreSorter extends ScoreSorter<ScoreSaberScore> {
sort(type: ScoreSortType, direction: SortDirection, items: ScoreSaberScore[]): ScoreSaberScore[] {
switch (type) {
case ScoreSortType.date:
return this.sortRecent(direction, items);
case ScoreSortType.pp:
return this.sortPp(direction, items);
case ScoreSortType.accuracy:
return this.sortAccuracy(direction, items);
case ScoreSortType.misses:
return this.sortMisses(direction, items);
default:
return items;
}
}
/**
* Sorts the scores by the time they were set.
*
* @param direction the direction to sort the scores
* @param items the scores to sort
* @returns the sorted scores
*/
sortRecent(direction: SortDirection, items: ScoreSaberScore[]): ScoreSaberScore[] {
return items.sort((a, b) =>
direction === SortDirection.ASC
? a.timestamp.getTime() - b.timestamp.getTime()
: b.timestamp.getTime() - a.timestamp.getTime()
);
}
/**
* Sorts the scores by their pp value
*
* @param direction the direction to sort the scores
* @param items the scores to sort
* @returns the sorted scores
*/
sortPp(direction: SortDirection, items: ScoreSaberScore[]): ScoreSaberScore[] {
return items.sort((a, b) => (direction === SortDirection.ASC ? a.pp - b.pp : b.pp - a.pp));
}
/**
* Sorts the scores by their accuracy value
*
* @param direction the direction to sort the scores
* @param items the scores to sort
* @returns the sorted scores
*/
sortAccuracy(direction: SortDirection, items: ScoreSaberScore[]): ScoreSaberScore[] {
return items.sort((a, b) => (direction === SortDirection.ASC ? a.accuracy - b.accuracy : b.accuracy - a.accuracy));
}
/**
* Sorts the scores by their misses
*
* @param direction the direction to sort the scores
* @param items the scores to sort
* @returns the sorted scores
*/
sortMisses(direction: SortDirection, items: ScoreSaberScore[]): ScoreSaberScore[] {
return items.sort((a, b) => (direction === SortDirection.ASC ? a.misses - b.misses : b.misses - a.misses));
}
}

View File

@ -0,0 +1,14 @@
import { ScoreSortType } from "./sort-type";
import { SortDirection } from "./sort-direction";
export abstract class ScoreSorter<T> {
/**
* Sorts the items
*
* @param type the type of sort
* @param direction the direction of the sort
* @param items the items to sort
* @returns the sorted items
*/
public abstract sort(type: ScoreSortType, direction: SortDirection, items: T[]): T[];
}

View File

@ -0,0 +1,4 @@
export enum SortDirection {
ASC = "asc",
DESC = "desc",
}

View File

@ -0,0 +1,6 @@
export enum ScoreSortType {
date = "date",
pp = "pp",
accuracy = "accuracy",
misses = "misses",
}

View File

@ -0,0 +1,5 @@
import { ScoreSaberScoreSorter } from "./impl/scoresaber-sorter";
export const ScoreSorters = {
scoreSaber: new ScoreSaberScoreSorter(),
};

View File

@ -1,6 +1,6 @@
import { isServer } from "./utils";
export type CookieName = "playerId" | "lastScoreSort";
export type CookieName = "playerId" | "lastScoreSort" | "lastScoreSortDirection";
/**
* Gets the value of a cookie

View File

@ -4,6 +4,8 @@ import PlayerScoresResponse from "../response/player-scores-response";
import { Config } from "../config";
import { ScoreSort } from "../score/score-sort";
import LeaderboardScoresResponse from "../response/leaderboard-scores-response";
import { ScoreSortType } from "../sorter/sort-type";
import { SortDirection } from "../sorter/sort-direction";
/**
* Fetches the player's scores
@ -12,17 +14,19 @@ import LeaderboardScoresResponse from "../response/leaderboard-scores-response";
* @param id the player id
* @param page the page
* @param sort the sort
* @param direction the direction to sort
* @param search the search
*/
export async function fetchPlayerScores<S, L>(
leaderboard: Leaderboards,
id: string,
page: number,
sort: ScoreSort,
sort: ScoreSortType,
direction: SortDirection,
search?: string
) {
return kyFetch<PlayerScoresResponse<S, L>>(
`${Config.apiUrl}/scores/player/${leaderboard}/${id}/${page}/${sort}${search ? `?search=${search}` : ""}`
`${Config.apiUrl}/scores/player/${leaderboard}/${id}/${page}/${sort}/${direction}${search ? `?search=${search}` : ""}`
);
}