Skip to content

Commit cf3e6f2

Browse files
authored
unread badge (#547)
# PRの概要 ## 具体的な変更内容 ## 影響範囲 ## 動作要件 ## 補足 ## レビューリクエストを出す前にチェック! - [ ] 改めてセルフレビューしたか - [ ] 手動での動作検証を行ったか - [ ] server の機能追加ならば、テストを書いたか - 理由: 書いた | server の機能追加ではない - [ ] 間違った使い方が存在するならば、それのドキュメントをコメントで書いたか - 理由: 書いた | 間違った使い方は存在しない - [ ] わかりやすいPRになっているか <!-- レビューリクエスト後は、Slackでもメンションしてお願いすることを推奨します。 -->
1 parent 4eeb364 commit cf3e6f2

File tree

15 files changed

+183
-55
lines changed

15 files changed

+183
-55
lines changed

Makefile

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,19 @@ dev-db:
7070
-e POSTGRES_DB=database \
7171
postgres:alpine
7272
@echo "Waiting for PostgreSQL to be ready..."
73-
@sleep 5 # PostgreSQLが起動するまでの待機(必要に応じて調整)
73+
@sleep 2 # PostgreSQLが起動するまでの待機(必要に応じて調整)
7474
@until docker exec postgres pg_isready -U user -d database; do \
7575
echo "Waiting for PostgreSQL to be ready..."; \
7676
sleep 1; \
7777
done
7878
@echo "PostgreSQL is ready. Running seed..."
79-
@cd server; bunx prisma generate; bunx prisma db push; cd ..
80-
@make seed;
79+
@cd server; \
80+
if command -v prisma; then\
81+
prisma generate; prisma db push;\
82+
else \
83+
bunx prisma generate; bunx prisma db push;\
84+
fi
85+
@make seed
8186
@echo "Seeding completed."
8287

8388
# Sync (install/update packages, generate prisma, etc)
@@ -88,7 +93,7 @@ sync-web:
8893

8994
sync-server:
9095
cd server; bun install --frozen-lockfile
91-
cd server; bunx prisma generate
96+
cd server; if command -v prisma; then prisma generate; else bunx prisma generate; fi
9297
# copy .env.sample -> .env only if .env is not there
9398

9499
sync-root:

common/zod/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export const DMOverviewSchema = z.object({
146146
name: NameSchema,
147147
thumbnail: z.string(),
148148
lastMsg: MessageSchema.optional(),
149+
unreadMessages: z.number(),
149150
});
150151

151152
export const SharedRoomOverviewSchema = z.object({

flake.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
biome
2828
pkg-config
2929
openssl
30+
lefthook
31+
pkgs.prisma
3032
] ++ [
3133
rust-pkgs
3234
];

server/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@
3636
"globals": "^15.8.0",
3737
"prisma": "^5.11.0",
3838
"typescript": "^5.4.5"
39-
}
39+
},
40+
"trustedPackages": ["prisma"]
4041
}

server/prisma/schema.prisma

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ model InterestSubject {
5252
id Int @id @default(autoincrement())
5353
name String
5454
group String // such as Computer Science | name = ML
55+
Interest Interest[] // ignore this
5556
5657
@@unique([name, group])
57-
Interest Interest[] // ignore this
5858
}
5959

6060
// User->Interest->InterestSubject
@@ -138,6 +138,7 @@ model Message {
138138
createdAt DateTime @default(now()) // @readonly
139139
edited Boolean @default(false)
140140
content String
141+
read Boolean
141142
relation Relationship? @relation(fields: [relationId], references: [id], onDelete: Cascade)
142143
relationId Int?
143144
sharedRoom SharedRoom? @relation(fields: [sharedRoomId], references: [id], onDelete: Cascade)

server/src/database/chat.ts

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
getPendingRequestsFromUser,
2020
getPendingRequestsToUser,
2121
} from "./requests";
22+
import { getUserByID } from "./users";
2223

2324
export async function getOverview(
2425
user: UserID,
@@ -35,59 +36,17 @@ export async function getOverview(
3536

3637
//マッチングしている人のオーバービュー
3738
const matchingOverview = await Promise.all(
38-
matched.value.map(async (friend) => {
39-
const lastMessageResult = await getLastMessage(user, friend.id);
40-
const lastMessage = lastMessageResult.ok
41-
? lastMessageResult.value
42-
: undefined;
43-
const overview: DMOverview = {
44-
isDM: true,
45-
matchingStatus: "matched",
46-
friendId: friend.id,
47-
name: friend.name,
48-
thumbnail: friend.pictureUrl,
49-
lastMsg: lastMessage,
50-
};
51-
return overview;
52-
}),
39+
matched.value.map(async (m) => getOverviewBetween(user, m.id)),
5340
);
5441

5542
//自分にリクエストを送ってきた人のオーバービュー
5643
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-
}),
44+
senders.value.map((s) => getOverviewBetween(user, s.id)),
7245
);
7346

7447
//自分がリクエストを送った人のオーバービュー
7548
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-
}),
49+
receivers.value.map((r) => getOverviewBetween(user, r.id)),
9150
);
9251

9352
const sharedRooms: {
@@ -130,6 +89,67 @@ export async function getOverview(
13089
}
13190
}
13291

