Commit b5231b65 authored by 何 广一's avatar 何 广一
Browse files

zod check & clear test data

parent ce20f0d3
No preview for this file type
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../interface'
import * as Db from '../../room-api';
import * as Auth from '../../login-interface';
/*
- url: /api/auth/loggedIn
- method: POST
- Read & Check Cookie
*/
export async function POST(req: NextRequest): Promise<NextResponse<Ty.BackendResponse<Auth.LoggedInResult | null>>> {
const cookieData = await Db.VerifyCookie(req.cookies)
if('code' in cookieData)return NextResponse.json(cookieData);
const argsCheck = Auth.LoggedInArgsSchema.safeParse(await req.json());
if (!argsCheck.success) return Db.IncorrectArgumentResponse(argsCheck.error);
const { user } = argsCheck.data;
const result = await Db.CheckLoggedIn(cookieData.authToken, user);
//invalidUserErrorCode
//reloginErrorCode
//incorrectUserName
const res = NextResponse.json(result);
if (result.code === Auth.invalidUserErrorCode || result.code === Auth.reloginErrorCode) {
res.cookies.set('userToken', '', { maxAge: 0, path: '/' });
res.cookies.set('authToken', '', { maxAge: 0, path: '/' });
}
return res;
}
\ No newline at end of file
import {z} from 'zod';
import * as Ty from '../interface';
export const RoomAddArgsSchema = z.object({
user: z.string().min(2).max(100),
roomName: z.string().min(1).max(100)
});
export const RoomDeleteArgsSchema = z.object({
user: z.string().min(2).max(100),
roomId: z.number()
});
export const maxMessageLength = 1000;
export const MessageAddArgsSchema = z.object({
sender: z.string().min(2).max(100),
roomId: z.number(),
content: z.string().min(1).max(maxMessageLength)
});
export const RoomMessageListArgsSchema = z.object({
roomId: z.coerce.number().int()
});
export const RoomMessageGetUpdateArgsSchema = z.object({
roomId: z.coerce.number().int(),
sinceMessageId: z.coerce.number().int()
});
......@@ -30,6 +30,14 @@ export interface AutoLoginArgs {
cookie: LoginCookieData;
}
export interface LoggedInArgs {
user: string;
}
export interface LoggedInResult {
user: string;
}
export const cookieSchema = z.object({
userToken: z.string().nullable(),
authToken: z.string().nullable()
......@@ -46,6 +54,10 @@ export const authArgsSchema = z.object({
userIP: z.string().max(100).nullable()
});
export const LoggedInArgsSchema = z.object({
user: z.string().min(2).max(100)
});
export const reloginErrorCode = 1919810;
export const invalidUserErrorCode = 8848;
export const abnormalLoginState = 108;
......
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../interface'
import * as Db from '../../room-api';
import { MessageAddArgsSchema } from '../../interface-schema';
/*
- url: /api/message/add
......@@ -18,7 +19,10 @@ export async function POST(req: NextRequest): Promise<NextResponse<Ty.BackendRes
const cookieData = await Db.VerifyCookie(req.cookies);
if('code' in cookieData)return NextResponse.json(cookieData);
const { roomId, content, sender } = await req.json() as Ty.MessageAddArgs;
const parseResult = MessageAddArgsSchema.safeParse(await req.json());
if(!parseResult.success) return Db.IncorrectArgumentResponse(parseResult.error);
const { roomId, content, sender } = parseResult.data;
const checkResult = await Db.CheckUser(sender, cookieData.userToken);
if (!checkResult) return Db.IncorrectUserResponse(cookieData.userToken);
......
......@@ -4,6 +4,7 @@ import { createHash, randomBytes } from 'crypto';
import * as Ty from '../interface'
import {prisma, redis} from "@/lib/db";
import * as Auth from './login-interface';
import {z} from "zod";
export async function MakeError(message: string, code: number = 1, status: number = 500): Promise<NextResponse<Ty.BackendResponse<null>>> {
return NextResponse.json({
......@@ -275,6 +276,7 @@ export async function Login(args: Auth.AuthArgs) : Promise<Ty.BackendResponse<Au
token: authToken,
userName: username,
/* 登录过期模拟 : 改成 + 60000 即可模拟1分钟过期 */
//expiresAt: new Date(Date.now() + 60000), // 1 minute
expiresAt: new Date(Date.now() + 60 * 60 * 1000 * 24 * 7), // 7 days
ua: userAgent,
ip: userIP
......@@ -450,4 +452,49 @@ export const IncorrectUserResponse = async (token: string) : Promise<NextRespons
message: '用户名与登录令牌不匹配,将重定向到正确用户名。',
data: { user: await getUserNameByToken(token) }
});
};
\ No newline at end of file
};
export const IncorrectArgumentResponse = async (zerr: z.ZodError) : Promise<NextResponse<Ty.BackendResponse<null>>> => {
console.error(`ZodError: ${zerr}`);
return NextResponse.json({
code: Auth.incorrectUserName,
message: '参数格式错误。: ' + zerr.message,
data: null
}, {status : 400});
};
export const CheckLoggedIn = async (token: string, userName: string) : Promise<Ty.BackendResponse<Auth.LoggedInResult | null>> => {
const authToken = await prisma.authToken.findUnique({
where: { token },
select: { user: true, expiresAt: true }
});
if (!authToken) return {
code: Auth.invalidUserErrorCode,
message: '无效的用户信息,请重新登录。',
data: null
}
if (authToken.expiresAt < new Date()) {
await prisma.authToken.delete({
where: { token }
});
return {
code: Auth.reloginErrorCode,
message: '登录过期,请重新登录。',
data: null
};
}
if (authToken.user.name !== userName) {
return {
code: Auth.incorrectUserName,
message: '用户名与登录令牌不匹配,将重定向到正确用户名。',
data: { user: authToken.user.name }
};
}
return {
code: 0,
message: '用户已登录',
data: { user: authToken.user.name }
};
}
\ No newline at end of file
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../interface'
import * as Db from '../../room-api';
import { RoomAddArgsSchema } from '../../interface-schema';
/*
- url: /api/room/add
......@@ -20,7 +21,10 @@ export async function POST(req: NextRequest) : Promise<NextResponse<Ty.BackendRe
const cookieData = await Db.VerifyCookie(req.cookies);
if('code' in cookieData)return NextResponse.json(cookieData);
const { user, roomName }: Ty.RoomAddArgs = await req.json();
const parseResult = RoomAddArgsSchema.safeParse(await req.json());
if(!parseResult.success) return Db.IncorrectArgumentResponse(parseResult.error);
const { user, roomName }: Ty.RoomAddArgs = parseResult.data;
const checkResult = await Db.CheckUser(user, cookieData.userToken);
if (!checkResult) return Db.IncorrectUserResponse(cookieData.userToken);
......
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../interface'
import * as Db from '../../room-api';
import { RoomDeleteArgsSchema } from '../../interface-schema';
/*
- url: /api/room/delete
......@@ -18,7 +19,10 @@ export async function POST(req: NextRequest): Promise<NextResponse<Ty.BackendRes
const cookieData = await Db.VerifyCookie(req.cookies);
if('code' in cookieData) return NextResponse.json(cookieData);
const { user, roomId } = await req.json() as Ty.RoomDeleteArgs;
const parseResult = RoomDeleteArgsSchema.safeParse(await req.json());
if(!parseResult.success) return Db.IncorrectArgumentResponse(parseResult.error);
const { user, roomId } = parseResult.data;
const checkResult = await Db.CheckUser(user, cookieData.userToken);
if (!checkResult) return Db.IncorrectUserResponse(cookieData.userToken);
......
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../../interface'
import * as Db from '../../../room-api';
import { RoomMessageGetUpdateArgsSchema } from '../../../interface-schema';
/*
- url: /api/room/message/getUpdate
......@@ -21,10 +22,12 @@ export async function GET(req: NextRequest): Promise<NextResponse<Ty.BackendResp
const cookieData = await Db.VerifyCookie(req.cookies);
if('code' in cookieData) return NextResponse.json(cookieData);
const Params = req.nextUrl.searchParams;
const result = await Db.UpdateMessageList({
roomId: Number(Params.get('roomId')),
sinceMessageId: Number(Params.get('sinceMessageId'))
});
const Params: Record<string, string> = {};
req.nextUrl.searchParams.forEach((value, key) => {Params[key] = value;});
const parseResult = RoomMessageGetUpdateArgsSchema.safeParse(Params);
if(!parseResult.success) return Db.IncorrectArgumentResponse(parseResult.error);
const Args : Ty.RoomMessageGetUpdateArgs = parseResult.data;
const result = await Db.UpdateMessageList(Args);
return NextResponse.json(result);
}
\ No newline at end of file
import { NextRequest, NextResponse } from 'next/server';
import * as Ty from '../../../../interface'
import * as Db from '../../../room-api';
import { RoomMessageListArgsSchema } from '../../../interface-schema';
/*
- url: /api/room/message/list
......@@ -18,7 +19,13 @@ export async function GET(req: NextRequest): Promise<NextResponse<Ty.BackendResp
const cookieData = await Db.VerifyCookie(req.cookies);
if('code' in cookieData) return NextResponse.json(cookieData);
const Params = req.nextUrl.searchParams;
const response = await Db.GetMessageList({ roomId: Number(Params.get('roomId')) });
const Params: Record<string, string> = {};
req.nextUrl.searchParams.forEach((value, key) => {Params[key] = value;});
const parseResult = RoomMessageListArgsSchema.safeParse(Params);
if(!parseResult.success) return Db.IncorrectArgumentResponse(parseResult.error);
const Args : Ty.RoomMessageListArgs = parseResult.data;
const response = await Db.GetMessageList(Args);
return NextResponse.json(response);
}
'use client'
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useRoom } from './room-context'
import { after } from 'node:test';
import { set } from 'zod';
......@@ -18,7 +18,10 @@ export default function ConfirmModalProvider() {
errorModalMsg,
triggerCreateRoom } = useRoom();
const [createPending, setCreatePending] = useState(false);
useEffect(() => {
setCreatePending(false);
if (showCreateModal) {
setNewRoomName('');
}
......@@ -30,6 +33,12 @@ export default function ConfirmModalProvider() {
setAfterErrorModalEvent(null);
}
const handleCreateRoom = (pending : boolean) => {
if(pending)return;
setCreatePending(true);
triggerCreateRoom();
}
return (
<>
{showCreateModal &&
......@@ -50,14 +59,18 @@ export default function ConfirmModalProvider() {
/>
<div className="flex justify-end space-x-2">
<button
className="bg-gray-300 px-4 py-2 rounded"
className="bg-gray-200 px-4 py-2 rounded hover:bg-gray-300 active:bg-gray-400"
onClick={() => setShowCreateModal(false)}
>
取消
</button>
<button
className="bg-blue-500 text-white px-4 py-2 rounded"
onClick={triggerCreateRoom}
className={
createPending ?
"bg-gray-300 text-gray-500 px-4 py-2 rounded cursor-not-allowed" :
"bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 active:bg-blue-700"
}
onClick={() => handleCreateRoom(createPending)}
>
确定
</button>
......
......@@ -86,7 +86,7 @@ export default function RoomListProvider() {
return roomData.roomId;
} catch (error) {
console.error('创建房间失败:', error);
setRoomsError(error.message);
setRoomsError((error as Error).message);
}
return -1;
......@@ -176,7 +176,7 @@ export default function RoomListProvider() {
return true;
} catch (error) {
console.error('删除房间失败:', error);
setRoomsError(error.message);
setRoomsError((error as Error).message);
}
return false;
......@@ -234,7 +234,7 @@ export default function RoomListProvider() {
}
} catch (error) {
console.error('房间列表获取失败:', error);
setRoomsError(error.message);
setRoomsError((error as Error).message);
} finally {
setRoomsLoading(false);
}
......@@ -292,7 +292,7 @@ export default function RoomListProvider() {
}
<h3>{room.roomName}</h3>
{room.lastMessage ? (
<p>{room.lastMessage.sender}: {room.lastMessage.content}</p>
<p className="truncate">{room.lastMessage.sender}: {room.lastMessage.content}</p>
) : (
<p className="text-gray-500">暂无消息</p>
)}
......
......@@ -4,6 +4,7 @@ import { useRoom } from './room-context'
import { useEffect, useState } from 'react'
import * as Ty from '../interface'
import * as Auth from '../api/login-interface'
import { maxMessageLength } from '../api/interface-schema'
export default function SendMessageProvider() {
......@@ -29,7 +30,8 @@ export default function SendMessageProvider() {
}
- response: null (只要code为0即为成功)
*/
const sendMessage = async () => {
const sendMessage = async (allowed : boolean) => {
if (!allowed) return;
if (pending) return;
setPending(true);
......@@ -79,8 +81,8 @@ export default function SendMessageProvider() {
}
throw new Error(`发送失败: ${data.message}`);
}
} catch (error: any) {
setSendError(error.message);
} catch (error) {
setSendError((error as Error).message);
} finally {
setPending(false);
}
......@@ -93,6 +95,11 @@ export default function SendMessageProvider() {
}
}, [sendError]);
const [allowed, setAllowed] = useState(false);
useEffect(() => {
setAllowed((currentRoomInfo && text.length && text.length <= maxMessageLength && !pending)?true:false);
}, [currentRoomInfo, text, pending]);
return (
<div className="bg-white p-4">
<textarea
......@@ -103,13 +110,21 @@ export default function SendMessageProvider() {
onChange={(e) => setText(e.target.value)}
disabled={!currentRoomInfo}
/>
<div className="text-xs text-gray-500 mt-1 ">
{text.length}/{maxMessageLength}
{
text.length > maxMessageLength && (
<span className="text-red-500 ml-2">超出最大长度</span>
)
}
</div>
<button
className={
currentRoomInfo && text.length && !pending ?
allowed ?
"bg-blue-500 text-white py-2 px-4 mt-2 w-full hover:bg-blue-600 active:bg-blue-700" :
"bg-gray-300 text-gray-500 py-2 px-4 mt-2 w-full cursor-not-allowed"
}
onClick={sendMessage}
onClick={() => sendMessage(allowed)}
>
发送
</button>
......
......@@ -3,7 +3,9 @@
import { useEffect } from 'react';
import { redirect } from 'next/navigation';
import { useRoom } from './room-context'
import { useLocalBackend } from '../interface';
import { useLocalBackend, urlPrefix, BackendResponse } from '../interface';
import * as Auth from '../api/login-interface';
function UsernameCheck_External() {
const { username } = useRoom()
......@@ -19,8 +21,58 @@ function UsernameCheck_External() {
}
function UsernameCheck_Local() {
//URL /api/auth/logged
//TODO
//URL /api/auth/loggedIn
//invalidUserErrorCode
//reloginErrorCode
//incorrectUserName
const { username, setUsername, setAfterErrorModalEvent, triggerErrorModal } = useRoom();
const checkLogin = async () => {
try{
const Args : Auth.LoggedInArgs = { user: username };
const res = await fetch(`${urlPrefix}/api/auth/loggedIn`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(Args),
});
const data : BackendResponse<Auth.LoggedInResult | null> = await res.json();
if (data.code === Auth.incorrectUserName) {
const result = data.data as Auth.LoggedInResult;
setUsername(result.user);
setAfterErrorModalEvent(() => {
window.location.href = `/chat?username=${encodeURIComponent(result.user)}`;
});
throw new Error(data.message);
}
else if (data.code !== 0) {
setUsername('');
setAfterErrorModalEvent(() => {
window.location.href = `/`;
});
throw new Error(`登录错误: ${data.message}`);
}
} catch (error) {
console.error(error);
triggerErrorModal((error as Error).message);
}
};
//check every 5 minutes
useEffect(() => {
checkLogin();
const interval = setInterval(() => {
checkLogin();
}, 5 * 60 * 1000); // 5 minutes
return () => clearInterval(interval);
}, []);
return null;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment