From e980337d843523bcb3f177e67887a340236fd705 Mon Sep 17 00:00:00 2001 From: tsa96 Date: Tue, 12 Nov 2024 21:09:50 +0000 Subject: [PATCH 1/8] refactor(back): return more varied response codes when SteamService fails Helpful for switching over error codes in C++. --- .../src/app/modules/maps/maps.controller.ts | 7 +++- .../modules/runs/leaderboard-runs.service.ts | 10 +++--- .../src/app/modules/steam/steam.service.ts | 35 ++++++++++++------- 3 files changed, 33 insertions(+), 19 deletions(-) diff --git a/apps/backend/src/app/modules/maps/maps.controller.ts b/apps/backend/src/app/modules/maps/maps.controller.ts index 6914a9e55..003b7b37c 100644 --- a/apps/backend/src/app/modules/maps/maps.controller.ts +++ b/apps/backend/src/app/modules/maps/maps.controller.ts @@ -590,8 +590,13 @@ export class MapsController { description: "When the filtering by 'friends', and the user doesn't have any Steam friends" }) + @ApiConflictResponse({ + description: + "When filtering by 'friends', the user's friend list is private" + }) @ApiServiceUnavailableResponse({ - description: "Steam fails to return the user's friends list (Tuesdays lol)" + description: + "When filtering by 'friends', and Steam fails to return the user's friends list (Tuesdays lol)" }) getLeaderboards( @Param('mapID', ParseIntSafePipe) mapID: number, diff --git a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts index 64ff4a734..f341f1298 100644 --- a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts +++ b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts @@ -5,7 +5,6 @@ import { Inject, Injectable, NotFoundException, - ServiceUnavailableException, UnauthorizedException } from '@nestjs/common'; import { Prisma } from '@prisma/client'; @@ -125,11 +124,10 @@ export class LeaderboardRunsService { } else if (query.filter?.[0] === 'friends') { // Regular skip/take should work fine here. - const steamFriends = await this.steamService - .getSteamFriends(loggedInUserSteamID) - .catch(() => { - throw new ServiceUnavailableException(); - }); + // Fetch Steam friends, leave errors uncaught, this function will throw + // an appropriate response. + const steamFriends = + await this.steamService.getSteamFriends(loggedInUserSteamID); if (steamFriends.length === 0) throw new GoneException('No friends detected :('); diff --git a/apps/backend/src/app/modules/steam/steam.service.ts b/apps/backend/src/app/modules/steam/steam.service.ts index 1f092016c..87cd1a0d1 100644 --- a/apps/backend/src/app/modules/steam/steam.service.ts +++ b/apps/backend/src/app/modules/steam/steam.service.ts @@ -1,7 +1,9 @@ import { HttpService } from '@nestjs/axios'; import { + ConflictException, Injectable, InternalServerErrorException, + Logger, ServiceUnavailableException, UnauthorizedException } from '@nestjs/common'; @@ -9,6 +11,7 @@ import { ConfigService } from '@nestjs/config'; import { catchError, lastValueFrom, map } from 'rxjs'; import * as AppTicket from 'steam-appticket'; import { SteamFriendData, SteamUserSummaryData } from './steam.interface'; +import { AxiosError } from 'axios'; @Injectable() export class SteamService { @@ -25,6 +28,8 @@ export class SteamService { private readonly steamApiKey: string; private readonly steamTicketsSecretKey: string; + private readonly logger = new Logger('Steam Service'); + /** * Handler for ISteamUser/GetPlayerSummaries/v2/ */ @@ -57,9 +62,11 @@ export class SteamService { /** * Handler for ISteamUser/GetFriendList/v1/ + * Throws several different different errors for different failures, which can + * be left uncaught if you want to throw an error response if this fails. */ async getSteamFriends(steamID: bigint): Promise { - const response = await lastValueFrom( + return lastValueFrom( this.http .get('https://api.steampowered.com/ISteamUser/GetFriendList/v1/', { params: { @@ -69,20 +76,24 @@ export class SteamService { } }) .pipe( - catchError((_) => { - throw new ServiceUnavailableException( - 'Failed to retrieve friends list from steam' - ); + map((res) => res?.data?.friendslist?.friends), + catchError((error: AxiosError) => { + if (error.response) { + if (error.response.status === 401) { + throw new ConflictException(); + } else { + throw new InternalServerErrorException( + `ISteamUser/GetFriendList/v1/ returned ${error.status}, which we don't know how to handle!` + ); + } + } else { + throw new ServiceUnavailableException( + 'Steam friends list API did not respond to our request' + ); + } }) ) ); - - if (!response.data) - throw new InternalServerErrorException( - 'Failed to get Steam friends list' - ); - - return response.data.friendslist.friends; } /** From c7953abb92ad03b8456a19a1a78502929691fb90 Mon Sep 17 00:00:00 2001 From: tsa96 Date: Tue, 12 Nov 2024 22:41:27 +0000 Subject: [PATCH 2/8] feat(back): add filterSteamIDs param to maps/:id/leaderboard expand This is used by ingame leaderboards for getting times for players in your lobby, wasn't supported on the new backend until now. --- apps/backend-e2e/src/maps-2.e2e-spec.ts | 19 ++++++++++++++++++- .../src/app/dto/queries/map-queries.dto.ts | 8 +++++++- .../modules/runs/leaderboard-runs.service.ts | 10 +++++----- .../src/types/queries/map-queries.model.ts | 1 + 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/backend-e2e/src/maps-2.e2e-spec.ts b/apps/backend-e2e/src/maps-2.e2e-spec.ts index 152889173..13143155f 100644 --- a/apps/backend-e2e/src/maps-2.e2e-spec.ts +++ b/apps/backend-e2e/src/maps-2.e2e-spec.ts @@ -1178,7 +1178,9 @@ describe('Maps Part 2', () => { beforeAll(async () => { [[u1, token], u2, u3, map] = await Promise.all([ - db.createAndLoginUser(), + db.createAndLoginUser({ + data: { steamID: BigInt(Number.MAX_SAFE_INTEGER) * 2n } + }), db.createUser(), db.createUser(), db.createMapWithFullLeaderboards() // Creates a bunch of ahop leaderboards @@ -1303,6 +1305,21 @@ describe('Maps Part 2', () => { expect(res.body.data[1].userID).toBe(u3.id); }); + it('should respond with filtered runs given using the steamIDs param', async () => { + const res = await req.get({ + url: `maps/${map.id}/leaderboard`, + query: { + gamemode: Gamemode.AHOP, + steamIDs: `${u1.steamID},${u3.steamID}` + }, + validatePaged: { type: MinimalLeaderboardRunDto, count: 2 }, + token + }); + + expect(res.body.data[0].userID).toBe(u1.id); + expect(res.body.data[1].userID).toBe(u3.id); + }); + // Test that permissions checks are getting called // Yes, u1 has runs on the map, but we don't actually test for that it('should 403 if the user does not have permission to access to the map', async () => { diff --git a/apps/backend/src/app/dto/queries/map-queries.dto.ts b/apps/backend/src/app/dto/queries/map-queries.dto.ts index d668b3188..39ec54259 100644 --- a/apps/backend/src/app/dto/queries/map-queries.dto.ts +++ b/apps/backend/src/app/dto/queries/map-queries.dto.ts @@ -36,6 +36,7 @@ import { IntQueryProperty, SingleExpandQueryProperty, SkipQueryProperty, + StringCsvQueryProperty, StringQueryProperty, TakeQueryProperty } from '../decorators'; @@ -228,9 +229,14 @@ export class MapLeaderboardGetQueryDto }) readonly filter?: MapRunsGetFilter; - @IntCsvQueryProperty({ description: 'List of users to limit results to' }) + @IntCsvQueryProperty({ description: 'List of user IDs to limit results to' }) readonly filterUserIDs?: number[]; + @StringCsvQueryProperty({ + description: 'List of user Steam IDs to limit results to' + }) + readonly steamIDs?: string[]; + @BooleanQueryProperty({ description: 'Whether to order by date or not (false for reverse)' }) diff --git a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts index f341f1298..77fc5a614 100644 --- a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts +++ b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts @@ -79,6 +79,10 @@ export class LeaderboardRunsService { where.userID = { in: query.filterUserIDs }; } + if (query.steamIDs) { + where.user = { steamID: { in: query.steamIDs.map(BigInt) } }; + } + const select = { ...this.minimalRunsSelect, stats: Boolean(query.expand) @@ -106,10 +110,6 @@ export class LeaderboardRunsService { } }; - // if (query.filterUserIDs) { - // where.userID = { in: query.filterUserIDs }; - // } - const userRun = await this.db.leaderboardRun.findUnique({ where: whereAround }); @@ -132,7 +132,7 @@ export class LeaderboardRunsService { if (steamFriends.length === 0) throw new GoneException('No friends detected :('); - // Doing this with a window function is gonna be fun... + // Overrides filterSteamIDs if exists where.user = { steamID: { in: steamFriends.map((item) => BigInt(item.steamid)) } }; diff --git a/libs/constants/src/types/queries/map-queries.model.ts b/libs/constants/src/types/queries/map-queries.model.ts index b0b403b33..01f392968 100644 --- a/libs/constants/src/types/queries/map-queries.model.ts +++ b/libs/constants/src/types/queries/map-queries.model.ts @@ -184,6 +184,7 @@ export type MapLeaderboardGetQuery = PagedQuery & { expand?: MapRunsGetExpand; filter?: MapRunsGetFilter; filterUserIDs?: number[]; + steamIDs?: string[]; orderByDate?: boolean; }; From bdae4e9403de1e2f81380e0b3ba582eb50cfd18a Mon Sep 17 00:00:00 2001 From: tsa96 Date: Tue, 12 Nov 2024 23:01:36 +0000 Subject: [PATCH 3/8] refactor(back): rename maps/:id/leaderboard GET filterUserIDs param to userIDs Just me being silly. Also, we may find requests using this param are have very long params, exceeding --- apps/backend-e2e/src/maps-2.e2e-spec.ts | 6 +++--- apps/backend/src/app/dto/queries/map-queries.dto.ts | 2 +- .../src/app/modules/runs/leaderboard-runs.service.ts | 4 ++-- libs/constants/src/types/queries/map-queries.model.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/backend-e2e/src/maps-2.e2e-spec.ts b/apps/backend-e2e/src/maps-2.e2e-spec.ts index 13143155f..8d12f4b2e 100644 --- a/apps/backend-e2e/src/maps-2.e2e-spec.ts +++ b/apps/backend-e2e/src/maps-2.e2e-spec.ts @@ -1290,12 +1290,12 @@ describe('Maps Part 2', () => { token })); - it('should respond with filtered runs given using the filterUserID param', async () => { + it('should respond with filtered runs given using the userIDs param', async () => { const res = await req.get({ url: `maps/${map.id}/leaderboard`, query: { gamemode: Gamemode.AHOP, - filterUserIDs: `${u1.id},${u3.id}` + userIDs: `${u1.id},${u3.id}` }, validatePaged: { type: MinimalLeaderboardRunDto, count: 2 }, token @@ -1394,7 +1394,7 @@ describe('Maps Part 2', () => { query: { gamemode: Gamemode.AHOP, filter: 'around', - filterUserIDs: u7.id + userIDs: u7.id }, status: 200, token: u7Token, diff --git a/apps/backend/src/app/dto/queries/map-queries.dto.ts b/apps/backend/src/app/dto/queries/map-queries.dto.ts index 39ec54259..280648a5d 100644 --- a/apps/backend/src/app/dto/queries/map-queries.dto.ts +++ b/apps/backend/src/app/dto/queries/map-queries.dto.ts @@ -230,7 +230,7 @@ export class MapLeaderboardGetQueryDto readonly filter?: MapRunsGetFilter; @IntCsvQueryProperty({ description: 'List of user IDs to limit results to' }) - readonly filterUserIDs?: number[]; + readonly userIDs?: number[]; @StringCsvQueryProperty({ description: 'List of user Steam IDs to limit results to' diff --git a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts index 77fc5a614..3e4152cfb 100644 --- a/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts +++ b/apps/backend/src/app/modules/runs/leaderboard-runs.service.ts @@ -75,8 +75,8 @@ export class LeaderboardRunsService { style: query.style }; - if (query.filterUserIDs) { - where.userID = { in: query.filterUserIDs }; + if (query.userIDs) { + where.userID = { in: query.userIDs }; } if (query.steamIDs) { diff --git a/libs/constants/src/types/queries/map-queries.model.ts b/libs/constants/src/types/queries/map-queries.model.ts index 01f392968..bc1aed4bf 100644 --- a/libs/constants/src/types/queries/map-queries.model.ts +++ b/libs/constants/src/types/queries/map-queries.model.ts @@ -183,7 +183,7 @@ export type MapLeaderboardGetQuery = PagedQuery & { style?: Style; // Default 0 expand?: MapRunsGetExpand; filter?: MapRunsGetFilter; - filterUserIDs?: number[]; + userIDs?: number[]; steamIDs?: string[]; orderByDate?: boolean; }; From 1e67b8a31974d53f8a7f8784dcd5dc60e33627b8 Mon Sep 17 00:00:00 2001 From: tsa96 Date: Tue, 12 Nov 2024 22:46:44 +0000 Subject: [PATCH 4/8] fix(back): use strings for users/ GET steamIDs param, fix type Before, IntCsvQueryProperty was parsing this to BigInt at runtime, but TypeScript thought it was a string. The code just happened to work, even though types made no sense. I don't know if bigints or strings are fastest here, just sticking with strings since they're simplest. --- apps/backend/src/app/dto/queries/user-queries.dto.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/backend/src/app/dto/queries/user-queries.dto.ts b/apps/backend/src/app/dto/queries/user-queries.dto.ts index c7bec83dc..c0f7e69ce 100644 --- a/apps/backend/src/app/dto/queries/user-queries.dto.ts +++ b/apps/backend/src/app/dto/queries/user-queries.dto.ts @@ -18,7 +18,8 @@ import { EnumQueryProperty, ExpandQueryProperty, IntCsvQueryProperty, - IntQueryProperty + IntQueryProperty, + StringCsvQueryProperty } from '../decorators'; import { IsBigInt } from '../../validators'; import { PagedQueryDto } from './pagination.dto'; @@ -64,12 +65,10 @@ export class UsersGetAllQueryDto @IsOptional() readonly steamID?: string; - @IntCsvQueryProperty({ + @StringCsvQueryProperty({ description: 'Filter by CSV list of Steam Community IDs', - example: '123135674,7987347263,98312287631', - bigint: true + example: '123135674,7987347263,98312287631' }) - @IsBigInt({ each: true }) @IsOptional() readonly steamIDs?: string[]; From ab1c850b668845a2c557d666f2487e40a94ec7fa Mon Sep 17 00:00:00 2001 From: tsa96 Date: Sat, 16 Nov 2024 04:43:15 +0000 Subject: [PATCH 5/8] refactor(shared): rename CombinedMapStatuses to MapStatuses I'm using this a lot in Pano and this name is so UGLY --- apps/backend-e2e/src/maps-2.e2e-spec.ts | 4 ++-- apps/backend-e2e/src/maps.e2e-spec.ts | 8 ++++---- .../src/app/modules/maps/map-credits.service.ts | 4 ++-- .../src/app/modules/maps/map-image.service.ts | 6 ++---- .../backend/src/app/modules/maps/maps.service.ts | 16 ++++++++-------- .../home/user-maps/home-user-maps.component.ts | 4 ++-- .../pages/maps/map-edit/map-edit.component.ts | 6 ++---- .../pages/maps/map-info/map-info.component.ts | 6 +++--- libs/constants/src/enums/map-status.enum.ts | 2 +- scripts/src/seed.script.ts | 4 ++-- 10 files changed, 28 insertions(+), 32 deletions(-) diff --git a/apps/backend-e2e/src/maps-2.e2e-spec.ts b/apps/backend-e2e/src/maps-2.e2e-spec.ts index 8d12f4b2e..b66ceead1 100644 --- a/apps/backend-e2e/src/maps-2.e2e-spec.ts +++ b/apps/backend-e2e/src/maps-2.e2e-spec.ts @@ -28,7 +28,7 @@ import { import { ActivityType, AdminActivityType, - CombinedMapStatuses, + MapStatuses, Gamemode, MapCreditType, MapStatus, @@ -1141,7 +1141,7 @@ describe('Maps Part 2', () => { }); for (const status of Enum.values(MapStatus)) { - const shouldPass = CombinedMapStatuses.IN_SUBMISSION.includes(status); + const shouldPass = MapStatuses.IN_SUBMISSION.includes(status); const expectedStatus = shouldPass ? 200 : 403; it(`should ${expectedStatus} if the map is not in the ${MapStatus[status]} state`, async () => { diff --git a/apps/backend-e2e/src/maps.e2e-spec.ts b/apps/backend-e2e/src/maps.e2e-spec.ts index 5ac9d7d3f..ff500e359 100644 --- a/apps/backend-e2e/src/maps.e2e-spec.ts +++ b/apps/backend-e2e/src/maps.e2e-spec.ts @@ -8,7 +8,7 @@ import crypto from 'node:crypto'; import { ActivityType, Ban, - CombinedMapStatuses, + MapStatuses, Gamemode as GM, Gamemode, MapCreditType, @@ -984,7 +984,7 @@ describe('Maps', () => { await db.createMap({ submitter: { connect: { id: user.id } }, - status: CombinedMapStatuses.IN_SUBMISSION[1] + status: MapStatuses.IN_SUBMISSION[1] }); await req.postAttach({ @@ -1012,7 +1012,7 @@ describe('Maps', () => { }); describe('Permission checks', () => { - for (const status of CombinedMapStatuses.IN_SUBMISSION) { + for (const status of MapStatuses.IN_SUBMISSION) { it(`should 403 if the user already has a map in ${MapStatus[status]} and is not a MAPPER`, async () => { await db.createMap({ submitter: { connect: { id: user.id } }, @@ -2529,7 +2529,7 @@ describe('Maps', () => { Promise.all([db.cleanup('mMap'), fileStore.deleteDirectory('maplist')]) ); - for (const status of CombinedMapStatuses.IN_SUBMISSION) { + for (const status of MapStatuses.IN_SUBMISSION) { it(`should allow the submitter to change most data during ${MapStatus[status]}`, async () => { const map = await db.createMap({ ...createMapData, status }); diff --git a/apps/backend/src/app/modules/maps/map-credits.service.ts b/apps/backend/src/app/modules/maps/map-credits.service.ts index 66166bfd5..8054ff77c 100644 --- a/apps/backend/src/app/modules/maps/map-credits.service.ts +++ b/apps/backend/src/app/modules/maps/map-credits.service.ts @@ -10,7 +10,7 @@ import { Prisma } from '@prisma/client'; import * as Bitflags from '@momentum/bitflags'; import { ActivityType, - CombinedMapStatuses, + MapStatuses, CombinedRoles, MapCreditsGetExpand, MapCreditType @@ -114,7 +114,7 @@ export class MapCreditsService { throw new ForbiddenException('User is not the submitter of this map'); } - if (!CombinedMapStatuses.IN_SUBMISSION.includes(map.status)) { + if (!MapStatuses.IN_SUBMISSION.includes(map.status)) { throw new ForbiddenException('Cannot change map in its current state'); } } diff --git a/apps/backend/src/app/modules/maps/map-image.service.ts b/apps/backend/src/app/modules/maps/map-image.service.ts index 7d148d06c..c1decf31a 100644 --- a/apps/backend/src/app/modules/maps/map-image.service.ts +++ b/apps/backend/src/app/modules/maps/map-image.service.ts @@ -9,7 +9,7 @@ import { } from '@nestjs/common'; import { AdminActivityType, - CombinedMapStatuses, + MapStatuses, CombinedRoles, imgLargePath, imgMediumPath, @@ -66,9 +66,7 @@ export class MapImageService { const isMod = Bitflags.has(user.roles, CombinedRoles.MOD_OR_ADMIN); const isSubmitter = map.submitterID === userID; - const isInSubmission = CombinedMapStatuses.IN_SUBMISSION.includes( - map.status - ); + const isInSubmission = MapStatuses.IN_SUBMISSION.includes(map.status); if (!isMod) { if (!isSubmitter) diff --git a/apps/backend/src/app/modules/maps/maps.service.ts b/apps/backend/src/app/modules/maps/maps.service.ts index 40ce3a6b4..255e882db 100644 --- a/apps/backend/src/app/modules/maps/maps.service.ts +++ b/apps/backend/src/app/modules/maps/maps.service.ts @@ -21,7 +21,7 @@ import { AdminActivityType, Ban, bspPath, - CombinedMapStatuses, + MapStatuses, CombinedRoles, FlatMapList, LeaderboardType, @@ -204,8 +204,8 @@ export class MapsService { if (Bitflags.has(CombinedRoles.MOD_OR_ADMIN, roles)) { where.status = { in: filter - ? intersection(filter, CombinedMapStatuses.IN_SUBMISSION) - : CombinedMapStatuses.IN_SUBMISSION + ? intersection(filter, MapStatuses.IN_SUBMISSION) + : MapStatuses.IN_SUBMISSION }; } else if (Bitflags.has(Role.REVIEWER, roles)) { if (filter?.length > 0) { @@ -639,7 +639,7 @@ export class MapsService { throw new ForbiddenException('User is banned from map submission'); } - if (!CombinedMapStatuses.IN_SUBMISSION.includes(map.status)) { + if (!MapStatuses.IN_SUBMISSION.includes(map.status)) { throw new ForbiddenException('Map does not allow editing'); } @@ -759,7 +759,7 @@ export class MapsService { if ( !Bitflags.has(user.roles, CombinedRoles.MAPPER_AND_ABOVE) && user.submittedMaps.some((map) => - CombinedMapStatuses.IN_SUBMISSION.includes(map.status) + MapStatuses.IN_SUBMISSION.includes(map.status) ) ) { throw new ForbiddenException( @@ -1022,7 +1022,7 @@ export class MapsService { if (userID !== map.submitterID) throw new ForbiddenException('User is not the map submitter'); - if (!CombinedMapStatuses.IN_SUBMISSION.includes(map.status)) + if (!MapStatuses.IN_SUBMISSION.includes(map.status)) throw new ForbiddenException('Map can only be edited during submission'); // Force the submitter to keep their suggestions in sync with their zones. @@ -1246,7 +1246,7 @@ export class MapsService { const numInSubmissionStatuses = intersection( [newStatus, oldStatus], - CombinedMapStatuses.IN_SUBMISSION + MapStatuses.IN_SUBMISSION ).length; // If going from submission -> submission just update submission list, @@ -1615,7 +1615,7 @@ export class MapsService { ); await this.mapListService.updateMapList( - CombinedMapStatuses.IN_SUBMISSION.includes(map.status) + MapStatuses.IN_SUBMISSION.includes(map.status) ? FlatMapList.SUBMISSION : FlatMapList.APPROVED ); diff --git a/apps/frontend/src/app/pages/home/user-maps/home-user-maps.component.ts b/apps/frontend/src/app/pages/home/user-maps/home-user-maps.component.ts index 8a1e41f34..fd0297b06 100644 --- a/apps/frontend/src/app/pages/home/user-maps/home-user-maps.component.ts +++ b/apps/frontend/src/app/pages/home/user-maps/home-user-maps.component.ts @@ -1,5 +1,5 @@ import { Component, DestroyRef, OnInit } from '@angular/core'; -import { CombinedMapStatuses, MapStatus } from '@momentum/constants'; +import { MapStatuses, MapStatus } from '@momentum/constants'; import { SharedModule } from '../../../shared.module'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { LocalUserService } from '../../../services/data/local-user.service'; @@ -39,7 +39,7 @@ export class HomeUserMapsComponent implements OnInit { this.submission = response .filter(({ status }) => - CombinedMapStatuses.IN_SUBMISSION.includes(status) + MapStatuses.IN_SUBMISSION.includes(status) ) .map(({ statusCount }) => statusCount) .reduce((a, c) => a + c, 0) ?? 0; diff --git a/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.ts b/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.ts index 8f1dcdbe7..5deab89b8 100644 --- a/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.ts +++ b/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.ts @@ -16,7 +16,7 @@ import { } from '@angular/forms'; import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; import { - CombinedMapStatuses, + MapStatuses, DateString, LeaderboardType, MAP_IMAGE_HEIGHT, @@ -258,9 +258,7 @@ export class MapEditComponent implements OnInit, ConfirmDeactivate { this.isMod = this.localUserService.isMod; this.isSubmitter = map.submitterID === this.localUserService.user.value?.id; - this.inSubmission = CombinedMapStatuses.IN_SUBMISSION.includes( - map.status - ); + this.inSubmission = MapStatuses.IN_SUBMISSION.includes(map.status); if ( !this.isAdmin && diff --git a/apps/frontend/src/app/pages/maps/map-info/map-info.component.ts b/apps/frontend/src/app/pages/maps/map-info/map-info.component.ts index 4fc8cfb5c..d4821c1d4 100644 --- a/apps/frontend/src/app/pages/maps/map-info/map-info.component.ts +++ b/apps/frontend/src/app/pages/maps/map-info/map-info.component.ts @@ -2,7 +2,7 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Component, DestroyRef, OnInit } from '@angular/core'; import { switchMap, tap } from 'rxjs/operators'; import { - CombinedMapStatuses, + MapStatuses, DateString, MapCreditNames, MapCreditType, @@ -160,11 +160,11 @@ export class MapInfoComponent implements OnInit { this.prefix = prefix; this.credits = new GroupedMapCredits( this.map.credits ?? [], - CombinedMapStatuses.IN_SUBMISSION.includes(this.map.status) + MapStatuses.IN_SUBMISSION.includes(this.map.status) ? (this.map.submission?.placeholders ?? []) : [] ); - this.inSubmission = CombinedMapStatuses.IN_SUBMISSION.includes(map.status); + this.inSubmission = MapStatuses.IN_SUBMISSION.includes(map.status); // Show Review section first if in review, otherwise leaderboards (and the // tab view won't be visible anyway). this.currentSection = this.inSubmission diff --git a/libs/constants/src/enums/map-status.enum.ts b/libs/constants/src/enums/map-status.enum.ts index bf3ffda09..808479797 100644 --- a/libs/constants/src/enums/map-status.enum.ts +++ b/libs/constants/src/enums/map-status.enum.ts @@ -15,7 +15,7 @@ export enum MapStatus { DISABLED = 5 } -export const CombinedMapStatuses = Object.freeze({ +export const MapStatuses = Object.freeze({ IN_SUBMISSION: [ MapStatus.PRIVATE_TESTING, MapStatus.PUBLIC_TESTING, diff --git a/scripts/src/seed.script.ts b/scripts/src/seed.script.ts index f72421b9a..67f237266 100644 --- a/scripts/src/seed.script.ts +++ b/scripts/src/seed.script.ts @@ -15,7 +15,7 @@ import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; import { ActivityType, Ban, - CombinedMapStatuses, + MapStatuses, Gamemode, LeaderboardType, MapCreditType, @@ -449,7 +449,7 @@ prismaWrapper(async (prisma: PrismaClient) => { }); const status = Random.weighted(weights.mapStatusWeights); - const inSubmission = CombinedMapStatuses.IN_SUBMISSION.includes(status); + const inSubmission = MapStatuses.IN_SUBMISSION.includes(status); //#region Leaderboards, suggestions, etc... From 259c55650f143d59239a1a59960084b432c9c46c Mon Sep 17 00:00:00 2001 From: tsa96 Date: Sat, 16 Nov 2024 04:43:32 +0000 Subject: [PATCH 6/8] feat(shared): MapStatuses.PRIVATE --- libs/constants/src/enums/map-status.enum.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/constants/src/enums/map-status.enum.ts b/libs/constants/src/enums/map-status.enum.ts index 808479797..873b57d80 100644 --- a/libs/constants/src/enums/map-status.enum.ts +++ b/libs/constants/src/enums/map-status.enum.ts @@ -21,5 +21,6 @@ export const MapStatuses = Object.freeze({ MapStatus.PUBLIC_TESTING, MapStatus.CONTENT_APPROVAL, MapStatus.FINAL_APPROVAL - ] + ], + PRIVATE: [MapStatus.PRIVATE_TESTING, MapStatus.CONTENT_APPROVAL] }); From f4a6bc4be241580c03a3f2ef8d738d3549239133 Mon Sep 17 00:00:00 2001 From: tsa96 Date: Sat, 16 Nov 2024 04:44:47 +0000 Subject: [PATCH 7/8] refactor(front): prevent enter key from submitting map submission/edit forms Pressed this accidentally when adding private testers, accidentally submitted the entire thing. Not good! --- .../src/app/pages/maps/map-edit/map-edit.component.html | 2 ++ .../maps/submission-form/map-submission-form.component.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.html b/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.html index 3a2cfbb1d..91b80e0ba 100644 --- a/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.html +++ b/apps/frontend/src/app/pages/maps/map-edit/map-edit.component.html @@ -11,6 +11,7 @@ [mSpinner]="loading" class="card card--fancy my-8 flex-col overflow-visible p-4" [ngClass]="isSubmitter ? '!flex' : '!hidden'" + (keydown.enter)="$event.preventDefault()" >
Submit New Version
@@ -83,6 +84,7 @@ [mSpinner]="loading" class="card card--fancy my-8 flex-col gap-8 overflow-visible p-4" [ngClass]="isAdmin || isMod || isSubmitter ? '!flex' : '!hidden'" + (keydown.enter)="$event.preventDefault()" >

Update Map

diff --git a/apps/frontend/src/app/pages/maps/submission-form/map-submission-form.component.html b/apps/frontend/src/app/pages/maps/submission-form/map-submission-form.component.html index 0ccfb5a49..0c803047c 100644 --- a/apps/frontend/src/app/pages/maps/submission-form/map-submission-form.component.html +++ b/apps/frontend/src/app/pages/maps/submission-form/map-submission-form.component.html @@ -15,7 +15,7 @@

Welcome to the Momentum Mod map submission page!

Discord, where you can also find all sorts of mapping support and helpful people!

-
+
From a63e1342135c598971762e67921fada9559a65bb Mon Sep 17 00:00:00 2001 From: tsa96 Date: Sat, 16 Nov 2024 04:46:46 +0000 Subject: [PATCH 8/8] refactor(front): show up to 2 authors in map-list-item, better styling --- .../map-list/map-list-item.component.html | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/apps/frontend/src/app/components/map-list/map-list-item.component.html b/apps/frontend/src/app/components/map-list/map-list-item.component.html index 504369207..3f2ce9c0c 100644 --- a/apps/frontend/src/app/components/map-list/map-list-item.component.html +++ b/apps/frontend/src/app/components/map-list/map-list-item.component.html @@ -78,45 +78,49 @@ {{ name }}

-
-
-

- By - @if (authors[0].id) { - {{ authors[0].alias }} - } @else { - {{ authors[0].alias }} - } - - @if (authors.length > 1) {, + {{ authors.length - 1 | plural: 'author' }}} -

- @if (isSubmission) { - @if (!isSubmitterAnAuthor(map)) { -

- Submitted by - {{ map.submitter.alias }} -

- } +
+

+ By + @if (authors[0]?.id) { + {{ authors[0].alias }} + } @else { + {{ authors[0].alias }} } - @if (isAdminPage || isSubmission) { -

- @if (map.status !== MapStatus.DISABLED) { - Current Status: - {{ MapStatusName.get(map.status) }} + + @if (authors.length > 1) {, + @if (authors[1]?.id) { + {{ authors[1].alias }} } @else { - Current Status: - {{ MapStatusName.get(map.status) }} - @if (!map.currentVersion?.bspHash) { - Files deleted! - } + {{ authors[1].alias }} } + } + + @if (authors.length > 2) {, + {{ authors.length - 1 | plural: 'author' }}} +

+ @if (isSubmission) { + @if (!isSubmitterAnAuthor(map)) { +

+ Submitted by + {{ map.submitter.alias }}

} -
-
- -

Added {{ map.createdAt | timeAgo }}

-
+ } + @if (isAdminPage || isSubmission) { +

+ @if (map.status !== MapStatus.DISABLED) { + Current Status: + {{ MapStatusName.get(map.status) }} + } @else { + Current Status: + {{ MapStatusName.get(map.status) }} + @if (!map.currentVersion?.bspHash) { + Files deleted! + } + } +

+ } + +

Added {{ map.createdAt | timeAgo }}