release 0.3.4
This commit is contained in:
106
apps/web/app/api/auth/[...nextauth]/route.ts
Normal file
106
apps/web/app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import NextAuth, { NextAuthOptions, Theme } from "next-auth";
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||
import prisma from "@/lib/db/prisma";
|
||||
import EmailProvider from "next-auth/providers/email";
|
||||
import GithubProvider from "next-auth/providers/github";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { createTransport } from "nodemailer";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers: [
|
||||
GithubProvider({
|
||||
clientId: process.env.GITHUB_ID as string,
|
||||
clientSecret: process.env.GITHUB_SECRET as string,
|
||||
}),
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID as string,
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
|
||||
}),
|
||||
EmailProvider({
|
||||
server: process.env.EMAIL_SERVER,
|
||||
from: process.env.EMAIL_FROM,
|
||||
maxAge: 10 * 60, // 10min, 邮箱链接失效时间,默认24小时
|
||||
async sendVerificationRequest({
|
||||
identifier: email,
|
||||
url,
|
||||
provider,
|
||||
theme,
|
||||
}) {
|
||||
const { host } = new URL(url);
|
||||
const transport = createTransport(provider.server);
|
||||
const result = await transport.sendMail({
|
||||
to: email,
|
||||
from: provider.from,
|
||||
subject: `You are logging in to Inke`,
|
||||
text: text({ url, host }),
|
||||
html: html({ url, host, theme }),
|
||||
});
|
||||
const failed = result.rejected.concat(result.pending).filter(Boolean);
|
||||
if (failed.length) {
|
||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`);
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
|
||||
/**
|
||||
*使用HTML body 代替正文内容
|
||||
*/
|
||||
function html(params: { url: string; host: string; theme: Theme }) {
|
||||
const { url, host, theme } = params;
|
||||
|
||||
const escapedHost = host.replace(/\./g, "​.");
|
||||
|
||||
const brandColor = theme.brandColor || "#346df1";
|
||||
const color = {
|
||||
background: "#f9f9f9",
|
||||
text: "#444",
|
||||
mainBackground: "#fff",
|
||||
buttonBackground: brandColor,
|
||||
buttonBorder: brandColor,
|
||||
buttonText: theme.buttonText || "#fff",
|
||||
};
|
||||
|
||||
return `
|
||||
<body style="background: ${color.background};">
|
||||
<table width="100%" border="0" cellspacing="10" cellpadding="0"
|
||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
<strong>Welcome to Inke</strong> 🎉
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 5px 0;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||
target="_blank"
|
||||
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||
in now</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left"
|
||||
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
Button click without response? Try open this <a href="${url}" target="_blank">link</a> in your browser. If you did not request this email you can safely ignore it.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
`;
|
||||
}
|
||||
|
||||
/** 不支持HTML 的邮件客户端会显示下面的文本信息 */
|
||||
function text({ url, host }: { url: string; host: string }) {
|
||||
return `Welcome to Inke! This is a magic link, click on it to log in ${url}\n`;
|
||||
}
|
38
apps/web/app/api/collaboration/count/route.ts
Normal file
38
apps/web/app/api/collaboration/count/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { findCollaborationInviteCount } from "@/lib/db/collaboration";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("id");
|
||||
if (!id || id === "undefined") {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty roomId",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findCollaborationInviteCount(id);
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Not joined the collaboration space",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
45
apps/web/app/api/collaboration/id/route.ts
Normal file
45
apps/web/app/api/collaboration/id/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import {
|
||||
findCollaborationByDBId,
|
||||
findCollaborationInviteCount,
|
||||
} from "@/lib/db/collaboration";
|
||||
|
||||
// /invite/:id 邀请页调用,查询此邀请详细信息,不需要登录,点击“加入协作”后才需要鉴权
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("id");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty roomId",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findCollaborationByDBId(id);
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Not joined the collaboration space",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
65
apps/web/app/api/collaboration/local-id/route.ts
Normal file
65
apps/web/app/api/collaboration/local-id/route.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import {
|
||||
findCollaborationByDBId,
|
||||
findCollaborationBylocalId,
|
||||
} from "@/lib/db/collaboration";
|
||||
|
||||
// /invite/:id 邀请页调用,查询此邀请详细信息,不需要登录,点击“加入协作”后才需要鉴权
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("localId");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty id",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findCollaborationBylocalId(id, user.id);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Not joined collaboration",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
107
apps/web/app/api/collaboration/room/route.ts
Normal file
107
apps/web/app/api/collaboration/room/route.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import { findCollaborationByRoomId } from "@/lib/db/collaboration";
|
||||
|
||||
// post页面获取详情时调用,需要用户id查询是否已经加入,加入才开启协作模式
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login first",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Not found user",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("roomId");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty roomId",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户是否已经加入了这个协作(已经创建了这个room记录)
|
||||
const res = await findCollaborationByRoomId(id, user.id);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "You haven't joined this collaboration yet",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
code: 500,
|
||||
msg: error,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { roomId } = await req.json();
|
||||
|
||||
if (!roomId) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty roomId",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findCollaborationByRoomId(roomId);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Not found",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
code: 500,
|
||||
msg: error,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// fix error: "DYNAMIC_SERVER_USAGE"
|
||||
export const dynamic = "force-dynamic";
|
180
apps/web/app/api/collaboration/route.ts
Normal file
180
apps/web/app/api/collaboration/route.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import {
|
||||
createCollaboration,
|
||||
deleteCollaborationNote,
|
||||
findCollaborationByRoomId,
|
||||
findUserCollaborations,
|
||||
} from "@/lib/db/collaboration";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findUserCollaborations(user.id);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Not joined collaboration",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { localId, roomId, title } = await req.json();
|
||||
if (!localId || !roomId) {
|
||||
return NextResponse.json({
|
||||
code: 405,
|
||||
msg: "Empty params",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 判断用户是否已经加入此协作
|
||||
const find_res = await findCollaborationByRoomId(roomId, user.id);
|
||||
|
||||
if (find_res) {
|
||||
return NextResponse.json({
|
||||
code: 301,
|
||||
msg: "Joined! Redirecting...",
|
||||
data: find_res,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: 限制新建个数
|
||||
// 新建
|
||||
const res = await createCollaboration(user.id, localId, roomId, title);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successfully!Redirecting...",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
code: 500,
|
||||
msg: error,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("id");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty id",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await deleteCollaborationNote(id);
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
// fix error: "DYNAMIC_SERVER_USAGE"
|
||||
export const dynamic = "force-dynamic";
|
109
apps/web/app/api/generate/bot/route.ts
Normal file
109
apps/web/app/api/generate/bot/route.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import OpenAI from "openai";
|
||||
import { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { getRandomElement } from "@/lib/utils";
|
||||
import { Account_Plans } from "../../../../lib/consts";
|
||||
|
||||
const api_key = process.env.OPENAI_API_KEY || "";
|
||||
const api_keys = process.env.OPENAI_API_KEYs || "";
|
||||
|
||||
const openai = new OpenAI({
|
||||
baseURL: process.env.OPENAI_API_PROXY || "https://api.openai.com",
|
||||
});
|
||||
|
||||
// IMPORTANT! Set the runtime to edge: https://vercel.com/docs/functions/edge-functions/edge-runtime
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
try {
|
||||
// Check if the OPENAI_API_KEY is set, if not return 400
|
||||
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === "") {
|
||||
return new Response(
|
||||
"Missing OPENAI_API_KEY – make sure to add it to your .env file.",
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt, plan, messages, system } = await req.json();
|
||||
|
||||
const planN = Number(plan || "5");
|
||||
|
||||
if (
|
||||
messages &&
|
||||
messages.length > Account_Plans[planN].ai_bot_history_length
|
||||
) {
|
||||
return new Response("You have reached the history message limit.", {
|
||||
status: 429,
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
|
||||
const ip = req.headers.get("x-forwarded-for");
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: kv,
|
||||
limiter: Ratelimit.slidingWindow(
|
||||
Account_Plans[planN].ai_generate_day,
|
||||
"1 d",
|
||||
),
|
||||
});
|
||||
|
||||
// console.log("plan", planN, Account_Plans[planN], ip);
|
||||
|
||||
const { success, limit, reset, remaining } = await ratelimit.limit(
|
||||
`novel_ratelimit_${ip}`,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return new Response(
|
||||
"You have reached your request limit for the day.",
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
"X-RateLimit-Limit": limit.toString(),
|
||||
"X-RateLimit-Remaining": remaining.toString(),
|
||||
"X-RateLimit-Reset": reset.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openai.apiKey = getRandomElement(api_keys.split(",")) || api_key;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-3.5-turbo-16k",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"As a note assistant, communicate with users based on the input note content.",
|
||||
// "Do not reply to questions unrelated to the notes. If there are questions unrelated to the notes, please reply 'Please ask questions related to the notes'",
|
||||
},
|
||||
{
|
||||
role: "system",
|
||||
content: `Note content: \n${system}`,
|
||||
},
|
||||
...messages,
|
||||
],
|
||||
temperature: 0.7,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
stream: true,
|
||||
n: 1,
|
||||
});
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
const stream = OpenAIStream(response);
|
||||
|
||||
// Respond with the stream
|
||||
return new StreamingTextResponse(stream);
|
||||
} catch (error) {
|
||||
return new Response(`Server error\n${error}`, {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
99
apps/web/app/api/generate/continue/route.ts
Normal file
99
apps/web/app/api/generate/continue/route.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import OpenAI from "openai";
|
||||
import { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { getRandomElement } from "@/lib/utils";
|
||||
import { Account_Plans } from "../../../../lib/consts";
|
||||
|
||||
const api_key = process.env.OPENAI_API_KEY || "";
|
||||
const api_keys = process.env.OPENAI_API_KEYs || "";
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
// IMPORTANT! Set the runtime to edge: https://vercel.com/docs/functions/edge-functions/edge-runtime
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
try {
|
||||
// Check if the OPENAI_API_KEY is set, if not return 400
|
||||
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === "") {
|
||||
return new Response(
|
||||
"Missing OPENAI_API_KEY – make sure to add it to your .env file.",
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt, plan } = await req.json();
|
||||
|
||||
const planN = Number(plan || "5");
|
||||
|
||||
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
|
||||
const ip = req.headers.get("x-forwarded-for");
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: kv,
|
||||
limiter: Ratelimit.slidingWindow(
|
||||
Account_Plans[planN].ai_generate_day,
|
||||
"1 d",
|
||||
),
|
||||
});
|
||||
|
||||
// console.log("plan", planN, Account_Plans[planN], ip);
|
||||
|
||||
const { success, limit, reset, remaining } = await ratelimit.limit(
|
||||
`novel_ratelimit_${ip}`,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return new Response(
|
||||
"You have reached your request limit for the day.",
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
"X-RateLimit-Limit": limit.toString(),
|
||||
"X-RateLimit-Remaining": remaining.toString(),
|
||||
"X-RateLimit-Reset": reset.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openai.apiKey = getRandomElement(api_keys.split(",")) || api_key;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-3.5-turbo-16k",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are an AI writing assistant that continues existing text based on context from prior text." +
|
||||
"Give more weight/priority to the later characters than the beginning ones. " +
|
||||
`Limit your response to no more than ${Account_Plans[planN].ai_generate_chars} characters, but make sure to construct complete sentences.`,
|
||||
// "Use Markdown formatting when appropriate.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
stream: true,
|
||||
n: 1,
|
||||
});
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
const stream = OpenAIStream(response);
|
||||
|
||||
// Respond with the stream
|
||||
return new StreamingTextResponse(stream);
|
||||
} catch (error) {
|
||||
return new Response("Server error", {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
101
apps/web/app/api/generate/edit/route.ts
Normal file
101
apps/web/app/api/generate/edit/route.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import OpenAI from "openai";
|
||||
import { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { getRandomElement } from "@/lib/utils";
|
||||
import { Account_Plans } from "../../../../lib/consts";
|
||||
|
||||
const api_key = process.env.OPENAI_API_KEY || "";
|
||||
const api_keys = process.env.OPENAI_API_KEYs || "";
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
// IMPORTANT! Set the runtime to edge: https://vercel.com/docs/functions/edge-functions/edge-runtime
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
try {
|
||||
// Check if the OPENAI_API_KEY is set, if not return 400
|
||||
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === "") {
|
||||
return new Response(
|
||||
"Missing OPENAI_API_KEY – make sure to add it to your .env file.",
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt, plan } = await req.json();
|
||||
|
||||
const planN = Number(plan || "5");
|
||||
|
||||
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
|
||||
const ip = req.headers.get("x-forwarded-for");
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: kv,
|
||||
limiter: Ratelimit.slidingWindow(
|
||||
Account_Plans[planN].ai_generate_day,
|
||||
"1 d",
|
||||
),
|
||||
});
|
||||
|
||||
// console.log("plan", planN, Account_Plans[planN], ip);
|
||||
|
||||
const { success, limit, reset, remaining } = await ratelimit.limit(
|
||||
`novel_ratelimit_${ip}`,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return new Response(
|
||||
"You have reached your request limit for the day.",
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
"X-RateLimit-Limit": limit.toString(),
|
||||
"X-RateLimit-Remaining": remaining.toString(),
|
||||
"X-RateLimit-Reset": reset.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openai.apiKey = getRandomElement(api_keys.split(",")) || api_key;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-3.5-turbo-16k",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: `I hope you can take on roles such as spell proofreading and rhetorical improvement,
|
||||
or other roles related to text editing, optimization, and abbreviation. I will
|
||||
communicate with you in any language, and you will recognize the language. Please only answer the corrected and improved parts, and
|
||||
do not write explanations.
|
||||
Limit your response to no more than ${Account_Plans[planN].ai_generate_chars} characters,
|
||||
but make sure to construct complete sentences.`,
|
||||
// "Use Markdown formatting when appropriate.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
stream: true,
|
||||
n: 1,
|
||||
});
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
const stream = OpenAIStream(response);
|
||||
|
||||
// Respond with the stream
|
||||
return new StreamingTextResponse(stream);
|
||||
} catch (error) {
|
||||
return new Response("Server error", {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,81 +0,0 @@
|
||||
import OpenAI from "openai";
|
||||
import { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
|
||||
// Create an OpenAI API client (that's edge friendly!)
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY || "",
|
||||
});
|
||||
|
||||
// IMPORTANT! Set the runtime to edge: https://vercel.com/docs/functions/edge-functions/edge-runtime
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
// Check if the OPENAI_API_KEY is set, if not return 400
|
||||
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === "") {
|
||||
return new Response(
|
||||
"Missing OPENAI_API_KEY – make sure to add it to your .env file.",
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (
|
||||
process.env.NODE_ENV != "development" &&
|
||||
process.env.KV_REST_API_URL &&
|
||||
process.env.KV_REST_API_TOKEN
|
||||
) {
|
||||
const ip = req.headers.get("x-forwarded-for");
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: kv,
|
||||
limiter: Ratelimit.slidingWindow(50, "1 d"),
|
||||
});
|
||||
|
||||
const { success, limit, reset, remaining } = await ratelimit.limit(
|
||||
`novel_ratelimit_${ip}`,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return new Response("You have reached your request limit for the day.", {
|
||||
status: 429,
|
||||
headers: {
|
||||
"X-RateLimit-Limit": limit.toString(),
|
||||
"X-RateLimit-Remaining": remaining.toString(),
|
||||
"X-RateLimit-Reset": reset.toString(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let { prompt } = await req.json();
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-3.5-turbo",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"You are an AI writing assistant that continues existing text based on context from prior text. " +
|
||||
"Give more weight/priority to the later characters than the beginning ones. " +
|
||||
"Limit your response to no more than 200 characters, but make sure to construct complete sentences.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
stream: true,
|
||||
n: 1,
|
||||
});
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
const stream = OpenAIStream(response);
|
||||
|
||||
// Respond with the stream
|
||||
return new StreamingTextResponse(stream);
|
||||
}
|
96
apps/web/app/api/generate/translate/route.ts
Normal file
96
apps/web/app/api/generate/translate/route.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import OpenAI from "openai";
|
||||
import { OpenAIStream, StreamingTextResponse } from "ai";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Ratelimit } from "@upstash/ratelimit";
|
||||
import { getRandomElement } from "@/lib/utils";
|
||||
import { Account_Plans } from "../../../../lib/consts";
|
||||
|
||||
const api_key = process.env.OPENAI_API_KEY || "";
|
||||
const api_keys = process.env.OPENAI_API_KEYs || "";
|
||||
|
||||
const openai = new OpenAI();
|
||||
|
||||
// IMPORTANT! Set the runtime to edge: https://vercel.com/docs/functions/edge-functions/edge-runtime
|
||||
export const runtime = "edge";
|
||||
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
try {
|
||||
// Check if the OPENAI_API_KEY is set, if not return 400
|
||||
if (!process.env.OPENAI_API_KEY || process.env.OPENAI_API_KEY === "") {
|
||||
return new Response(
|
||||
"Missing OPENAI_API_KEY – make sure to add it to your .env file.",
|
||||
{
|
||||
status: 400,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const { prompt, plan } = await req.json();
|
||||
|
||||
const planN = Number(plan || "5");
|
||||
|
||||
if (process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN) {
|
||||
const ip = req.headers.get("x-forwarded-for");
|
||||
const ratelimit = new Ratelimit({
|
||||
redis: kv,
|
||||
limiter: Ratelimit.slidingWindow(
|
||||
Account_Plans[planN].ai_generate_day,
|
||||
"1 d",
|
||||
),
|
||||
});
|
||||
|
||||
// console.log("plan", planN, Account_Plans[planN], ip);
|
||||
|
||||
const { success, limit, reset, remaining } = await ratelimit.limit(
|
||||
`novel_ratelimit_${ip}`,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
return new Response(
|
||||
"You have reached your request limit for the day.",
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
"X-RateLimit-Limit": limit.toString(),
|
||||
"X-RateLimit-Remaining": remaining.toString(),
|
||||
"X-RateLimit-Reset": reset.toString(),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
openai.apiKey = getRandomElement(api_keys.split(",")) || api_key;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: "gpt-3.5-turbo-16k",
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content:
|
||||
"I hope you can play the role of translator and spell proofreader. I will communicate with you in any language, and you will recognize the language and translate it to answer me.",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: prompt,
|
||||
},
|
||||
],
|
||||
temperature: 0.7,
|
||||
top_p: 1,
|
||||
frequency_penalty: 0,
|
||||
presence_penalty: 0,
|
||||
stream: true,
|
||||
n: 1,
|
||||
});
|
||||
|
||||
// Convert the response into a friendly text-stream
|
||||
const stream = OpenAIStream(response);
|
||||
|
||||
// Respond with the stream
|
||||
return new StreamingTextResponse(stream);
|
||||
} catch (error) {
|
||||
return new Response("Server error", {
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
}
|
51
apps/web/app/api/share/all/route.ts
Normal file
51
apps/web/app/api/share/all/route.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import { findUserShares } from "@/lib/db/share";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "UnAuth",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findUserShares(user.id);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
168
apps/web/app/api/share/route.ts
Normal file
168
apps/web/app/api/share/route.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { getServerSession } from "next-auth";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { authOptions } from "../auth/[...nextauth]/route";
|
||||
import { getUserByEmail } from "@/lib/db/user";
|
||||
import {
|
||||
createShareNote,
|
||||
deleteShareNote,
|
||||
findShareByLocalId,
|
||||
findUserSharesCount,
|
||||
updateShareClick,
|
||||
updateShareKeeps,
|
||||
updateShareNote,
|
||||
} from "@/lib/db/share";
|
||||
import { Account_Plans } from "@/lib/consts";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("id");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty id",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await findShareByLocalId(id);
|
||||
|
||||
if (res) {
|
||||
await updateShareClick(res.id, res.click); // 数据库id
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
if (!session?.user) {
|
||||
return NextResponse.json({
|
||||
code: 401,
|
||||
msg: "Unauthorized! Please login",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const user = await getUserByEmail(session.user.email);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const { data } = await req.json();
|
||||
if (!data) {
|
||||
return NextResponse.json({
|
||||
code: 405,
|
||||
msg: "Empty data",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const find_share_count = await findUserSharesCount(user.id);
|
||||
|
||||
if (
|
||||
find_share_count >= Account_Plans[Number(user.plan)].note_upload_count
|
||||
) {
|
||||
return NextResponse.json({
|
||||
code: 429,
|
||||
msg: "You have exceeded the maximum number of uploads, please upgrade your plan.",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
// 必需要用户ID
|
||||
const find_res = await findShareByLocalId(data.id, user.id);
|
||||
|
||||
if (find_res) {
|
||||
const update_res = await updateShareNote(data, find_res.id);
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Updated!",
|
||||
data: update_res,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await createShareNote(data, user.id);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
code: 500,
|
||||
msg: error,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get("id");
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 403,
|
||||
msg: "Empty id",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const res = await deleteShareNote(id);
|
||||
if (res) {
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "Successed!",
|
||||
data: res,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
// fix error: "DYNAMIC_SERVER_USAGE"
|
||||
export const dynamic = "force-dynamic";
|
45
apps/web/app/api/share/update/keep/route.ts
Normal file
45
apps/web/app/api/share/update/keep/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { findShareByDBId, updateShareKeeps } from "@/lib/db/share";
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { id } = await req.json();
|
||||
if (!id) {
|
||||
return NextResponse.json({
|
||||
code: 405,
|
||||
msg: "Empty id",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
const find_res = await findShareByDBId(id);
|
||||
|
||||
if (find_res) {
|
||||
const res = await updateShareKeeps(id, find_res.keeps);
|
||||
|
||||
return NextResponse.json({
|
||||
code: 200,
|
||||
msg: "success",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
code: 404,
|
||||
msg: "Something wrong",
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return NextResponse.json({
|
||||
code: 500,
|
||||
msg: error,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// fix error: "DYNAMIC_SERVER_USAGE"
|
||||
export const dynamic = "force-dynamic";
|
9
apps/web/app/api/status/route.ts
Normal file
9
apps/web/app/api/status/route.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
subject: "website",
|
||||
status: "live",
|
||||
color: "green",
|
||||
});
|
||||
}
|
@@ -1,17 +1,46 @@
|
||||
import { put } from "@vercel/blob";
|
||||
import { Account_Plans } from "@/lib/consts";
|
||||
import { NextResponse } from "next/server";
|
||||
// import { getServerSession } from "next-auth";
|
||||
// import { authOptions } from "../auth/[...nextauth]/route";
|
||||
// import { getUserByEmail } from "@/lib/db/user";
|
||||
import COS from "cos-nodejs-sdk-v5";
|
||||
import { Readable } from "stream";
|
||||
|
||||
export const runtime = "edge";
|
||||
const uploadFile = (stream: Readable, filename: string): Promise<any> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {
|
||||
Bucket: "gcloud-1303456836",
|
||||
Region: "ap-chengdu",
|
||||
Key: "inke/" + filename,
|
||||
Body: stream,
|
||||
};
|
||||
|
||||
cos.putObject(params, (err: any, data: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
var cos = new COS({
|
||||
SecretId: process.env.TencentSecretID || "",
|
||||
SecretKey: process.env.TencentSecretKey || "",
|
||||
});
|
||||
|
||||
export async function POST(req: Request) {
|
||||
if (!process.env.BLOB_READ_WRITE_TOKEN) {
|
||||
return new Response(
|
||||
"Missing BLOB_READ_WRITE_TOKEN. Don't forget to add that to your .env file.",
|
||||
{
|
||||
status: 401,
|
||||
},
|
||||
);
|
||||
}
|
||||
// if (!process.env.BLOB_READ_WRITE_TOKEN) {
|
||||
// return new Response(
|
||||
// "Missing BLOB_READ_WRITE_TOKEN. Don't forget to add that to your .env file.",
|
||||
// {
|
||||
// status: 401,
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
const file = req.body || "";
|
||||
const filename = req.headers.get("x-vercel-filename") || "file.txt";
|
||||
@@ -22,10 +51,25 @@ export async function POST(req: Request) {
|
||||
const finalName = filename.includes(fileType)
|
||||
? filename
|
||||
: `${filename}${fileType}`;
|
||||
const blob = await put(finalName, file, {
|
||||
contentType,
|
||||
access: "public",
|
||||
});
|
||||
|
||||
return NextResponse.json(blob);
|
||||
const fileStream = Readable.from(file as any);
|
||||
|
||||
const res = await uploadFile(fileStream, finalName);
|
||||
// console.log("上传结果", res, res.Location);
|
||||
|
||||
if (res && res.statusCode === 200) {
|
||||
return NextResponse.json({ url: "https://" + res.Location });
|
||||
}
|
||||
|
||||
// if (
|
||||
// blob &&
|
||||
// Number(blob.size) > Account_Plans[plan].image_upload_size * 1024 * 1024
|
||||
// ) {
|
||||
// return new Response(
|
||||
// "You have exceeded the maximum size of uploads, please upgrade your plan.",
|
||||
// {
|
||||
// status: 429,
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
50
apps/web/app/api/users/route.ts
Normal file
50
apps/web/app/api/users/route.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getUserByEmail, getUserById, updateUserName } from "@/lib/db/user";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const email = searchParams.get("email");
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (email) {
|
||||
const user = await getUserByEmail(email);
|
||||
return NextResponse.json(user);
|
||||
} else if (id && id !== "undefined") {
|
||||
const user = await getUserById(id);
|
||||
return NextResponse.json(user);
|
||||
}
|
||||
|
||||
return NextResponse.json("empty params");
|
||||
} catch (error) {
|
||||
return NextResponse.json(error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Record<string, string | string | undefined[]> },
|
||||
) {
|
||||
try {
|
||||
const { userId, userName } = await req.json();
|
||||
if (!userId || !userName) {
|
||||
return NextResponse.json("empty content");
|
||||
}
|
||||
|
||||
const res = await updateUserName(userId, userName);
|
||||
|
||||
if (res) {
|
||||
return NextResponse.json(res);
|
||||
}
|
||||
|
||||
return NextResponse.json("something wrong");
|
||||
} catch {
|
||||
return NextResponse.json("error");
|
||||
}
|
||||
}
|
||||
|
||||
// fix error: "DYNAMIC_SERVER_USAGE"
|
||||
export const dynamic = "force-dynamic";
|
Reference in New Issue
Block a user