Skip to content

Commit

Permalink
リクエストを送ったら、チャットでメッセージが送れるようになる (#538)
Browse files Browse the repository at this point in the history
# 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]>
  • Loading branch information
KaichiManabe and aster-void authored Dec 2, 2024
1 parent 3d751bc commit 7a845bb
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 65 deletions.
8 changes: 8 additions & 0 deletions common/zod/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,15 @@ export const SendMessageSchema = z.object({
content: z.string().min(1, { message: "Content must not be empty." }),
});

export const MatchingStatusSchema = z.union([
z.literal("myRequest"),
z.literal("otherRequest"),
z.literal("matched"),
]);

export const DMOverviewSchema = z.object({
isDM: z.literal(true),
matchingStatus: MatchingStatusSchema,
friendId: UserIDSchema,
name: NameSchema,
thumbnail: z.string(),
Expand Down Expand Up @@ -153,6 +160,7 @@ export const DMRoomSchema = z.object({
export const PersonalizedDMRoomSchema = z.object({
name: NameSchema,
thumbnail: z.string(),
matchingStatus: MatchingStatusSchema,
});

export const SharedRoomSchema = z.object({
Expand Down
63 changes: 59 additions & 4 deletions server/src/database/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,35 @@ import type {
} from "common/types";
import { prisma } from "./client";
import { getRelation } from "./matches";
import { getMatchedUser } from "./requests";
import {
getMatchedUser,
getPendingRequestsFromUser,
getPendingRequestsToUser,
} from "./requests";

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

const dm = await Promise.all(
const senders = await getPendingRequestsToUser(user);
if (!senders.ok) return Err(senders.error);

const receivers = await getPendingRequestsFromUser(user);
if (!receivers.ok) return Err(receivers.error);

//マッチングしている人のオーバービュー
const matchingOverview = await Promise.all(
matched.value.map(async (friend) => {
const lastMessageResult = await getLastMessage(user, friend.id);
const lastMessage = lastMessageResult.ok
? lastMessageResult.value
: undefined;
const overview: DMOverview = {
isDM: true,
matchingStatus: "matched",
friendId: friend.id,
name: friend.name,
thumbnail: friend.pictureUrl,
Expand All @@ -41,6 +52,44 @@ export async function getOverview(
}),
);

//自分にリクエストを送ってきた人のオーバービュー
const senderOverview = await Promise.all(
senders.value.map(async (sender) => {
const lastMessageResult = await getLastMessage(user, sender.id);
const lastMessage = lastMessageResult.ok
? lastMessageResult.value
: undefined;
const overview: DMOverview = {
isDM: true,
matchingStatus: "otherRequest",
friendId: sender.id,
name: sender.name,
thumbnail: sender.pictureUrl,
lastMsg: lastMessage,
};
return overview;
}),
);

//自分がリクエストを送った人のオーバービュー
const receiverOverview = await Promise.all(
receivers.value.map(async (receiver) => {
const lastMessageResult = await getLastMessage(user, receiver.id);
const lastMessage = lastMessageResult.ok
? lastMessageResult.value
: undefined;
const overview: DMOverview = {
isDM: true,
matchingStatus: "myRequest",
friendId: receiver.id,
name: receiver.name,
thumbnail: receiver.pictureUrl,
lastMsg: lastMessage,
};
return overview;
}),
);

const sharedRooms: {
id: number;
name: string;
Expand All @@ -61,7 +110,13 @@ export async function getOverview(
};
return overview;
});
return Ok([...shared, ...dm]);

return Ok([
...matchingOverview,
...senderOverview,
...receiverOverview,
...shared,
]);
} catch (e) {
return Err(e);
}
Expand Down
25 changes: 17 additions & 8 deletions server/src/functions/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ export async function sendDM(
send: SendMessage,
): Promise<http.Response<Message>> {
const rel = await getRelation(from, to);
if (!rel.ok || rel.value.status !== "MATCHED")
return http.forbidden("cannot send to non-friend");
if (!rel.ok || rel.value.status === "REJECTED")
return http.forbidden(
"You cannot send a message because the friendship request was rejected.",
);

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

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

const room = await db.getDMbetween(requester, _with);
const room = await db.getDMbetween(user, friend);
if (!room.ok) return http.internalError();

const friendData = await getUserByID(_with);
const friendData = await getUserByID(friend);
if (!friendData.ok) return http.notFound("friend not found");

const personalized: PersonalizedDMRoom & DMRoom = {
name: friendData.value.name,
thumbnail: friendData.value.pictureUrl,
matchingStatus:
rel.value.status === "MATCHED"
? "matched"
: rel.value.sendingUserId === user //どっちが送ったリクエストなのかを判定
? "myRequest"
: "otherRequest",
...room.value,
};

Expand Down
8 changes: 4 additions & 4 deletions server/src/router/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ router.get("/overview", async (req, res) => {
res.status(result.code).send(result.body);
});

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

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

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

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

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

Expand Down
15 changes: 5 additions & 10 deletions web/api/chat/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
InitRoom,
Message,
MessageID,
PersonalizedDMRoom,
RoomOverview,
SendMessage,
ShareRoomID,
Expand Down Expand Up @@ -83,22 +84,16 @@ export async function sendDM(
return res.json();
}

export async function getDM(friendId: UserID): Promise<
DMRoom & {
name: string;
thumbnail: string;
}
> {
export async function getDM(
friendId: UserID,
): Promise<DMRoom & PersonalizedDMRoom> {
const res = await credFetch("GET", endpoints.dmWith(friendId));
if (res.status === 401) throw new ErrUnauthorized();
if (res.status !== 200)
throw new Error(
`getDM() failed: expected status code 200, got ${res.status}`,
);
const json: DMRoom & {
name: string;
thumbnail: string;
} = await res.json();
const json: DMRoom & PersonalizedDMRoom = await res.json();
if (!Array.isArray(json?.messages)) return json;
for (const m of json.messages) {
m.createdAt = new Date(m.createdAt);
Expand Down
32 changes: 13 additions & 19 deletions web/app/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
"use client";
import type { DMRoom, PersonalizedDMRoom } from "common/types";
import Link from "next/link";
import { useEffect, useState } from "react";
import * as chat from "~/api/chat/chat";
import { RoomWindow } from "~/components/chat/RoomWindow";

export default function Page({ params }: { params: { id: string } }) {
const id = Number.parseInt(params.id);
const [room, setRoom] = useState<
| ({
id: number;
isDM: true;
messages: {
id: number;
creator: number;
createdAt: Date;
content: string;
edited: boolean;
}[];
} & {
name: string;
thumbnail: string;
})
| null
>(null);
const [room, setRoom] = useState<(DMRoom & PersonalizedDMRoom) | null>(null);
useEffect(() => {
(async () => {
const room = await chat.getDM(id);
Expand All @@ -31,8 +17,16 @@ export default function Page({ params }: { params: { id: string } }) {

return (
<>
<p>idは{id}です。</p>
{room ? <RoomWindow friendId={id} room={room} /> : <p>データないよ</p>}
{room ? (
<RoomWindow friendId={id} room={room} />
) : (
<p>
Sorry, an unexpected error has occurred.
<Link href="/home" className="text-blue-600">
Go Back
</Link>
</p>
)}
</>
);
}
43 changes: 43 additions & 0 deletions web/components/chat/RoomList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,49 @@ export function RoomList(props: RoomListProps) {
</p>
{roomsData?.map((room) => {
if (room.isDM) {
if (room.matchingStatus === "otherRequest") {
return (
<Box
key={room.friendId}
onClick={(e) => {
e.stopPropagation();
navigateToRoom(room);
}}
>
<HumanListItem
key={room.friendId}
id={room.friendId}
name={room.name}
pictureUrl={room.thumbnail}
rollUpName={true}
lastMessage={room.lastMsg?.content}
statusMessage="リクエストを受けました"
/>
</Box>
);
}
if (room.matchingStatus === "myRequest") {
return (
<Box
key={room.friendId}
onClick={(e) => {
e.stopPropagation();
navigateToRoom(room);
}}
>
<HumanListItem
key={room.friendId}
id={room.friendId}
name={room.name}
pictureUrl={room.thumbnail}
rollUpName={true}
lastMessage={room.lastMsg?.content}
statusMessage="リクエスト中 メッセージを送りましょう!"
/>
</Box>
);
}
// if (room.matchingStatus === "matched")
return (
<Box
key={room.friendId}
Expand Down
Loading

0 comments on commit 7a845bb

Please sign in to comment.