Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static map list headers #1064

Merged
merged 6 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions apps/backend-e2e/src/admin.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1769,23 +1769,27 @@ describe('Admin', () => {
oldVersion.body.submissions + 1
);

const approvedMapList = await fileStore.getMapListVersion(
const approved = await fileStore.getMapListVersion(
FlatMapList.APPROVED,
newVersion.body.approved
);
expect(approvedMapList).toHaveLength(1);
expect(approvedMapList[0]).toMatchObject({
expect(approved.ident).toBe('MSML');
expect(approved.numMaps).toBe(1);
expect(approved.data).toHaveLength(1);
expect(approved.data[0]).toMatchObject({
id: map.id,
leaderboards: expect.anything(),
info: expect.anything()
});
expect(approvedMapList[0]).not.toHaveProperty('zones');
expect(approved.data[0]).not.toHaveProperty('zones');

const submissionMapList = await fileStore.getMapListVersion(
const submission = await fileStore.getMapListVersion(
FlatMapList.SUBMISSION,
newVersion.body.submissions
);
expect(submissionMapList).toHaveLength(0);
expect(submission.ident).toBe('MSML');
expect(submission.numMaps).toBe(0);
expect(submission.data).toHaveLength(0);
});

it('should 400 when moving from FA to approved if leaderboards are not provided', async () => {
Expand Down Expand Up @@ -1832,12 +1836,14 @@ describe('Admin', () => {
expect(newVersion.body.submissions).toBe(oldVersion.body.submissions);
expect(newVersion.body.approved).toBe(oldVersion.body.approved + 1);

const approvedMapList = await fileStore.getMapListVersion(
const { ident, numMaps, data } = await fileStore.getMapListVersion(
FlatMapList.APPROVED,
newVersion.body.approved
);
expect(approvedMapList).toHaveLength(1);
expect(approvedMapList[0]).toMatchObject({ id: map2.id });
expect(ident).toBe('MSML');
expect(numMaps).toBe(1);
expect(data).toHaveLength(1);
expect(data[0].id).toBe(map2.id);
});

it('should return 404 if map not found', () =>
Expand Down Expand Up @@ -1973,11 +1979,13 @@ describe('Admin', () => {
expect(newVersion.body.submissions).toBe(oldVersion.body.submissions);
expect(newVersion.body.approved).toBe(oldVersion.body.approved + 1);

const approvedMapList = await fileStore.getMapListVersion(
const { ident, numMaps, data } = await fileStore.getMapListVersion(
FlatMapList.APPROVED,
newVersion.body.approved
);
expect(approvedMapList).toHaveLength(0);
expect(ident).toBe('MSML');
expect(numMaps).toBe(0);
expect(data).toHaveLength(0);
});

it('should return 404 if map not found', () =>
Expand Down
11 changes: 6 additions & 5 deletions apps/backend-e2e/src/maps.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2120,13 +2120,15 @@ describe('Maps', () => {
oldListVersion.body.submissions + 1
);

const submissionMapList = await fileStore.getMapListVersion(
const { ident, numMaps, data } = await fileStore.getMapListVersion(
FlatMapList.SUBMISSION,
newListVersion.body.submissions
);
expect(submissionMapList).toHaveLength(1);
expect(submissionMapList[0].id).toBe(map.id);
expect(submissionMapList[0]).not.toHaveProperty('zones');
expect(ident).toBe('MSML');
expect(numMaps).toBe(1);
expect(data).toHaveLength(1);
expect(data[0].id).toBe(map.id);
expect(data[0]).not.toHaveProperty('zones');
});

it('should 400 for bad zones', async () => {
Expand Down Expand Up @@ -3016,7 +3018,6 @@ describe('Maps', () => {
const newListVersion = await req.get({
url: 'maps/maplistversion',
status: 200,

token
});

Expand Down
3 changes: 3 additions & 0 deletions apps/backend/src/app/dto/map/map-version.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export class MapVersionDto implements MapVersion {
@IsUUID()
readonly id: string;

@Exclude()
readonly mapID: number;

@ApiProperty()
@IsInt()
readonly versionNum: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
TrackType
} from '@momentum/constants';
import { ZonesStub } from '@momentum/formats/zone';
import { LeaderboardHandler } from './leaderboard-handler.util';
import * as LeaderboardHandler from './leaderboard-handler.util';

describe('LeaderboardHandler', () => {
describe('getCompatibleSuggestions', () => {
Expand Down
154 changes: 76 additions & 78 deletions apps/backend/src/app/modules/maps/leaderboard-handler.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,29 @@ export interface LeaderboardProps
linear?: boolean;
}

export const LeaderboardHandler = {
/**
* Expand an array of map suggestions to data we can create all the
* leaderboards we want from, including stages and all compatible gamemodes
*/
getMaximalLeaderboards: <T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): LeaderboardProps[] =>
LeaderboardHandler.getCompatibleLeaderboards([
...LeaderboardHandler.setLeaderboardLinearity(leaderboards, zones),
...LeaderboardHandler.getStageLeaderboards(leaderboards, zones)
]),
/**
* Expand an array of map suggestions to data we can create all the
* leaderboards we want from, including stages and all compatible gamemodes
*/
export function getMaximalLeaderboards<T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): LeaderboardProps[] {
return getCompatibleLeaderboards([
...setLeaderboardLinearity(leaderboards, zones),
...getStageLeaderboards(leaderboards, zones)
]);
}

/**
* Expand an array of MapSubmissionSuggestions in one containing equiv.
* entries for gamemodes that's "incompatible" with the suggestion's gamemode.
* E.g. a rocket jump track also gets a sticky jump entry, but not surf.
*/
getCompatibleLeaderboards: <T extends LeaderboardProps>(
leaderboards: T[]
): LeaderboardProps[] =>
/**
* Expand an array of MapSubmissionSuggestions in one containing equiv.
* entries for gamemodes that's "incompatible" with the suggestion's gamemode.
* E.g. a rocket jump track also gets a sticky jump entry, but not surf.
*/
export function getCompatibleLeaderboards<T extends LeaderboardProps>(
leaderboards: T[]
): LeaderboardProps[] {
return (
leaderboards
.flatMap(({ trackType, trackNum, linear, gamemode }) =>
Enum.values(Gamemode) // Note: this will include `suggestion`
Expand All @@ -51,65 +52,62 @@ export const LeaderboardHandler = {
gamemode: newGamemode
}))
)
.filter(
// Filter out any duplicates
(x, i, array) =>
!array.some(
(y, j) =>
x.trackType === y.trackType &&
x.trackNum === y.trackNum &&
x.gamemode === y.gamemode &&
i < j
)
),
// Filter out any duplicates
.filter((x, i, array) => !array.some((y, j) => isEqual(x, y) && i < j))
);
}

/**
* Returns leaderboard create inputs for all the stages on all gamemodes of
* a staged main track
*
* Stages have no important user-submitted data and tedious for them to
* create in frontend, so we may as well automatically generate them
*/
getStageLeaderboards: <T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): T[] =>
zones.tracks.main.zones.segments.length === 1
? []
: leaderboards
.filter(({ trackType }) => trackType === TrackType.MAIN)
.flatMap((lb: T) =>
arrayFrom(
zones.tracks.main.zones.segments.length,
(i) =>
({
gamemode: lb.gamemode,
// Whether is ranked depends on main Track, doesn't have a tier.
type: (lb as T & { type?: LeaderboardType }).type,
trackType: TrackType.STAGE,
trackNum: i + 1
}) as unknown as T
)
),
/**
* Returns leaderboard create inputs for all the stages on all gamemodes of
* a staged main track
*
* Stages have no important user-submitted data and tedious for them to
* create in frontend, so we may as well automatically generate them
*/
export function getStageLeaderboards<T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): T[] {
return zones.tracks.main.zones.segments.length === 1
? []
: leaderboards
.filter(({ trackType }) => trackType === TrackType.MAIN)
.flatMap((lb: T) =>
arrayFrom(
zones.tracks.main.zones.segments.length,
(i) =>
({
gamemode: lb.gamemode,
// Whether is ranked depends on main Track, doesn't have a tier.
type: (lb as T & { type?: LeaderboardType }).type,
trackType: TrackType.STAGE,
trackNum: i + 1
}) as unknown as T
)
);
}

isEqual: <T extends LeaderboardProps, U extends LeaderboardProps>(
x: T,
y: U
) =>
export function isEqual<T extends LeaderboardProps, U extends LeaderboardProps>(
x: T,
y: U
) {
return (
x.gamemode === y.gamemode &&
x.trackType === y.trackType &&
x.trackNum === y.trackNum,
x.trackNum === y.trackNum
);
}

/**
* Set linear (true/false/undef) for each suggestion based on zones
*/
setLeaderboardLinearity: <T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): T[] =>
leaderboards.map((lb) => ({
...lb,
linear:
lb.trackType === TrackType.MAIN ? isLinearMainTrack(zones) : undefined
}))
};
/**
* Set linear (true/false/undef) for each suggestion based on zones
*/
export function setLeaderboardLinearity<T extends LeaderboardProps>(
leaderboards: T[],
zones: MapZones
): T[] {
return leaderboards.map((lb) => ({
...lb,
linear:
lb.trackType === TrackType.MAIN ? isLinearMainTrack(zones) : undefined
}));
}
Loading