api.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. interface RawRoute {
  2. name: string;
  3. url: string;
  4. source_host: string;
  5. time: string;
  6. }
  7. export interface Route {
  8. name: string;
  9. url: string;
  10. time?: Date;
  11. }
  12. export interface Config {
  13. host: string;
  14. }
  15. function toRoute(route: RawRoute): Route {
  16. const { name, url, time } = route;
  17. return { name, url, time: new Date(time) };
  18. }
  19. interface RouteResponse {
  20. ok: boolean;
  21. error?: string;
  22. route?: RawRoute;
  23. }
  24. interface RoutesResponse {
  25. ok: boolean;
  26. error?: string;
  27. routes?: RawRoute[];
  28. next: string;
  29. }
  30. async function fromResponse<T extends { ok: boolean; error?: string }, V>(
  31. res: Response,
  32. getValue: (json: T) => V | null
  33. ): Promise<V | null> {
  34. if (res.status == 404) {
  35. return null;
  36. }
  37. const data = (await res.json()) as T;
  38. const { ok, error } = data;
  39. const value = getValue(data);
  40. if (!ok || !value) {
  41. throw new ApiError(error ?? "Oof. Something went sideways.");
  42. }
  43. return value;
  44. }
  45. export class ApiError extends Error {
  46. constructor(message: string) {
  47. super(message);
  48. this.name = "ApiError";
  49. }
  50. }
  51. export async function getRoute(name: string): Promise<Route> {
  52. const route = await fromResponse(
  53. await fetch(`/api/url/${name}`),
  54. (data: RouteResponse) => (data.route ? toRoute(data.route) : null)
  55. );
  56. return route ?? { name, url: "" };
  57. }
  58. export async function getConfig(): Promise<Config> {
  59. const { host } = await fetch("/api/config").then((res) => res.json());
  60. return host === "" ? { host: location.host } : { host };
  61. }
  62. export async function getRoutes(
  63. next: string,
  64. limit: number = 1000
  65. ): Promise<[Route[], string]> {
  66. const value = await fromResponse(
  67. await fetch(`/api/urls/?cursor=${next}&limit=${limit}`),
  68. (data: RoutesResponse) =>
  69. [data.routes?.map(toRoute) ?? [], data.next] as [Route[], string]
  70. );
  71. return value ?? [[], next];
  72. }
  73. export async function* getAllRoutes(
  74. pageSize: number = 1000
  75. ): AsyncGenerator<Route[]> {
  76. let cursor = "";
  77. do {
  78. const [routes, next] = await getRoutes(cursor, pageSize);
  79. yield routes;
  80. cursor = next;
  81. } while (cursor !== "");
  82. }
  83. export async function postRoute(name: string, url: string): Promise<Route> {
  84. const route = await fromResponse(
  85. await fetch(`/api/url/${name}`, {
  86. method: "POST",
  87. headers: {
  88. "Content-Type": "application/json",
  89. },
  90. body: JSON.stringify({ url }),
  91. }),
  92. (data: RouteResponse) => (data.route ? toRoute(data.route) : null)
  93. );
  94. return route ?? { name, url };
  95. }
  96. export async function deleteRoute(name: string): Promise<Route> {
  97. const route = await fromResponse(
  98. await fetch(`/api/url/${name}`, {
  99. method: "DELETE",
  100. }),
  101. () => ({ name, url: "" })
  102. );
  103. return route ?? { name, url: "" };
  104. }
  105. export function apiErrorToString(e: unknown): string {
  106. if (e instanceof ApiError) {
  107. return e.message;
  108. }
  109. return "Oops! Something went sideways!";
  110. }