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
5 changes: 3 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@
## Phase 2 — Permissions & Moderation

- [ ] Granular permission system (beyond owner/admin/member)
- [~] Member management UI (kick, banish, silence, role assignment) (in progress in this PR)
- [x] Member management UI (kick, banish, silence, role assignment)
- [ ] Rate limiting enforcement (API-level + per-channel)
- [ ] Audit logs

## Phase 3 — Social Features

- [x] Shareable invite links (not just email invites) — schema, API, and UI implemented
- [ ] Ally (friend) system with requests
- [x] Ally (friend) system with requests — schema, API, allies page, user profile popover with ally actions
- [ ] User blocking
- [ ] Privacy settings

Expand Down Expand Up @@ -79,6 +79,7 @@

## Backlog (Short-Term Hardening)

- [x] Fix stale presence after server restart (heartbeat TTL + reconciliation sweep).
- [ ] Add explicit error logging in `initializeConnection` before disconnecting a socket (include `socket.id` + `userId` context).
- [ ] Update onboarding `normalizeSlugInput` to collapse repeated hyphens while typing.
- [ ] Use `DM_CHANNEL_TYPES` constant everywhere in DM route filters to avoid drift.
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import createApp from "@/lib/helpers/app/create-app"
import configureOpenAPI from "@/lib/helpers/openapi/configure-openapi"
import { globalRateLimit } from "@/middleware/rate-limit"
import index from "@/routes/index.route"
import alliesRouter from "@/routes/v1/allies/index"
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 uploadsRouter from "@/routes/v1/uploads/index"
import usersRouter from "@/routes/v1/users/index"
import waitlistRouter from "@/routes/waitlist/index"

const app = createApp()
Expand All @@ -35,11 +37,13 @@ app.route("/", index)
// Route mounting — chained for Hono RPC type inference
const routes = app
.route("/", waitlistRouter)
.route("/v1", alliesRouter)
.route("/v1", channelsRouter)
.route("/v1", guildsRouter)
.route("/v1", invitesRouter)
.route("/v1", dmsRouter)
.route("/v1", uploadsRouter)
.route("/v1", usersRouter)
Comment thread
BuckyMcYolo marked this conversation as resolved.

export type AppType = typeof routes

Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/lib/helpers/openapi/message-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export const messageReactionSchema = z.object({
emoji: z.string(),
count: z.number().int().nonnegative(),
reactedByCurrentUser: z.boolean(),
reactors: z.array(
z.object({
id: z.string().uuid(),
name: z.string(),
})
),
})

const httpsUrlSchema = z.string().regex(/^https?:\/\//i)
Expand Down
7 changes: 7 additions & 0 deletions apps/api/src/lib/helpers/openapi/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export const payloadTooLargeSchema = jsonContent({
description: "Payload too large",
})

export const badRequestSchema = jsonContent({
schema: errorSchema.openapi({
example: { success: false, message: "Bad request" },
}),
description: "Bad request",
})

export const notFoundSchema = jsonContent({
schema: errorSchema.openapi({
example: { success: false, message: "Not found" },
Expand Down
8 changes: 8 additions & 0 deletions apps/api/src/lib/queries/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ export async function fetchMessagePage(
messageId: messageReaction.messageId,
emoji: messageReaction.emoji,
userId: messageReaction.userId,
userName: user.name,
})
.from(messageReaction)
.innerJoin(user, eq(messageReaction.userId, user.id))
.where(inArray(messageReaction.messageId, messageIds))
: []

Expand Down Expand Up @@ -138,6 +140,7 @@ export async function fetchMessagePage(
emoji: string
count: number
reactedByCurrentUser: boolean
reactors: Array<{ id: string; name: string }>
}
>
>()
Expand All @@ -161,9 +164,14 @@ export async function fetchMessagePage(
emoji: reactionRow.emoji,
count: 0,
reactedByCurrentUser: false,
reactors: [],
}

existingReaction.count += 1
existingReaction.reactors.push({
id: reactionRow.userId,
name: reactionRow.userName,
})
if (reactionRow.userId === currentUserId) {
existingReaction.reactedByCurrentUser = true
}
Expand Down
Loading