diff --git a/next.config.js b/next.config.js index ae970f22..f3bf87b4 100644 --- a/next.config.js +++ b/next.config.js @@ -62,6 +62,7 @@ module.exports = withBundleAnalyzer( experimental: { scrollRestoration: true, appDir: true, + serverComponentsExternalPackages: ["nodejieba", "rehype-react", "pinyin"], }, output: "standalone", productionBrowserSourceMaps: true, diff --git a/src/app/api/check-domain/route.ts b/src/app/api/check-domain/route.ts new file mode 100644 index 00000000..14a0076e --- /dev/null +++ b/src/app/api/check-domain/route.ts @@ -0,0 +1,18 @@ +import { NextResponse } from "next/server" + +import { NextServerResponse, getQuery } from "~/lib/server-helper" +import { checkDomainServer } from "~/models/site.model" + +export async function GET(req: Request) { + const { handle, domain } = getQuery(req) + + if (!handle || !domain) { + NextResponse.json({ error: "Missing characterId or domain" }) + return + } + const res = new NextServerResponse() + + return res.status(200).json({ + data: await checkDomainServer(domain as string, handle as string), + }) +} diff --git a/src/app/api/healthcheck/route.ts b/src/app/api/healthcheck/route.ts new file mode 100644 index 00000000..993a67aa --- /dev/null +++ b/src/app/api/healthcheck/route.ts @@ -0,0 +1,8 @@ +import { NextServerResponse } from "~/lib/server-helper" + +export async function GET() { + const res = new NextServerResponse() + return res.status(200).json({ + ok: true, + }) +} diff --git a/src/pages/api/host2handle.ts b/src/app/api/host2handle/route.ts similarity index 87% rename from src/pages/api/host2handle.ts rename to src/app/api/host2handle/route.ts index f97d02a9..e3bec39c 100644 --- a/src/pages/api/host2handle.ts +++ b/src/app/api/host2handle/route.ts @@ -1,14 +1,10 @@ -import { NextApiRequest, NextApiResponse } from "next" - import { OUR_DOMAIN } from "~/lib/env" import { cacheGet } from "~/lib/redis.server" +import { NextServerResponse, getQuery } from "~/lib/server-helper" import { checkDomainServer, fetchTenant } from "~/models/site.model" -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - let { host } = req.query +export async function GET(req: Request) { + let { host } = getQuery(req) let realHost: string if (Array.isArray(host)) { @@ -57,6 +53,6 @@ export default async function handler( }, }) } - - res.status(200).json(result) + const res = new NextServerResponse() + return res.status(200).json(result) } diff --git a/src/app/api/import/mirror.xyz/route.ts b/src/app/api/import/mirror.xyz/route.ts new file mode 100644 index 00000000..eb1bc94b --- /dev/null +++ b/src/app/api/import/mirror.xyz/route.ts @@ -0,0 +1,21 @@ +import { NextServerResponse, getQuery } from "~/lib/server-helper" + +export async function GET(req: Request) { + const { address } = getQuery(req) + + const result = await ( + await fetch("https://mirror.xyz/api/graphql", { + headers: { + "content-type": "application/json", + Referer: `https://mirror.xyz/${address}/`, + origin: "https://mirror.xyz", + "user-agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", + }, + body: `{"operationName":"ProjectPage","variables":{"projectAddress":"${address}","limit":1000},"query":"query ProjectPage($projectAddress: String\u0021, $limit: Int, $cursor: Int) {\\n projectFeed(projectAddress: $projectAddress, limit: $limit, cursor: $cursor) {\\n _id\\n domain\\n ens\\n theme {\\n accent\\n colorMode\\n __typename\\n }\\n displayName\\n ens\\n address\\n ...projectPage\\n ...publicationLayoutProject\\n __typename\\n }\\n}\\n\\nfragment projectPage on ProjectType {\\n _id\\n address\\n avatarURL\\n description\\n displayName\\n domain\\n ens\\n theme {\\n accent\\n colorMode\\n __typename\\n }\\n headerImage {\\n id\\n url\\n __typename\\n }\\n posts {\\n ... on crowdfund {\\n _id\\n id\\n __typename\\n }\\n ... on entry {\\n _id\\n id\\n body\\n digest\\n title\\n publishedAtTimestamp\\n writingNFT {\\n _id\\n optimisticNumSold\\n proxyAddress\\n purchases {\\n numSold\\n __typename\\n }\\n __typename\\n }\\n featuredImage {\\n mimetype\\n url\\n __typename\\n }\\n publisher {\\n ...publisherDetails\\n __typename\\n }\\n settings {\\n ...entrySettingsDetails\\n __typename\\n }\\n __typename\\n }\\n ... on SubscriberEditionType {\\n _id\\n __typename\\n }\\n __typename\\n }\\n __typename\\n}\\n\\nfragment publisherDetails on PublisherType {\\n project {\\n ...projectDetails\\n __typename\\n }\\n member {\\n ...projectDetails\\n __typename\\n }\\n __typename\\n}\\n\\nfragment projectDetails on ProjectType {\\n _id\\n address\\n avatarURL\\n description\\n displayName\\n domain\\n ens\\n gaTrackingID\\n mailingListURL\\n headerImage {\\n ...mediaAsset\\n __typename\\n }\\n theme {\\n ...themeDetails\\n __typename\\n }\\n __typename\\n}\\n\\nfragment mediaAsset on MediaAssetType {\\n id\\n cid\\n mimetype\\n sizes {\\n ...mediaAssetSizes\\n __typename\\n }\\n url\\n __typename\\n}\\n\\nfragment mediaAssetSizes on MediaAssetSizesType {\\n og {\\n ...mediaAssetSize\\n __typename\\n }\\n lg {\\n ...mediaAssetSize\\n __typename\\n }\\n md {\\n ...mediaAssetSize\\n __typename\\n }\\n sm {\\n ...mediaAssetSize\\n __typename\\n }\\n __typename\\n}\\n\\nfragment mediaAssetSize on MediaAssetSizeType {\\n src\\n height\\n width\\n __typename\\n}\\n\\nfragment themeDetails on UserProfileThemeType {\\n accent\\n colorMode\\n __typename\\n}\\n\\nfragment entrySettingsDetails on EntrySettingsType {\\n description\\n metaImage {\\n ...mediaAsset\\n __typename\\n }\\n title\\n __typename\\n}\\n\\nfragment publicationLayoutProject on ProjectType {\\n _id\\n avatarURL\\n displayName\\n domain\\n address\\n ens\\n gaTrackingID\\n mailingListURL\\n description\\n __typename\\n}\\n"}`, + method: "POST", + }) + ).json() + + return new NextServerResponse().status(200).json(result) +} diff --git a/src/pages/api/nfts.ts b/src/app/api/ntfs/route.ts similarity index 82% rename from src/pages/api/nfts.ts rename to src/app/api/ntfs/route.ts index 3b77bc63..3da554f4 100644 --- a/src/pages/api/nfts.ts +++ b/src/app/api/ntfs/route.ts @@ -1,19 +1,15 @@ -import { NextApiRequest, NextApiResponse } from "next" import { Asset } from "unidata.js" import { IPFS_GATEWAY } from "~/lib/env" import { cacheGet, getRedis } from "~/lib/redis.server" +import { NextServerResponse, getQuery } from "~/lib/server-helper" import { getNFTs } from "~/models/site.model" -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const query = req.query - +export async function GET(req: Request): Promise { + const query = getQuery(req) + const res = new NextServerResponse() if (!query.address) { - res.status(400).end() - return + return res.status(400).end() } const redis = await getRedis() @@ -24,7 +20,7 @@ export default async function handler( cache = await redis?.get(redisKey) } catch (error) {} if (cache) { - res.status(200).json(JSON.parse(cache)) + return res.status(200).json(JSON.parse(cache)) } else { const result = await getNFTs(query.address as string) await Promise.all( @@ -60,7 +56,7 @@ export default async function handler( }), ) redis?.set(redisKey, JSON.stringify(result), "EX", 60 * 60 * 24) - res.status(200).json({ + return res.status(200).json({ list: [], }) } diff --git a/src/pages/api/pages.ts b/src/app/api/pages/route.ts similarity index 72% rename from src/pages/api/pages.ts rename to src/app/api/pages/route.ts index 8781951c..63e2e221 100644 --- a/src/pages/api/pages.ts +++ b/src/app/api/pages/route.ts @@ -1,13 +1,9 @@ -import { NextApiRequest, NextApiResponse } from "next" - +import { NextServerResponse, getQuery } from "~/lib/server-helper" import { PageVisibilityEnum } from "~/lib/types" import { getPagesBySite } from "~/models/page.model" -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const query = req.query +export async function GET(req: Request) { + const query = getQuery(req) const result = await getPagesBySite({ characterId: +(query.characterId || 0) as number, @@ -23,5 +19,6 @@ export default async function handler( }), }) - res.status(200).json(result) + const res = new NextServerResponse() + return res.status(200).json(result) } diff --git a/src/pages/api/redirection.tsx b/src/app/api/redirection/route.ts similarity index 81% rename from src/pages/api/redirection.tsx rename to src/app/api/redirection/route.ts index 2f4676f7..fdb41a19 100644 --- a/src/pages/api/redirection.tsx +++ b/src/app/api/redirection/route.ts @@ -1,9 +1,10 @@ import { NoteEntity } from "crossbell.js" -import { NextApiRequest, NextApiResponse } from "next" +import { redirect } from "next/navigation" import { IS_VERCEL_PREVIEW } from "~/lib/constants" import { getDefaultSlug } from "~/lib/default-slug" import { getSiteLink } from "~/lib/helpers" +import { NextServerResponse, getQuery } from "~/lib/server-helper" import { checkDomainServer } from "~/models/site.model" async function getOriginalNote( @@ -30,15 +31,12 @@ async function getOriginalNote( } } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - let { characterId, noteId } = req.query +export async function GET(req: Request): Promise { + let { characterId, noteId } = getQuery(req) + const res = new NextServerResponse() if (!characterId || typeof characterId !== "string") { - res.status(400).json({ error: "Missing characterId" }) - return + return res.status(400).json({ error: "Missing characterId" }) } let character @@ -84,6 +82,8 @@ export default async function handler( if (IS_VERCEL_PREVIEW) { const path = new URL(link).pathname - res.redirect(`/_site/${character.handle}${path}`) - } else res.redirect(link) + redirect(`/_site/${character.handle}${path}`) + } else { + redirect(link) + } } diff --git a/src/pages/api/score.ts b/src/app/api/score/route.ts similarity index 90% rename from src/pages/api/score.ts rename to src/app/api/score/route.ts index de24f67b..ddc7f8c9 100644 --- a/src/pages/api/score.ts +++ b/src/app/api/score/route.ts @@ -1,8 +1,7 @@ -import { NextApiRequest, NextApiResponse } from "next" - import { toGateway } from "~/lib/ipfs-parser" import prisma from "~/lib/prisma.server" import { cacheGet } from "~/lib/redis.server" +import { NextServerResponse, getQuery } from "~/lib/server-helper" const getOriginalScore = async (cid: string) => { try { @@ -61,7 +60,7 @@ const getOriginalScore = async (cid: string) => { } } -export async function getScore(cid: string) { +async function getScore(cid: string) { const score = await cacheGet({ key: ["summary_score", cid], getValueFun: async () => { @@ -113,18 +112,16 @@ export async function getScore(cid: string) { return score } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - let { cid } = req.query +export async function GET(req: Request): Promise { + let { cid } = getQuery(req) + + const res = new NextServerResponse() if (!cid) { - res.status(400).send("Bad Request") - return + return res.status(400).send("Bad Request") } - res.status(200).json({ + return res.status(200).json({ data: await getScore(cid as string), }) } diff --git a/src/pages/api/slug2id.ts b/src/app/api/slug2id/route.ts similarity index 78% rename from src/pages/api/slug2id.ts rename to src/app/api/slug2id/route.ts index 51fb49be..a5fea7b6 100644 --- a/src/pages/api/slug2id.ts +++ b/src/app/api/slug2id/route.ts @@ -1,7 +1,6 @@ -import { NextApiRequest, NextApiResponse } from "next" - import { getNoteSlug } from "~/lib/helpers" import { cacheDelete, cacheGet } from "~/lib/redis.server" +import { NextServerResponse, getQuery } from "~/lib/server-helper" export async function getIdBySlug(slug: string, characterId: string | number) { slug = (slug as string)?.toLowerCase?.() @@ -54,16 +53,15 @@ export async function getIdBySlug(slug: string, characterId: string | number) { return result } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - let { characterId, slug } = req.query - +// GET /api/slug2id?characterId=52055&slug=note-144 +export async function GET(req: Request): Promise { + let { characterId, slug } = getQuery(req) + const res = new NextServerResponse() if (!slug || !characterId) { - res.status(400).send("Bad Request") - return + return res.status(400).send("Bad Request") } - res.status(200).send(await getIdBySlug(slug as string, characterId as string)) + return res + .status(200) + .send(await getIdBySlug(slug as string, characterId as string)) } diff --git a/src/pages/api/summary.ts b/src/app/api/summary/route.ts similarity index 92% rename from src/pages/api/summary.ts rename to src/app/api/summary/route.ts index 8c7bf66b..fa838c43 100644 --- a/src/pages/api/summary.ts +++ b/src/app/api/summary/route.ts @@ -1,14 +1,14 @@ import { OpenAI } from "langchain" import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains" import { PromptTemplate } from "langchain/prompts" -import { NextApiRequest, NextApiResponse } from "next" +import removeMarkdown from "remove-markdown" import { Metadata } from "@prisma/client" import { toGateway } from "~/lib/ipfs-parser" import prisma from "~/lib/prisma.server" import { cacheGet } from "~/lib/redis.server" -import removeMarkdown from "remove-markdown" +import { NextServerResponse, getQuery } from "~/lib/server-helper" const model = new OpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, @@ -120,18 +120,15 @@ export async function getSummary(cid: string, lang: string = "en") { return summary } -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - let { cid, lang } = req.query +export async function GET(req: Request): Promise { + let { cid, lang } = getQuery(req) + const res = new NextServerResponse() if (!cid) { - res.status(400).send("Bad Request") - return + return res.status(400).send("Bad Request") } - res.status(200).json({ + return res.status(200).json({ data: await getSummary(cid as string, lang as string), }) } diff --git a/src/lib/server-helper.ts b/src/lib/server-helper.ts new file mode 100644 index 00000000..7d1c6d0d --- /dev/null +++ b/src/lib/server-helper.ts @@ -0,0 +1,45 @@ +export const getQuery = (req: Request) => { + const url = new URL(req.url) + const searchParams = url.searchParams + const obj = {} as Record + + for (const [key, value] of searchParams.entries()) { + obj[key] = value + } + + return obj +} + +export class NextServerResponse { + #status: number = 200 + constructor() {} + + status(status: number) { + this.#status = status + return this + } + + json(data: any) { + const nextData = JSON.stringify(data) + + const contentType = "application/json" + + return new Response(nextData, { headers: { contentType } }) + } + + send(data: any) { + // if (data instanceof Stream) { + // return + // } + + if (typeof data === "object" || typeof data === "undefined") { + return this.json(data) + } + + return new Response(data, { status: this.#status }) + } + + end() { + return new Response("", { status: this.#status }) + } +} diff --git a/src/pages/api/check-domain.tsx b/src/pages/api/check-domain.tsx deleted file mode 100644 index 64229d33..00000000 --- a/src/pages/api/check-domain.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" - -import { checkDomainServer } from "~/models/site.model" - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse, -) { - const { handle, domain } = req.query - - if (!handle || !domain) { - res.status(400).json({ error: "Missing characterId or domain" }) - return - } - - res.status(200).json({ - data: await checkDomainServer(domain as string, handle as string), - }) -} diff --git a/src/pages/api/healthcheck.ts b/src/pages/api/healthcheck.ts deleted file mode 100644 index 52f5b68c..00000000 --- a/src/pages/api/healthcheck.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" - -export default async function healthcheck( - req: NextApiRequest, - res: NextApiResponse, -) { - res.status(200).json({ - ok: true, - }) -} diff --git a/src/queries/page.server.ts b/src/queries/page.server.ts index 7ba9558e..9ea15750 100644 --- a/src/queries/page.server.ts +++ b/src/queries/page.server.ts @@ -1,9 +1,9 @@ import { QueryClient } from "@tanstack/react-query" +import { getIdBySlug } from "~/app/api/slug2id/route" +import { getSummary } from "~/app/api/summary/route" import { cacheGet } from "~/lib/redis.server" import * as pageModel from "~/models/page.model" -import { getIdBySlug } from "~/pages/api/slug2id" -import { getSummary } from "~/pages/api/summary" export const fetchGetPage = async ( input: Parameters[0],