Skip to content

Commit b568d64

Browse files
authored
Merge pull request #487 from sinfo/fran/notifications
Notifications
2 parents 12f5a31 + 2ec1e2d commit b568d64

11 files changed

Lines changed: 387 additions & 57 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Version 2 of eventdeck
88

99
## Documentation
1010

11-
- swagger: http://petstore.swagger.io/?url=http%3A%2F%2Flocalhost%3A8080%2Fstatic%2Fswagger.json#/auth/authCallback
11+
- swagger: make run-doc

backend/src/mongodb/notification.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,19 @@ func (n *NotificationsType) DeleteNotification(notificationID primitive.ObjectID
264264

265265
return &notification, nil
266266
}
267+
268+
// DeleteAllMemberNotifications deletes all notifications for a member
269+
func (n *NotificationsType) DeleteAllMemberNotifications(memberID primitive.ObjectID) (int64, error) {
270+
ctx = context.Background()
271+
272+
filter := bson.M{
273+
"member": memberID,
274+
}
275+
276+
deleteResult, err := n.Collection.DeleteMany(ctx, filter)
277+
if err != nil {
278+
return 0, err
279+
}
280+
281+
return deleteResult.DeletedCount, nil
282+
}

backend/src/router/init.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ func InitializeRouter() {
250250
meRouter.HandleFunc("/image", authMember(setMyImage)).Methods("POST")
251251
meRouter.HandleFunc("/notifications", authMember(getMyNotifications)).Methods("GET")
252252
meRouter.HandleFunc("/notifications/{id}", authMember(deleteMyNotification)).Methods("DELETE")
253+
meRouter.HandleFunc("/notifications", authMember(deleteAllMyNotifications)).Methods("DELETE")
253254

254255
// member handlers
255256
memberRouter := r.PathPrefix("/members").Subrouter()

backend/src/router/me.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,28 @@ func deleteMyNotification(w http.ResponseWriter, r *http.Request) {
255255

256256
json.NewEncoder(w).Encode(notification)
257257
}
258+
259+
func deleteAllMyNotifications(w http.ResponseWriter, r *http.Request) {
260+
261+
credentials, ok := r.Context().Value(credentialsKey).(models.AuthorizationCredentials)
262+
263+
if !ok {
264+
http.Error(w, "Could not parse credentials", http.StatusBadRequest)
265+
return
266+
}
267+
268+
memberID := credentials.ID
269+
270+
if _, err := mongodb.Members.GetMember(memberID); err != nil {
271+
http.Error(w, "Could not find member: " + err.Error(), http.StatusNotFound)
272+
return
273+
}
274+
275+
count, err := mongodb.Notifications.DeleteAllMemberNotifications(memberID)
276+
if err != nil {
277+
http.Error(w, "Could not delete notifications: " + err.Error(), http.StatusExpectationFailed)
278+
return
279+
}
280+
281+
json.NewEncoder(w).Encode(map[string]int64{"deletedCount": count})
282+
}

backend/static/swagger.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

backend/swagger/me-notifications.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,49 @@
3838
"description": "Unauthorized"
3939
}
4040
}
41+
},
42+
43+
"delete": {
44+
"tags": [
45+
"me",
46+
"notifications"
47+
],
48+
"summary": "Delete all my notifications",
49+
"operationId": "deleteAllMyNotifications",
50+
"consumes": [
51+
"multipart/form-data"
52+
],
53+
"produces": [
54+
"application/json"
55+
],
56+
"security": [
57+
{
58+
"Bearer": []
59+
}
60+
],
61+
"parameters": [],
62+
"responses": {
63+
"200": {
64+
"description": "Deleted notifications count",
65+
"schema": {
66+
"type": "object",
67+
"properties": {
68+
"deletedCount": {
69+
"type": "integer",
70+
"format": "int64"
71+
}
72+
}
73+
}
74+
},
75+
"417": {
76+
"description": "Unable to delete notifications"
77+
},
78+
"404": {
79+
"description": "Member not found"
80+
},
81+
"401": {
82+
"description": "Unauthorized"
83+
}
84+
}
4185
}
4286
}

frontend/src/api/notifications.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Notification } from "@/dto/notifications";
2+
import { instance } from ".";
3+
4+
export const getMyNotifications = () => instance.get<Notification[]>('/me/notifications');
5+
export const deleteMyNotification = (id: string) => instance.delete<Notification>(`/me/notifications/${id}`);
6+
export const deleteAllMyNotifications = () => instance.delete<void>('/me/notifications');

