Skip to content

Commit 2d1743e

Browse files
Francisca105luckspt
authored andcommitted
refactor: improve notification handling and type safety in
1 parent d6e9e06 commit 2d1743e

File tree

3 files changed

+116
-41
lines changed

3 files changed

+116
-41
lines changed

backend/src/mongodb/notification.go

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ func (n *NotificationsType) NotifyMember(memberID primitive.ObjectID, data Creat
194194
return
195195
}
196196

197-
notification, err = n.GetNotification(insertResult.InsertedID.(primitive.ObjectID))
197+
_, err = n.GetNotification(insertResult.InsertedID.(primitive.ObjectID))
198198
if err != nil {
199199
log.Println("unable to retrieve created notification: ", err.Error())
200200
return
@@ -229,6 +229,16 @@ func (n *NotificationsType) GetMemberNotifications(memberID primitive.ObjectID)
229229
return nil, err
230230
}
231231

232+
type savedNotif struct {
233+
notifMap map[string]interface{}
234+
speakerID *primitive.ObjectID
235+
companyID *primitive.ObjectID
236+
}
237+
238+
var saved = make([]savedNotif, 0)
239+
speakerSet := make(map[primitive.ObjectID]struct{})
240+
companySet := make(map[primitive.ObjectID]struct{})
241+
232242
for cur.Next(ctx) {
233243

234244
// decode into models.Notification first
@@ -261,27 +271,81 @@ func (n *NotificationsType) GetMemberNotifications(memberID primitive.ObjectID)
261271
notifMap["session"] = notification.Session.Hex()
262272
}
263273

264-
// Try to embed the speaker object if present
274+
var sID *primitive.ObjectID
275+
var cID *primitive.ObjectID
276+
265277
if notification.Speaker != nil {
266-
if sp, err := Speakers.GetSpeaker(*notification.Speaker); err == nil {
267-
// embed full speaker object
268-
notifMap["speaker"] = sp
269-
} else {
270-
// fallback to id
271-
notifMap["speaker"] = notification.Speaker.Hex()
272-
}
278+
sID = notification.Speaker
279+
// tentatively store the hex (fallback) until we embed
280+
notifMap["speaker"] = notification.Speaker.Hex()
281+
speakerSet[*sID] = struct{}{}
273282
}
274283

275-
// Try to embed the company object if present
276284
if notification.Company != nil {
277-
if co, err := Companies.GetCompany(*notification.Company); err == nil {
278-
notifMap["company"] = co
279-
} else {
280-
notifMap["company"] = notification.Company.Hex()
285+
cID = notification.Company
286+
notifMap["company"] = notification.Company.Hex()
287+
companySet[*cID] = struct{}{}
288+
}
289+
290+
saved = append(saved, savedNotif{notifMap: notifMap, speakerID: sID, companyID: cID})
291+
}
292+
293+
// If we encountered speaker or company IDs, fetch them in batch and embed
294+
var speakersByID = make(map[primitive.ObjectID]*models.Speaker)
295+
var companiesByID = make(map[primitive.ObjectID]*models.Company)
296+
297+
if len(speakerSet) > 0 {
298+
ids := make([]primitive.ObjectID, 0, len(speakerSet))
299+
for id := range speakerSet {
300+
ids = append(ids, id)
301+
}
302+
303+
// batch find speakers
304+
spCur, err := Speakers.Collection.Find(ctx, bson.M{"_id": bson.M{"$in": ids}})
305+
if err == nil {
306+
for spCur.Next(ctx) {
307+
var sp models.Speaker
308+
if err := spCur.Decode(&sp); err == nil {
309+
speakersByID[sp.ID] = &sp
310+
}
311+
}
312+
spCur.Close(ctx)
313+
}
314+
}
315+
316+
if len(companySet) > 0 {
317+
ids := make([]primitive.ObjectID, 0, len(companySet))
318+
for id := range companySet {
319+
ids = append(ids, id)
320+
}
321+
322+
// batch find companies
323+
coCur, err := Companies.Collection.Find(ctx, bson.M{"_id": bson.M{"$in": ids}})
324+
if err == nil {
325+
for coCur.Next(ctx) {
326+
var co models.Company
327+
if err := coCur.Decode(&co); err == nil {
328+
companiesByID[co.ID] = &co
329+
}
330+
}
331+
coCur.Close(ctx)
332+
}
333+
}
334+
335+
// build final notifications embedding the fetched objects when available
336+
for _, s := range saved {
337+
if s.speakerID != nil {
338+
if sp, ok := speakersByID[*s.speakerID]; ok {
339+
s.notifMap["speaker"] = sp
340+
}
341+
}
342+
if s.companyID != nil {
343+
if co, ok := companiesByID[*s.companyID]; ok {
344+
s.notifMap["company"] = co
281345
}
282346
}
283347

284-
notifications = append(notifications, notifMap)
348+
notifications = append(notifications, s.notifMap)
285349
}
286350

