Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x 39x | import { Prisma } from "@prisma/client";
import { AppError } from "@ontrack/backend-common";
import type { FastifyPluginAsync, FastifyRequest } from "fastify";
import type { AppConfig } from "../config.js";
import { assertServiceToken } from "../lib/service-token.js";
import { readUserUid } from "../lib/user-uid.js";
import { prisma } from "../lib/prisma.js";
const trackIdParamSchema = {
type: "object",
required: ["trackId"],
additionalProperties: false,
properties: {
trackId: { type: "string", pattern: "^[1-9][0-9]*$" },
},
} as const;
const toggleResponseSchema = {
type: "object",
required: ["favorited"],
additionalProperties: false,
properties: {
favorited: { type: "boolean" },
},
} as const;
const favoritedResponseSchema = {
type: "object",
required: ["trackIds"],
additionalProperties: false,
properties: {
trackIds: { type: "array", items: { type: "integer", minimum: 1 } },
},
} as const;
export const favoritesRoutes: FastifyPluginAsync<{ config: AppConfig }> = async (app, opts) => {
const { config } = opts;
function authorize(request: FastifyRequest): string {
const token = request.headers["x-service-token"];
assertServiceToken(Array.isArray(token) ? token[0] : token, config.serviceToken);
return readUserUid(request);
}
/** Id треков, добавленных текущим пользователем в избранное. */
app.get(
"/tracks/favorited",
{ schema: { response: { 200: favoritedResponseSchema } } },
async (request, reply) => {
const uid = authorize(request);
const rows = await prisma.trackFavorite.findMany({
where: { firebaseUid: uid },
select: { trackId: true },
});
return reply.send({ trackIds: rows.map((r) => r.trackId) });
},
);
/** Тоггл избранного для трека текущим пользователем; отдаёт новое состояние. */
app.post(
"/tracks/:trackId/favorite",
{ schema: { params: trackIdParamSchema, response: { 200: toggleResponseSchema } } },
async (request, reply) => {
const uid = authorize(request);
const { trackId } = request.params as { trackId: string };
const id = Number.parseInt(trackId, 10);
const existing = await prisma.trackFavorite.findUnique({
where: { trackId_firebaseUid: { trackId: id, firebaseUid: uid } },
});
let favorited: boolean;
if (existing) {
await prisma.trackFavorite.delete({ where: { id: existing.id } });
favorited = false;
} else {
try {
await prisma.trackFavorite.create({ data: { trackId: id, firebaseUid: uid } });
favorited = true;
} catch (err) {
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2003") {
throw new AppError(404, "TRACK_NOT_FOUND", "Track not found");
}
// P2002: гонка — запись уже создана; считаем «в избранном».
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002") {
favorited = true;
} else {
throw err;
}
}
}
return reply.send({ favorited });
},
);
};
|