kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cd7cada2f86b4e866a15b4323bb8d6d7ab5bba8b 261 lines 6.7 kB view raw
1import { Hono } from "hono"; 2import { describeRoute, resolver, validator } from "hono-openapi"; 3import * as v from "valibot"; 4import { subscribeToEvent } from "../events"; 5import { notificationSchema } from "../schemas"; 6import clearNotifications from "./controllers/clear-notifications"; 7import createNotification from "./controllers/create-notification"; 8import getNotifications from "./controllers/get-notifications"; 9import markAllNotificationsAsRead from "./controllers/mark-all-notifications-as-read"; 10import markAsRead from "./controllers/mark-notification-as-read"; 11 12const bulkResultSchema = v.object({ 13 success: v.boolean(), 14 count: v.optional(v.number()), 15}); 16 17const notification = new Hono<{ 18 Variables: { 19 userId: string; 20 }; 21}>() 22 .get( 23 "/", 24 describeRoute({ 25 operationId: "listNotifications", 26 tags: ["Notifications"], 27 description: "Get all notifications for the current user", 28 responses: { 29 200: { 30 description: "List of notifications", 31 content: { 32 "application/json": { 33 schema: resolver(v.array(notificationSchema)), 34 }, 35 }, 36 }, 37 }, 38 }), 39 async (c) => { 40 const userId = c.get("userId"); 41 const notifications = await getNotifications(userId); 42 return c.json(notifications); 43 }, 44 ) 45 .post( 46 "/", 47 describeRoute({ 48 operationId: "createNotification", 49 tags: ["Notifications"], 50 description: "Create a new notification for a user", 51 responses: { 52 200: { 53 description: "Notification created successfully", 54 content: { 55 "application/json": { schema: resolver(notificationSchema) }, 56 }, 57 }, 58 }, 59 }), 60 validator( 61 "json", 62 v.object({ 63 title: v.optional(v.nullable(v.string())), 64 message: v.optional(v.nullable(v.string())), 65 type: v.string(), 66 eventData: v.optional(v.nullable(v.record(v.string(), v.unknown()))), 67 relatedEntityId: v.optional(v.string()), 68 relatedEntityType: v.optional(v.string()), 69 }), 70 ), 71 async (c) => { 72 const { 73 title, 74 message, 75 type, 76 eventData, 77 relatedEntityId, 78 relatedEntityType, 79 } = c.req.valid("json"); 80 const userId = c.get("userId"); 81 const notification = await createNotification({ 82 userId, 83 title, 84 content: message, 85 type, 86 eventData, 87 resourceId: relatedEntityId, 88 resourceType: relatedEntityType, 89 }); 90 return c.json(notification); 91 }, 92 ) 93 .patch( 94 "/:id/read", 95 describeRoute({ 96 operationId: "markNotificationAsRead", 97 tags: ["Notifications"], 98 description: "Mark a specific notification as read", 99 responses: { 100 200: { 101 description: "Notification marked as read", 102 content: { 103 "application/json": { schema: resolver(notificationSchema) }, 104 }, 105 }, 106 }, 107 }), 108 validator("param", v.object({ id: v.string() })), 109 async (c) => { 110 const { id } = c.req.valid("param"); 111 const userId = c.get("userId"); 112 const notification = await markAsRead(id, userId); 113 return c.json(notification); 114 }, 115 ) 116 .patch( 117 "/read-all", 118 describeRoute({ 119 operationId: "markAllNotificationsAsRead", 120 tags: ["Notifications"], 121 description: "Mark all notifications as read for the current user", 122 responses: { 123 200: { 124 description: "All notifications marked as read", 125 content: { 126 "application/json": { schema: resolver(bulkResultSchema) }, 127 }, 128 }, 129 }, 130 }), 131 async (c) => { 132 const userId = c.get("userId"); 133 const result = await markAllNotificationsAsRead(userId); 134 return c.json(result); 135 }, 136 ) 137 .delete( 138 "/clear-all", 139 describeRoute({ 140 operationId: "clearAllNotifications", 141 tags: ["Notifications"], 142 description: "Clear all notifications for the current user", 143 responses: { 144 200: { 145 description: "All notifications cleared", 146 content: { 147 "application/json": { schema: resolver(bulkResultSchema) }, 148 }, 149 }, 150 }, 151 }), 152 async (c) => { 153 const userId = c.get("userId"); 154 const result = await clearNotifications(userId); 155 return c.json(result); 156 }, 157 ); 158 159subscribeToEvent<{ 160 taskId: string; 161 userId: string; 162 title: string; 163 projectId: string; 164}>("task.created", async (data) => { 165 if (data.userId) { 166 await createNotification({ 167 userId: data.userId, 168 type: "task_created", 169 eventData: { 170 taskTitle: data.title, 171 }, 172 resourceId: data.taskId, 173 resourceType: "task", 174 }); 175 } 176}); 177 178subscribeToEvent<{ 179 workspaceId: string; 180 workspaceName: string; 181 ownerEmail: string; 182 ownerId?: string; 183}>("workspace.created", async (data) => { 184 if (data.ownerId) { 185 await createNotification({ 186 userId: data.ownerId, 187 type: "workspace_created", 188 eventData: { 189 workspaceName: data.workspaceName, 190 }, 191 resourceId: data.workspaceId, 192 resourceType: "workspace", 193 }); 194 } 195}); 196 197subscribeToEvent<{ 198 taskId: string; 199 userId: string; 200 oldStatus: string; 201 newStatus: string; 202 title: string; 203 assigneeId?: string; 204}>("task.status_changed", async (data) => { 205 if (data.assigneeId && data.assigneeId !== data.userId) { 206 await createNotification({ 207 userId: data.assigneeId, 208 type: "task_status_changed", 209 eventData: { 210 taskTitle: data.title, 211 oldStatus: data.oldStatus, 212 newStatus: data.newStatus, 213 }, 214 resourceId: data.taskId, 215 resourceType: "task", 216 }); 217 } 218}); 219 220subscribeToEvent<{ 221 taskId: string; 222 userId: string; 223 oldAssignee: string | null; 224 newAssignee: string; 225 newAssigneeId: string; 226 title: string; 227}>("task.assignee_changed", async (data) => { 228 if (data.newAssigneeId) { 229 await createNotification({ 230 userId: data.newAssigneeId, 231 type: "task_assignee_changed", 232 eventData: { 233 taskTitle: data.title, 234 }, 235 resourceId: data.taskId, 236 resourceType: "task", 237 }); 238 } 239}); 240 241subscribeToEvent<{ 242 timeEntryId: string; 243 taskId: string; 244 userId: string; 245 taskOwnerId?: string; 246 taskTitle?: string; 247}>("time-entry.created", async (data) => { 248 if (data.taskOwnerId && data.taskOwnerId !== data.userId) { 249 await createNotification({ 250 userId: data.taskOwnerId, 251 type: "time_entry_created", 252 eventData: { 253 taskTitle: data.taskTitle ?? null, 254 }, 255 resourceId: data.taskId, 256 resourceType: "task", 257 }); 258 } 259}); 260 261export default notification;