Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

リクエストを送ったら、チャットでメッセージが送れるようになる #538

Merged
merged 9 commits into from
Dec 2, 2024
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