Skip to content

Commit

Permalink
Player Power Rankings (#55)
Browse files Browse the repository at this point in the history
* power rankings

* add power rankings to spec

* fix coverage reporting
  • Loading branch information
owens1127 authored Jun 14, 2024
1 parent b27c96f commit 999cdd0
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 18 deletions.
1 change: 1 addition & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
env:
CLIENT_SECRET: "secret-token"
JWT_SECRET: "jwt-secret"
POSTGRES_USER: readonly
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_READONLY_PASSWORD }}
run: bun run test --coverage --coverageReporters=lcov --coverageReporters=html --bail=false --testTimeout=30000 --ci
continue-on-error: true
Expand Down
8 changes: 7 additions & 1 deletion open-api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3000,7 +3000,13 @@
{
"schema": {
"type": "string",
"enum": ["clears", "freshClears", "sherpas", "speedrun"]
"enum": [
"clears",
"freshClears",
"sherpas",
"speedrun",
"powerRankings"
]
},
"required": true,
"name": "category",
Expand Down
49 changes: 49 additions & 0 deletions src/data-access-layer/__test__/leaderboard/power-rankings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { z } from "zod"
import { cleanupPostgresAfterAll } from "../../../routes/testUtil"
import { zIndividualLeaderboardEntry } from "../../../schema/components/LeaderboardData"
import { zNaturalNumber } from "../../../schema/util"
import {
getIndividualWorldFirstPowerRankingsLeaderboard,
searchIndividualWorldFirstPowerRankingsLeaderboard
} from "../../leaderboard/individual/power-rankings"

cleanupPostgresAfterAll()

describe("getIndividualWorldFirstPowerRankingsLeaderboard", () => {
it("returns the correct shape", async () => {
const data = await getIndividualWorldFirstPowerRankingsLeaderboard({
skip: 24921,
take: 27
}).catch(console.error)

const parsed = z.array(zIndividualLeaderboardEntry).safeParse(data)
if (!parsed.success) {
expect(parsed.error.errors).toHaveLength(0)
} else {
expect(parsed.data.length).toBeGreaterThan(0)
expect(parsed.success).toBe(true)
}
})
})

describe("searchIndividualWorldFirstPowerRankingsLeaderboard", () => {
it("returns the correct shape", async () => {
const data = await searchIndividualWorldFirstPowerRankingsLeaderboard({
take: 4,
membershipId: "4611686018488107374"
}).catch(console.error)

const parsed = z
.object({
page: zNaturalNumber(),
entries: z.array(zIndividualLeaderboardEntry)
})
.safeParse(data)
if (!parsed.success) {
expect(parsed.error.errors).toHaveLength(0)
} else {
expect(parsed.data.entries.length).toBeGreaterThan(0)
expect(parsed.success).toBe(true)
}
})
})
64 changes: 64 additions & 0 deletions src/data-access-layer/leaderboard/individual/power-rankings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { IndividualLeaderboardEntry } from "../../../schema/components/LeaderboardData"
import { postgres } from "../../../services/postgres"

export const getIndividualWorldFirstPowerRankingsLeaderboard = async ({
skip,
take
}: {
skip: number
take: number
}) => {
return await postgres.queryRows<IndividualLeaderboardEntry>(
`SELECT
world_first_player_rankings.position,
world_first_player_rankings.rank,
ROUND(world_first_player_rankings.score::numeric, 3) AS "value",
JSONB_BUILD_OBJECT(
'membershipId', membership_id::text,
'membershipType', membership_type,
'iconPath', icon_path,
'displayName', display_name,
'bungieGlobalDisplayName', bungie_global_display_name,
'bungieGlobalDisplayNameCode', bungie_global_display_name_code,
'lastSeen', last_seen,
'isPrivate', is_private
) as "playerInfo"
FROM world_first_player_rankings
JOIN player USING (membership_id)
WHERE position > $1 AND position <= ($1 + $2)
ORDER BY position ASC`,
{
params: [skip, take],
fetchCount: take
}
)
}

export const searchIndividualWorldFirstPowerRankingsLeaderboard = async ({
membershipId,
take
}: {
membershipId: bigint | string
take: number
}) => {
const result = await postgres.queryRow<{ position: number }>(
`SELECT position
FROM world_first_player_rankings
WHERE membership_id = $1::bigint
ORDER BY position ASC
LIMIT 1`,
{
params: [membershipId]
}
)
if (!result) return null

const page = Math.ceil(result.position / take)
return {
page,
entries: await getIndividualWorldFirstPowerRankingsLeaderboard({
skip: (page - 1) * take,
take
})
}
}
22 changes: 22 additions & 0 deletions src/routes/leaderboard/individual/global.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ describe("global leaderboard 200", () => {
search: "4611686018488107374"
}
))

test("power rankings", () =>
t(
{
category: "powerRankings"
},
{
count: 14,
page: 4
}
))

test("search power rankings", () =>
t(
{
category: "powerRankings"
},
{
count: 11,
search: "4611686018488107374"
}
))
})

describe("global leaderboard 404", () => {
Expand Down
44 changes: 27 additions & 17 deletions src/routes/leaderboard/individual/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@ import { z } from "zod"
import { RaidHubRoute } from "../../../RaidHubRoute"
import {
getIndividualGlobalLeaderboard,
individualGlobalLeaderboardSortColumns,
searchIndividualGlobalLeaderboard
} from "../../../data-access-layer/leaderboard/individual/global"
import {
getIndividualWorldFirstPowerRankingsLeaderboard,
searchIndividualWorldFirstPowerRankingsLeaderboard
} from "../../../data-access-layer/leaderboard/individual/power-rankings"
import { cacheControl } from "../../../middlewares/cache-control"
import { zLeaderboardData } from "../../../schema/components/LeaderboardData"
import { ErrorCode } from "../../../schema/errors/ErrorCode"
import { zLeaderboardPagination } from "../../../schema/query.ts/LeaderboardPagination"
import { zBigIntString } from "../../../schema/util"

const zCategory = z.enum(["clears", "freshClears", "sherpas", "speedrun"])
const zCategory = z.enum(["clears", "freshClears", "sherpas", "speedrun", "powerRankings"])

const categoryMap: Record<
z.infer<typeof zCategory>,
(typeof individualGlobalLeaderboardSortColumns)[number]
> = {
const categoryMap = {
clears: "clears",
freshClears: "fresh_clears",
sherpas: "sherpas",
speedrun: "speed"
}
} as const

export const leaderboardIndividualGlobalRoute = new RaidHubRoute({
method: "get",
Expand Down Expand Up @@ -52,11 +52,16 @@ export const leaderboardIndividualGlobalRoute = new RaidHubRoute({
const { page, count, search } = req.query

if (search) {
const data = await searchIndividualGlobalLeaderboard({
membershipId: search,
take: count,
column: categoryMap[category]
})
const data = await (category === "powerRankings"
? searchIndividualWorldFirstPowerRankingsLeaderboard({
membershipId: search,
take: count
})
: searchIndividualGlobalLeaderboard({
membershipId: search,
take: count,
column: categoryMap[category]
}))

if (!data) {
return RaidHubRoute.fail(ErrorCode.PlayerNotOnLeaderboardError, {
Expand All @@ -72,11 +77,16 @@ export const leaderboardIndividualGlobalRoute = new RaidHubRoute({
entries: data.entries
})
} else {
const entries = await getIndividualGlobalLeaderboard({
skip: (page - 1) * count,
take: count,
column: categoryMap[category]
})
const entries = await (category === "powerRankings"
? getIndividualWorldFirstPowerRankingsLeaderboard({
skip: (page - 1) * count,
take: count
})
: getIndividualGlobalLeaderboard({
skip: (page - 1) * count,
take: count,
column: categoryMap[category]
}))

return RaidHubRoute.ok({
type: "individual" as const,
Expand Down

0 comments on commit 999cdd0

Please sign in to comment.