Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
何 广一
chatroom
Commits
b5231b65
Commit
b5231b65
authored
Aug 26, 2025
by
何 广一
Browse files
zod check & clear test data
parent
ce20f0d3
Changes
14
Show whitespace changes
Inline
Side-by-side
chat-room/prisma/dev.db
View file @
b5231b65
No preview for this file type
chat-room/src/app/api/auth/loggedIn/route.tsx
0 → 100644
View file @
b5231b65
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
chat-room/src/app/api/interface-schema.tsx
0 → 100644
View file @
b5231b65
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
()
});
chat-room/src/app/api/login-interface.tsx
View file @
b5231b65
...
...
@@ -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
;
...
...
chat-room/src/app/api/message/add/route.tsx
View file @
b5231b65
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
);
...
...
chat-room/src/app/api/room-api.tsx
View file @
b5231b65
...
...
@@ -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
...
...
@@ -451,3 +453,48 @@ export const IncorrectUserResponse = async (token: string) : Promise<NextRespons
data
:
{
user
:
await
getUserNameByToken
(
token
)
}
});
};
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
chat-room/src/app/api/room/add/route.tsx
View file @
b5231b65
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
);
...
...
chat-room/src/app/api/room/delete/route.tsx
View file @
b5231b65
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
);
...
...
chat-room/src/app/api/room/message/getUpdate/route.tsx
View file @
b5231b65
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
chat-room/src/app/api/room/message/list/route.tsx
View file @
b5231b65
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
);
}
chat-room/src/app/chat/confirm-modal.tsx
View file @
b5231b65
'
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-
3
00 px-4 py-2 rounded"
className
=
"bg-gray-
2
00 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
>
...
...
chat-room/src/app/chat/room-list.tsx
View file @
b5231b65
...
...
@@ -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
>
)
}
...
...
chat-room/src/app/chat/send-message.tsx
View file @
b5231b65
...
...
@@ -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
>
...
...
chat-room/src/app/chat/username-check.tsx
View file @
b5231b65
...
...
@@ -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
;
}
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment