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

Dev #38

Merged
merged 3 commits into from
Sep 12, 2024
Merged

Dev #38

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:

- A unique constraint covering the columns `[key]` on the table `Statistic` will be added. If there are existing duplicate values, this will fail.

*/
-- CreateIndex
CREATE UNIQUE INDEX "Statistic_key_key" ON "Statistic"("key");
53 changes: 53 additions & 0 deletions prisma/migrations/20240912184048_better_stats_model/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Warnings:

- The primary key for the `Statistic` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `id` on the `Statistic` table. All the data in the column will be lost.
- You are about to drop the `PlayerStatisticTransaction` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `PlayerStatisticValue` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropForeignKey
ALTER TABLE "PlayerStatisticTransaction" DROP CONSTRAINT "PlayerStatisticTransaction_playerUuid_fkey";

-- DropForeignKey
ALTER TABLE "PlayerStatisticTransaction" DROP CONSTRAINT "PlayerStatisticTransaction_statisticId_fkey";

-- DropForeignKey
ALTER TABLE "PlayerStatisticValue" DROP CONSTRAINT "PlayerStatisticValue_playerUuid_fkey";

-- DropForeignKey
ALTER TABLE "PlayerStatisticValue" DROP CONSTRAINT "PlayerStatisticValue_statisticId_fkey";

-- DropIndex
DROP INDEX "Statistic_key_key";

-- AlterTable
ALTER TABLE "Statistic" DROP CONSTRAINT "Statistic_pkey",
DROP COLUMN "id",
ADD CONSTRAINT "Statistic_pkey" PRIMARY KEY ("key");

-- DropTable
DROP TABLE "PlayerStatisticTransaction";

-- DropTable
DROP TABLE "PlayerStatisticValue";

