packages feed

espial-0.0.34: frontend/src/api.ts

import ky from 'ky';

import { app } from './globals';
import type {
  AccountSettings,
  Bookmark,
  Note,
  TagCloud,
  TagCloudMode,
  TagSuggestionRequest,
  TagSuggestionResponse,
} from './types';

export type StarAction = 'star' | 'unstar';

function withCsrf(headers?: HeadersInit): HeadersInit {
  const a = app();
  const h = new Headers(headers);
  h.set(a.csrfHeaderName, a.csrfToken);
  return h;
}

function urlFor(path: string): string {
  // Preserve PureScript semantics: simple string concatenation.
  return `${app().homeR}${path}`;
}

function toRelativePath(urlOrPath: string): string {
  try {
    const u = new URL(urlOrPath, window.location.origin);
    const rel = `${u.pathname}${u.search}${u.hash}`;
    return rel.startsWith('/') ? rel.slice(1) : rel;
  } catch {
    return urlOrPath.replace(/^\/+/, '');
  }
}

async function request(
  method: string,
  path: string,
  opts: { headers?: HeadersInit; body?: BodyInit | null } = {},
) {
  const res = await ky(urlFor(path), {
    method,
    headers: withCsrf(opts.headers),
    body: opts.body ?? undefined,
    throwHttpErrors: false,
  });
  return res;
}

export async function toggleStar(bid: number, action: StarAction): Promise<void> {
  await request('POST', `bm/${bid.toString()}/${action}`, {
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
  });
}

export async function destroy(bid: number): Promise<void> {
  await request('DELETE', `bm/${bid.toString()}`, {
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
  });
}

export async function markRead(bid: number): Promise<void> {
  await request('POST', `bm/${bid.toString()}/read`, {
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
  });
}

export async function archiveBookmark(bid: number): Promise<void> {
  await request('POST', `bm/${bid.toString()}/archive`, {
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
  });
}

export async function editBookmark(
  bm: Bookmark,
): Promise<{ ok: boolean; status: number; bodyText: string }> {
  const res = await request('POST', 'api/add', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(bm),
  });
  return { ok: res.ok, status: res.status, bodyText: await res.text() };
}

export async function editNote(
  note: Note,
): Promise<{ ok: boolean; status: number; bodyText: string }> {
  const res = await request('POST', 'api/note/add', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(note),
  });
  return { ok: res.ok, status: res.status, bodyText: await res.text() };
}

export async function lookupTitle(bm: Bookmark): Promise<string | null> {
  const res = await request('POST', 'api/lookuptitle', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(bm),
  });
  if (res.status === 200) return await res.text();
  return null;
}

export async function getTagCloud(mode: TagCloudMode): Promise<TagCloud | null> {
  const res = await request('POST', 'api/tagcloud', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(mode),
  });
  if (!res.ok) return null;
  try {
    return await res.json();
  } catch {
    return null;
  }
}

export async function updateTagCloudMode(mode: TagCloudMode): Promise<void> {
  await request('POST', 'api/tagcloudmode', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(mode),
  });
}

export async function fetchTagSuggestions(
  data: TagSuggestionRequest,
): Promise<TagSuggestionResponse | null> {
  const res = await request('POST', 'api/tagSuggestions', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) return null;
  try {
    return await res.json<TagSuggestionResponse>();
  } catch {
    return null;
  }
}

export async function destroyNote(nid: number): Promise<void> {
  await request('DELETE', `api/note/${nid.toString()}`, {
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
  });
}

export async function editAccountSettings(us: AccountSettings): Promise<void> {
  await request('POST', 'api/accountSettings', {
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(us),
  });
}

export async function logout(): Promise<void> {
  const a = app();
  await request('POST', toRelativePath(a.authRlogoutR));
  window.location.reload();
}