aboutsummaryrefslogtreecommitdiffstats
path: root/backend/src/routes/score.ts
blob: e0f22819699e3a7c725837a88e8746468319fd2f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import express from "express";
import { prisma } from "../config/db";
import { PAGE_SIZE } from "../config/constants";

export const handleScoreUpload = async (
  req: express.Request,
  res: express.Response,
) => {
  try {
    const { meta, scores } = req.body;
    const userId = req.session.userId;
    if (!userId) {
      return res
        .status(401)
        .json({ error: "Unauthorized. Please log in to upload scores." });
    }

    // Basic universal validation
    if (!meta || !meta.game || !meta.service || !scores) {
      return res.status(400).json({
        error:
          "Invalid request format. Expected meta with game/service and scores array",
      });
    }
    let game = await prisma.game.findUnique({
      where: { internalName: meta.game },
    });
    if (!game) {
      game = await prisma.game.findFirst({
        where: { formattedName: meta.game },
      });
    }
    if (!game) {
      return res.status(400).json({
        error: `Game '${meta.game}' is not supported. Ensure that you are using the case-sensitive version of either the internal name or formatted name`,
      });
    }
    const internalGameName = game.internalName;
    const scoresArray = Array.isArray(scores) ? scores : [scores];

    const scoresToCreate = [];
    let skippedCount = 0;

    for (const scoreData of scoresArray) {
      // Check if exact same score data already exists
      const existingScore = await prisma.score.findFirst({
        where: {
          gameInternalName: internalGameName,
          userId: userId,
          data: {
            equals: scoreData,
          },
        },
      });

      if (existingScore) {
        skippedCount++;
      } else {
        scoresToCreate.push({
          gameInternalName: internalGameName,
          userId: userId,
          timestamp: scoreData.timestamp,
          data: scoreData,
        });
      }
    }

    const createdScores =
      scoresToCreate.length > 0
        ? await prisma.score.createMany({
            data: scoresToCreate,
          })
        : { count: 0 };

    res.status(200).json({
      message: "Score upload processed successfully",
      game: meta.game,
      service: meta.service,
      scoreCount: createdScores.count,
      skippedCount: skippedCount,
      totalProcessed: scoresArray.length,
    });
  } catch (error) {
    console.error("Score upload endpoint error:", error);
    res
      .status(500)
      .json({ error: "Internal server error. Unable to process score upload" });
  }
};

export const handleGetScores = async (
  req: express.Request,
  res: express.Response,
) => {
  try {
    const { userId, internalGameName, pageNum, sortKey, direction } = req.query;
    if (!userId || !internalGameName || !pageNum) {
      return res.status(400).json({ error: "Missing required parameters" });
    }

    const pageNumber = parseInt(pageNum as string);
    const gameInternalName = internalGameName as string;
    const userIdNumber = parseInt(userId as string);
    const sortKeyString = (sortKey as string) || "timestamp";
    const directionString =
      (direction as string)?.toLowerCase() === "asc" ? "asc" : "desc";

    const num_pages = Math.ceil(
      (await prisma.score.count({
        where: {
          gameInternalName,
          userId: userIdNumber,
        },
      })) / PAGE_SIZE,
    );

    let scores;

    if (sortKeyString === "timestamp") {
      scores = await prisma.score.findMany({
        where: {
          gameInternalName,
          userId: userIdNumber,
        },
        orderBy: {
          timestamp: directionString,
        },
        skip: (pageNumber - 1) * PAGE_SIZE,
        take: PAGE_SIZE,
      });
    } else {
      // everything else attempt to rawsql it
      scores = await prisma.$queryRawUnsafe<any[]>(
        `
        SELECT * FROM "Score"
        WHERE "gameInternalName" = $1 AND "userId" = $2
        ORDER BY (data->>'${sortKeyString}')::numeric ${directionString.toUpperCase()}
        OFFSET $3
        LIMIT $4
      `,
        gameInternalName,
        userIdNumber,
        (pageNumber - 1) * PAGE_SIZE,
        PAGE_SIZE,
      );
    }
    if (!scores) {
      return res.status(404).json({
        error:
          "No scores found. Either no scores exist or the sortKey provided is invalid for the game, sortKey: " +
          sortKeyString,
      });
    }

    const safeScores = scores.map((score) => ({
      ...score,
      timestamp:
        typeof score.timestamp === "bigint"
          ? Number(score.timestamp)
          : score.timestamp,
    }));

    res.status(200).json({
      scores: safeScores,
      num_pages,
    });
  } catch (error) {
    console.error("Failed to fetch scores:", error);
    res
      .status(500)
      .json({ error: "Internal server error. Unable to fetch scores" });
  }
};
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage