All files / src/services profileRepository.ts

94.8% Statements 73/77
80% Branches 8/10
100% Functions 4/4
94.8% Lines 73/77

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 781x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 1x 4x 4x 4x 4x 1x 1x 1x 1x 1x 4x 3x     4x 1x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 1x 2x 2x 2x 2x 1x 1x     2x  
import { RestError } from "@azure/data-tables";
import type { ProfileEntity, ProfilePutBody } from "../models/user";
import { PROFILE_TEXT_MAX } from "../models/user";
import {
  decodeJsonProperty,
  encodeJsonProperty,
  ensureTable,
  nowIso,
} from "./tablesService";
 
/**
 * Profiles table CRUD (data-model.md): PK = Google sub, RK = "profile", one
 * row per user. Normalization mirrors the extension's setProfile exactly so
 * the swap is behavior-preserving.
 */
 
const TABLE = "Profiles";
const ROW_KEY = "profile";
 
export interface StoredProfile {
  text: string;
  dealbreakers: string[];
  updatedAt: string;
}
 
function isNotFound(err: unknown): boolean {
  return err instanceof RestError && err.statusCode === 404;
}
 
export async function getProfile(sub: string): Promise<StoredProfile | null> {
  const client = await ensureTable(TABLE);
  try {
    const entity = await client.getEntity<ProfileEntity>(sub, ROW_KEY);
    return {
      text: entity.text,
      dealbreakers: decodeJsonProperty<string[]>(entity.dealbreakers, []),
      updatedAt: entity.updatedAt,
    };
  } catch (err) {
    if (isNotFound(err)) return null;
    throw err;
  }
}
 
export async function putProfile(
  sub: string,
  input: ProfilePutBody
): Promise<StoredProfile> {
  const profile: StoredProfile = {
    text: input.text.slice(0, PROFILE_TEXT_MAX),
    dealbreakers: input.dealbreakers.map((d) => d.trim()).filter(Boolean),
    updatedAt: nowIso(),
  };
  const client = await ensureTable(TABLE);
  await client.upsertEntity(
    {
      partitionKey: sub,
      rowKey: ROW_KEY,
      text: profile.text,
      dealbreakers: encodeJsonProperty(profile.dealbreakers),
      updatedAt: profile.updatedAt,
      schemaVersion: 1,
    },
    "Replace"
  );
  return profile;
}
 
export async function deleteProfile(sub: string): Promise<void> {
  const client = await ensureTable(TABLE);
  try {
    await client.deleteEntity(sub, ROW_KEY);
  } catch (err) {
    if (isNotFound(err)) return;
    throw err;
  }
}