Skip to content

Commit 7a845bb

Browse files
リクエストを送ったら、チャットでメッセージが送れるようになる (#538)
# PRの概要 close #499 ## 具体的な変更内容 - ユーザーがリクエストを送ると、マッチングしてなくてもチャットはできる状態となる - この時、このユーザーをacceptしますか?というモーダルが常にチャット欄に出て、YESだったら、そのモーダルは消えてチャット可能、NOだったら、チャットは消える。 ## 影響範囲 サーバー側で、リクエストに関する制限を緩めた。 ## スクショ <img width="298" alt="スクリーンショット 2024-12-02 17 59 23" src="https://github.com/user-attachments/assets/73f40604-99fd-4547-abf2-2dbb79647ab8"> <img width="298" alt="スクリーンショット 2024-12-02 17 59 28" src="https://github.com/user-attachments/assets/80ef6ca9-acb6-4f62-8c8e-1784f7471450"> <img width="298" alt="スクリーンショット 2024-12-02 17 59 35" src="https://github.com/user-attachments/assets/49016809-d122-4695-8f51-e3b6d29d2ce9"> ## 補足 ## レビューリクエストを出す前にチェック! - [ ] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか <!-- レビューリクエスト後は、Slackでもメンションしてお願いすることを推奨します。 --> --------- Co-authored-by: aster <[email protected]>
1 parent 3d751bc commit 7a845bb

File tree

9 files changed

+232
-65
lines changed

9 files changed

+232
-65
lines changed

common/zod/schemas.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,15 @@ export const SendMessageSchema = z.object({
123123
content: z.string().min(1, { message: "Content must not be empty." }),
124124
});
125125

126+
export const MatchingStatusSchema = z.union([
127+
z.literal("myRequest"),
128+
z.literal("otherRequest"),
129+
z.literal("matched"),
130+
]);
131+
126132
export const DMOverviewSchema = z.object({
127133
isDM: z.literal(true),
134+
matchingStatus: MatchingStatusSchema,
128135
friendId: UserIDSchema,
129136
name: NameSchema,
130137
thumbnail: z.string(),
@@ -153,6 +160,7 @@ export const DMRoomSchema = z.object({
153160
export const PersonalizedDMRoomSchema = z.object({
154161
name: NameSchema,
155162
thumbnail: z.string(),
163+
matchingStatus: MatchingStatusSchema,
156164
});
157165

158166
export const SharedRoomSchema = z.object({

server/src/database/chat.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,35 @@ import type {
1414
} from "common/types";
1515
import { prisma } from "./client";
1616
import { getRelation } from "./matches";
17-
import { getMatchedUser } from "./requests";
17+
import {
18+
getMatchedUser,
19+
getPendingRequestsFromUser,
20+
getPendingRequestsToUser,
21+
} from "./requests";
1822

19-
// ユーザーの参加しているすべての Room の概要 (Overview) の取得
2023
export async function getOverview(
2124
user: UserID,
2225
): Promise<Result<RoomOverview[]>> {
2326
try {
2427
const matched = await getMatchedUser(user);
2528
if (!matched.ok) return Err(matched.error);
2629

27-
const dm = await Promise.all(
30+
const senders = await getPendingRequestsToUser(user);
31+
if (!senders.ok) return Err(senders.error);
32+
33+
const receivers = await getPendingRequestsFromUser(user);
34+
if (!receivers.ok) return Err(receivers.error);
35+
36+
//マッチングしている人のオーバービュー
37+
const matchingOverview = await Promise.all(
2838
matched.value.map(async (friend) => {
2939
const lastMessageResult = await getLastMessage(user, friend.id);
3040
const lastMessage = lastMessageResult.ok
3141
? lastMessageResult.value
3242
: undefined;
3343
const overview: DMOverview = {
3444
isDM: true,
45+
matchingStatus: "matched",
3546
friendId: friend.id,
3647
name: friend.name,
3748
thumbnail: friend.pictureUrl,
@@ -41,6 +52,44 @@ export async function getOverview(
4152
}),
4253
);
4354

55+
//自分にリクエストを送ってきた人のオーバービュー
56+
const senderOverview = await Promise.all(
57+
senders.value.map(async (sender) => {
58+
const lastMessageResult = await getLastMessage(user, sender.id);
59+
const lastMessage = lastMessageResult.ok
60+
? lastMessageResult.value
61+
: undefined;
62+
const overview: DMOverview = {
63+
isDM: true,
64+
matchingStatus: "otherRequest",
65+
friendId: sender.id,
66+
name: sender.name,
67+
thumbnail: sender.pictureUrl,
68+
lastMsg: lastMessage,
69+
};
70+
return overview;
71+
}),
72+
);
73+
74+
//自分がリクエストを送った人のオーバービュー
75+
const receiverOverview = await Promise.all(
76+
receivers.value.map(async (receiver) => {
77+
const lastMessageResult = await getLastMessage(user, receiver.id);
78+
const lastMessage = lastMessageResult.ok
79+
? lastMessageResult.value
80+
: undefined;
81+
const overview: DMOverview = {
82+
isDM: true,
83+
matchingStatus: "myRequest",
84+
friendId: receiver.id,
85+
name: receiver.name,
86+
thumbnail: receiver.pictureUrl,
87+
lastMsg: lastMessage,
88+
};
89+
return overview;
90+
}),
91+
);
92+
4493
const sharedRooms: {
4594
id: number;
4695
name: string;
@@ -61,7 +110,13 @@ export async function getOverview(
61110
};
62111
return overview;
63112
});
64-
return Ok([...shared, ...dm]);
113+
114+
return Ok([
115+
...matchingOverview,
116+
...senderOverview,
117+
...receiverOverview,
118+
...shared,
119+
]);
65120
} catch (e) {
66121
return Err(e);
67122
}

server/src/functions/chat.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ export async function sendDM(
4242
send: SendMessage,
4343
): Promise<http.Response<Message>> {
4444
const rel = await getRelation(from, to);
45-
if (!rel.ok || rel.value.status !== "MATCHED")
46-
return http.forbidden("cannot send to non-friend");
45+
if (!rel.ok || rel.value.status === "REJECTED")
46+
return http.forbidden(
47+
"You cannot send a message because the friendship request was rejected.",
48+
);
4749

4850
// they are now MATCHED
4951
const msg: Omit<Message, "id"> = {
@@ -59,21 +61,28 @@ export async function sendDM(
5961
}
6062

6163
export async function getDM(
62-
requester: UserID,
63-
_with: UserID,
64+
user: UserID,
65+
friend: UserID,
6466
): Promise<http.Response<PersonalizedDMRoom & DMRoom>> {
65-
if (!areMatched(requester, _with))
66-
return http.forbidden("cannot DM with a non-friend");
67+
const rel = await getRelation(user, friend);
68+
if (!rel.ok || rel.value.status === "REJECTED")
69+
return http.forbidden("cannot send to rejected-friend");
6770

68-
const room = await db.getDMbetween(requester, _with);
71+
const room = await db.getDMbetween(user, friend);
6972
if (!room.ok) return http.internalError();
7073

71-
const friendData = await getUserByID(_with);
74+
const friendData = await getUserByID(friend);
7275
if (!friendData.ok) return http.notFound("friend not found");
7376

7477
const personalized: PersonalizedDMRoom & DMRoom = {
7578
name: friendData.value.name,
7679
thumbnail: friendData.value.pictureUrl,
80+
matchingStatus:
81+
rel.value.status === "MATCHED"
82+
? "matched"
83+
: rel.value.sendingUserId === user //どっちが送ったリクエストなのかを判定
84+
? "myRequest"
85+
: "otherRequest",
7786
...room.value,
7887
};
7988

server/src/router/chat.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ router.get("/overview", async (req, res) => {
2323
res.status(result.code).send(result.body);
2424
});
2525

26-
// send DM to userid.
26+
// send DM to userId.
2727
router.post("/dm/to/:userid", async (req, res) => {
2828
const user = await safeGetUserId(req);
2929
if (!user.ok) return res.status(401).send("auth error");
3030
const friend = safeParseInt(req.params.userid);
31-
if (!friend.ok) return res.status(400).send("bad param encoding: `userid`");
31+
if (!friend.ok) return res.status(400).send("bad param encoding: `userId`");
3232

3333
const send = SendMessageSchema.safeParse(req.body);
3434
if (!send.success) {
@@ -42,14 +42,14 @@ router.post("/dm/to/:userid", async (req, res) => {
4242
res.status(result.code).send(result.body);
4343
});
4444

45-
// GET a DM Room with userid, CREATE one if not found.
45+
// GET a DM Room with userId, CREATE one if not found.
4646
router.get("/dm/with/:userid", async (req, res) => {
4747
const user = await safeGetUserId(req);
4848
if (!user.ok) return res.status(401).send("auth error");
4949

5050
const friend = safeParseInt(req.params.userid);
5151
if (!friend.ok)
52-
return res.status(400).send("invalid param `userid` formatting");
52+
return res.status(400).send("invalid param `userId` formatting");
5353

5454
const result = await core.getDM(user.value, friend.value);
5555

web/api/chat/chat.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {
33
InitRoom,
44
Message,
55
MessageID,
6+
PersonalizedDMRoom,
67
RoomOverview,
78
SendMessage,
89
ShareRoomID,
@@ -83,22 +84,16 @@ export async function sendDM(
8384
return res.json();
8485
}
8586

86-
export async function getDM(friendId: UserID): Promise<
87-
DMRoom & {
88-
name: string;
89-
thumbnail: string;
90-
}
91-
> {
87+
export async function getDM(
88+
friendId: UserID,
89+
): Promise<DMRoom & PersonalizedDMRoom> {
9290
const res = await credFetch("GET", endpoints.dmWith(friendId));
9391
if (res.status === 401) throw new ErrUnauthorized();
9492
if (res.status !== 200)
9593
throw new Error(
9694
`getDM() failed: expected status code 200, got ${res.status}`,
9795
);
98-
const json: DMRoom & {
99-
name: string;
100-
thumbnail: string;
101-
} = await res.json();
96+
const json: DMRoom & PersonalizedDMRoom = await res.json();
10297
if (!Array.isArray(json?.messages)) return json;
10398
for (const m of json.messages) {
10499
m.createdAt = new Date(m.createdAt);

web/app/chat/[id]/page.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,13 @@
11
"use client";
2+
import type { DMRoom, PersonalizedDMRoom } from "common/types";
3+
import Link from "next/link";
24
import { useEffect, useState } from "react";
35
import * as chat from "~/api/chat/chat";
46
import { RoomWindow } from "~/components/chat/RoomWindow";
57

68
export default function Page({ params }: { params: { id: string } }) {
79
const id = Number.parseInt(params.id);
8-
const [room, setRoom] = useState<
9-
| ({
10-
id: number;
11-
isDM: true;
12-
messages: {
13-
id: number;
14-
creator: number;
15-
createdAt: Date;
16-
content: string;
17-
edited: boolean;
18-
}[];
19-
} & {
20-
name: string;
21-
thumbnail: string;
22-
})
23-
| null
24-
>(null);
10+
const [room, setRoom] = useState<(DMRoom & PersonalizedDMRoom) | null>(null);
2511
useEffect(() => {
2612
(async () => {
2713
const room = await chat.getDM(id);
@@ -31,8 +17,16 @@ export default function Page({ params }: { params: { id: string } }) {
3117

3218
return (
3319
<>
34-
<p>idは{id}です。</p>
35-
{room ? <RoomWindow friendId={id} room={room} /> : <p>データないよ</p>}
20+
{room ? (
21+
<RoomWindow friendId={id} room={room} />
22+
) : (
23+
<p>
24+
Sorry, an unexpected error has occurred.
25+
<Link href="/home" className="text-blue-600">
26+
Go Back
27+
</Link>
28+
</p>
29+
)}
3630
</>
3731
);
3832
}

web/components/chat/RoomList.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,49 @@ export function RoomList(props: RoomListProps) {
3434
</p>
3535
{roomsData?.map((room) => {
3636
if (room.isDM) {
37+
if (room.matchingStatus === "otherRequest") {
38+
return (
39+
<Box
40+
key={room.friendId}
41+
onClick={(e) => {
42+
e.stopPropagation();
43+
navigateToRoom(room);
44+
}}
45+
>
46+
<HumanListItem
47+
key={room.friendId}
48+
id={room.friendId}
49+
name={room.name}
50+
pictureUrl={room.thumbnail}
51+
rollUpName={true}
52+
lastMessage={room.lastMsg?.content}
53+
statusMessage="リクエストを受けました"
54+
/>
55+
</Box>
56+
);
57+
}
58+
if (room.matchingStatus === "myRequest") {
59+
return (
60+
<Box
61+
key={room.friendId}
62+
onClick={(e) => {
63+
e.stopPropagation();
64+
navigateToRoom(room);
65+
}}
66+
>
67+
<HumanListItem
68+
key={room.friendId}
69+
id={room.friendId}
70+
name={room.name}
71+
pictureUrl={room.thumbnail}
72+
rollUpName={true}
73+
lastMessage={room.lastMsg?.content}
74+
statusMessage="リクエスト中 メッセージを送りましょう!"
75+
/>
76+
</Box>
77+
);
78+
}
79+
// if (room.matchingStatus === "matched")
3780
return (
3881
<Box
3982
key={room.friendId}

0 commit comments

Comments
 (0)