Skip to content
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
593 changes: 115 additions & 478 deletions open-api/openapi.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/core/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const apiVersion = "1.3.3"
export const apiVersion = "1.3.4"
2 changes: 1 addition & 1 deletion src/integrations/postgres/pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
sql: string,
options?: Omit<QueryOptions, "objectRows"> & { fetchCount: number }
): Promise<T[]> {
return await this.connector.queryRows<T>(sql, options)
}
async prepareStatement(sql: string) {

Check warning on line 46 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

44-46 lines are not covered with tests
return await this.connector.prepareStatement(sql)
}

Expand All @@ -67,10 +67,10 @@
pool_name: this.metricLabel
},
value
)
}

[Symbol.dispose]() {

Check warning on line 73 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

70-73 lines are not covered with tests
clearInterval(this.metricsInterval)
}
}
Expand All @@ -80,7 +80,7 @@
objectRows: true
} as const

export class RaidHubConnector implements RaidHubQuerier {

Check warning on line 83 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

83 line is not covered with tests
private readonly connection: Pool | Connection
constructor(connection: Pool | Connection) {
this.connection = connection
Expand Down Expand Up @@ -115,66 +115,66 @@
console.log(executeTime, sql)
}

return rows as T[]
}

async prepareStatement(sql: string) {
const stmnt = await this.connection.prepare(sql)
return new RaidHubStatement(stmnt)
}

async execute(sql: string, options?: Omit<ScriptExecuteOptions, keyof typeof QueryOptions>) {
const { results, totalTime } = await this.connection.execute(sql, {
...options,
...QueryOptions
})
if (!process.env.PROD && process.env.NODE_ENV !== "test") {
results.forEach(({ executeTime }) => {
console.log(executeTime, executeTime)
})
console.log(totalTime, sql)
}

Check warning on line 137 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

118-137 lines are not covered with tests
return results.map(r => (r.rows ?? []) as unknown[])
}
}

export class RaidHubPoolTransaction extends RaidHubPool {
async transaction(cb: (conn: RaidHubConnector) => Promise<void>) {
const conn = await this.acquire()
await conn.startTransaction()
try {
const result = await cb(new RaidHubConnector(conn))
await conn.commit()
return result
} catch (err) {
await conn.rollback()
throw err
} finally {

Check warning on line 153 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

142-153 lines are not covered with tests
void this.release(conn)
await this.release(conn)
}
}
}

class RaidHubStatement {
private readonly stmnt: PreparedStatement
constructor(stmnt: PreparedStatement) {
this.stmnt = stmnt
}

async execute(options?: Omit<QueryOptions, keyof typeof QueryOptions>) {
const { rows, executeTime } = await this.stmnt.execute({
...options,
...QueryOptions
})
if (!process.env.PROD && process.env.NODE_ENV !== "test") {
console.log(executeTime, this.stmnt.sql)
}

return (rows ?? []) as unknown[]
}

async close() {

Check warning on line 177 in src/integrations/postgres/pool.ts

View workflow job for this annotation

GitHub Actions / test

161-177 lines are not covered with tests
await this.stmnt.close()
}
}
4 changes: 0 additions & 4 deletions src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ export const router = new RaidHubRouter({
path: "/instance/:instanceId",
route: instanceRoute
},
{
path: "/activity/:instanceId",
route: instanceRoute.deprecatedCopy()
},
{
path: "/leaderboard",
route: leaderboardRouter
Expand Down
13 changes: 9 additions & 4 deletions src/routes/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { RaidHubRoute } from "@/core/RaidHubRoute"
import { cacheControl } from "@/middleware/cache-control"
import { zActivityDefinition } from "@/schema/components/ActivityDefinition"
import { zFeatDefinition } from "@/schema/components/FeatDefinition"
import { zVersionDefinition } from "@/schema/components/VersionDefinition"
import { zNaturalNumber, zNumericalRecordKey } from "@/schema/util"
import {
listActivityDefinitions,
listFeatDefinitions,
listHashes,
listVersionDefinitions
} from "@/services/manifest/definitions"
Expand Down Expand Up @@ -94,16 +96,18 @@ export const manifestRoute = new RaidHubRoute({
description: "A tailwindcss color class for the tier"
})
})
)
),
feats: z.array(zFeatDefinition)
})
.strict()
}
},
handler: async () => {
const [activities, versions, hashes] = await Promise.all([
const [activities, versions, hashes, feats] = await Promise.all([
listActivityDefinitions(),
listVersionDefinitions(),
listHashes()
listHashes(),
listFeatDefinitions()
])
const raids = activities.filter(a => a.isRaid)
const pantheonId = 101
Expand Down Expand Up @@ -154,7 +158,8 @@ export const manifestRoute = new RaidHubRoute({
resprisedChallengeVersionIds: versions.filter(v => v.isChallengeMode).map(v => v.id),
pantheonIds: [pantheonId],
versionsForActivity: versionsForActivity,
rankingTiers: TierBreaks
rankingTiers: TierBreaks,
feats: feats
})
}
})
4 changes: 0 additions & 4 deletions src/routes/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ export const playerRouter = new RaidHubRouter({
path: "/history",
route: playerHistoryRoute
},
{
path: "/activities",
route: playerHistoryRoute.deprecatedCopy()
},
{
path: "/basic",
route: playerBasicRoute
Expand Down
2 changes: 1 addition & 1 deletion src/routes/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import { getAtlasStatus } from "@/services/atlas"
import { getFloodgatesRecentId, getFloodgatesStatus } from "@/services/floodgates"
import { getInstanceBasic } from "@/services/instance/instance"
import { getLatestInstanceByDate } from "@/services/instance/recent"
import { getLatestInstanceByDate } from "@/services/instance/latest"
import { z } from "zod"

// This state tracks the status of the Destiny API and debounces it with a grace period of 60 seconds.
Expand Down Expand Up @@ -97,7 +97,7 @@

if (isDestinyApiEnabled) {
statusState.debounceOnlineEvent()
} else {

Check warning on line 100 in src/routes/status.ts

View workflow job for this annotation

GitHub Actions / test

100 line is not covered with tests
statusState.debounceOfflineEvent()
}

Expand Down Expand Up @@ -142,7 +142,7 @@

const instance = await getInstanceBasic(latestInstanceId)
if (!instance) {
return null

Check warning on line 145 in src/routes/status.ts

View workflow job for this annotation

GitHub Actions / test

145 line is not covered with tests
}

return {
Expand Down Expand Up @@ -175,8 +175,8 @@
backlog === 0 &&
floodgatesStatus.ackRateSeconds > 0 &&
floodgatesStatus.ingressRateSeconds > 0
) {
status = "Live"

Check warning on line 179 in src/routes/status.ts

View workflow job for this annotation

GitHub Actions / test

178-179 lines are not covered with tests
}

return {
Expand Down
20 changes: 20 additions & 0 deletions src/schema/components/FeatDefinition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { registry } from "@/schema/registry"
import { zUInt32, zWholeNumber } from "@/schema/util"
import { z } from "zod"

export type FeatDefinition = z.input<typeof zFeatDefinition>
export const zFeatDefinition = registry.register(
"FeatDefinition",
z
.object({
hash: zUInt32(),
skullHash: zUInt32(),
name: z.string(),
shortName: z.string(),
description: z.string(),
shortDescription: z.string(),
iconPath: z.string(),
modifierPowerContribution: zWholeNumber()
})
.strict()
)
60 changes: 28 additions & 32 deletions src/schema/components/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,38 @@ import { registry } from "@/schema/registry"
import { zInt64, zISODateString, zNaturalNumber, zUInt32, zWholeNumber } from "@/schema/util"
import { z } from "zod"

const zInstancePrimitive = z.object({
instanceId: zInt64(),
hash: zUInt32(),
completed: z.boolean(),
flawless: z.boolean().nullable(),
fresh: z.boolean().nullable(),
playerCount: zNaturalNumber(),
skullHashes: z.array(
zUInt32().openapi({
description: "Mapped to a FeatDefinition by its skullHash"
})
),
score: zWholeNumber(),
dateStarted: zISODateString(),
dateCompleted: zISODateString(),
season: zNaturalNumber(),
duration: zNaturalNumber().openapi({
description: "Instance duration in seconds"
}),
platformType: zDestinyMembershipType.openapi({
description:
"If all players are on the same platform, this will be the platform type. Otherwise, it will be `0`."
})
})

export type Instance = z.input<typeof zInstance>
export const zInstance = registry.register(
"Instance",
z
.object({
instanceId: zInt64(),
hash: zUInt32(),
zInstancePrimitive
.extend({
activityId: zNaturalNumber(),
versionId: zNaturalNumber(),
completed: z.boolean(),
flawless: z.boolean().nullable(),
fresh: z.boolean().nullable(),
playerCount: zNaturalNumber(),
score: zWholeNumber(),
dateStarted: zISODateString(),
dateCompleted: zISODateString(),
season: zNaturalNumber(),
duration: zNaturalNumber().openapi({
description: "Instance duration in seconds"
}),
platformType: zDestinyMembershipType.openapi({
description:
"If all players are on the same platform, this will be the platform type. Otherwise, it will be `0`."
}),
isDayOne: z.boolean().openapi({
description: "If the instance was completed before the day one end date"
}),
Expand All @@ -45,19 +53,7 @@ export const zInstance = registry.register(
export type InstanceBasic = z.input<typeof zInstanceBasic>
export const zInstanceBasic = registry.register(
"InstanceBasic",
z.object({
instanceId: zInt64(),
hash: zUInt32(),
completed: z.boolean(),
playerCount: zNaturalNumber(),
score: zWholeNumber(),
fresh: z.boolean().nullable(),
flawless: z.boolean().nullable(),
dateStarted: zISODateString(),
dateCompleted: zISODateString(),
season: zNaturalNumber(),
duration: zNaturalNumber(),
platformType: zDestinyMembershipType.nullable(),
zInstancePrimitive.extend({
dateResolved: zISODateString()
})
)
2 changes: 1 addition & 1 deletion src/services/atlas/atlas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@ describe("getAtlasStatus no mock", () => {
const result = await getAtlasStatus()

expect(result.isCrawling).toBeTrue()
expect(result.lag).toBeWithin(15, 60)
expect(result.lag).toBeWithin(15, 80)
})
})
2 changes: 2 additions & 0 deletions src/services/instance/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
score AS "score",
fresh AS "fresh",
flawless AS "flawless",
skull_hashes AS "skullHashes",
date_started AS "dateStarted",
date_completed AS "dateCompleted",
season_id AS "season",
Expand Down Expand Up @@ -149,7 +150,7 @@
}
)
if (!metaData) {
throw new Error("Metadata not found")

Check warning on line 153 in src/services/instance/instance.ts

View workflow job for this annotation

GitHub Actions / test

153 line is not covered with tests
}
return metaData
}
Expand Down Expand Up @@ -179,6 +180,7 @@
score AS "score",
fresh AS "fresh",
flawless AS "flawless",
skull_hashes AS "skullHashes",
date_started AT TIME ZONE 'UTC' AS "dateStarted",
date_completed AT TIME ZONE 'UTC' AS "dateCompleted",
season_id AS "season",
Expand Down
File renamed without changes.
16 changes: 16 additions & 0 deletions src/services/manifest/definitions.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { zActivityDefinition } from "@/schema/components/ActivityDefinition"
import { zFeatDefinition } from "@/schema/components/FeatDefinition"
import { zVersionDefinition } from "@/schema/components/VersionDefinition"
import { zNaturalNumber, zUInt32 } from "@/schema/util"
import { describe, expect, it } from "bun:test"
Expand All @@ -8,6 +9,7 @@ import {
getRaidId,
getVersionId,
listActivityDefinitions,
listFeatDefinitions,
listHashes,
listVersionDefinitions
} from "./definitions"
Expand Down Expand Up @@ -105,3 +107,17 @@ describe("listHashes", () => {
}
})
})

describe("listFeatDefinitions", () => {
it("returns the correct shape", async () => {
const data = await listFeatDefinitions().catch(console.error)

const parsed = z.array(zFeatDefinition).safeParse(data)
if (!parsed.success) {
expect(parsed.error.errors).toEqual([])
} else {
expect(parsed.data.length).toBeGreaterThan(0)
expect(parsed.success).toBe(true)
}
})
})
16 changes: 16 additions & 0 deletions src/services/manifest/definitions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { postgres } from "@/integrations/postgres"
import { ActivityDefinition } from "@/schema/components/ActivityDefinition"
import { FeatDefinition } from "@/schema/components/FeatDefinition"
import { VersionDefinition } from "@/schema/components/VersionDefinition"

export const getRaidId = async (raidPath: string) => {
Expand Down Expand Up @@ -79,3 +80,18 @@ export const listHashes = async () => {
FROM activity_version`
)
}

export const listFeatDefinitions = async () => {
return await postgres.queryRows<FeatDefinition>(
`SELECT
hash,
skull_hash AS "skullHash",
name,
name_short AS "shortName",
description,
description_short AS "shortDescription",
icon_path AS "iconPath",
modifier_power_contribution AS "modifierPowerContribution"
FROM activity_feat_definition`
)
}
1 change: 1 addition & 0 deletions src/services/player-instances/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const getActivities = async (
score AS "score",
fresh AS "fresh",
flawless AS "flawless",
skull_hashes AS "skullHashes",
date_started AS "dateStarted",
date_completed AS "dateCompleted",
season_id AS "season",
Expand Down
1 change: 1 addition & 0 deletions src/services/player-instances/instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export async function getInstances({
score AS "score",
fresh AS "fresh",
flawless AS "flawless",
skull_hashes AS "skullHashes",
date_started AS "dateStarted",
date_completed AS "dateCompleted",
season_id AS "season",
Expand Down
1 change: 1 addition & 0 deletions src/services/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const getPlayerActivityStats = async (membershipId: bigint | string) => {
'score', fastest.score,
'fresh', fastest.fresh,
'flawless', fastest.flawless,
'skullHashes', fastest.skull_hashes,
'dateStarted', fastest.date_started,
'dateCompleted', fastest.date_completed,
'season', fastest.season_id,
Expand Down
20 changes: 14 additions & 6 deletions src/services/reporting/update-blacklist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,23 @@ export const blacklistInstance = async (data: {
)
)

void playerStmnt.close()
await playerStmnt.close()
})
}

export const removeInstanceBlacklist = async (instanceId: bigint | string) => {
return await postgresWritable.queryRow(
`DELETE FROM blacklist_instance WHERE instance_id = $1::bigint`,
{
return await postgresWritable.transaction(async conn => {
// Delete from blacklist_instance
await conn.queryRow(`DELETE FROM blacklist_instance WHERE instance_id = $1::bigint`, {
params: [instanceId]
}
)
})

// Set instance.is_whitelisted to true
await conn.queryRow(
`UPDATE instance SET is_whitelisted = true WHERE instance_id = $1::bigint`,
{
params: [instanceId]
}
)
})
}