monorepo packages (#344)

This commit is contained in:
Archer
2023-09-24 18:02:09 +08:00
committed by GitHub
parent a4ff5a3f73
commit 3d7178d06f
535 changed files with 12048 additions and 227 deletions

View File

@@ -0,0 +1,201 @@
import type { NextApiRequest } from 'next';
import Cookie from 'cookie';
import { App, OpenApi, User, KB } from '../mongo';
import type { AppSchema, UserModelSchema } from '@/types/mongoSchema';
import { ERROR_ENUM } from '../errorCode';
import { authJWT } from './tools';
import { authOpenApiKey } from '../support/openapi/auth';
export enum AuthUserTypeEnum {
token = 'token',
root = 'root',
apikey = 'apikey'
}
export const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
// 获取 cookie
const cookies = Cookie.parse(cookie || '');
const cookieToken = cookies.token || token;
if (!cookieToken) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return await authJWT(cookieToken);
};
/* auth balance */
export const authBalanceByUid = async (uid: string) => {
const user = await User.findById<UserModelSchema>(
uid,
'_id username balance openaiAccount timezone'
);
if (!user) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
if (user.balance <= 0) {
return Promise.reject(ERROR_ENUM.insufficientQuota);
}
return user;
};
/* uniform auth user */
export const authUser = async ({
req,
authToken = false,
authRoot = false,
authBalance = false
}: {
req: NextApiRequest;
authToken?: boolean;
authRoot?: boolean;
authBalance?: boolean;
}) => {
const parseAuthorization = async (authorization?: string) => {
if (!authorization) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
// Bearer fastgpt-xxxx-appId
const auth = authorization.split(' ')[1];
if (!auth) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
const { apikey, appId: authorizationAppid = '' } = await (async () => {
const arr = auth.split('-');
// abandon
if (arr.length === 3) {
return {
apikey: `${arr[0]}-${arr[1]}`,
appId: arr[2]
};
}
if (arr.length === 2) {
return {
apikey: auth
};
}
return Promise.reject(ERROR_ENUM.unAuthorization);
})();
// auth apikey
const { userId, appId: apiKeyAppId = '' } = await authOpenApiKey({ apikey });
return {
uid: userId,
apikey,
appId: apiKeyAppId || authorizationAppid
};
};
const parseRootKey = async (rootKey?: string, userId = '') => {
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return userId;
};
const { cookie, token, apikey, rootkey, userid, authorization } = (req.headers || {}) as {
cookie?: string;
token?: string;
apikey?: string;
rootkey?: string; // abandon
userid?: string;
authorization?: string;
};
let uid = '';
let appId = '';
let openApiKey = apikey;
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
if (authToken) {
uid = await authCookieToken(cookie, token);
authType = AuthUserTypeEnum.token;
} else if (authRoot) {
uid = await parseRootKey(rootkey, userid);
authType = AuthUserTypeEnum.root;
} else if (cookie || token) {
uid = await authCookieToken(cookie, token);
authType = AuthUserTypeEnum.token;
} else if (apikey) {
const parseResult = await authOpenApiKey({ apikey });
uid = parseResult.userId;
authType = AuthUserTypeEnum.apikey;
openApiKey = parseResult.apikey;
} else if (authorization) {
const authResponse = await parseAuthorization(authorization);
uid = authResponse.uid;
appId = authResponse.appId;
openApiKey = authResponse.apikey;
authType = AuthUserTypeEnum.apikey;
} else if (rootkey) {
uid = await parseRootKey(rootkey, userid);
authType = AuthUserTypeEnum.root;
} else {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
// balance check
const user = await (() => {
if (authBalance) {
return authBalanceByUid(uid);
}
})();
return {
userId: String(uid),
appId,
authType,
user,
apikey: openApiKey
};
};
// 模型使用权校验
export const authApp = async ({
appId,
userId,
authUser = true,
authOwner = true,
reserveDetail = false
}: {
appId: string;
userId: string;
authUser?: boolean;
authOwner?: boolean;
reserveDetail?: boolean; // focus reserve detail
}) => {
// 获取 app 数据
const app = await App.findById<AppSchema>(appId);
if (!app) {
return Promise.reject('App is not exists');
}
/*
Access verification
1. authOwner=true or authUser = true , just owner can use
2. authUser = false and share, anyone can use
*/
if (authOwner || authUser) {
if (userId !== String(app.userId)) return Promise.reject(ERROR_ENUM.unAuthModel);
}
return {
app,
showModelDetail: userId === String(app.userId)
};
};
// 知识库操作权限
export const authKb = async ({ kbId, userId }: { kbId: string; userId: string }) => {
const kb = await KB.findOne({
_id: kbId,
userId
});
if (kb) {
return kb;
}
return Promise.reject(ERROR_ENUM.unAuthKb);
};

View File

@@ -0,0 +1,93 @@
import { ChatItemType } from '@/types/chat';
import { Chat, App, ChatItem } from '@/service/mongo';
import { ChatSourceEnum } from '@/constants/chat';
type Props = {
chatId: string;
appId: string;
userId: string;
variables?: Record<string, any>;
isOwner: boolean;
source: `${ChatSourceEnum}`;
shareId?: string;
content: [ChatItemType, ChatItemType];
};
export async function saveChat({
chatId,
appId,
userId,
variables,
isOwner,
source,
shareId,
content
}: Props) {
try {
const chatHistory = await Chat.findOne(
{
chatId,
userId,
appId
},
'_id'
);
const promise: any[] = [
ChatItem.insertMany(
content.map((item) => ({
chatId,
userId,
appId,
...item
}))
)
];
if (chatHistory) {
promise.push(
Chat.updateOne(
{ chatId, userId, appId },
{
title: content[0].value.slice(0, 20),
updateTime: new Date()
}
)
);
} else {
promise.push(
Chat.create({
chatId,
userId,
appId,
variables,
title: content[0].value.slice(0, 20),
source,
shareId
})
);
}
if (isOwner && source === ChatSourceEnum.online) {
promise.push(
App.findByIdAndUpdate(appId, {
updateTime: new Date()
})
);
}
await Promise.all(promise);
} catch (error) {
Chat.updateOne(
{ chatId, userId },
{
$push: {
content: {
$each: [],
$slice: -10
}
}
}
);
}
}

View File

@@ -0,0 +1,24 @@
export const getChatModel = (model?: string) => {
return global.chatModels.find((item) => item.model === model);
};
export const getVectorModel = (model?: string) => {
return (
global.vectorModels.find((item) => item.model === model) || {
model: 'UnKnow',
name: 'UnKnow',
defaultToken: 500,
price: 0,
maxToken: 3000
}
);
};
export const getModel = (model?: string) => {
return [
...global.chatModels,
...global.vectorModels,
global.qaModel,
global.extractModel,
global.cqModel
].find((item) => item.model === model);
};

View File

@@ -0,0 +1,117 @@
import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next';
import NextCors from 'nextjs-cors';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
import { generateQA } from '../events/generateQA';
import { generateVector } from '../events/generateVector';
import { ERROR_ENUM } from '../errorCode';
/* 密码加密 */
export const hashPassword = (psw: string) => {
return crypto.createHash('sha256').update(psw).digest('hex');
};
/* 生成 token */
export const generateToken = (userId: string) => {
const key = process.env.TOKEN_KEY as string;
const token = jwt.sign(
{
userId,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
},
key
);
return token;
};
// auth token
export const authJWT = (token: string) =>
new Promise<string>((resolve, reject) => {
const key = process.env.TOKEN_KEY as string;
jwt.verify(token, key, function (err, decoded: any) {
if (err || !decoded?.userId) {
reject(ERROR_ENUM.unAuthorization);
return;
}
resolve(decoded.userId);
});
});
/* set cookie */
export const setCookie = (res: NextApiResponse, token: string) => {
res.setHeader(
'Set-Cookie',
`token=${token}; Path=/; HttpOnly; Max-Age=604800; Samesite=None; Secure;`
);
};
/* clear cookie */
export const clearCookie = (res: NextApiResponse) => {
res.setHeader('Set-Cookie', 'token=; Path=/; Max-Age=0');
};
export function withNextCors(handler: NextApiHandler): NextApiHandler {
return async function nextApiHandlerWrappedWithNextCors(
req: NextApiRequest,
res: NextApiResponse
) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const origin = req.headers.origin;
await NextCors(req, res, {
methods,
origin: origin,
optionsSuccessStatus: 200
});
return handler(req, res);
};
}
/* start task */
export const startQueue = () => {
for (let i = 0; i < global.systemEnv.qaMaxProcess; i++) {
generateQA();
}
for (let i = 0; i < global.systemEnv.vectorMaxProcess; i++) {
generateVector();
}
};
export const sseResponse = ({
res,
event,
data
}: {
res: NextApiResponse;
event?: string;
data: string;
}) => {
if (res.closed) return;
event && res.write(`event: ${event}\n`);
res.write(`data: ${data}\n\n`);
};
/* add logger */
export const addLog = {
info: (msg: string, obj?: Record<string, any>) => {
global.logger?.info(msg, { meta: obj });
},
error: (msg: string, error?: any) => {
global.logger?.error(msg, {
meta: {
stack: error?.stack,
...(error?.config && {
config: {
headers: error.config.headers,
url: error.config.url,
data: error.config.data
}
}),
...(error?.response && {
response: {
status: error.response.status,
statusText: error.response.statusText
}
})
}
});
}
};