Commit 496f7668 authored by 何 广一's avatar 何 广一
Browse files

prisma & auth api

parent 35afac8f
import * as Ty from '../interface'
export interface RoomInfo extends Ty.RoomPreviewInfo {
creator: string;
}
export let State = {
maxRoomId: 2 as number,
maxMessageId: 3 as number,
fakeRooms: [
{
roomId: 1,
roomName: '房间1',
lastMessage: null,
creator: '用户1'
},
{
roomId: 2,
roomName: '房间2',
lastMessage: null,
creator: '用户2'
}
] as RoomInfo[],
fakeMsg: [
{
messageId: 1,
roomId: 1,
sender: '用户1',
content: '你好',
time: Date.now()
},
{
messageId: 2,
roomId: 1,
sender: '用户2',
content: '你好啊',
time: Date.now()
},
{
messageId: 3,
roomId: 2,
sender: '用户1',
content: '房间2的消息',
time: Date.now()
}
] as Ty.Message[],
}
......@@ -2,6 +2,7 @@
import { useState, useEffect } from 'react'
import * as Ty from '../interface'
import { useRoom } from './room-context'
import { notAuthorizedToDeleteRoomErrorCode } from '../api/login-interface'
export default function RoomListProvider() {
......@@ -119,13 +120,13 @@ export default function RoomListProvider() {
/*
Error : You are not the creator. deletion failed.
{
"code": 2,
"code": notAuthorizedToDeleteRoomErrorCode,// code=2
"message": "auth error: invalid identity",
"data": null
}
*/
if (data.code === 2) {
if (data.code === notAuthorizedToDeleteRoomErrorCode) {
triggerErrorModal('你不是房间创建者,无法删除房间。');
return false;
}
......
......@@ -14,6 +14,7 @@ export default function SendMessageProvider() {
} = useRoom()
const [text, setText] = useState('');
const [sendError, setSendError] = useState<string | null>(null);
const [pending, setPending] = useState(false);
/*
- url: /api/message/add
......@@ -27,9 +28,13 @@ export default function SendMessageProvider() {
- response: null (只要code为0即为成功)
*/
const sendMessage = async () => {
if (pending) return;
setPending(true);
setSendError(null);
if (!currentRoomInfo) return;
if (!text.trim()) return;
const Args : Ty.MessageAddArgs = {
roomId: currentRoomInfo.roomId,
......@@ -54,8 +59,10 @@ export default function SendMessageProvider() {
} else {
throw new Error(`发送失败: ${data.message}`);
}
} catch (error) {
} catch (error: any) {
setSendError(error.message);
} finally {
setPending(false);
}
}
......@@ -78,7 +85,7 @@ export default function SendMessageProvider() {
/>
<button
className={
currentRoomInfo && text.length ?
currentRoomInfo && text.length && !pending ?
"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"
}
......
......@@ -4,7 +4,7 @@
===============后端选择===============
======================================
*/
const useLocalBackend : boolean = true;
export const useLocalBackend : boolean = true;
/*
======================================
======================================
......
'use client';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useLocalBackend } from './interface';
import * as Auth from './api/login-interface';
import * as Ty from './interface';
export default function Home() {
function Home_Local(){
const [isLogin, setIsLogin] = useState(true);
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [session, setSession] = useState<Auth.Session | null>(null);
const [pageError, setPageError] = useState<string | null>(null);
const [isErrorInstant, setIsErrorInstant] = useState(false);
const [pending, setPending] = useState(false);
const router = useRouter();
const initSession = async () => {
//url : /api/auth/startSession
setPageError(null);
setIsErrorInstant(false);
try{
const res = await fetch(`${Ty.urlPrefix}/api/auth/startSession`, {
method: 'GET',
});
if (!res.ok) {
throw new Error('无法获取会话信息,错误码:' + res.status);
}
const data: Ty.BackendResponse<Auth.Session> = await res.json();
if (data.code !== 0) {
throw new Error('会话获取错误,错误信息:' + data.message);
}
setSession(data.data);
} catch (error) {
console.error(error);
setSession(null);
setPageError('无法初始化会话,请重试');
setIsErrorInstant(true);
}
}
useEffect(() => {
initSession();
}, []);
const updatePassword = (value: string, confirm: string | null) => {
setPassword(value);
if (value.length < Auth.minPasswordLength) {
setPageError(`密码长度不能少于 ${Auth.minPasswordLength} 个字符`);
setIsErrorInstant(true);
} else if (value.length > Auth.maxPasswordLength) {
setPageError(`密码长度不能超过 ${Auth.maxPasswordLength} 个字符`);
setIsErrorInstant(true);
} else {
setPageError(null);
setIsErrorInstant(false);
}
if (confirm) {
if (value !== confirm) {
setPageError('两次输入的密码不一致');
setIsErrorInstant(true);
} else {
setPageError(null);
setIsErrorInstant(false);
}
}
}
const updateConfirmPassword = (password: string, value: string) => {
setConfirmPassword(value);
if (password !== value) {
setPageError('两次输入的密码不一致');
setIsErrorInstant(true);
} else {
setPageError(null);
setIsErrorInstant(false);
}
};
const handleSubmit = async (asLogin : boolean) => {
setPending(true);
setPageError(null);
setIsErrorInstant(false);
const Args : Auth.AuthArgs = {
sessionId: session?.sessionId || '',
username,
password,
userAgent: navigator.userAgent,
userIP: null // 服务端填充
};
try{
if(asLogin)
{
//URL : /api/auth/login
const res = await fetch(`${Ty.urlPrefix}/api/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(Args),
});
if (!res.ok) {
throw new Error('登录失败,错误码:' + res.status);
}
const data: Ty.BackendResponse<Auth.LoginResult> = await res.json();
if (data.code !== 0) {
throw new Error('登录失败,错误信息:' + data.message);
}
router.push(`/chat?username=${encodeURIComponent(username)}`);
} else {
//URL : /api/auth/register
const res = await fetch(`${Ty.urlPrefix}/api/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(Args),
});
if (!res.ok) {
throw new Error('注册失败,错误码:' + res.status);
}
const data: Ty.BackendResponse<Auth.RegisterResult> = await res.json();
if (data.code !== 0) {
throw new Error('注册失败,错误信息:' + data.message);
}
router.refresh();
}
} catch (error) {
console.error(error);
setPageError((asLogin ? '登录错误:' : '注册错误:') + error.message);
setIsErrorInstant(false);
} finally {
setPending(false);
}
};
return (
<div className="bg-gray-100 h-screen flex flex-col items-center justify-center relative">
<h1 className="text-2xl mb-4">请输入用户名</h1>
<input
className="border border-gray-300 rounded px-3 py-2 mb-4"
type="text"
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value.trim())}
required
/>
<input
className="border border-gray-300 rounded px-3 py-2 mb-4"
type="password"
value={password}
placeholder="密码"
onChange={(e) => updatePassword(e.target.value.trim(), isLogin ? null : confirmPassword)}
required
/>
{!isLogin &&
<input
className="border border-gray-300 rounded px-3 py-2 mb-4"
type="password"
value={confirmPassword}
placeholder="确认密码"
onChange={(e) => updateConfirmPassword(password, e.target.value.trim())}
required
/>
}
<p className="text-red-500 mb-4">{pageError !== null && pageError}</p>
<div className="flex flex-row">
<button
className=
{username.length && password.length && session && !pending && !(pageError && isErrorInstant) ?
"bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 active:bg-blue-700" :
"bg-gray-300 text-gray-500 px-4 py-2 rounded cursor-not-allowed"
}
onClick={() => handleSubmit(isLogin)}
>
{isLogin ? '登录' : '注册'}
</button>
<button
className="
bg-blue-500 text-white px-4 py-2 rounded ml-4
hover:bg-blue-600 active:bg-blue-700"
onClick={() => setIsLogin(v => !v)}
>
{isLogin ? '切换到注册' : '切换到登录'}
</button>
</div>
</div>
);
}
function Home_External() {
const [username, setUsername] = useState('');
const router = useRouter();
const handleSubmit = () => {
if (!username.trim()) return; // 简单校验
/* 这段后面换成登录 */
if (!username.trim()) return;
router.push(`/chat?username=${encodeURIComponent(username)}`);
};
......@@ -36,3 +234,9 @@ export default function Home() {
</div>
);
}
const HomeFn = useLocalBackend ? Home_Local : Home_External;
export default function Home() {
return HomeFn();
}
\ No newline at end of file
import { PrismaClient } from "@/generated/prisma";
import { createClient, RedisClientType } from 'redis';
import { RateLimiterRedis } from 'rate-limiter-flexible';
declare global{
var prisma:PrismaClient | undefined;
var redisClient: RedisClientType | undefined;
var rateLimiter:RateLimiterRedis | undefined;
}
const connectRedisClient = async () => {
const client = createClient();
client.on('error', (err) => console.error('Redis Client Error', err));
await client.connect();
return client;
};
const getRateLimiter = () => {
const rateLimiter = new RateLimiterRedis({
storeClient: redis,
keyPrefix: 'rateLimiter',
points: 5, // 5 points
duration: 1, // per second
});
return rateLimiter;
};
export const prisma = global.prisma || new PrismaClient();
export const redis = global.redisClient || await connectRedisClient();
export const rateLimiter = global.rateLimiter || getRateLimiter();
if(process.env.NODE_ENV!=='production'){
global.prisma=prisma;
}
\ No newline at end of file
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