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

Map review endpoints #905

Merged
merged 39 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a7f85a8
chore(deps): bump submodules
tsa96 Jan 8, 2024
c9b4269
chore(deps): remove duplicate Axios dependency
tsa96 Jan 23, 2024
82fc28a
feat(utils): add deepEqual fn
tsa96 Jan 24, 2024
796f657
feat(formats): add MapReviewSuggestion support to suggestions validator
tsa96 Jan 24, 2024
3c2da69
fix(back-e2e): use fancy FormData handling in correct cases in reques…
tsa96 Jan 24, 2024
4f86f34
fix(back): support deleting >1000 files at once
tsa96 Jan 26, 2024
dd40c8b
refactor(front): allow returning patched object in HttpService patch()
tsa96 Feb 3, 2024
6321f64
chore(back): remove empty /admin/user-stats endpoint
tsa96 Feb 7, 2024
32cccb4
docs(back): fix type in CreateMapDto swagger docs
tsa96 Feb 7, 2024
b11853f
chore(back): move is-bigint.validator
tsa96 Feb 8, 2024
776698b
feat(back): ImageFileValidator
tsa96 Feb 8, 2024
fb4a90e
feat(back): ParseFilesPipe
tsa96 Feb 8, 2024
f0425de
feat(back): use image file validators in map image endpoints
tsa96 Feb 8, 2024
6c26641
chore(back): consolidate MAX_IMAGE_SIZE/MAX_MAP_IMAGE_SIZE constants
tsa96 Feb 8, 2024
8ced0f5
refactor(back): update models/dtos for map reviews
tsa96 Feb 8, 2024
c3e96aa
feat(back-e2e): downloadHttp util
tsa96 Feb 8, 2024
aa308fb
chore(eslint): error on angular/nest imports in opposite projects
tsa96 Feb 8, 2024
08ba35d
build(deps): add explicit dependency on uuid
tsa96 Feb 8, 2024
d8b24cb
refactor(back): support only allowing submission maps in getMapAndChe…
tsa96 Feb 8, 2024
ddf845d
feat(back): improve existing map review endpoints and move to separat…
tsa96 Feb 8, 2024
a0bcef2
feat(back): /maps/:mapID/reviews POST
tsa96 Feb 8, 2024
d3edac9
feat(front): /map-review/:reviewID PATCH
tsa96 Feb 8, 2024
3c83231
feat(back): /admin/map-review/:reviewID PATCH
tsa96 Feb 8, 2024
0ccb54b
feat(back): /admin/map-review/:reviewID DELETE
tsa96 Feb 8, 2024
ec98da1
feat(back): /map-review/:reviewID/comments GET
tsa96 Feb 20, 2024
143a9f3
feat(back): /map-review/:reviewID/comments POST
tsa96 Feb 8, 2024
f1a26a7
feat(back): /map-review/comments/:commentID PATCH
tsa96 Feb 8, 2024
8e313d7
feat(back): /map-review/comments/:commentID DELETE
tsa96 Feb 8, 2024
9ea0f84
docs(back): improve map update swagger docs
tsa96 Feb 8, 2024
61e384a
style(db): lowerCamelCase for User adminActivities field
tsa96 Feb 8, 2024
455d0c4
refactor(shared): missing fields on some map submission models
tsa96 Feb 8, 2024
597b789
refactor(shared): tiny type simplification in CreateMapCredit model
tsa96 Feb 8, 2024
3325764
fix(formats): don't generate stage zones in generateRandomMapZones if…
tsa96 Feb 8, 2024
bc3161c
refactor(back-e2e): generate submitter and submission in db.createMap…
tsa96 Feb 8, 2024
0859383
feat(back): delete map review files after approval
tsa96 Feb 8, 2024
40213c8
feat(back): block map approval if map has outstanding reviews
tsa96 Feb 8, 2024
acf99f0
refactor(back): use tabs in exception-handler logs
tsa96 Feb 7, 2024
08d513b
feat(front): support MapReviewSuggestions in suggestionsValidator
tsa96 Feb 8, 2024
b5762b0
fix(back): include full map submission versions in map submission pos…
tsa96 Feb 8, 2024
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
8 changes: 8 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ module.exports = {
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'frontend',
bannedExternalImports: ['@nestjs/common', '@prisma/client']
},
{
sourceTag: 'backend',
bannedExternalImports: ['@angular/core']
},
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*']
Expand Down
Binary file added apps/backend-e2e/files/1920_1080.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend-e2e/files/2560_1440.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend-e2e/files/2560_1440.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/backend-e2e/files/very_large_image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
285 changes: 285 additions & 0 deletions apps/backend-e2e/src/admin.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import {
AdminActivityDto,
MapDto,
MapReviewDto,
ReportDto,
UserDto
} from '../../backend/src/app/dto';
Expand All @@ -15,6 +16,7 @@ import {
Ban,
Gamemode,
MapCreditType,
mapReviewAssetPath,
MapStatus,
MapStatusNew,
MapSubmissionType,
Expand Down Expand Up @@ -2268,6 +2270,79 @@ describe('Admin', () => {
).toHaveLength(0);
});

