All files / src/routes account.ts

80% Statements 36/45
75% Branches 6/8
100% Functions 3/3
80% Lines 36/45

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 631x               2x 2x 2x 2x 2x 2x 2x 2x 2x   2x 2x 2x 2x   2x                   2x   1x     1x 39x   39x 3x 3x 3x 3x     39x 2x 2x 1x 39x     39x 1x 1x 1x 1x 39x 39x  
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";
import type { ObjectStore } from "../storage/object-store.js";
 
/** Удаляет персональные данные пользователя: профиль, велики (+фото в R2), лайки, избранное, аватар. */
export async function purgeUserData(uid: string, objectStore: ObjectStore | null): Promise<void> {
  const profile = await prisma.userProfile.findUnique({
    where: { firebaseUid: uid },
    select: { avatarKey: true },
  });
  const bikes = await prisma.bike.findMany({
    where: { ownerUid: uid },
    select: { photoKey: true },
  });
 
  await prisma.trackLike.deleteMany({ where: { firebaseUid: uid } });
  await prisma.trackFavorite.deleteMany({ where: { firebaseUid: uid } });
  await prisma.bike.deleteMany({ where: { ownerUid: uid } });
  await prisma.userProfile.deleteMany({ where: { firebaseUid: uid } });
 
  if (objectStore) {
    if (profile?.avatarKey) {
      await objectStore.delete(profile.avatarKey).catch(() => undefined);
    }
    for (const bike of bikes) {
      if (bike.photoKey) {
        await objectStore.delete(bike.photoKey).catch(() => undefined);
      }
    }
  }
}
 
export const accountRoutes: FastifyPluginAsync<{
  config: AppConfig;
  objectStore: ObjectStore | null;
}> = async (app, opts) => {
  const { config, objectStore } = 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);
  }
 
  /** Удалить мои данные — профиль/велики/лайки/аватар. Аккаунт и треки остаются. */
  app.delete("/account/data", async (request, reply) => {
    const uid = authorize(request);
    await purgeUserData(uid, objectStore);
    return reply.send({ ok: true });
  });
 
  /** Удалить аккаунт — персональные данные + осиротить треки (owner → NULL). Firebase удаляет клиент. */
  app.delete("/account", async (request, reply) => {
    const uid = authorize(request);
    await purgeUserData(uid, objectStore);
    await prisma.gpxTrack.updateMany({ where: { ownerUid: uid }, data: { ownerUid: null } });
    return reply.send({ ok: true });
  });
};