-- CreateTable
CREATE TABLE "PlayerStatisticRecord" (
"id" TEXT NOT NULL,
"playerUuid" TEXT NOT NULL,
"statisticKey" TEXT NOT NULL,
"value" INTEGER NOT NULL,
"reason" TEXT,
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "PlayerStatisticRecord_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "PlayerStatisticRecord" ADD CONSTRAINT "PlayerStatisticRecord_playerUuid_fkey" FOREIGN KEY ("playerUuid") REFERENCES "Player"("uuid") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "PlayerStatisticRecord" ADD CONSTRAINT "PlayerStatisticRecord_statisticKey_fkey" FOREIGN KEY ("statisticKey") REFERENCES "Statistic"("key") ON DELETE RESTRICT ON UPDATE CASCADE;
31 changes: 7 additions & 24 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ model Player {
member Member? @relation(fields: [memberDiscordId], references: [discordId])
memberDiscordId String? @unique

statValues PlayerStatisticValue[]
statTransactions PlayerStatisticTransaction[]
stats PlayerStatisticRecord[]
}

model Member {
Expand Down Expand Up @@ -312,44 +311,28 @@ enum StatisticType {
}

model Statistic {
id String @id @default(uuid())
key String
key String @id

displayName String?
color String @default("&7")

type StatisticType @default(TRANSACTION)

transactions PlayerStatisticTransaction[]
values PlayerStatisticValue[]
values PlayerStatisticRecord[]
}

model PlayerStatisticTransaction {
id String @id @default(uuid())
model PlayerStatisticRecord {
id String @id @default(uuid())

player Player @relation(fields: [playerUuid], references: [uuid])
playerUuid String

statistic Statistic @relation(fields: [statisticId], references: [id])
statisticId String
statistic Statistic @relation(fields: [statisticKey], references: [key])
statisticKey String

value Int
reason String?

timestamp DateTime @default(now())
}

model PlayerStatisticValue {
id String @id @default(uuid())

player Player @relation(fields: [playerUuid], references: [uuid])
playerUuid String

statistic Statistic @relation(fields: [statisticId], references: [id])
statisticId String

value Int
reason String?

updatedAt DateTime @updatedAt
}
30 changes: 30 additions & 0 deletions src/realms/rest/endpoints/controllers/Batch.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Controller, POST } from "fastify-decorators"
import { HasApiKey, RequestWithKey } from "../../helpers/decorators/HasApiKey"
import { HasSchemaScope } from "../../helpers/decorators/HasSchemaScope"
import { FastifyReply } from "fastify"
import {
PlayerStatBatchBodySchema,
PlayerStatManipulationMultipleSchema
} from "../schemas/Player.schema"
import PlayerService from "../services/Player.service"

@Controller({ route: "/batch" })
export default class BatchController {
constructor(private playerService: PlayerService) {}

@POST({
url: "/stats",
options: {
schema: PlayerStatManipulationMultipleSchema
}
})
@HasApiKey()
@HasSchemaScope()
async getAvailableGames(
request: RequestWithKey<{ Body: PlayerStatBatchBodySchema }>,
reply: FastifyReply
) {
await this.playerService.batchUpdateStats(request.body)
return reply.status(204).send()
}
}
71 changes: 70 additions & 1 deletion src/realms/rest/endpoints/controllers/Player.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import {
PlayerRemovePermissionGroupSchema,
PlayerRemovePermissionsBodySchema,
PlayerRemovePermissionsSchema,
PlayerSetPermissionGroupsSchema
PlayerSetPermissionGroupsSchema,
PlayerStatGetSchema,
PlayerStatGetSingleSchema,
PlayerStatManipulationSchema,
PlayerStatManageBodySchema,
PlayerStatParamsSchema,
PlayerStatQuerySchema
} from "../schemas/Player.schema"
import { HasSchemaScope } from "../../helpers/decorators/HasSchemaScope"
import { Permission } from "@prisma/client"
Expand Down Expand Up @@ -368,4 +374,67 @@ export default class PlayerController {
const onlinePlayers = await this.playerService.getOnlinePlayers()
return reply.code(200).send(onlinePlayers)
}

@PUT({
url: "/:uuid/stats/:statKey/manipulate",
options: {
schema: PlayerStatManipulationSchema
}
})
@HasApiKey()
@HasSchemaScope()
async manipulatePlayerStatistic(
req: RequestWithKey<{
Params: PlayerStatParamsSchema
Querystring: PlayerStatQuerySchema
Body: PlayerStatManageBodySchema
}>,
reply: FastifyReply
) {
await this.playerService.manipulatePlayerStatistic(
req.params.uuid,
req.params.statKey,
req.body.value,
req.body.reason,
req.query.set
)
return reply.code(204).send()
}

@GET({
url: "/:uuid/stats/:statKey",
options: {
schema: PlayerStatGetSingleSchema
}
})
@HasApiKey()
@HasSchemaScope()
async getPlayerStatistic(
req: RequestWithKey<{ Params: PlayerStatParamsSchema }>,
reply: FastifyReply
) {
const playerStatistic = await this.playerService.getPlayerStatistic(
req.params.uuid,
req.params.statKey
)
return reply.code(200).send(playerStatistic)
}

@GET({
url: "/:uuid/stats",
options: {
schema: PlayerStatGetSchema
}
})
@HasApiKey()
@HasSchemaScope()
async getPlayerStatistics(
req: RequestWithKey<{ Params: PlayerInfoParamsSchema }>,
reply: FastifyReply
) {
const playerStatistics = await this.playerService.getPlayerStatistics(
req.params.uuid
)
return reply.code(200).send(playerStatistics)
}
}
115 changes: 115 additions & 0 deletions src/realms/rest/endpoints/schemas/Player.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PlayerSchema from "../../schemas/Player.schema"
import { ApiScope, ChatChannels } from "@prisma/client"
import PermissionSchema from "../../schemas/Permission.schema"
import PermissionInputSchema from "../../schemas/PermissionInput.schema"
import PlayerStatisticRecordSchema from "../../schemas/PlayerStatisticRecord.schema"

const PlayerCreateBodySchema = Type.Object({
memberDiscordId: Type.String(),
Expand Down Expand Up @@ -367,3 +368,117 @@ export const PlayerMigrateSchema: FastifySchema = {
200: Type.Ref(PlayerSchema)
}
}

// Increment Player Stat

const PlayerStatParamsSchema = Type.Intersect([
PlayerInfoParamsSchema,
Type.Object({
statKey: Type.String()
})
])

export type PlayerStatParamsSchema = Static<typeof PlayerStatParamsSchema>

const PlayerStatManageBodySchema = Type.Object({
value: Type.Number(),
reason: Type.Optional(Type.String())
})

export type PlayerStatManageBodySchema = Static<
typeof PlayerStatManageBodySchema
>

const PlayerStatQuerySchema = Type.Object({
set: Type.Optional(Type.Boolean())
})

export type PlayerStatQuerySchema = Static<typeof PlayerStatQuerySchema>

export const PlayerStatManipulationSchema: FastifySchema = {
tags: ["players"],
summary: "Increment/set a player's stat",
operationId: "manipulatePlayerStatistic",
security: [
{
apiKey: [ApiScope.PLAYERS]
}
],
params: PlayerStatParamsSchema,
querystring: PlayerStatQuerySchema,
body: PlayerStatManageBodySchema,
response: {
204: Type.Object({}),
404: Type.Object({
error: Type.String({ enum: ["player-not-found"] })
})
}
}

// Batch Stat Manipulation

const PlayerStatBatchBodySchema = Type.Array(
Type.Object({
playerUuid: Type.String(),
statKey: Type.String(),
value: Type.Number(),
reason: Type.Optional(Type.String())
})
)

export type PlayerStatBatchBodySchema = Static<typeof PlayerStatBatchBodySchema>

export const PlayerStatManipulationMultipleSchema: FastifySchema = {
tags: ["batch"],
summary: "Increment/set multiple player stats",
operationId: "batchManipulatePlayerStatistics",
security: [
{
apiKey: [ApiScope.PLAYERS]
}
],
body: PlayerStatBatchBodySchema,
response: {
200: Type.Object({})
}
}

// Get All Player Stats

export const PlayerStatGetSchema: FastifySchema = {
tags: ["players"],
summary: "Get a player's stats",
operationId: "getPlayerStatistics",
security: [
{
apiKey: [ApiScope.PLAYERS]
}
],
params: PlayerInfoParamsSchema,
response: {
200: Type.Array(Type.Ref(PlayerStatisticRecordSchema)),
404: Type.Object({
error: Type.String({ enum: ["statistic-not-found"] })
})
}
}

// Get Player Stat

export const PlayerStatGetSingleSchema: FastifySchema = {
tags: ["players"],
summary: "Get a player's single stat",
operationId: "getPlayerStatistic",
security: [
{
apiKey: [ApiScope.PLAYERS]
}
],
params: PlayerStatParamsSchema,
response: {
200: Type.Ref(PlayerStatisticRecordSchema),
404: Type.Object({
error: Type.String({ enum: ["statistic-not-found"] })
})
}
}
Loading
Loading