it('should delete any map review assets after map status changed from FA to approved', async () => {
const assetPath = mapReviewAssetPath('1');

await prisma.mapReview.create({
data: {
mainText:
'This map doesn’t scrape the bottom of the barrel. ' +
'This map isn’t the bottom of the barrel. ' +
'This map isn’t below the bottom of the barrel. ' +
'This map doesn’t deserve to be mentioned in the same sentence with barrels.',
resolved: true,
imageIDs: ['1'],
mmap: { connect: { id: map.id } },
reviewer: { connect: { id: mod.id } }
}
});

await fileStore.add(assetPath, Buffer.alloc(1024));
expect(await fileStore.exists(assetPath)).toBe(true);

await req.patch({
url: `admin/maps/${map.id}`,
status: 204,
body: { status: MapStatus.APPROVED, finalLeaderboards },
token: adminToken
});

expect(await fileStore.exists(assetPath)).toBe(false);
});

it('should 400 if map has unresolved reviews', async () => {
await prisma.mapReview.create({
data: {
resolved: false,
mainText:
'I hated this map. ' +
'Hated hated hated hated hated this map. Hated it. ' +
'Hated every simpering stupid vacant player-insulting stage of it',
imageIDs: ['1'],
mmap: { connect: { id: map.id } },
reviewer: { connect: { id: mod.id } }
}
});

await req.patch({
url: `admin/maps/${map.id}`,
status: 400,
body: { status: MapStatus.APPROVED, finalLeaderboards },
token: adminToken
});
});

it('should succeed if map has resolved=null reviews', async () => {
await prisma.mapReview.create({
data: {
resolved: null,
mainText:
'Was there no one connected with this project who read the ' +
'screenplay, considered the story, evaluated the proposed map and vomited?',
imageIDs: ['1'],
mmap: { connect: { id: map.id } },
reviewer: { connect: { id: mod.id } }
}
});

await req.patch({
url: `admin/maps/${map.id}`,
status: 204,
body: { status: MapStatus.APPROVED, finalLeaderboards },
token: adminToken
});
});