92+
async function getOverviewBetween(
93+
user: number,
94+
other: number,
95+
): Promise<DMOverview> {
96+
const relR = await getRelation(user, other);
97+
if (!relR.ok) throw relR.error;
98+
const rel = relR.value;
99+
100+
const friendId =
101+
rel.receivingUserId === user ? rel.sendingUserId : rel.receivingUserId;
102+
const lastMessage = getLastMessage(user, friendId).then((val) => {
103+
if (val.ok) return val.value;
104+
return undefined;
105+
});
106+
const unreadCount = unreadMessages(user, rel.id).then((val) => {
107+
if (val.ok) return val.value;
108+
throw val.error;
109+
});
110+
const friend = await getUserByID(friendId).then((val) => {
111+
if (val.ok) return val.value;
112+
throw val.error;
113+
});
114+
const overview: DMOverview = {
115+
isDM: true,
116+
matchingStatus: "matched",
117+
friendId: friendId,
118+
name: friend.name,
119+
thumbnail: friend.pictureUrl,
120+
lastMsg: await lastMessage,
121+
unreadMessages: await unreadCount,
122+
};
123+
return overview;
124+
}
125+
export async function markAsRead(
126+
rel: RelationshipID,
127+
reader: UserID,
128+
message: MessageID,
129+
) {
130+
const val = {
131+
readerId: reader,
132+
messageId: message,
133+
relationId: rel,
134+
};
135+
return await prisma.message.updateMany({
136+
where: {
137+
id: {
138+
lte: message,
139+
},
140+
relationId: rel,
141+
creator: {
142+
not: {
143+
equals: reader,
144+
},
145+
},
146+
},
147+
data: {
148+
read: true,
149+
},
150+
});
151+
}
152+
133153
/**
134154
* DM の送信
135155
* 送信者の id は呼び出す側で指定すること
@@ -142,6 +162,7 @@ export async function sendDM(
142162
const message = await prisma.message.create({
143163
data: {
144164
relationId: relation,
165+
read: false,
145166
...content,
146167
},
147168
});
@@ -372,3 +393,24 @@ export async function getLastMessage(
372393
return Err(e);
373394
}
374395
}
396+
397+
// only works on Relationship (= DM) for now.
398+
export async function unreadMessages(userId: UserID, roomId: RelationshipID) {
399+
try {
400+
// FIXME: this makes request twice to the database. it's not efficient.
401+
const unreadMessages = await prisma.message.count({
402+
where: {
403+
read: false,
404+
relationId: roomId,
405+
creator: {
406+
not: {
407+
equals: userId,
408+
},
409+
},
410+
},
411+
});
412+
return Ok(unreadMessages);
413+
} catch (e) {
414+
return Err(e);
415+
}
416+
}

server/src/database/matches.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export async function getRelation(
88
u2: UserID,
99
): Promise<Result<Relationship>> {
1010
try {
11-
// TODO!!!! FIXME!!!!!! FIX THIS findMany!!!!!
11+
// FIXME: fix this findMany
1212
const rel = await prisma.relationship.findMany({
1313
where: {
1414
OR: [

server/src/database/requests.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,29 @@ export async function getMatchedUser(
277277
}
278278
}
279279

280+
export async function getMatchedRelations(
281+
userId: UserID,
282+
): Promise<Result<Relationship[]>> {
283+
try {
284+
const found = await prisma.relationship.findMany({
285+
where: {
286+
status: "MATCHED",
287+
OR: [
288+
{
289+
sendingUserId: userId,
290+
},
291+
{
292+
receivingUserId: userId,
293+
},
294+
],
295+
},
296+
});
297+
return Ok(found);
298+
} catch (e) {
299+
return Err(e);
300+
}
301+
}
302+
280303
export async function matchWithMemo(userId: UserID) {
281304
try {
282305
const result = await prisma.relationship.create({

server/src/router/chat.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
} from "common/zod/schemas";
1010
import express from "express";
1111
import * as db from "../database/chat";
12-
import { safeGetUserId } from "../firebase/auth/db";
12+
import { getRelation } from "../database/matches";
13+
import { getUserId, safeGetUserId } from "../firebase/auth/db";
1314
import * as core from "../functions/chat";
1415
import * as ws from "../lib/socket/socket";
1516

@@ -56,6 +57,18 @@ router.get("/dm/with/:userid", async (req, res) => {
5657
return res.status(result.code).send(result.body);
5758
});
5859

60+
router.post("/mark-as-read/:rel/:messageId", async (req, res) => {
61+
const user = await getUserId(req);
62+
const message = Number.parseInt(req.params.messageId);
63+
const rel = Number.parseInt(req.params.rel);
64+
try {
65+
await db.markAsRead(rel, user, message);
66+
return res.status(200).end("ok");
67+
} catch (err) {
68+
return res.status(304).end("already marked");
69+
}
70+
});
71+
5972
// create a shared chat room.
6073
router.post("/shared", async (req, res) => {
6174
const user = await safeGetUserId(req);

web/api/chat/chat.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
Message,
55
MessageID,
66
PersonalizedDMRoom,
7+
RelationshipID,
78
RoomOverview,
89
SendMessage,
910
ShareRoomID,
@@ -12,7 +13,7 @@ import type {
1213
UserID,
1314
} from "common/types";
1415
import { ErrUnauthorized, credFetch } from "~/firebase/auth/lib";
15-
import endpoints from "../internal/endpoints";
16+
import * as endpoints from "../internal/endpoints";
1617

1718
/* TODO
1819
import { UserID } from "common/types";
@@ -35,6 +36,20 @@ export async function deleteMessage(
3536
);
3637
}
3738

39+
export async function markAsRead(
40+
relationId: RelationshipID,
41+
messageId: MessageID,
42+
) {
43+
const res = await credFetch(
44+
"POST",
45+
endpoints.markAsRead(relationId, messageId),
46+
);
47+
if (res.status !== 200 && res.status !== 304)
48+
throw new Error(
49+
`on markAsRead(), expected status code of 200 or 304, but got ${res.status}`,
50+
);
51+
}
52+
3853
export async function updateMessage(
3954
messageId: MessageID,
4055
newMessage: SendMessage,

0 commit comments

Comments
 (0)