Skip to content

Commit df107f8

Browse files
committed
Merge branch 'main' of github.com:ut-code/CourseMate into feat/Card-with-interests
2 parents ea38b7d + 1606008 commit df107f8

File tree

31 files changed

+522
-170
lines changed

31 files changed

+522
-170
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ setup-ci:
1616
make generate-sql
1717

1818
sync: sync-server sync-web sync-root sync-common
19-
lefthook install || true
19+
bunx lefthook install || true
2020
@echo '----------------------------------------------------------------------------------------------------------'
2121
@echo '| Most work is done. now running prisma-generate-sql (which might fail if .env.dev is not set configured)|'
2222
@echo '----------------------------------------------------------------------------------------------------------'

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({

flake.nix

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,11 @@
2727
biome
2828
pkg-config
2929
openssl
30-
lefthook
3130
] ++ [
3231
rust-pkgs
3332
];
3433
shellHook = ''
3534
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${pkgs.stdenv.cc.cc.lib}/lib
36-
lefthook install
3735
'' + prisma.shellHook;
3836
};
3937
});

server/src/database/chat.ts

Lines changed: 67 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,21 @@ export async function getOverview(
61110
};
62111
return overview;
63112
});
64-
return Ok([...shared, ...dm]);
113+
114+
const overview = [
115+
...matchingOverview,
116+
...senderOverview,
117+
...receiverOverview,
118+
...shared,
119+
];
120+
121+
const sortedOverviewByTime = overview.sort((a, b) => {
122+
const dateA = a.lastMsg?.createdAt ? a.lastMsg.createdAt.getTime() : 0;
123+
const dateB = b.lastMsg?.createdAt ? b.lastMsg.createdAt.getTime() : 0;
124+
return dateB - dateA;
125+
});
126+
127+
return Ok([...sortedOverviewByTime]);
65128
} catch (e) {
66129
return Err(e);
67130
}

server/src/database/requests.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,19 @@ export async function getMatchedUser(userId: UserID): Promise<Result<User[]>> {
183183
return Err(e);
184184
}
185185
}
186+
187+
export async function matchWithMemo(userId: UserID) {
188+
try {
189+
const result = await prisma.relationship.create({
190+
data: {
191+
status: "MATCHED",
192+
sendingUserId: userId,
193+
receivingUserId: 0, //KeepメモのUserId
194+
},
195+
});
196+
197+
return result;
198+
} catch (error) {
199+
return Err(error);
200+
}
201+
}

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

server/src/router/users.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import express, { type Request, type Response } from "express";
99
import {
1010
getPendingRequestsFromUser,
1111
getPendingRequestsToUser,
12+
matchWithMemo,
1213
} from "../database/requests";
1314
import {
1415
createUser,
@@ -124,10 +125,20 @@ router.get("/id/:id", async (req: Request, res: Response) => {
124125
// INSERT INTO "User" VALUES (body...)
125126
router.post("/", async (req: Request, res: Response) => {
126127
const partialUser = InitUserSchema.safeParse(req.body);
127-
if (!partialUser.success) return res.status(400).send("invalid format");
128+
if (!partialUser.success)
129+
return res.status(400).send({
130+
error: "Invalid input format",
131+
details: partialUser.error.errors,
132+
});
128133

129134
const user = await createUser(partialUser.data);
130-
if (!user.ok) return res.status(500).send();
135+
if (!user.ok) return res.status(500).send({ error: "Failed to create user" });
136+
137+
//ユーザー作成と同時にメモとマッチング
138+
const result = await matchWithMemo(user.value.id);
139+
if ("ok" in result && !result.ok) {
140+
return res.status(500).send({ error: "Failed to match user with memo" });
141+
}
131142
res.status(201).json(user.value);
132143
});
133144

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/api/user.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import type { Hook as UseHook } from "./share/types.ts";
1010

1111
const UserListSchema = z.array(UserSchema);
1212

13+
export function useAll(): Hook<User[]> {
14+
return useCustomizedSWR("users::all", all, UserListSchema);
15+
}
1316
export function useRecommended(): UseHook<User[]> {
1417
const url = endpoints.recommendedUsers;
1518
return useAuthorizedData<User[]>(url);
@@ -28,6 +31,11 @@ export function usePendingFromMe(): Hook<User[]> {
2831
);
2932
}
3033

34+
async function all(): Promise<User[]> {
35+
const res = await credFetch("GET", endpoints.users);
36+
return res.json();
37+
}
38+
3139
async function matched(): Promise<User[]> {
3240
const res = await credFetch("GET", endpoints.matchedUsers);
3341
return res.json();
@@ -131,6 +139,8 @@ export async function deleteAccount(): Promise<void> {
131139
const res = await credFetch("DELETE", endpoints.me);
132140
if (res.status !== 204)
133141
throw new Error(
134-
`failed to delete account: expected status code 204, but got ${res.status} with text ${await res.text()}`,
142+
`failed to delete account: expected status code 204, but got ${
143+
res.status
144+
} with text ${await res.text()}`,
135145
);
136146
}

0 commit comments

Comments
 (0)