frontend/src/components/Navbar.vue

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import CompanyOrSpeakerAutocompleteWithDialog from "./CompanyOrSpeakerAutocomple
1919
import type { Company } from "@/dto/companies";
2020
import type { Speaker } from "@/dto/speakers";
2121
import { useMagicKeys } from "@vueuse/core";
22+
import Notification from "./navbar/Notification.vue";
2223
2324
const isOpen = ref(false);
2425
const authStore = useAuthStore();
@@ -29,6 +30,8 @@ const logout = () => {
2930
router.push({ name: "landing" });
3031
};
3132
33+
34+
3235
interface NavigationItem {
3336
name: string;
3437
to: RouteLocationRaw;
@@ -96,67 +99,45 @@ watch(shortcutLinux, () => {
9699
</script>
97100

98101
<template>
99-
<section
100-
class="fixed top-0 left-0 right-0 z-50 w-full flex items-center bg-white py-4 border-b border-gray-200"
101-
>
102+
<section class="fixed top-0 left-0 right-0 z-50 w-full flex items-center bg-white py-4 border-b border-gray-200">
102103
<div class="container mx-auto px-4 md:px-6 lg:px-8">
103104
<nav class="flex items-center justify-between">
104105
<div class="flex items-center gap-3">
105-
<RouterLink :to="{ name: 'dashboard' }" class="text-2xl font-bold"
106-
>Deck</RouterLink
107-
>
106+
<RouterLink :to="{ name: 'dashboard' }" class="text-2xl font-bold">Deck</RouterLink>
108107

109108
<Select v-model="eventStore.selectedEvent">
110109
<SelectTrigger :loading="eventsLoading">
111110
<SelectValue placeholder="Edition" />
112111
</SelectTrigger>
113112
<SelectContent>
114-
<SelectItem
115-
v-for="event in sortedEvents"
116-
:key="event.id"
117-
:value="event"
118-
>
113+
<SelectItem v-for="event in sortedEvents" :key="event.id" :value="event">
119114
{{ event.name }}
120115
</SelectItem>
121116
</SelectContent>
122117
</Select>
123118
</div>
124119

125-
<CompanyOrSpeakerAutocompleteWithDialog
126-
:autofocus="showSuggestions"
127-
:force-show-suggestions="showSuggestions"
128-
class="hidden md:inline w-full px-3"
129-
placeholder="Search"
130-
@company-selected="companySelected"
131-
@speaker-selected="speakerSelected"
132-
show-create
133-
/>
120+
<CompanyOrSpeakerAutocompleteWithDialog :autofocus="showSuggestions" :force-show-suggestions="showSuggestions"
121+
class="hidden md:inline w-full px-3" placeholder="Search" @company-selected="companySelected"
122+
@speaker-selected="speakerSelected" show-create />
134123

135124
<!-- Desktop Navigation -->
136125
<div class="hidden md:flex items-center space-x-4">
137-
<RouterLink
138-
v-for="item in navigation"
139-
:key="item.name"
140-
:to="item.to"
141-
class="text-gray-600 hover:text-gray-900"
142-
:title="item.name"
143-
>
126+
<Notification />
127+
<RouterLink v-for="item in navigation" :key="item.name" :to="item.to"
128+
class="text-gray-600 hover:text-gray-900" :title="item.name">
144129
<component v-if="item.icon" :is="item.icon" class="h-5 w-5" />
145130
<span v-else>{{ item.name }}</span>
146131
</RouterLink>
147132

148-
<Button
149-
variant="ghost"
150-
size="sm"
151-
@click="logout"
152-
class="text-gray-600 hover:text-gray-900"
153-
>
133+
<Button variant="ghost" size="sm" @click="logout" class="text-gray-600 hover:text-gray-900">
154134
<LogOut class="h-4 w-4" />
155135
</Button>
156136
</div>
157137

158138
<!-- Mobile Navigation Button -->
159139
<div class="md:hidden">
140+
<Notification />
160141
<Button variant="ghost" @click="isOpen = !isOpen">
161142
<Menu v-if="!isOpen" class="h-6 w-6" />
162143
<X v-else class="h-6 w-6" />
@@ -165,35 +146,21 @@ watch(shortcutLinux, () => {
165146
</nav>
166147

167148
<!-- Mobile Navigation Menu -->
168-
<div
169-
v-if="isOpen"
170-
class="md:hidden absolute top-full left-0 w-full bg-white border-b border-gray-200 py-4"
171-
>
172-
<CompanyOrSpeakerAutocompleteWithDialog
173-
class="w-full px-3 pb-3"
174-
placeholder="Search"
175-
@company-selected="companySelected"
176-
@speaker-selected="speakerSelected"
177-
/>
149+
<div v-if="isOpen" class="md:hidden absolute top-full left-0 w-full bg-white border-b border-gray-200 py-4">
150+
<CompanyOrSpeakerAutocompleteWithDialog class="w-full px-3 pb-3" placeholder="Search"
151+
@company-selected="companySelected" @speaker-selected="speakerSelected" />
178152

179153
<div class="container mx-auto px-4">
180154
<div class="flex flex-col space-y-4">
181-
<RouterLink
182-
v-for="item in navigation"
183-
:key="item.name"
184-
:to="item.to"
185-
class="text-gray-600 hover:text-gray-900 flex items-center gap-2"
186-
>
155+
156+
<RouterLink v-for="item in navigation" :key="item.name" :to="item.to"
157+
class="text-gray-600 hover:text-gray-900 flex items-center gap-2">
187158
<component v-if="item.icon" :is="item.icon" class="h-4 w-4" />
188159
<span>{{ item.name }}</span>
189160
</RouterLink>
190161

191-
<Button
192-
variant="ghost"
193-
size="sm"
194-
@click="logout"
195-
class="text-gray-600 hover:text-gray-900 justify-start p-0"
196-
>
162+
<Button variant="ghost" size="sm" @click="logout"
163+
class="text-gray-600 hover:text-gray-900 justify-start p-0">
197164
<LogOut class="h-4 w-4 mr-2" />
198165
Logout
199166
</Button>

0 commit comments

Comments
 (0)