Skip to content

✨ Implement wrappers of /api/pages/:projectname/search/titles #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/scrapbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type {
Page,
PageList,
Scrapbox,
SearchedTitle,
} from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
import type { Page } from "https://raw.githubusercontent.com/scrapbox-jp/types/0.0.8/mod.ts";
export * from "https://esm.sh/@progfay/[email protected]";
Expand Down
104 changes: 104 additions & 0 deletions rest/link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import type { ErrorLike, SearchedTitle } from "../deps/scrapbox.ts";
import { cookie } from "./auth.ts";
import { UnexpectedResponseError } from "./error.ts";
import { tryToErrorLike } from "../is.ts";
import { BaseOptions, Result, setDefaults } from "./util.ts";

export interface GetLinksOptions extends BaseOptions {
/** 次のリンクリストを示すID */
followingId?: string;
}

/** 指定したprojectのリンクデータを取得する
*
* @param project データを取得したいproject
*/
export const getLinks = async (
project: string,
options?: GetLinksOptions,
): Promise<
Result<{
pages: SearchedTitle[];
followingId: string;
}, ErrorLike>
> => {
const { sid, hostName, fetch, followingId } = setDefaults(options ?? {});
const path = `https://${hostName}/api/pages/${project}/search/titles${
followingId ? `?followingId=${followingId}` : ""
}`;

const res = await fetch(
path,
sid ? { headers: { Cookie: cookie(sid) } } : undefined,
);

if (!res.ok) {
const text = await res.text();
const value = tryToErrorLike(text);
if (!value) {
throw new UnexpectedResponseError({
path: new URL(path),
...res,
body: text,
});
}
return { ok: false, value };
}
const pages = (await res.json()) as SearchedTitle[];
return {
ok: true,
value: { pages, followingId: res.headers.get("X-following-id") ?? "" },
};
};

/** 指定したprojectの全てのリンクデータを取得する
*
* responseで返ってきたリンクデータの塊ごとに返す
*
* @param project データを取得したいproject
* @return 認証が通らなかったらエラーを、通ったらasync generatorを返す
*/
export const readLinksBulk = async (
project: string,
options?: BaseOptions,
): Promise<ErrorLike | AsyncGenerator<SearchedTitle[], void, unknown>> => {
const first = await getLinks(project, options);
if (!first.ok) return first.value;

return async function* () {
yield first.value.pages;
let followingId = first.value.followingId;

while (!followingId) {
const result = await getLinks(project, { followingId, ...options });

// すでに認証は通っているので、ここでエラーになるはずがない
if (!result.ok) {
throw new Error("The authorization cannot be unavailable");
}
yield result.value.pages;
followingId = result.value.followingId;
}
}();
};

/** 指定したprojectの全てのリンクデータを取得し、一つづつ返す
*
* @param project データを取得したいproject
* @return 認証が通らなかったらエラーを、通ったらasync generatorを返す
*/
export const readLinks = async (
project: string,
options?: BaseOptions,
): Promise<ErrorLike | AsyncGenerator<SearchedTitle, void, unknown>> => {
const reader = await readLinksBulk(project, options);
if ("name" in reader) return reader;

return async function* () {
for await (const titles of reader) {
for (const title of titles) {
yield title;
}
}
}();
};