it('should 400 when moving from FA to approved if leaderboards are not provided', async () => {
await req.patch({
url: `admin/maps/${map.id}`,
Expand Down Expand Up @@ -2405,6 +2480,216 @@ describe('Admin', () => {
});
});

describe('admin/map-review/{reviewID}', () => {
describe('PATCH', () => {
let u1, u1Token, adminToken, modToken, reviewerToken, map, review;

beforeAll(async () => {
[[u1, u1Token], adminToken, modToken, reviewerToken] =
await Promise.all([
db.createAndLoginUser(),
db.loginNewUser({ data: { roles: Role.ADMIN } }),
db.loginNewUser({ data: { roles: Role.MODERATOR } }),
db.loginNewUser({ data: { roles: Role.REVIEWER } })
]);

map = await db.createMap({ status: MapStatusNew.PUBLIC_TESTING });
});

afterAll(() => db.cleanup('mMap', 'user'));

beforeEach(async () => {
review = await prisma.mapReview.create({
data: {
mainText: 'im sick today so cba to think of silly messages',
suggestions: [
{
trackType: TrackType.MAIN,
trackNum: 0,
gamemode: Gamemode.AHOP,
tier: 10,
gameplayRating: 1
}
],
mmap: { connect: { id: map.id } },
reviewer: { connect: { id: u1.id } },
resolved: false
}
});
});

afterEach(() => db.cleanup('mapReview'));

it('should allow admin to update resolved status', async () => {
const res = await req.patch({
url: `admin/map-review/${review.id}`,
status: 200,
body: { resolved: true },
validate: MapReviewDto,
token: adminToken
});

expect(res.body.resolved).toBe(true);
});

it('should allow mod to update resolved status', async () => {
const res = await req.patch({
url: `admin/map-review/${review.id}`,
status: 200,
body: { resolved: true },
validate: MapReviewDto,
token: modToken
});

expect(res.body.resolved).toBe(true);
});

it('should allow reviewer to update resolved status', async () => {
const res = await req.patch({
url: `admin/map-review/${review.id}`,
status: 200,
body: { resolved: true },
validate: MapReviewDto,
token: reviewerToken
});

expect(res.body.resolved).toBe(true);
});

it('should not allow review author to access', () =>
req.patch({
url: `admin/map-review/${review.id}`,
status: 403,
body: { resolved: true },
token: u1Token
}));

it('should 400 for bad update data', () =>
req.patch({
url: `admin/map-review/${review.id}`,
status: 400,
body: { mainText: "admins cant rewrite people's reviews" },
token: adminToken
}));

it('should return 404 for missing review', () =>
req.patch({
url: `admin/map-review/${NULL_ID}`,
status: 404,
body: { resolved: true },
token: adminToken
}));

it('should 401 when no access token is provided', () =>
req.unauthorizedTest('admin/map-review/1', 'patch'));
});

describe('DELETE', () => {
let u1, u1Token, adminToken, modToken, reviewerToken, map, review;
const assetPath = mapReviewAssetPath('1');

beforeAll(async () => {
[[u1, u1Token], adminToken, modToken, reviewerToken] =
await Promise.all([
db.createAndLoginUser(),
db.loginNewUser({ data: { roles: Role.ADMIN } }),
db.loginNewUser({ data: { roles: Role.MODERATOR } }),
db.loginNewUser({ data: { roles: Role.REVIEWER } })
]);

map = await db.createMap({ status: MapStatusNew.PUBLIC_TESTING });
});

afterAll(() => db.cleanup('mMap', 'user'));

beforeEach(async () => {
review = await prisma.mapReview.create({
data: {
mainText: 'auuaghauguhhh!!',
imageIDs: ['1'],
suggestions: [
{
trackType: TrackType.MAIN,
trackNum: 0,
gamemode: Gamemode.AHOP,
tier: 10,
gameplayRating: 1
}
],
mmap: { connect: { id: map.id } },
reviewer: { connect: { id: u1.id } },
resolved: false
}
});

await fileStore.add(assetPath, Buffer.alloc(1024));
});

afterEach(() => db.cleanup('mapReview'));

it('should allow an admin to delete a map review', async () => {
await req.del({
url: `admin/map-review/${review.id}`,
status: 204,
token: adminToken
});

expect(
await prisma.mapReview.findUnique({ where: { id: review.id } })
).toBeNull();
});

it('should delete any stored assets', async () => {
expect(await fileStore.exists(assetPath)).toBe(true);

await req.del({
url: `admin/map-review/${review.id}`,
status: 204,
token: adminToken
});

expect(await fileStore.exists(assetPath)).toBe(false);
});

it('should allow a mod to delete a map review', async () => {
await req.del({
url: `admin/map-review/${review.id}`,
status: 204,
token: modToken
});

expect(
await prisma.mapReview.findUnique({ where: { id: review.id } })
).toBeNull();
});

it('should not allow a reviewer to delete a map review', async () => {
await req.del({
url: `admin/map-review/${review.id}`,
status: 403,
token: reviewerToken
});
});

it('should not allow review author to delete a map review (via this endpoint)', () =>
req.del({
url: `admin/map-review/${review.id}`,
status: 403,
token: u1Token
}));

it('should return 404 for missing review', () =>
req.del({
url: `admin/map-review/${NULL_ID}`,
status: 404,
token: adminToken
}));

it('should 401 when no access token is provided', () =>
req.unauthorizedTest('admin/map-review/1', 'del'));
});
});

describe('admin/reports', () => {
describe('GET', () => {
let adminToken, u1, u1Token, r1, _r2;
Expand Down
Loading
Loading