diff --git a/fe_calckey/frontend/client/src/os.ts b/fe_calckey/frontend/client/src/os.ts index 4c74e0b..08f3b8a 100644 --- a/fe_calckey/frontend/client/src/os.ts +++ b/fe_calckey/frontend/client/src/os.ts @@ -18,9 +18,9 @@ import { FrontendApiEndpoints, MagApiClient, Method, + types, } from "magnetar-common"; import { magReactionToLegacy } from "@/scripts-mag/mag-util"; -import { types } from "magnetar-common"; export const pendingApiRequestsCount = ref(0); @@ -35,11 +35,13 @@ export async function magApi< T["method"] & Method, T["pathParams"] & string[], T["request"], - T["response"] + T["response"], + T["paginated"] & boolean > >( endpoint: T, - data: T["request"], + data: T["request"] & + (T["paginated"] extends true ? Partial : any), pathParams: { [K in keyof T["pathParams"] as T["pathParams"][K] & string]: | string diff --git a/fe_calckey/frontend/magnetar-common/src/be-api.ts b/fe_calckey/frontend/magnetar-common/src/be-api.ts index 082240e..40dddb3 100644 --- a/fe_calckey/frontend/magnetar-common/src/be-api.ts +++ b/fe_calckey/frontend/magnetar-common/src/be-api.ts @@ -1,16 +1,20 @@ +import { PaginationShape } from "./types/PaginationShape"; + export type Method = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; export interface BackendApiEndpoint< M extends Method, PP extends string[], T, - R + R, + PG extends boolean > { method: M; endpoint: string; pathParams: PP; request?: T; response?: R; + paginated: PG; } function nestedUrlSearchParams(data: any, topLevel: boolean = true): string { @@ -52,6 +56,39 @@ export interface MagApiError { message: string; } +export interface PaginatedResult { + prev?: URL; + data: T; + next?: URL; +} + +function extractHeaderRel( + headers: Headers, + rel: "prev" | "next" +): URL | undefined { + for (const [k, v] of headers) { + if (k.toLowerCase() !== "link") { + continue; + } + + for (const links of v.split(",").map(String.prototype.trim)) { + const [url, relPar] = links.split(";").map(String.prototype.trim); + if (!url || !relPar) { + continue; + } + + const urlMatch = url.match(/^<(.+?)>$/)?.[1]; + const relMatch = relPar.match(/rel="(.+?)"/)?.[1]; + + if (relMatch == rel && urlMatch) { + return new URL(urlMatch); + } + } + } + + return undefined; +} + export class MagApiClient { private readonly baseUrl: string; @@ -64,18 +101,24 @@ export class MagApiClient { T["method"] & Method, T["pathParams"] & string[], T["request"], - T["response"] + T["response"], + T["paginated"] & boolean > >( - { endpoint, method }: T, - data: T["request"], + { endpoint, method, paginated }: T, + data: T["request"] & + (T["paginated"] extends true ? Partial : any), pathParams: { [K in keyof T["pathParams"] as T["pathParams"][K] & string]: | string | number; }, token?: string | null | undefined - ): Promise { + ): Promise< + T["paginated"] extends true + ? PaginatedResult + : T["response"] + > { type Response = T["response"]; const authorizationToken = token ?? undefined; @@ -97,7 +140,7 @@ export class MagApiClient { } } - return await fetch(url, { + return (await fetch(url, { method, body: method !== "GET" ? JSON.stringify(data) : undefined, credentials: "omit", @@ -108,9 +151,25 @@ export class MagApiClient { const body = res.status === 204 ? null : await res.json(); if (res.status === 200) { - return body as Response; + if (paginated) { + return { + prev: extractHeaderRel(res.headers, "prev"), + data: body as Response, + next: extractHeaderRel(res.headers, "next"), + }; + } else { + return body as Response; + } } else if (res.status === 204) { - return null as any as Response; + if (paginated) { + return { + prev: extractHeaderRel(res.headers, "prev"), + data: null as any as Response, + next: extractHeaderRel(res.headers, "next"), + }; + } else { + return null as any as Response; + } } else { throw body as MagApiError; } @@ -121,6 +180,8 @@ export class MagApiClient { code: "Client:GenericApiError", message: e, } as MagApiError; - }); + })) as T["paginated"] extends true + ? PaginatedResult + : Response; } } diff --git a/fe_calckey/frontend/magnetar-common/src/types.ts b/fe_calckey/frontend/magnetar-common/src/types.ts index 719292a..df69bec 100644 --- a/fe_calckey/frontend/magnetar-common/src/types.ts +++ b/fe_calckey/frontend/magnetar-common/src/types.ts @@ -48,3 +48,4 @@ export { StartFilter } from "./types/StartFilter"; export { NoFilter } from "./types/NoFilter"; export { TimelineType } from "./types/TimelineType"; export { Empty } from "./types/Empty"; +export { PaginationShape } from "./types/PaginationShape"; diff --git a/fe_calckey/frontend/magnetar-common/src/types/Empty.ts b/fe_calckey/frontend/magnetar-common/src/types/Empty.ts index 07d3e62..990129e 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/Empty.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/Empty.ts @@ -1,3 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type Empty = null; \ No newline at end of file +export type Empty = Record; \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/GetTimelineReq.ts b/fe_calckey/frontend/magnetar-common/src/types/GetTimelineReq.ts index e3630cf..192b02d 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/GetTimelineReq.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/GetTimelineReq.ts @@ -1,5 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { NoteListFilter } from "./NoteListFilter"; -import type { SpanFilter } from "./SpanFilter"; -export interface GetTimelineReq { limit?: bigint, filter?: NoteListFilter, range?: SpanFilter, } \ No newline at end of file +export interface GetTimelineReq { filter?: NoteListFilter, } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/PaginationShape.ts b/fe_calckey/frontend/magnetar-common/src/types/PaginationShape.ts new file mode 100644 index 0000000..2f8bf97 --- /dev/null +++ b/fe_calckey/frontend/magnetar-common/src/types/PaginationShape.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { SpanFilter } from "./SpanFilter"; + +export interface PaginationShape { pagination: SpanFilter, limit: bigint, } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowRequestsSelf.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowRequestsSelf.ts index 2d2da20..1b5106f 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowRequestsSelf.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowRequestsSelf.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Empty } from "../Empty"; -import type { PackUserBase } from "../packed/PackUserBase"; +import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll"; export const GetFollowRequestsSelf = { endpoint: "/users/@self/follow-requests", pathParams: [] as [], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as Empty, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersById.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersById.ts index 419b860..f70529e 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersById.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersById.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Empty } from "../Empty"; -import type { PackUserBase } from "../packed/PackUserBase"; +import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll"; export const GetFollowersById = { endpoint: "/users/:id/followers", pathParams: ["id"] as ["id"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as Empty, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersSelf.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersSelf.ts index a3ee873..d9f6cad 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersSelf.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowersSelf.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Empty } from "../Empty"; -import type { PackUserBase } from "../packed/PackUserBase"; +import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll"; export const GetFollowersSelf = { endpoint: "/users/@self/followers", pathParams: [] as [], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as Empty, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingById.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingById.ts index c782707..e2dc2cf 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingById.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingById.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Empty } from "../Empty"; -import type { PackUserBase } from "../packed/PackUserBase"; +import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll"; export const GetFollowingById = { endpoint: "/users/:id/following", pathParams: ["id"] as ["id"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as Empty, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingSelf.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingSelf.ts index 691c8eb..79e0f30 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingSelf.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetFollowingSelf.ts @@ -1,12 +1,13 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Empty } from "../Empty"; -import type { PackUserBase } from "../packed/PackUserBase"; +import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll"; export const GetFollowingSelf = { endpoint: "/users/@self/following", pathParams: [] as [], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as Empty, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetManyUsersById.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetManyUsersById.ts index 20f6507..9a7ab3b 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetManyUsersById.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetManyUsersById.ts @@ -7,6 +7,7 @@ export const GetManyUsersById = { pathParams: [] as [], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as ManyUsersByIdReq, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: false as false } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetNoteById.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetNoteById.ts index d292e2f..ba7edb6 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetNoteById.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetNoteById.ts @@ -7,6 +7,7 @@ export const GetNoteById = { pathParams: ["id"] as ["id"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as NoteByIdReq, - response: undefined as unknown as PackNoteMaybeFull + response: undefined as unknown as PackNoteMaybeFull, + paginated: false as false } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetTimeline.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetTimeline.ts index 6d433db..b9ea4d6 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetTimeline.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetTimeline.ts @@ -7,6 +7,7 @@ export const GetTimeline = { pathParams: ["timeline_type"] as ["timeline_type"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as GetTimelineReq, - response: undefined as unknown as Array + response: undefined as unknown as Array, + paginated: true as true } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserByAcct.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserByAcct.ts index c5849c3..56d6ec4 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserByAcct.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserByAcct.ts @@ -7,6 +7,7 @@ export const GetUserByAcct = { pathParams: ["user_acct"] as ["user_acct"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as UserByIdReq, - response: undefined as unknown as PackUserMaybeAll + response: undefined as unknown as PackUserMaybeAll, + paginated: false as false } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserById.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserById.ts index fd203be..267069e 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserById.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserById.ts @@ -7,6 +7,7 @@ export const GetUserById = { pathParams: ["user_id"] as ["user_id"], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as UserByIdReq, - response: undefined as unknown as PackUserMaybeAll + response: undefined as unknown as PackUserMaybeAll, + paginated: false as false } \ No newline at end of file diff --git a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserSelf.ts b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserSelf.ts index 40303ef..2e37a87 100644 --- a/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserSelf.ts +++ b/fe_calckey/frontend/magnetar-common/src/types/endpoints/GetUserSelf.ts @@ -7,6 +7,7 @@ export const GetUserSelf = { pathParams: [] as [], method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH", request: undefined as unknown as UserSelfReq, - response: undefined as unknown as PackUserSelfMaybeAll + response: undefined as unknown as PackUserSelfMaybeAll, + paginated: false as false } \ No newline at end of file diff --git a/magnetar_sdk/macros/src/lib.rs b/magnetar_sdk/macros/src/lib.rs index 4089e71..e357f0c 100644 --- a/magnetar_sdk/macros/src/lib.rs +++ b/magnetar_sdk/macros/src/lib.rs @@ -155,6 +155,7 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { let mut method = None; let mut request = None; let mut response = None; + let mut paginated = false; let mut request_args = None; let mut response_args = None; @@ -203,6 +204,17 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { name = new_name.value(); } + "paginated" => { + let Expr::Lit(ExprLit { + lit: Lit::Bool(val), + .. + }) = value + else { + panic!("expected a boolean"); + }; + + paginated = val.value(); + } "endpoint" => { let Expr::Lit(ExprLit { lit: Lit::Str(endpoint_val), @@ -376,6 +388,7 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { const NAME: &'static str = #name; const ENDPOINT: &'static str = #endpoint; const METHOD: Method = #method; + const PAGINATED: bool = #paginated; type Request = #request; type Response = #response; @@ -397,7 +410,8 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { pathParams: {} as {},\n \ method: \"{}\" as \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\",\n \ request: undefined as unknown as {},\n \ - response: undefined as unknown as {}\n\ + response: undefined as unknown as {},\n \ + paginated: {} as {}\n\ }} ", Self::name(), @@ -406,7 +420,9 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream { #path_params, Self::METHOD, #call_name_req, - #call_name_res + #call_name_res, + #paginated, + #paginated ) } diff --git a/magnetar_sdk/src/endpoints/mod.rs b/magnetar_sdk/src/endpoints/mod.rs index 8b3cad3..93e2768 100644 --- a/magnetar_sdk/src/endpoints/mod.rs +++ b/magnetar_sdk/src/endpoints/mod.rs @@ -34,6 +34,7 @@ pub trait Endpoint { const NAME: &'static str; const ENDPOINT: &'static str; const METHOD: Method; + const PAGINATED: bool; type Request: Serialize + DeserializeOwned + Send + Sync + 'static; type Response: Serialize + DeserializeOwned + Send + Sync + 'static; diff --git a/magnetar_sdk/src/endpoints/timeline.rs b/magnetar_sdk/src/endpoints/timeline.rs index 8ee6203..e5d505d 100644 --- a/magnetar_sdk/src/endpoints/timeline.rs +++ b/magnetar_sdk/src/endpoints/timeline.rs @@ -1,7 +1,5 @@ use crate::endpoints::Endpoint; use crate::types::note::{NoteListFilter, PackNoteMaybeFull}; -use crate::types::SpanFilter; -use crate::util_types::U64Range; use http::Method; use magnetar_sdk_macros::Endpoint; use serde::{Deserialize, Serialize}; @@ -11,16 +9,8 @@ use ts_rs::TS; #[derive(Serialize, Deserialize, TS)] #[ts(export)] pub struct GetTimelineReq { - #[ts(optional)] - pub limit: Option>, #[ts(optional)] pub filter: Option, - #[ts(optional)] - pub range: Option, -} - -pub fn default_timeline_limit() -> U64Range { - 30.try_into().unwrap() } #[derive(Endpoint)] @@ -29,5 +19,6 @@ pub fn default_timeline_limit() -> U64Range