Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dist

# Rust (Tauri)
target/

**/src-tauri/gen/schemas

# Debug
npm-debug.log*
Expand Down
6 changes: 4 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
- [x] Typing indicators
- [x] Pinned messages panel
- [ ] Thread support
- [ ] Desktop app (Tauri) with native notifications for mentions, DMs, etc.
- [ ] Notification preferences
- [x] Desktop app (Tauri) — native window wrapper, notification plugin wired up
- [x] Desktop/browser notifications — notification:bootstrap on connect, unread state context, auto-mark-as-read, browser Notification API + Tauri native notifications
- [x] Notification preferences — user_notification_settings table, API (get/update), settings UI (desktop/DM notification levels, permission request)
- [x] Unread indicators (Discord-style) — channel/DM text highlights, mention badges, left-side unread pill
- [x] Reaction tooltips (who reacted with each emoji)
- [x] User profile popover (bio, status, online indicator, ally actions)
- [x] Remember last visited channel per guild (localStorage)
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import channelsRouter from "@/routes/v1/channels/index"
import dmsRouter from "@/routes/v1/dms/index"
import guildsRouter from "@/routes/v1/guilds/index"
import invitesRouter from "@/routes/v1/invites/index"
import notificationSettingsRouter from "@/routes/v1/notification-settings/index"
import privacySettingsRouter from "@/routes/v1/privacy-settings/index"
import uploadsRouter from "@/routes/v1/uploads/index"
import usersRouter from "@/routes/v1/users/index"
Expand Down Expand Up @@ -44,6 +45,7 @@ const routes = app
.route("/v1", channelsRouter)
.route("/v1", guildsRouter)
.route("/v1", invitesRouter)
.route("/v1", notificationSettingsRouter)
.route("/v1", privacySettingsRouter)
.route("/v1", dmsRouter)
.route("/v1", uploadsRouter)
Expand Down
61 changes: 61 additions & 0 deletions apps/api/src/routes/v1/notification-settings/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { db, eq, schema } from "@repo/db"
import * as HttpStatusCodes from "@/lib/helpers/http/status-codes"
import type { AppRouteHandler } from "@/lib/types/app-types"
import type {
GetNotificationSettingsRoute,
UpdateNotificationSettingsRoute,
} from "./routes"

const DEFAULT_SETTINGS = {
desktopNotifications: "all_messages" as const,
dmNotifications: "all_messages" as const,
}

export const getNotificationSettings: AppRouteHandler<
GetNotificationSettingsRoute
> = async (c) => {
const currentUser = c.var.user

const settings = await db
.select({
desktopNotifications:
schema.userNotificationSettings.desktopNotifications,
dmNotifications: schema.userNotificationSettings.dmNotifications,
})
.from(schema.userNotificationSettings)
.where(eq(schema.userNotificationSettings.userId, currentUser.id))
.limit(1)
.then((rows) => rows[0])

return c.json(settings ?? DEFAULT_SETTINGS, HttpStatusCodes.OK)
}

export const updateNotificationSettings: AppRouteHandler<
UpdateNotificationSettingsRoute
> = async (c) => {
const currentUser = c.var.user
const body = c.req.valid("json")

const updated = await db
.insert(schema.userNotificationSettings)
.values({
userId: currentUser.id,
...body,
})
.onConflictDoUpdate({
target: schema.userNotificationSettings.userId,
set: body,
})
.returning({
desktopNotifications:
schema.userNotificationSettings.desktopNotifications,
dmNotifications: schema.userNotificationSettings.dmNotifications,
})
.then((rows) => rows[0])

if (!updated) {
return c.json(DEFAULT_SETTINGS, HttpStatusCodes.OK)
}

return c.json(updated, HttpStatusCodes.OK)
}
12 changes: 12 additions & 0 deletions apps/api/src/routes/v1/notification-settings/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createRouter } from "@/lib/helpers/app/create-app"
import * as handlers from "@/routes/v1/notification-settings/handlers"
import * as routes from "@/routes/v1/notification-settings/routes"

const notificationSettingsRouter = createRouter()
.openapi(routes.getNotificationSettings, handlers.getNotificationSettings)
.openapi(
routes.updateNotificationSettings,
handlers.updateNotificationSettings
)

export default notificationSettingsRouter
57 changes: 57 additions & 0 deletions apps/api/src/routes/v1/notification-settings/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { createRoute } from "@hono/zod-openapi"
import * as HttpStatusCodes from "@/lib/helpers/http/status-codes"
import jsonContent from "@/lib/helpers/openapi/json-content"
import {
internalServerErrorSchema,
unauthorizedSchema,
} from "@/lib/helpers/openapi/schemas"
import { sessionAuthMiddleware } from "@/middleware/session-auth"
import {
getNotificationSettingsResponseSchema,
updateNotificationSettingsBodySchema,
updateNotificationSettingsResponseSchema,
} from "./schema"

export const getNotificationSettings = createRoute({
path: "/notification-settings",
method: "get",
summary: "Get notification settings",
description: "Returns the current user's notification settings.",
tags: ["Notification Settings"],
middleware: [sessionAuthMiddleware] as const,
responses: {
[HttpStatusCodes.OK]: jsonContent({
schema: getNotificationSettingsResponseSchema,
description: "Notification settings",
}),
[HttpStatusCodes.UNAUTHORIZED]: unauthorizedSchema,
[HttpStatusCodes.INTERNAL_SERVER_ERROR]: internalServerErrorSchema,
},
})

export type GetNotificationSettingsRoute = typeof getNotificationSettings

export const updateNotificationSettings = createRoute({
path: "/notification-settings",
method: "patch",
summary: "Update notification settings",
description: "Updates the current user's notification settings.",
tags: ["Notification Settings"],
middleware: [sessionAuthMiddleware] as const,
request: {
body: jsonContent({
schema: updateNotificationSettingsBodySchema,
description: "Notification settings to update",
}),
},
responses: {
[HttpStatusCodes.OK]: jsonContent({
schema: updateNotificationSettingsResponseSchema,
description: "Updated notification settings",
}),
[HttpStatusCodes.UNAUTHORIZED]: unauthorizedSchema,
[HttpStatusCodes.INTERNAL_SERVER_ERROR]: internalServerErrorSchema,
},
})

export type UpdateNotificationSettingsRoute = typeof updateNotificationSettings
11 changes: 11 additions & 0 deletions apps/api/src/routes/v1/notification-settings/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
notificationSettingsResponseSchema,
updateNotificationSettingsSchema,
} from "@repo/db/schema"

export const getNotificationSettingsResponseSchema =
notificationSettingsResponseSchema
export const updateNotificationSettingsBodySchema =
updateNotificationSettingsSchema
export const updateNotificationSettingsResponseSchema =
notificationSettingsResponseSchema
3 changes: 2 additions & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"dev": "pnpm tauri dev",
"build": "pnpm tauri build"
},
"keywords": [],
"author": "",
Expand Down
Loading