refactor: typesafe api call (#16)
This commit is contained in:
parent
b10280712a
commit
83befff7f7
|
|
@ -12,6 +12,7 @@ export default defineConfig(
|
|||
arrowParens: true,
|
||||
braceStyle: "1tbs",
|
||||
},
|
||||
ignores: ["src/renderer/src/hono.ts"],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,734 @@
|
|||
import * as hono_hono_base from 'hono/hono-base';
|
||||
import * as hono_utils_http_status from 'hono/utils/http-status';
|
||||
import * as hono from 'hono';
|
||||
|
||||
declare const routes: hono_hono_base.HonoBase<hono.Env, {
|
||||
"/reads": {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$delete: {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/collections": {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
entryId: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data: boolean;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
entryId: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$delete: {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
entryId: string;
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/categories": {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
view?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data?: string[] | undefined;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
view?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$delete: {
|
||||
input: {
|
||||
json: {
|
||||
feedIdList: string[];
|
||||
deleteSubscriptions: boolean;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
feedIdList: string[];
|
||||
deleteSubscriptions: boolean;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$patch: {
|
||||
input: {
|
||||
json: {
|
||||
category: string;
|
||||
feedIdList: string[];
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
category: string;
|
||||
feedIdList: string[];
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 2;
|
||||
error: string;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 400;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
category: string;
|
||||
feedIdList: string[];
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/feeds/refresh": {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 2;
|
||||
error: "Feed not found";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 400;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized" | "Only admins can refresh feeds";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/discover": {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
keyword: string;
|
||||
type?: "auto" | "rss" | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data: {
|
||||
feed: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
id: string;
|
||||
image: string | null;
|
||||
url: string;
|
||||
siteUrl: string | null;
|
||||
checkedAt: Date;
|
||||
nextCheckAt: Date;
|
||||
lastModifiedHeader: string | null;
|
||||
etagHeader: string | null;
|
||||
ttl: number | null;
|
||||
errorMessage: string | null;
|
||||
errorAt: Date | null;
|
||||
};
|
||||
entries?: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
content: string | null;
|
||||
id: string;
|
||||
url: string | null;
|
||||
feedId: string;
|
||||
guid: string;
|
||||
author: string | null;
|
||||
changedAt: Date;
|
||||
publishedAt: Date;
|
||||
images: string | null;
|
||||
categories: string | null;
|
||||
}[] | undefined;
|
||||
docs?: string | undefined;
|
||||
isSubscribed?: boolean | undefined;
|
||||
subscriptionCount?: number | undefined;
|
||||
}[];
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
keyword: string;
|
||||
type?: "auto" | "rss" | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/auth-app/new-session": {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data: {
|
||||
userId: string;
|
||||
sessionToken: string;
|
||||
expires: string;
|
||||
};
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Failed to create session";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 500;
|
||||
};
|
||||
};
|
||||
"/auth-app/update-account": {
|
||||
$patch: {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
name?: string | null | undefined;
|
||||
image?: string | null | undefined;
|
||||
handle?: string | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
name?: string | null | undefined;
|
||||
image?: string | null | undefined;
|
||||
handle?: string | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
[x: `/import/${string}`]: {
|
||||
[x: `$${Lowercase<string>}`]: {
|
||||
input: Partial<hono.ValidationTargets>;
|
||||
output: any;
|
||||
outputFormat: string;
|
||||
status: hono_utils_http_status.StatusCode;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/entries": {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
feedId?: string | undefined;
|
||||
view?: number | undefined;
|
||||
category?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
offset?: number | undefined;
|
||||
feedIdList?: string[] | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
total: number;
|
||||
data?: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
id: string;
|
||||
feeds: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
id: string;
|
||||
image: string | null;
|
||||
url: string;
|
||||
siteUrl: string | null;
|
||||
checkedAt: Date;
|
||||
nextCheckAt: Date;
|
||||
lastModifiedHeader: string | null;
|
||||
etagHeader: string | null;
|
||||
ttl: number | null;
|
||||
errorMessage: string | null;
|
||||
errorAt: Date | null;
|
||||
};
|
||||
url: string | null;
|
||||
feedId: string;
|
||||
guid: string;
|
||||
author: string | null;
|
||||
changedAt: Date;
|
||||
publishedAt: Date;
|
||||
images: string | null;
|
||||
categories: string | null;
|
||||
collected: boolean;
|
||||
read: boolean;
|
||||
}[] | undefined;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
feedId?: string | undefined;
|
||||
view?: number | undefined;
|
||||
category?: string | undefined;
|
||||
limit?: number | undefined;
|
||||
offset?: number | undefined;
|
||||
feedIdList?: string[] | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data?: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
content: string | null;
|
||||
id: string;
|
||||
feeds: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
id: string;
|
||||
image: string | null;
|
||||
url: string;
|
||||
siteUrl: string | null;
|
||||
checkedAt: Date;
|
||||
nextCheckAt: Date;
|
||||
lastModifiedHeader: string | null;
|
||||
etagHeader: string | null;
|
||||
ttl: number | null;
|
||||
errorMessage: string | null;
|
||||
errorAt: Date | null;
|
||||
};
|
||||
url: string | null;
|
||||
feedId: string;
|
||||
guid: string;
|
||||
author: string | null;
|
||||
changedAt: Date;
|
||||
publishedAt: Date;
|
||||
images: string | null;
|
||||
categories: string | null;
|
||||
collected: boolean;
|
||||
read: boolean;
|
||||
} | undefined;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
} & {
|
||||
"/subscriptions": {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
view?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
data?: {
|
||||
title: string | null;
|
||||
userId: string;
|
||||
feeds: {
|
||||
description: string | null;
|
||||
title: string | null;
|
||||
id: string;
|
||||
image: string | null;
|
||||
url: string;
|
||||
siteUrl: string | null;
|
||||
checkedAt: Date;
|
||||
nextCheckAt: Date;
|
||||
lastModifiedHeader: string | null;
|
||||
etagHeader: string | null;
|
||||
ttl: number | null;
|
||||
errorMessage: string | null;
|
||||
errorAt: Date | null;
|
||||
};
|
||||
feedId: string;
|
||||
view: number;
|
||||
category: string | null;
|
||||
isPrivate: boolean | null;
|
||||
}[] | undefined;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
query: {
|
||||
view?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
url: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
url: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 2;
|
||||
error: string;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 400;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
url: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$delete: {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
url?: string | undefined;
|
||||
feedId?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
url?: string | undefined;
|
||||
feedId?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 2;
|
||||
error: string;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 400;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
csrfToken: string;
|
||||
url?: string | undefined;
|
||||
feedId?: string | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
$patch: {
|
||||
input: {
|
||||
json: {
|
||||
feedId: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 0;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 200;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
feedId: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 2;
|
||||
error: string;
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 400;
|
||||
} | {
|
||||
input: {
|
||||
json: {
|
||||
feedId: string;
|
||||
view: number;
|
||||
csrfToken: string;
|
||||
category?: string | null | undefined;
|
||||
isPrivate?: boolean | null | undefined;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: 1;
|
||||
error: "Unauthorized";
|
||||
};
|
||||
outputFormat: "json";
|
||||
status: 401;
|
||||
};
|
||||
};
|
||||
}, "/">;
|
||||
type AppType = typeof routes;
|
||||
|
||||
export type { AppType };
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
import type { apiClient } from "@renderer/queries/api-fetch"
|
||||
import type { InferResponseType } from "hono/client"
|
||||
|
||||
export type ActiveList = {
|
||||
level: string
|
||||
id: string | number
|
||||
|
|
@ -22,16 +25,15 @@ export type FeedResponse = {
|
|||
errorMessage?: string
|
||||
}
|
||||
|
||||
export type SubscriptionResponse = {
|
||||
userId: string
|
||||
feedId: string
|
||||
view: number
|
||||
category?: string
|
||||
title?: string
|
||||
isPrivate?: boolean
|
||||
unread?: number
|
||||
feeds: FeedResponse
|
||||
}[]
|
||||
export type SubscriptionResponse = Array<
|
||||
Exclude<
|
||||
Extract<
|
||||
InferResponseType<typeof apiClient.subscriptions.$get>,
|
||||
{ code: 0 }
|
||||
>["data"],
|
||||
undefined
|
||||
>[number] & { unread?: number }
|
||||
>
|
||||
|
||||
export type EntriesResponse = {
|
||||
author?: string
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { getCsrfToken } from "@hono/auth-js/react"
|
||||
import type { AppType } from "@renderer/hono"
|
||||
import { hc } from "hono/client"
|
||||
import { ofetch } from "ofetch"
|
||||
|
||||
export const apiFetch = ofetch.create({
|
||||
|
|
@ -18,3 +20,27 @@ export const apiFetch = ofetch.create({
|
|||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const apiClient = hc<AppType>(
|
||||
import.meta.env.VITE_API_URL,
|
||||
{
|
||||
fetch: async (...args) => {
|
||||
const options = args[1] as RequestInit
|
||||
options.credentials = "include"
|
||||
if (options.method && options.method.toLowerCase() !== "get") {
|
||||
const csrfToken = await getCsrfToken()
|
||||
if (!options.body) {
|
||||
options.body = JSON.stringify({})
|
||||
}
|
||||
if (options.body instanceof FormData) {
|
||||
options.body.append("csrfToken", csrfToken)
|
||||
} else {
|
||||
if (typeof options.body === "string") {
|
||||
options.body = JSON.stringify({ csrfToken, ...JSON.parse(options.body) })
|
||||
}
|
||||
}
|
||||
}
|
||||
return fetch(args[0], options)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { SubscriptionResponse } from "@renderer/lib/types"
|
||||
import { apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { apiClient, apiFetch } from "@renderer/queries/api-fetch"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { parse } from "tldts"
|
||||
|
||||
|
|
@ -16,13 +16,11 @@ export const useSubscriptions = (view?: number) =>
|
|||
useQuery({
|
||||
queryKey: ["subscriptions", view],
|
||||
queryFn: async () => {
|
||||
const { data: subscriptions } = await apiFetch<{
|
||||
data: SubscriptionResponse
|
||||
}>("/subscriptions", {
|
||||
query: {
|
||||
view,
|
||||
},
|
||||
})
|
||||
const res = await (await apiClient.subscriptions.$get({ query: { view: String(view) } })).json()
|
||||
if (res.code === 1) {
|
||||
throw new Error(res.error)
|
||||
}
|
||||
const subscriptions = res.data as SubscriptionResponse
|
||||
|
||||
const categories = {
|
||||
list: {},
|
||||
|
|
|
|||
Loading…
Reference in New Issue