287351
if err := cur.Err(); err != nil {

frontend/src/components/navbar/Notification.vue

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,33 @@ import Image from "../Image.vue";
2121
2222
const getActor = (notification: Notification) => {
2323
if (notification.speaker && typeof notification.speaker === "object") {
24+
const speaker = notification.speaker as Speaker;
2425
return {
25-
id: (notification.speaker as Speaker).id,
26-
name: (notification.speaker as Speaker).name,
27-
avatar:
28-
(notification.speaker as Speaker).imgs.internal ||
29-
(notification.speaker as Speaker).imgs.speaker,
26+
id: speaker.id,
27+
name: speaker.name,
28+
avatar: speaker.imgs.internal || speaker.imgs.speaker,
3029
};
3130
}
3231
if (notification.company && typeof notification.company === "object") {
32+
const company = notification.company as Company;
3333
return {
34-
id: (notification.company as Company).id,
35-
name: (notification.company as Company).name,
36-
avatar:
37-
(notification.company as Company).imgs?.internal ||
38-
(notification.company as Company).imgs?.public,
34+
id: company.id,
35+
name: company.name,
36+
avatar: company.imgs?.internal || company.imgs?.public,
3937
};
4038
}
4139
return null;
4240
};
4341
44-
const makeMessage = (notification: Notification) => {
42+
const makeMessage = (
43+
notification: Notification,
44+
actorArg?: { id?: string; name?: string; avatar?: string } | null,
45+
) => {
4546
const thread = notification.thread;
4647
const kind = notification.kind;
47-
// Actor is the entity (company/speaker) related to the notification
48-
const actor = getActor(notification);
49-
const actorName = actor ? actor.name : "";
48+
// Actor is the entity (company/speaker) related to the notification. Prefer the precomputed actor when provided.
49+
const actor = actorArg ?? getActor(notification);
50+
const actorName = actor && actor.name ? actor.name : "";
5051
const isActorPresent = actorName.length > 0;
5152
5253
switch (kind) {
@@ -102,10 +103,18 @@ const { data: notifications } = useQuery({
102103
query: getMyNotifications,
103104
});
104105
105-
const notificationItems = computed(() => {
106+
type NotificationEntry = {
107+
n: Notification;
108+
actor: { id?: string; name?: string; avatar?: string } | null;
109+
};
110+
111+
const notificationItems = computed<NotificationEntry[]>(() => {
106112
const items = (notifications.value?.data as Notification[]) || [];
107113
// sort newest first — try common timestamp fields (date, createdAt, created_at)
108-
return items.slice().sort((a, b) => b.date?.localeCompare(a.date ?? "") || 0);
114+
const sorted = items
115+
.slice()
116+
.sort((a, b) => b.date?.localeCompare(a.date ?? "") || 0);
117+
return sorted.map((n) => ({ n, actor: getActor(n) }));
109118
});
110119
111120
const _deleteNotificationMutation = useDeleteNotificationMutation();
@@ -116,7 +125,7 @@ const removeNotification = async (id: string) => {
116125
};
117126
118127
const removeAllNotifications = async () => {
119-
const ids = notificationItems.value.map((i) => i.id);
128+
const ids = notificationItems.value.map((i) => i.n.id);
120129
if (!ids.length) return;
121130
await _deleteAllNotificationsMutation.mutate();
122131
};
@@ -189,29 +198,31 @@ const onNotificationClick = async (n: Notification) => {
189198

190199
<ul>
191200
<li
192-
v-for="n in notificationItems"
193-
:key="n.id"
201+
v-for="entry in notificationItems"
202+
:key="entry.n.id"
194203
class="flex items-center justify-between px-3 py-2 hover:bg-gray-50 cursor-pointer"
195-
@click="onNotificationClick(n)"
204+
@click="onNotificationClick(entry.n)"
196205
>
197206
<div class="flex items-center gap-3">
198207
<Image
199-
v-if="getActor(n)?.avatar"
200-
:src="getActor(n)?.avatar"
208+
v-if="entry.actor?.avatar"
209+
:src="entry.actor?.avatar"
201210
alt="actor"
202211
class="h-8 w-8 rounded-full object-cover"
203212
/>
204213
<div class="text-sm">
205-
<div class="font-medium">{{ makeMessage(n) }}</div>
214+
<div class="font-medium">
215+
{{ makeMessage(entry.n, entry.actor) }}
216+
</div>
206217
<div class="text-xs text-gray-500">
207-
{{ getActor(n)?.name || n.date }}
218+
{{ entry.actor?.name || entry.n.date }}
208219
</div>
209220
</div>
210221
</div>
211222
<div>
212223
<button
213224
class="text-red-500 text-sm"
214-
@click.stop.prevent="removeNotification(n.id)"
225+
@click.stop.prevent="removeNotification(entry.n.id)"
215226
>
216227
<Trash :size="16" />
217228
</button>

frontend/src/mutations/notifications.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export const useDeleteNotificationMutation = defineMutation(() => {
1818
data: [],
1919
};
2020
const newData = (prev.data || []).filter(
21-
(n) => (n as Notification).id !== id,
21+
(n: Notification) => n.id !== id,
2222
);
2323
queryCache.setQueryData(["notifications"], { ...prev, data: newData });
2424
// cancel ongoing queries

0 commit comments

Comments
 (0)