-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add internal server error * add clan routes
- Loading branch information
Showing
21 changed files
with
1,418 additions
and
68 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { cleanupPostgresAfterAll } from "../../routes/testUtil" | ||
import { zClanLeaderboardEntry } from "../../schema/components/Clan" | ||
import { getClanStats } from "../clan" | ||
|
||
cleanupPostgresAfterAll() | ||
|
||
describe("getClanStats", () => { | ||
it("returns the correct shape", async () => { | ||
const data = await getClanStats("3148408").catch(console.error) | ||
|
||
const parsed = zClanLeaderboardEntry.safeParse(data) | ||
if (!parsed.success) { | ||
expect(parsed.error.errors).toHaveLength(0) | ||
} else { | ||
expect(parsed.success).toBe(true) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { z } from "zod" | ||
import { cleanupPostgresAfterAll } from "../../../routes/testUtil" | ||
import { zClanLeaderboardEntry } from "../../../schema/components/Clan" | ||
import { getClanLeaderboard } from "../../leaderboard/clan" | ||
|
||
cleanupPostgresAfterAll() | ||
|
||
describe("getClanLeaderboard", () => { | ||
it("returns the correct shape", async () => { | ||
const data = await getClanLeaderboard({ | ||
skip: 0, | ||
take: 10, | ||
column: "weighted_contest_score" | ||
}).catch(console.error) | ||
|
||
const parsed = z.array(zClanLeaderboardEntry).safeParse(data) | ||
if (!parsed.success) { | ||
expect(parsed.error.errors).toHaveLength(0) | ||
} else { | ||
expect(parsed.data).toHaveLength(10) | ||
} | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { ClanLeaderboardEntry } from "../schema/components/Clan" | ||
import { postgres } from "../services/postgres" | ||
|
||
export const getClanStats = async (groupId: bigint | string) => { | ||
return await postgres.queryRow<ClanLeaderboardEntry>( | ||
`SELECT | ||
JSONB_BUILD_OBJECT( | ||
'groupId', clan."group_id", | ||
'name', clan.name, | ||
'callSign', clan.call_sign, | ||
'motto', clan."motto", | ||
'clanBannerData', clan."clan_banner_data", | ||
'lastUpdated', TO_CHAR(clan."updated_at" AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') | ||
) AS "clan", | ||
"known_member_count" AS "knownMemberCount", | ||
"clears", | ||
"average_clears" AS "averageClears", | ||
"fresh_clears" AS "freshClears", | ||
"average_fresh_clears" AS "averageFreshClears", | ||
"sherpas", | ||
"average_sherpas" AS "averageSherpas", | ||
"time_played_seconds" AS "timePlayedSeconds", | ||
"average_time_played_seconds" AS "averageTimePlayedSeconds", | ||
"total_contest_score" AS "totalContestScore", | ||
"weighted_contest_score" AS "weightedContestScore" | ||
FROM clan_leaderboard | ||
INNER JOIN clan USING (group_id) | ||
WHERE group_id = $1::bigint`, | ||
{ | ||
params: [groupId] | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { ClanLeaderboardEntry } from "../../schema/components/Clan" | ||
import { postgres } from "../../services/postgres" | ||
|
||
export const clanLeaderboardSortColumns = [ | ||
"clears", | ||
"average_clears", | ||
"fresh_clears", | ||
"average_fresh_clears", | ||
"sherpas", | ||
"average_sherpas", | ||
"time_played_seconds", | ||
"average_time_played_seconds", | ||
"total_contest_score", | ||
"weighted_contest_score" | ||
] as const | ||
|
||
const validateColumn = (column: (typeof clanLeaderboardSortColumns)[number]) => { | ||
if (!clanLeaderboardSortColumns.includes(column)) { | ||
// Just an extra layer of run-time validation to ensure that the column is one of the valid columns | ||
throw new TypeError(`Invalid column: ${column}`) | ||
} | ||
} | ||
|
||
export const getClanLeaderboard = async ({ | ||
skip, | ||
take, | ||
column | ||
}: { | ||
skip: number | ||
take: number | ||
column: (typeof clanLeaderboardSortColumns)[number] | ||
}) => { | ||
validateColumn(column) | ||
|
||
return await postgres.queryRows<ClanLeaderboardEntry>( | ||
`SELECT | ||
JSONB_BUILD_OBJECT( | ||
'groupId', clan."group_id", | ||
'name', clan.name, | ||
'callSign', clan.call_sign, | ||
'motto', clan."motto", | ||
'clanBannerData', clan."clan_banner_data", | ||
'lastUpdated', TO_CHAR(clan."updated_at" AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') | ||
) AS "clan", | ||
"known_member_count" AS "knownMemberCount", | ||
"clears", | ||
"average_clears" AS "averageClears", | ||
"fresh_clears" AS "freshClears", | ||
"average_fresh_clears" AS "averageFreshClears", | ||
"sherpas", | ||
"average_sherpas" AS "averageSherpas", | ||
"time_played_seconds" AS "timePlayedSeconds", | ||
"average_time_played_seconds" AS "averageTimePlayedSeconds", | ||
"total_contest_score" AS "totalContestScore", | ||
"weighted_contest_score" AS "weightedContestScore" | ||
FROM clan_leaderboard | ||
INNER JOIN clan USING (group_id) | ||
ORDER BY ${column} DESC | ||
OFFSET $1 | ||
LIMIT $2`, | ||
{ | ||
params: [skip, take], | ||
fetchCount: take | ||
} | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { clanStatsRoute } from "./clan" | ||
import { cleanupPostgresAfterAll, expectErr, expectOk } from "./testUtil" | ||
|
||
cleanupPostgresAfterAll() | ||
|
||
describe("clan 200", () => { | ||
const t = async (groupId: string) => { | ||
const result = await clanStatsRoute.$mock({ params: { groupId } }) | ||
|
||
expectOk(result) | ||
} | ||
|
||
test("Elysium", () => t("3148408")) | ||
|
||
test("Passion", () => t("4999487")) | ||
}) | ||
|
||
describe("clan 404", () => { | ||
const t = async (groupId: string) => { | ||
const result = await clanStatsRoute.$mock({ | ||
params: { | ||
groupId | ||
} | ||
}) | ||
|
||
expectErr(result) | ||
} | ||
|
||
test("1", () => t("1")) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { z } from "zod" | ||
import { RaidHubRoute } from "../RaidHubRoute" | ||
import { getClanStats } from "../data-access-layer/clan" | ||
import { cacheControl } from "../middlewares/cache-control" | ||
import { zClanLeaderboardEntry } from "../schema/components/Clan" | ||
import { ErrorCode } from "../schema/errors/ErrorCode" | ||
import { zBigIntString } from "../schema/util" | ||
|
||
export const clanStatsRoute = new RaidHubRoute({ | ||
method: "get", | ||
description: "Get the stats for a clan. Data updates weekly.", | ||
params: z.object({ | ||
groupId: zBigIntString() | ||
}), | ||
middleware: [cacheControl(30)], | ||
response: { | ||
success: { | ||
statusCode: 200, | ||
schema: zClanLeaderboardEntry | ||
}, | ||
errors: [ | ||
{ | ||
statusCode: 404, | ||
type: ErrorCode.ClanNotFound, | ||
schema: z.object({ | ||
groupId: zBigIntString() | ||
}) | ||
} | ||
] | ||
}, | ||
async handler({ params }) { | ||
const groupId = params.groupId | ||
|
||
const stats = await getClanStats(groupId) | ||
if (!stats) { | ||
return RaidHubRoute.fail(ErrorCode.ClanNotFound, { groupId }) | ||
} | ||
|
||
return RaidHubRoute.ok(stats) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { cleanupPostgresAfterAll, expectOk } from "../testUtil" | ||
import { clanLeaderboardRoute } from "./clan" | ||
|
||
cleanupPostgresAfterAll() | ||
|
||
describe("clan leaderboard 200", () => { | ||
const t = async (query?: Record<string, unknown>) => { | ||
const result = await clanLeaderboardRoute.$mock({ query }) | ||
|
||
expectOk(result) | ||
expect(result.parsed.length).toBeGreaterThan(0) | ||
} | ||
|
||
test("weighted contest ranking", () => | ||
t({ | ||
count: 61, | ||
page: 1, | ||
column: "weighted_contest_score" | ||
})) | ||
|
||
test("sherpas", () => | ||
t({ | ||
count: 14, | ||
page: 3, | ||
column: "sherpas" | ||
})) | ||
|
||
test("average_sherpas", () => | ||
t({ | ||
count: 27, | ||
page: 2, | ||
column: "average_sherpas" | ||
})) | ||
|
||
test("clears", () => | ||
t({ | ||
count: 10, | ||
page: 5, | ||
column: "clears" | ||
})) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { z } from "zod" | ||
import { RaidHubRoute } from "../../RaidHubRoute" | ||
import { | ||
clanLeaderboardSortColumns, | ||
getClanLeaderboard | ||
} from "../../data-access-layer/leaderboard/clan" | ||
import { cacheControl } from "../../middlewares/cache-control" | ||
import { zClanLeaderboardEntry } from "../../schema/components/Clan" | ||
import { zPage } from "../../schema/util" | ||
|
||
export const clanLeaderboardRoute = new RaidHubRoute({ | ||
method: "get", | ||
description: "Get a page of the clan leaderboard based on query parameters", | ||
query: z.object({ | ||
count: z.coerce.number().int().min(10).max(100).default(50), | ||
page: zPage(), | ||
column: z.enum(clanLeaderboardSortColumns).default("weighted_contest_score") | ||
}), | ||
response: { | ||
errors: [], | ||
success: { | ||
statusCode: 200, | ||
schema: z.array(zClanLeaderboardEntry) | ||
} | ||
}, | ||
middleware: [cacheControl(60)], | ||
async handler(req) { | ||
const { page, count, column } = req.query | ||
|
||
const entries = await getClanLeaderboard({ | ||
skip: (page - 1) * count, | ||
take: count, | ||
column | ||
}) | ||
|
||
return RaidHubRoute.ok(entries) | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.