Skip to content

Conversation

@kzndotsh
Copy link
Contributor

@kzndotsh kzndotsh commented Jan 15, 2026

Summary by Sourcery

Add end-to-end XMPP account management support, including OAuth scope exposure, API endpoints, database schema, and a user-facing settings page.

New Features:

  • Expose an XMPP OAuth scope and include XMPP username in user info when requested.
  • Introduce XMPP account REST API endpoints for creating, retrieving, updating, and deleting user XMPP accounts backed by Prosody.
  • Add a user dashboard page and UI for managing XMPP accounts, including creation, status viewing, and deletion.
  • Provide a client library, React Query hooks, and query keys for interacting with XMPP account APIs from the frontend.
  • Add database schema and configuration for XMPP accounts and Prosody REST integration, plus a script to register Prosody as an OAuth client.

- Added a new client for interacting with the Prosody REST API, supporting account creation, deletion, and existence checks.
- Introduced configuration management for Prosody API credentials and endpoints.
- Implemented utility functions for XMPP username validation and JID formatting.
- Defined TypeScript types for account management and API responses.
- Introduced a new route for XMPP with path "/app/xmpp" and associated icon.
- Updated navigation order for the admin route to accommodate the new XMPP route.
- Configured metadata to prevent indexing by search engines.
- Introduced a new schema for XMPP accounts, including fields for user ID, JID, username, status, and timestamps.
- Added an enum for XMPP account status with values: active, suspended, and deleted.
- Updated the main schema index to include the new xmppAccount schema.
- Added 'xmpp' to the supported scopes in the OAuth provider configuration.
- Implemented custom user info claims to include XMPP username when the 'xmpp' scope is requested, querying the database for the associated username.
- Introduced a new module for XMPP API client functions, including methods for fetching, creating, updating, and deleting XMPP accounts.
- Added query keys for XMPP account management to facilitate data retrieval.
- Updated the main API index to export the new XMPP module.
- Added custom hooks for managing XMPP accounts, including fetching, creating, updating, and deleting accounts.
- Integrated TanStack Query for efficient data fetching and state management.
- Implemented cache invalidation strategies to ensure data consistency after mutations.
- Introduced a new component for managing XMPP accounts, allowing users to create and delete accounts.
- Integrated loading and error states for improved user experience.
- Displayed account information, including JID, username, and status, with options to copy JID to clipboard.
- Implemented user guidance for connecting to XMPP clients.
- Added API routes for managing XMPP accounts, including creation, retrieval, updating, and deletion.
- Implemented authentication and authorization checks to ensure secure access to account information.
- Handled username validation and uniqueness checks against both the database and Prosody server.
- Included error handling for various scenarios, such as account existence and invalid input formats.
- Added a new page for managing XMPP accounts, including session verification and data prefetching for server-side rendering.
- Integrated the XmppAccountManagement component and PageHeader for a cohesive user interface.
- Implemented hydration for efficient state management with TanStack Query.
- Introduced a new script for registering the Prosody XMPP server as an OAuth client in Better Auth.
- Supports confidential client creation with password grant type for legacy XMPP client compatibility.
- Includes environment variable configuration and outputs necessary credentials for Prosody setup.
- Changed the default formatter for Markdown files from 'yzhang.markdown-all-in-one' to 'DavidAnson.vscode-markdownlint' for improved linting and formatting capabilities.
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 15, 2026

Reviewer's Guide

Adds full XMPP integration: OAuth scope and userinfo claim, DB schema and API for XMPP accounts backed by Prosody REST, React Query client/hooks and dashboard UI for users to self-manage their XMPP accounts, route wiring, plus a script to register Prosody as an OAuth client.

Sequence diagram for creating an XMPP account

sequenceDiagram
  actor User
  participant Browser
  participant XmppAccountManagement
  participant ReactQuery_useCreateXmppAccount
  participant XmppApi_createXmppAccount
  participant Api_POST_api_xmpp_accounts
  participant Auth_requireAuth
  participant DB
  participant ProsodyRestClient
  participant ProsodyServer

  User->>Browser: Click_Create_XMPP_Account
  Browser->>XmppAccountManagement: handleCreate(username)
  XmppAccountManagement->>ReactQuery_useCreateXmppAccount: mutateAsync(username)
  ReactQuery_useCreateXmppAccount->>XmppApi_createXmppAccount: createXmppAccount(requestBody)
  XmppApi_createXmppAccount->>Api_POST_api_xmpp_accounts: HTTP_POST_/api/xmpp/accounts

  Api_POST_api_xmpp_accounts->>Auth_requireAuth: requireAuth(request)
  Auth_requireAuth-->>Api_POST_api_xmpp_accounts: userId

  Api_POST_api_xmpp_accounts->>DB: query_xmppAccount_by_userId
  DB-->>Api_POST_api_xmpp_accounts: existingAccount_or_null
  alt user_already_has_account
    Api_POST_api_xmpp_accounts-->>XmppApi_createXmppAccount: 409_Username_already_has_XMPP_account
    XmppApi_createXmppAccount-->>ReactQuery_useCreateXmppAccount: throw_Error
    ReactQuery_useCreateXmppAccount-->>XmppAccountManagement: onError
    XmppAccountManagement-->>Browser: Show_error_toast
  else no_existing_account
    Api_POST_api_xmpp_accounts->>DB: select_user_email_by_userId
    DB-->>Api_POST_api_xmpp_accounts: user_email
    Api_POST_api_xmpp_accounts->>Api_POST_api_xmpp_accounts: determine_or_validate_username
    Api_POST_api_xmpp_accounts->>DB: query_xmppAccount_by_username
    DB-->>Api_POST_api_xmpp_accounts: existingUsername_or_null
    Api_POST_api_xmpp_accounts->>ProsodyRestClient: checkProsodyAccountExists(username)
    ProsodyRestClient->>ProsodyServer: REST_GET_/accounts/username
    ProsodyServer-->>ProsodyRestClient: exists_or_404
    ProsodyRestClient-->>Api_POST_api_xmpp_accounts: boolean_exists

    Api_POST_api_xmpp_accounts->>ProsodyRestClient: createProsodyAccount(username)
    ProsodyRestClient->>ProsodyServer: REST_POST_/accounts_with_XML_stanza
    ProsodyServer-->>ProsodyRestClient: success
    ProsodyRestClient-->>Api_POST_api_xmpp_accounts: ProsodyRestAccountResponse

    Api_POST_api_xmpp_accounts->>DB: insert_xmppAccount_record
    DB-->>Api_POST_api_xmpp_accounts: newAccount

    Api_POST_api_xmpp_accounts-->>XmppApi_createXmppAccount: 201_ok_with_account
    XmppApi_createXmppAccount-->>ReactQuery_useCreateXmppAccount: XmppAccount
    ReactQuery_useCreateXmppAccount->>ReactQuery_useCreateXmppAccount: invalidateQueries_xmppAccounts_current
    ReactQuery_useCreateXmppAccount-->>XmppAccountManagement: onSuccess(account)
    XmppAccountManagement-->>Browser: Show_success_toast_and_clear_username
  end
  Browser-->>User: Render_account_details_view
Loading

Sequence diagram for deleting an XMPP account

sequenceDiagram
  actor User
  participant Browser
  participant XmppAccountManagement
  participant ReactQuery_useDeleteXmppAccount
  participant XmppApi_deleteXmppAccount
  participant Api_DELETE_api_xmpp_accounts_id
  participant Auth_requireAuth
  participant DB
  participant ProsodyRestClient
  participant ProsodyServer

  User->>Browser: Confirm_delete_in_dialog
  Browser->>XmppAccountManagement: handleDelete(account.id)
  XmppAccountManagement->>ReactQuery_useDeleteXmppAccount: mutateAsync(accountId)
  ReactQuery_useDeleteXmppAccount->>XmppApi_deleteXmppAccount: deleteXmppAccount(id)
  XmppApi_deleteXmppAccount->>Api_DELETE_api_xmpp_accounts_id: HTTP_DELETE_/api/xmpp/accounts/id

  Api_DELETE_api_xmpp_accounts_id->>Auth_requireAuth: requireAuth(request)
  Auth_requireAuth-->>Api_DELETE_api_xmpp_accounts_id: userId

  Api_DELETE_api_xmpp_accounts_id->>DB: query_xmppAccount_by_id
  DB-->>Api_DELETE_api_xmpp_accounts_id: account_or_null
  alt account_not_found
    Api_DELETE_api_xmpp_accounts_id-->>XmppApi_deleteXmppAccount: 404_not_found
    XmppApi_deleteXmppAccount-->>ReactQuery_useDeleteXmppAccount: throw_Error
    ReactQuery_useDeleteXmppAccount-->>XmppAccountManagement: onError
    XmppAccountManagement-->>Browser: Show_error_toast
  else account_found
    Api_DELETE_api_xmpp_accounts_id->>Api_DELETE_api_xmpp_accounts_id: check_owner_or_admin
    Api_DELETE_api_xmpp_accounts_id->>ProsodyRestClient: deleteProsodyAccount(username)
    ProsodyRestClient->>ProsodyServer: REST_DELETE_/accounts/username_with_XML_stanza
    ProsodyServer-->>ProsodyRestClient: success_or_404
    ProsodyRestClient-->>Api_DELETE_api_xmpp_accounts_id: ProsodyRestAccountResponse

    Api_DELETE_api_xmpp_accounts_id->>DB: update_xmppAccount_status_to_deleted
    DB-->>Api_DELETE_api_xmpp_accounts_id: deletedAccount

    Api_DELETE_api_xmpp_accounts_id-->>XmppApi_deleteXmppAccount: 200_ok
    XmppApi_deleteXmppAccount-->>ReactQuery_useDeleteXmppAccount: void
    ReactQuery_useDeleteXmppAccount->>ReactQuery_useDeleteXmppAccount: invalidateQueries_xmppAccounts_all
    ReactQuery_useDeleteXmppAccount-->>XmppAccountManagement: onSuccess
    XmppAccountManagement-->>Browser: Show_success_toast
  end
  Browser-->>User: Render_account_creation_form_state
Loading

ER diagram for new xmppAccount table and relation to user

erDiagram
  user {
    text id PK
    text email
    text name
  }

  xmpp_account {
    text id PK
    text user_id FK
    text jid
    text username
    enum status
    timestamp created_at
    timestamp updated_at
    jsonb metadata
  }

  user ||--o| xmpp_account : has_one
Loading

Class diagram for XMPP TypeScript types

classDiagram
  class XmppAccount {
    +string id
    +string userId
    +string jid
    +string username
    +XmppAccountStatus status
    +Date createdAt
    +Date updatedAt
    +Record~string,unknown~ metadata
  }

  class XmppAccountStatus {
    <<enumeration>>
    active
    suspended
    deleted
  }

  class CreateXmppAccountRequest {
    +string username
  }

  class UpdateXmppAccountRequest {
    +string username
    +XmppAccountStatus status
    +Record~string,unknown~ metadata
  }

  class ProsodyRestError {
    +string error
    +string message
  }

  class ProsodyRestAccountResponse {
    +boolean success
    +string error
    +string message
  }

  XmppAccount --> XmppAccountStatus
  CreateXmppAccountRequest --> XmppAccountStatus
  UpdateXmppAccountRequest --> XmppAccountStatus
  ProsodyRestAccountResponse --> ProsodyRestError
Loading

File-Level Changes

Change Details Files
Extend OAuth server to support an XMPP scope and expose the XMPP username via userinfo when requested.
  • Add 'xmpp' to the configured OAuth scopes list so tokens can request it.
  • Implement async customUserInfoClaims that, when 'xmpp' is in scopes, looks up the user’s xmppAccount row and adds xmpp_username to the userinfo claims.
src/lib/auth/config.ts
src/lib/db/schema/xmpp.ts
src/lib/db/schema/index.ts
Introduce XMPP account persistence in the database and wire it into the global schema.
  • Define xmpp_account table with status enum, unique userId/jid/username, timestamps, metadata, and supporting indices.
  • Export xmppAccount from the central schema index so it’s available to queries.
src/lib/db/schema/xmpp.ts
src/lib/db/schema/index.ts
Implement server-side XMPP account CRUD HTTP API backed by Prosody REST for creation/deletion checks.
  • Add /api/xmpp/accounts POST/GET for creating and fetching the current user’s XMPP account, including username validation/generation, uniqueness checks (DB and Prosody), Prosody account creation, and rollback on DB failure.
  • Add /api/xmpp/accounts/[id] GET/PATCH/DELETE for owner/admin-only access to accounts, updating username/status/metadata, soft-deleting accounts in DB after attempting Prosody deletion, and consistent error handling via APIError/handleAPIError.
  • Enforce authentication and authorization via requireAuth and isAdmin on all XMPP account routes and mark them dynamic for per-request behavior.
src/app/api/xmpp/accounts/route.ts
src/app/api/xmpp/accounts/[id]/route.ts
src/lib/xmpp/client.ts
src/lib/xmpp/config.ts
src/lib/xmpp/types.ts
src/lib/xmpp/utils.ts
Provide a typed client-layer and React Query hooks for XMPP account operations, plus query key organization.
  • Add XmppApiResponse-based client functions to call the XMPP account endpoints (fetch current or by id, create, update, delete) and surface API error messages as thrown Errors.
  • Define React Query hooks (useXmppAccount, useXmppAccountById, useCreateXmppAccount, useUpdateXmppAccount, useDeleteXmppAccount) that use the client functions and keep XMPP-related caches coherent via invalidate/setQueryData.
  • Extend queryKeys with xmppAccounts subkeys for current, details, and detail(id).
src/lib/api/xmpp.ts
src/hooks/use-xmpp-account.ts
src/lib/api/query-keys.ts
src/lib/api/index.ts
Add a dashboard page and UI component for end users to self-manage their XMPP account.
  • Create XmppAccountManagement client component that uses the XMPP hooks to show loading/error states, a create-account form (with optional username), and a detail view with JID copy, status badges, created date, and delete confirmation, including toast-based UX feedback.
  • Add /app/xmpp dashboard page that verifies the session, pre-populates React Query cache for the current XMPP account, wraps in HydrationBoundary, and uses PageHeader plus the new management component.
  • Wire the new page into the routeConfig with icon, navigation group/order, and robots metadata, adjusting the admin route order accordingly.
src/components/xmpp/xmpp-account-management.tsx
src/app/(dashboard)/app/xmpp/page.tsx
src/lib/routes/config.ts
Create a script to register Prosody as a confidential OAuth client for password and authorization_code grants.
  • Add a Node script that checks for an existing oauthClient by name, otherwise creates a new random clientId/clientSecret (or uses provided env values), with appropriate grant types, scopes, and flags for Prosody usage.
  • Script prints out the resulting credentials for use in Prosody config and exits with proper status codes on success/failure.
scripts/create-prosody-oauth-client.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • In the XMPP account route handlers under src/app/api/xmpp/accounts/[id]/route.ts, the params argument is typed and used as a Promise ({ params }: { params: Promise<{ id: string }> } with await params), but Next.js app router passes params as a plain object; this should be updated to { params }: { params: { id: string } } and used synchronously.
  • The XMPP client helper prosodyRequest in src/lib/xmpp/client.ts always sends Content-Type: application/xml, including for GET requests; if Prosody’s REST endpoint is strict about content types on non-body requests, you may want to only set this header when a body is present to avoid unexpected 4xx responses.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the XMPP account route handlers under `src/app/api/xmpp/accounts/[id]/route.ts`, the `params` argument is typed and used as a `Promise` (`{ params }: { params: Promise<{ id: string }> }` with `await params`), but Next.js app router passes `params` as a plain object; this should be updated to `{ params }: { params: { id: string } }` and used synchronously.
- The XMPP client helper `prosodyRequest` in `src/lib/xmpp/client.ts` always sends `Content-Type: application/xml`, including for GET requests; if Prosody’s REST endpoint is strict about content types on non-body requests, you may want to only set this header when a body is present to avoid unexpected 4xx responses.

## Individual Comments

### Comment 1
<location> `src/app/(dashboard)/app/xmpp/page.tsx:32-34` </location>
<code_context>
+
+  // Prefetch XMPP account data for SSR
+  // This reduces loading flash by populating the cache before components mount
+  await queryClient.prefetchQuery({
+    queryKey: queryKeys.xmppAccounts.current(),
+    queryFn: async () => {
+      // We'll fetch on the client side since this is a user-specific endpoint
+      // that requires authentication cookies
</code_context>

<issue_to_address>
**issue (bug_risk):** Prefetching `xmppAccounts.current` with `null` will hydrate the query as successful and block the real fetch for 30s.

Because `useXmppAccount` configures `staleTime: 30_000`, hydrating this query with `null` marks it as `status: 'success'` with `data: null` and prevents a refetch from `/api/xmpp/accounts` for 30s. Users who already have an account will see the "no account" UI during that period. Please avoid hydrating with `null` here (e.g. remove this prefetch, use a different key for a placeholder, or change the behavior to `initialData`/`staleTime: 0` rather than simulating a real fetch).
</issue_to_address>

### Comment 2
<location> `src/app/api/xmpp/accounts/[id]/route.ts:19-21` </location>
<code_context>
+ * GET /api/xmpp/accounts/[id]
+ * Get specific XMPP account details (admin or owner only)
+ */
+export async function GET(
+  request: NextRequest,
+  { params }: { params: Promise<{ id: string }> }
+) {
+  try {
</code_context>

<issue_to_address>
**suggestion:** The route handler types `params` as a Promise and awaits it, which doesn’t match Next.js route handler conventions.

In route handlers, `params` should be a plain object, not a Promise. Typing it as `Promise<{ id: string }>` and using `await params` is misleading and may confuse tooling and readers. Please change the signature to `{ params }: { params: { id: string } }` and remove the `await` in GET/PATCH/DELETE.

Suggested implementation:

```typescript
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const { userId } = await requireAuth(request);
    const { id } = params;

```

You should make the same change to the `PATCH` and `DELETE` handlers in this file:

1. Change their signatures from `{ params }: { params: Promise<{ id: string }> }` to `{ params }: { params: { id: string } }`.
2. Replace any `const { id } = await params;` with `const { id } = params;`.

If there are any helper functions or tests that were updated to treat `params` as a Promise, update them to pass/use a plain object instead.
</issue_to_address>

### Comment 3
<location> `src/components/xmpp/xmpp-account-management.tsx:151-160` </location>
<code_context>
+            <code className="rounded-md bg-muted px-2 py-1 text-sm">
+              {account.jid}
+            </code>
+            <Button
+              onClick={() => {
+                navigator.clipboard.writeText(account.jid);
+                toast.success("Copied", {
+                  description: "JID copied to clipboard",
</code_context>

<issue_to_address>
**suggestion:** Using `navigator.clipboard` directly may fail in non-secure contexts or older browsers without user feedback.

`navigator.clipboard.writeText` can throw or be unavailable (e.g. non-HTTPS, older browsers), which would silently fail here. Consider checking `navigator.clipboard?.writeText` first and handling failures (e.g. show an error toast on absence/rejection) instead of always showing success.
</issue_to_address>

### Comment 4
<location> `src/lib/xmpp/client.ts:27` </location>
<code_context>
+/**
+ * Make a request to Prosody REST API
+ */
+async function prosodyRequest<T>(
+  endpoint: string,
+  options: RequestInit = {}
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring this Prosody client module to separate transport, error handling, and XML construction so each concern is handled by a focused helper.

You can simplify this module by separating concerns a bit more while keeping behavior identical.

### 1. Split transport into XML/JSON helpers instead of a generic `T`

Right now `prosodyRequest<T>` guesses at the response type and does `text as unknown as T`, which makes call sites lie about what they really get.

You can centralize the fetch + error handling, then provide two focused helpers:

```ts
function createAuthHeader(): string {
  const { username, password } = xmppConfig.prosody;
  const credentials = Buffer.from(`${username}:${password}`).toString("base64");
  return `Basic ${credentials}`;
}

async function prosodyFetch(endpoint: string, options: RequestInit = {}): Promise<Response> {
  const { restUrl } = xmppConfig.prosody;
  const url = `${restUrl}${endpoint}`;

  const response = await fetch(url, {
    ...options,
    headers: {
      "Content-Type": "application/xml",
      Authorization: createAuthHeader(),
      ...options.headers,
    },
  });

  if (!response.ok) {
    let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
    try {
      const errorData = (await response.json()) as ProsodyRestError;
      errorMessage = errorData.error ?? errorData.message ?? errorMessage;
    } catch {
      // non‑JSON error, keep default message
    }
    throw new Error(errorMessage);
  }

  return response;
}

async function prosodyXmlRequest(endpoint: string, options: RequestInit = {}): Promise<string> {
  const response = await prosodyFetch(endpoint, options);
  return response.text();
}

async function prosodyJsonRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
  const response = await prosodyFetch(endpoint, options);
  return (await response.json()) as T;
}
```

Then your current usages can be explicit about what they expect:

```ts
const result = await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
  method: "POST",
  body: stanza,
});
```

No more `text as unknown as T` and no need to inspect `content-type` at every call site.

### 2. Centralize error interpretation instead of repeating string checks

You repeat `"exists"`, `"409"`, `"conflict"`, `"not found"`, `"404"` string matching in three functions. That can move to a single helper, making call sites easier to read and safer to extend:

```ts
type ProsodyDomainError =
  | "AccountAlreadyExists"
  | "AccountNotFound";

function interpretProsodyError(error: unknown): ProsodyDomainError | null {
  if (!(error instanceof Error)) return null;
  const msg = error.message.toLowerCase();

  if (msg.includes("exists") || msg.includes("409") || msg.includes("conflict")) {
    return "AccountAlreadyExists";
  }

  if (msg.includes("not found") || msg.includes("404")) {
    return "AccountNotFound";
  }

  return null;
}
```

Usage in `createProsodyAccount`:

```ts
try {
  const result = await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
    method: "POST",
    body: stanza,
  });
  return result;
} catch (error) {
  const domainError = interpretProsodyError(error);
  if (domainError === "AccountAlreadyExists") {
    throw new Error(`XMPP account already exists: ${jid}`);
  }
  throw error;
}
```

Usage in `deleteProsodyAccount`:

```ts
try {
  const result = await prosodyJsonRequest<ProsodyRestAccountResponse>(
    `/accounts/${encodeURIComponent(username)}`,
    { method: "DELETE", body: stanza },
  );
  return result;
} catch (error) {
  const domainError = interpretProsodyError(error);
  if (domainError === "AccountNotFound") {
    return { success: true };
  }
  throw error;
}
```

Usage in `checkProsodyAccountExists`:

```ts
try {
  await prosodyXmlRequest(`/accounts/${encodeURIComponent(username)}`, { method: "GET" });
  return true;
} catch (error) {
  const domainError = interpretProsodyError(error);
  if (domainError === "AccountNotFound") {
    return false;
  }
  throw error;
}
```

This keeps existing semantics but removes duplicated string inspection.

### 3. Extract XML stanza builders

Inline multi-line XML makes the functions noisy and duplicates boilerplate. Small helpers keep protocol details in one place:

```ts
function buildCreateAccountStanza(username: string): string {
  return `<?xml version="1.0"?>
<iq type="set" to="${xmppConfig.domain}" id="create_${Date.now()}">
  <query xmlns="jabber:iq:register">
    <username>${username}</username>
  </query>
</iq>`;
}

function buildDeleteAccountStanza(): string {
  return `<?xml version="1.0"?>
<iq type="set" to="${xmppConfig.domain}" id="delete_${Date.now()}">
  <query xmlns="jabber:iq:register">
    <remove/>
  </query>
</iq>`;
}
```

Then the high-level functions become easier to scan:

```ts
export async function createProsodyAccount(username: string): Promise<ProsodyRestAccountResponse> {
  const jid = formatJid(username, xmppConfig.domain);
  const stanza = buildCreateAccountStanza(username);

  try {
    return await prosodyJsonRequest<ProsodyRestAccountResponse>("/accounts", {
      method: "POST",
      body: stanza,
    });
  } catch (error) {
    const domainError = interpretProsodyError(error);
    if (domainError === "AccountAlreadyExists") {
      throw new Error(`XMPP account already exists: ${jid}`);
    }
    throw error;
  }
}
```

Overall, these changes keep all behavior but:  
- remove the unsafe generic `prosodyRequest<T>` pattern,  
- consolidate error mapping in one place, and  
- separate XML construction from transport logic, reducing duplication and cognitive load.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 132 to 137
// Note: Changing username in XMPP typically requires recreating the account
// This is a limitation of XMPP - usernames are typically immutable
// For now, we'll update the database but note that Prosody may need manual intervention
updates.username = newUsername;
updates.jid = formatJid(newUsername, xmppConfig.domain);
}

This comment was marked as outdated.

@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds full XMPP support: DB schema, Prosody REST client/config, server API routes, client API/hooks/components, OAuth scope/claims, environment keys and provisioning script; plus a large rework of observability surface (removals, consolidations, and new helpers/utils).

Changes

Cohort / File(s) Summary
Editor / Tooling
\.vscode/settings.json, \.cursor/hooks.json, package.json
Markdown formatter changed; removed afterFileEdit hook from .cursor/hooks.json; dev script simplified and dev:grab added; removed react-grab devDependency.
Environment keys
src/lib/xmpp/keys.ts, src/env.ts, AGENTS.md
New t3-env keys() for XMPP/Prosody; env extends xmpp(); documentation updated to describe env approach.
XMPP Config & Client
src/lib/xmpp/config.ts, src/lib/xmpp/client.ts, src/lib/xmpp/types.ts, src/lib/xmpp/utils.ts
New xmppConfig with runtime validation; Prosody REST client (create/check/delete, auth header, XML escaping, custom error); types and utility helpers (username validation, JID parse/format, status guard).
Database schema
src/lib/db/schema/xmpp.ts, src/lib/db/schema/index.ts
New xmpp_account table and xmppAccountStatusEnum (active/suspended/deleted); exported in schema index.
API surface (server)
src/app/api/xmpp/accounts/route.ts, src/app/api/xmpp/accounts/[id]/route.ts
New REST endpoints: POST/GET current account and GET/PATCH/DELETE by id; auth and ownership/admin checks, validation, Prosody sync, rollback on partial failures, soft-delete.
API surface (client)
src/lib/api/xmpp.ts, src/lib/api/index.ts, src/lib/api/query-keys.ts
Client helpers for fetch/create/update/delete XMPP accounts; queryKeys namespace xmppAccounts; re-exported from API barrel.
Auth & OAuth
src/lib/auth/config.ts, scripts/create-prosody-oauth-client.ts
OAuth scopes extended with xmpp; added customUserInfoClaims to include xmpp_username when scope requested; script to create Prosody OAuth client in DB.
React hooks & UI
src/hooks/use-xmpp-account.ts, src/components/xmpp/xmpp-account-management.tsx, src/app/(dashboard)/app/xmpp/page.tsx, locale/en/routes.json, src/lib/routes/config.ts
TanStack Query hooks for XMPP; client component for account management (create/delete, status, copy JID); server page wrapping component; new localized route label and protected UI route /app/xmpp added.
Observability rework
many under src/lib/observability/*, src/lib/observability/index.ts, src/instrumentation.ts, src/lib/observability/utils.ts, etc.
Large reorganization: removal of multiple prior modules (cache, enrichment, error, fingerprinting, levels, log, metrics, queue, sampling, scopes, span, trace, troubleshooting, http) and addition/consolidation of new helpers and client/server initialization changes; observability barrel reworked to export initializeSentryEdge/Server, helpers, keys, and utils. instrumentation.ts updated for Sentry captureRequestError usage and sync onRequestError.
Observability utilities (new)
src/lib/observability/helpers.ts, src/lib/observability/utils.ts, src/lib/observability/client.ts, src/lib/observability/server.ts, src/lib/observability/index.ts
New consolidated helpers module (metrics, spans, cache/queue/http instrumentation, context helpers) added at large scale; utils provides parseError/captureError/log; client.ts expanded Sentry client logic (sanitization, fingerprinting, sampling, integrations). Barrel exports reorganized.
Miscellaneous
.gitignore, src/instrumentation.ts, .cursor/hooks.json
Added .cursor/hooks/logs to .gitignore; instrumentation error handling tightened; cursor hook removed.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client App
    participant API as API Route<br/>/api/xmpp/accounts
    participant DB as Database
    participant Prosody as Prosody Server

    Client->>API: POST /api/xmpp/accounts (username?)
    API->>API: Verify auth & ownership
    API->>DB: Check existing xmpp_account
    API->>API: Validate or generate username
    API->>Prosody: POST /accounts (XML IQ create)
    Prosody-->>API: Created / Conflict
    API->>DB: INSERT xmpp_account (jid, username, status)
    DB-->>API: Record saved
    API-->>Client: 201 Created (XmppAccount)
Loading
sequenceDiagram
    participant Client as Client App
    participant API as API Route<br/>/api/xmpp/accounts/[id]
    participant Prosody as Prosody Server
    participant DB as Database

    Client->>API: DELETE /api/xmpp/accounts/{id}
    API->>API: Verify auth & ownership/admin
    API->>Prosody: DELETE /accounts/{username} (tolerant of 404)
    Prosody-->>API: Success or 404
    API->>DB: UPDATE xmpp_account SET status='deleted', updatedAt=now()
    DB-->>API: Soft-delete applied
    API-->>Client: 200 OK / Success
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: xmpp integration' directly and clearly summarizes the main change: adding XMPP integration to the codebase.
Description check ✅ Passed The description comprehensively explains the XMPP integration features including OAuth scope, API endpoints, database schema, UI, and client library support.
Docstring Coverage ✅ Passed Docstring coverage is 96.88% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/xmpp-integration


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 65e51a6 and 9b83af7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .cursor/hooks.json
  • .gitignore
  • AGENTS.md
  • locale/en/routes.json
  • package.json
  • src/env.ts
  • src/instrumentation.ts
  • src/lib/api/utils.ts
  • src/lib/observability/cache.ts
  • src/lib/observability/client.ts
  • src/lib/observability/enrichment.ts
  • src/lib/observability/error.ts
  • src/lib/observability/fingerprinting.ts
  • src/lib/observability/helpers.ts
  • src/lib/observability/http.ts
  • src/lib/observability/index.ts
  • src/lib/observability/levels.ts
  • src/lib/observability/log.ts
  • src/lib/observability/metrics.ts
  • src/lib/observability/queue.ts
  • src/lib/observability/sampling.ts
  • src/lib/observability/scopes.ts
  • src/lib/observability/server.ts
  • src/lib/observability/span.ts
  • src/lib/observability/trace.ts
  • src/lib/observability/troubleshooting.ts
  • src/lib/observability/utils.ts
  • src/lib/routes/i18n.ts
  • src/lib/xmpp/config.ts
  • src/lib/xmpp/keys.ts

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Fix all issues with AI agents
In `@scripts/create-prosody-oauth-client.ts`:
- Around line 39-47: The code prints sensitive data for existing clients: when
existingClient is non-empty the block logs client.clientSecret; remove or mask
the secret rather than printing it. Update the early-return branch that uses
existingClient[0] (variable client) to either omit client.clientSecret entirely
or replace it with a masked placeholder (e.g., "(redacted)" or show only last 4
chars) before logging, keeping other fields (clientId, name, disabled)
unchanged. Ensure no plaintext secret is written to console in the function that
handles existing clients.

In `@src/app/api/xmpp/accounts/`[id]/route.ts:
- Around line 225-234: Replace brittle string matching in the DELETE handler
with a concrete error type from the Prosody client: export a custom
ProsodyAccountNotFoundError (or add an error.code) from deleteProsodyAccount in
client.ts and throw that when the account isn’t present; then in the catch block
of the route handler check if error instanceof ProsodyAccountNotFoundError (or
compare error.code) and only wrap/throw an APIError for other error types,
including a safe error.message fallback for non-Error values.
- Around line 132-137: Detect and block username changes instead of updating the
DB directly: in the route handler where you build `updates` (the block that sets
`updates.username` and `updates.jid` using `formatJid`), compare `newUsername`
to the existing account's username and if they differ return an HTTP 400/422
with a clear error like "username cannot be changed via API; recreate account or
contact admin", and do not set `updates.username` or `updates.jid`; leave
Prosody synchronization for a separate flow (or add a dedicated rename endpoint
that handles Prosody delete/create).
- Line 77: The request body in route handler is directly cast using "as
UpdateXmppAccountRequest" (const body = (await request.json()) as
UpdateXmppAccountRequest) with no runtime validation; replace the direct cast
with Zod validation by parsing the raw JSON via the corresponding Zod schema
(e.g., UpdateXmppAccountSchema) using parse/parseAsync or safeParse and handle
validation errors (return 400 with error details) before using "body" in the
rest of the handler; update references in this file (route.ts) to use the
validated result instead of the unchecked cast.

In `@src/app/api/xmpp/accounts/route.ts`:
- Around line 90-109: The uniqueness checks using db.query.xmppAccount.findFirst
and checkProsodyAccountExists have a TOCTOU race: two concurrent requests can
pass both checks before creation; fix by performing the account creation inside
a database transaction (use the same transaction for any DB inserts/queries)
relying on a UNIQUE constraint on xmppAccount.username as the primary guard, and
handle the constraint violation error from the transactional insert to return a
409 response; also treat Prosody creation (createProsodyAccount) as
authoritative where possible—attempt Prosody create first or roll back DB insert
on Prosody conflict—and consolidate error handling currently at the rollback
path to detect DB unique-violation and Prosody "already exists" errors and
return a consistent "Username already taken" 409 response.

In `@src/lib/auth/config.ts`:
- Around line 281-297: The db query inside customUserInfoClaims can throw and
has no error handling; wrap the call to db.query.xmppAccount.findFirst in a
try/catch, call Sentry.captureException(err) (and optionally add context like {
function: "customUserInfoClaims", userId: user.id }) in the catch, and ensure
you do not rethrow so the function returns the existing claims even if the query
fails; update the customUserInfoClaims implementation and related error path
accordingly.

In `@src/lib/xmpp/client.ts`:
- Around line 83-88: The XML stanza construction directly interpolates username
into the template string (variable stanza) which risks malformed XML; implement
or reuse an XML-escaping helper (e.g., escapeXml that replaces & < > " ' with
entities) and apply it to username before inserting into the stanza (keep
validating with isValidXmppUsername but add defense-in-depth), ensuring the
escaped value is used where stanza is built (still using xmppConfig.domain and
the existing id creation).

In `@src/lib/xmpp/config.ts`:
- Around line 41-43: The check for xmppConfig.prosody.restUrl is unreachable
because a default of "http://localhost:5281/rest" is assigned earlier; either
remove that default so the validation (if (!xmppConfig.prosody.restUrl) ...) can
enforce an explicit PROSODY_REST_URL, or delete the unreachable validation block
entirely; locate the default assignment for xmppConfig.prosody.restUrl and
remove it to require explicit config, or remove the throw/if block to accept the
default, and update any related docs/tests to reflect the chosen behavior.

In `@src/lib/xmpp/utils.ts`:
- Around line 57-60: Sanitization can leave multiple invalid leading characters
because XMPP_USERNAME_START_REGEX is applied only once; update the sanitization
to remove all leading invalid chars from localpart (reference: variable
sanitized, symbols localpart, XMPP_USERNAME_SANITIZE_REGEX,
XMPP_USERNAME_START_REGEX, XMPP_USERNAME_MAX_LENGTH). Fix by making the "start"
replace greedy (ensure XMPP_USERNAME_START_REGEX matches one-or-more leading
invalid chars, e.g. anchored with +) or loop/remove repeatedly until no leading
invalid char remains, then apply the length slice; adjust
XMPP_USERNAME_START_REGEX or the replace logic accordingly so sanitized never
starts with invalid characters.
🧹 Nitpick comments (13)
scripts/create-prosody-oauth-client.ts (1)

56-72: Consider adding timestamps and note password grant deprecation.

Two observations:

  1. The createdAt and updatedAt fields from the schema are not being set. Consider adding them for audit purposes.

  2. The password grant type is deprecated in OAuth 2.1. While it may be necessary for legacy XMPP client support (as documented), ensure this is intentional and document any migration path.

Suggested change to add timestamps
     const [newClient] = await db
       .insert(oauthClient)
       .values({
         id: randomUUID(),
         clientId,
         clientSecret,
         name: clientName,
         redirectUris: [], // Not needed for server-to-server auth
         grantTypes: ["authorization_code", "password"], // Password grant for legacy clients
         tokenEndpointAuthMethod: "client_secret_post", // Prosody will use POST for auth
         scopes: ["openid", "xmpp"], // Required scopes
         skipConsent: true, // Trusted first-party client
         public: false, // Confidential client
         disabled: false,
+        createdAt: new Date(),
+        updatedAt: new Date(),
       })
       .returning();
src/lib/db/schema/xmpp.ts (1)

37-42: Redundant indexes on unique columns.

The jid and username columns already have unique() constraints, which implicitly create indexes in PostgreSQL. The explicit indexes on lines 39-40 are redundant and add minor overhead during writes.

♻️ Suggested fix
   (table) => [
     index("xmpp_account_userId_idx").on(table.userId),
-    index("xmpp_account_jid_idx").on(table.jid),
-    index("xmpp_account_username_idx").on(table.username),
     index("xmpp_account_status_idx").on(table.status),
   ]
src/hooks/use-xmpp-account.ts (1)

49-106: Consider adding onError handlers for error observability.

The mutation hooks work correctly but lack onError handlers. Per coding guidelines, consider capturing exceptions with Sentry for better error observability in production.

♻️ Example for useCreateXmppAccount
+import * as Sentry from "@sentry/nextjs"

 export function useCreateXmppAccount() {
   const queryClient = useQueryClient();

   return useMutation({
     mutationFn: (data: CreateXmppAccountRequest) => createXmppAccount(data),
     onSuccess: () => {
       queryClient.invalidateQueries({
         queryKey: queryKeys.xmppAccounts.current(),
       });
     },
+    onError: (error) => {
+      Sentry.captureException(error)
+    },
   });
 }

Apply the same pattern to useUpdateXmppAccount and useDeleteXmppAccount.

src/components/xmpp/xmpp-account-management.tsx (4)

43-60: Missing Sentry error capture in handleCreate.

Per coding guidelines, exceptions should be captured using Sentry.captureException(error) in catch blocks. The error is only shown via toast but not reported to Sentry for monitoring.

♻️ Suggested fix
+'use client'
+
+import * as Sentry from '@sentry/nextjs'
 import { useState } from "react";
 // ... other imports
 
   const handleCreate = async () => {
     try {
       await createMutation.mutateAsync({
         username: username.trim() || undefined,
       });
       toast.success("XMPP account created", {
         description: "Your XMPP account has been created successfully.",
       });
       setUsername("");
     } catch (error) {
+      Sentry.captureException(error);
       toast.error("Failed to create account", {
         description:
           error instanceof Error
             ? error.message
             : "An error occurred while creating your XMPP account.",
       });
     }
   };

62-80: Missing Sentry error capture in handleDelete.

Same issue as handleCreate - errors should be captured in Sentry for monitoring.

♻️ Suggested fix
   const handleDelete = async () => {
     if (!account) {
       return;
     }

     try {
       await deleteMutation.mutateAsync(account.id);
       toast.success("XMPP account deleted", {
         description: "Your XMPP account has been deleted successfully.",
       });
     } catch (error) {
+      Sentry.captureException(error);
       toast.error("Failed to delete account", {
         description:
           error instanceof Error
             ? error.message
             : "An error occurred while deleting your XMPP account.",
       });
     }
   };

191-202: Clipboard API lacks error handling.

navigator.clipboard.writeText can fail (e.g., non-secure context, permission denied). Consider wrapping in try-catch to prevent unhandled promise rejection.

♻️ Suggested fix
             <Button
               onClick={() => {
-                navigator.clipboard.writeText(account.jid);
-                toast.success("Copied", {
-                  description: "JID copied to clipboard",
-                });
+                navigator.clipboard.writeText(account.jid)
+                  .then(() => {
+                    toast.success("Copied", {
+                      description: "JID copied to clipboard",
+                    });
+                  })
+                  .catch(() => {
+                    toast.error("Failed to copy", {
+                      description: "Could not copy JID to clipboard",
+                    });
+                  });
               }}
               size="sm"
               variant="ghost"
             >
               Copy
             </Button>

228-233: Consider locale-aware date formatting.

Using toLocaleDateString() without explicit locale may produce inconsistent results. Per coding guidelines, this project handles i18n via next-intl - consider using a consistent date formatting approach.

src/app/(dashboard)/app/xmpp/page.tsx (1)

30-39: Prefetching with null provides no SSR benefit.

The comment explains the rationale, but prefetching with a queryFn that returns null doesn't actually reduce loading flash - the client will still need to fetch the data. This adds complexity without benefit.

Consider either:

  1. Removing the prefetch entirely (simpler)
  2. Implementing server-side fetching if the endpoint can be called server-side
♻️ Option 1: Remove unnecessary prefetch
 export default async function XmppPage() {
   // Verify user session
   await verifySession();

-  // Create QueryClient for this request (isolated per request)
-  const queryClient = getServerQueryClient();
-
-  // Prefetch XMPP account data for SSR
-  // This reduces loading flash by populating the cache before components mount
-  await queryClient.prefetchQuery({
-    queryKey: queryKeys.xmppAccounts.current(),
-    queryFn: async () => {
-      // We'll fetch on the client side since this is a user-specific endpoint
-      // that requires authentication cookies
-      return null;
-    },
-  });

   const resolver = await getServerRouteResolver();

   return (
-    <HydrationBoundary state={dehydrate(queryClient)}>
       <div className="flex flex-1 flex-col gap-4 p-4 pt-0">
         <div className="space-y-6">
           <PageHeader pathname="/app/xmpp" resolver={resolver} />
           <XmppAccountManagement />
         </div>
       </div>
-    </HydrationBoundary>
   );
 }
src/app/api/xmpp/accounts/route.ts (2)

144-152: Consider logging rollback failures.

When the DB insert fails after Prosody account creation, the rollback error is silently swallowed. This could leave orphaned accounts in Prosody with no visibility. Consider logging the rollback error for operational awareness.

♻️ Suggested improvement
     if (!newAccount) {
       // Rollback: Try to delete Prosody account if database insert fails
       try {
         await deleteProsodyAccount(username);
-      } catch {
-        // Ignore rollback errors
+      } catch (rollbackError) {
+        // Log rollback failure for operational visibility - orphaned Prosody account may exist
+        console.error(`Failed to rollback Prosody account for username ${username}:`, rollbackError);
       }
       throw new APIError("Failed to create XMPP account record", 500);
     }

31-31: Missing input validation with Zod.

Per coding guidelines, inputs should be validated with Zod schemas. The request body is cast directly without validation, which could lead to unexpected behavior with malformed input.

♻️ Suggested fix
+import { z } from "zod";
+
+const createXmppAccountSchema = z.object({
+  username: z.string().optional(),
+});
+
 export async function POST(request: NextRequest) {
   try {
     const { userId } = await requireAuth(request);
-    const body = (await request.json()) as CreateXmppAccountRequest;
+    const parseResult = createXmppAccountSchema.safeParse(await request.json());
+    if (!parseResult.success) {
+      return Response.json(
+        { ok: false, error: "Invalid request body" },
+        { status: 400 }
+      );
+    }
+    const body = parseResult.data;
src/lib/xmpp/client.ts (1)

34-41: Missing timeout for external API calls.

The fetch to Prosody REST API has no timeout configured. Per coding guidelines, external calls should have timeouts to prevent blocking indefinitely if Prosody is unresponsive.

♻️ Suggested fix with AbortController
+const PROSODY_REQUEST_TIMEOUT_MS = 10000; // 10 seconds

 async function prosodyRequest<T>(
   endpoint: string,
   options: RequestInit = {}
 ): Promise<T> {
   const { restUrl } = xmppConfig.prosody;
   const url = `${restUrl}${endpoint}`;

+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), PROSODY_REQUEST_TIMEOUT_MS);
+
-  const response = await fetch(url, {
-    ...options,
-    headers: {
-      "Content-Type": "application/xml",
-      Authorization: createAuthHeader(),
-      ...options.headers,
-    },
-  });
+  try {
+    const response = await fetch(url, {
+      ...options,
+      signal: controller.signal,
+      headers: {
+        "Content-Type": "application/xml",
+        Authorization: createAuthHeader(),
+        ...options.headers,
+      },
+    });
+    // ... rest of the function
+  } finally {
+    clearTimeout(timeoutId);
+  }
src/app/api/xmpp/accounts/[id]/route.ts (2)

199-208: Consider early return for already-deleted accounts.

The handler allows deleting an account that's already in "deleted" status, which will redundantly call Prosody and update the database. Consider returning early if the account is already deleted.

Suggested improvement
     if (!account) {
       return Response.json(
         { ok: false, error: "XMPP account not found" },
         { status: 404 }
       )
     }
+
+    if (account.status === "deleted") {
+      return Response.json({
+        ok: true,
+        message: "XMPP account already deleted",
+      })
+    }

1-13: Missing Sentry integration for error capturing and tracing.

Per coding guidelines, API routes should use Sentry.captureException(error) in catch blocks and Sentry.startSpan() to create spans for API calls with meaningful name and op properties.

Example integration
 import type { NextRequest } from "next/server"
 import { and, eq, ne } from "drizzle-orm"
+import * as Sentry from "@sentry/nextjs"
 
 import { APIError, handleAPIError, requireAuth } from "@/lib/api/utils"

Then wrap each handler's logic:

export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  return Sentry.startSpan(
    { name: 'GET /api/xmpp/accounts/[id]', op: 'http.server' },
    async () => {
      try {
        // ... existing logic
      } catch (error) {
        Sentry.captureException(error)
        return handleAPIError(error)
      }
    }
  )
}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ffa19ae and c7b7148.

📒 Files selected for processing (18)
  • .vscode/settings.json
  • scripts/create-prosody-oauth-client.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/app/api/xmpp/accounts/route.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/api/query-keys.ts
  • src/lib/api/xmpp.ts
  • src/lib/auth/config.ts
  • src/lib/db/schema/index.ts
  • src/lib/db/schema/xmpp.ts
  • src/lib/routes/config.ts
  • src/lib/xmpp/client.ts
  • src/lib/xmpp/config.ts
  • src/lib/xmpp/types.ts
  • src/lib/xmpp/utils.ts
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,jsx,ts,tsx}: Use Sentry.captureException(error) to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
Use Sentry.startSpan() function to create spans for meaningful actions within applications like button clicks, API calls, and function calls

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

When creating custom spans for API calls with Sentry.startSpan(), ensure the name and op properties are meaningful (e.g., op: "http.client" with descriptive names like GET /api/users/${userId}), and attach attributes based on relevant request information and metrics

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,ts,jsx,tsx}: Import Sentry using import * as Sentry from "@sentry/nextjs" when using logs in NextJS projects
Reference the Sentry logger using const { logger } = Sentry when using logging functionality
Use logger.fmt as a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{css,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

Use container query support with @container, @sm:, @md: for container-based breakpoints and @max-md: for max-width queries

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{ts,tsx,js,jsx,html}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities: transform-3d, rotate-x-*, rotate-y-*, rotate-z-*, scale-z-*, translate-z-*, perspective-*, and perspective-origin-*
Use linear gradient angles with bg-linear-45 syntax and gradient interpolation like bg-linear-to-r/oklch or bg-linear-to-r/srgb
Use conic and radial gradients with bg-conic and bg-radial-[at_25%_25%] utilities
Use inset-shadow-* and inset-ring-* utilities instead of deprecated shadow opacity utilities
Use field-sizing-content utility for auto-resizing textareas
Use scheme-light and scheme-dark utilities for color-scheme property
Use font-stretch-* utilities for variable font configuration
Chain variants together for composable variants (e.g., group-has-data-potato:opacity-100)
Use starting variant for @starting-style transitions
Use not-* variant for :not() pseudo-class (e.g., not-first:mb-4)
Use inert variant for styling elements with the inert attribute
Use nth-* variants: nth-3:, nth-last-5:, nth-of-type-4:, nth-last-of-type-6: for targeting specific elements
Use in-* variant as a simpler alternative to group-* without adding group class
Use open variant to support :popover-open pseudo-class
Use ** variant for targeting all descendants
Replace deprecated bg-opacity-* utilities with color values using slash notation (e.g., bg-black/50)
Replace deprecated text-opacity-* utilities with color values using slash notation (e.g., text-black/50)
Replace deprecated border-opacity-*, divide-opacity-* and similar opacity utilities with color slash notation
Use shadow-xs instead of shadow-sm and shadow-sm instead of shadow
Use drop-shadow-xs instead of drop-shadow-sm and drop-shadow-sm instead of drop-shadow
Use blur-xs instead of blur-sm and blur-sm instead of blur
Use rounded-xs instead of rounded-sm and rounded-sm instead of rounded
Use outline-hidden instead of outline-none for...

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, prefer unknown over any
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use @/auth module for all authentication operations
Use @/db for all database operations
Use @/components/ui/* for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • scripts/create-prosody-oauth-client.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Handle internationalization via next-intl with locale files in locale/ directory

Files:

  • src/lib/xmpp/config.ts
  • src/lib/api/query-keys.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/lib/xmpp/client.ts
  • src/lib/api/xmpp.ts
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/xmpp/utils.ts
  • src/lib/routes/config.ts
  • src/lib/db/schema/xmpp.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
  • src/lib/xmpp/types.ts
  • src/lib/db/schema/index.ts
  • src/lib/auth/config.ts
src/lib/api/query-keys.ts

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

src/lib/api/query-keys.ts: Query keys must maintain consistent array order (array order matters) and use serializable values only (no functions or non-serializable objects)
All query keys must follow the factory pattern with structure: resource.all, resource.lists(), resource.list(filters?), resource.detail(id), resource.details()

Files:

  • src/lib/api/query-keys.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

When creating custom spans for component actions with Sentry.startSpan(), ensure the name and op properties are meaningful for the activities in the call, and attach attributes based on relevant information and metrics

Files:

  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/components/xmpp/xmpp-account-management.tsx
{src/app/**,src/components/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries

Files:

  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/app/api/xmpp/accounts/route.ts
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code

Files:

  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/app/api/xmpp/accounts/route.ts
🧠 Learnings (21)
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually

Applied to files:

  • src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/query-keys.ts : All query keys must follow the factory pattern with structure: resource.all, resource.lists(), resource.list(filters?), resource.detail(id), resource.details()

Applied to files:

  • src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{ts,tsx} : Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries

Applied to files:

  • src/lib/api/query-keys.ts
  • src/hooks/use-xmpp-account.ts
  • src/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/query-keys.ts : Query keys must maintain consistent array order (array order matters) and use serializable values only (no functions or non-serializable objects)

Applied to files:

  • src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation

Applied to files:

  • src/lib/api/query-keys.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `next/head` or App Router metadata API for head elements

Applied to files:

  • src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Follow Next.js App Router conventions

Applied to files:

  • src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Perform auth checks using Better Auth's auth.api.getSession() in Server Components before prefetching data, and redirect if not authenticated

Applied to files:

  • src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : In Server Components, create a QueryClient per request using getServerQueryClient(), prefetch data in parallel with Promise.all(), and dehydrate using dehydrate(queryClient)

Applied to files:

  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/hooks/use-xmpp-account.ts
  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Use HydrationBoundary component from TanStack Query to transfer server-prefetched data to client cache in Server Components

Applied to files:

  • src/app/(dashboard)/app/xmpp/page.tsx
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/*route*.{ts,tsx,js,jsx} : Clean, non-duplicated REST routes in backend code

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/lib/routes/config.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/api/**/*.{ts,tsx,js,jsx} : API, DB, auth contracts, or tokens require mandatory review

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use TanStack Query for all server state management

Applied to files:

  • src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use functional components and hooks in React

Applied to files:

  • src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/hooks/use-admin.ts : Use queryClient.setQueryData() to update specific cache entries and queryClient.invalidateQueries() to invalidate related queries in mutation onSuccess handlers

Applied to files:

  • src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to **/*.{ts,tsx} : Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)

Applied to files:

  • src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{tsx} : Use useSuspenseQueries for parallel queries to avoid waterfalls and improve performance in Server Components and Client Components

Applied to files:

  • src/hooks/use-xmpp-account.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid barrel files (index files that re-export everything)

Applied to files:

  • src/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use selective imports over barrel exports for performance

Applied to files:

  • src/lib/api/index.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use `@/auth` module for all authentication operations

Applied to files:

  • src/lib/api/index.ts
  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/server-queries.ts : Server-side query functions in src/lib/api/server-queries.ts must use direct database access via ORM instead of HTTP requests

Applied to files:

  • src/lib/auth/config.ts
🧬 Code graph analysis (9)
src/app/(dashboard)/app/xmpp/page.tsx (3)
src/lib/api/hydration.ts (1)
  • getServerQueryClient (21-23)
src/lib/api/query-keys.ts (1)
  • queryKeys (13-77)
src/components/xmpp/xmpp-account-management.tsx (1)
  • XmppAccountManagement (37-292)
src/lib/xmpp/client.ts (3)
src/lib/xmpp/config.ts (1)
  • xmppConfig (9-32)
src/lib/xmpp/types.ts (2)
  • ProsodyRestError (44-47)
  • ProsodyRestAccountResponse (52-56)
src/lib/xmpp/utils.ts (1)
  • formatJid (75-85)
src/lib/api/xmpp.ts (1)
src/lib/xmpp/types.ts (3)
  • XmppAccount (14-23)
  • CreateXmppAccountRequest (28-30)
  • UpdateXmppAccountRequest (35-39)
src/lib/xmpp/utils.ts (1)
src/lib/xmpp/types.ts (1)
  • XmppAccountStatus (9-9)
src/components/xmpp/xmpp-account-management.tsx (2)
src/hooks/use-xmpp-account.ts (3)
  • useXmppAccount (26-32)
  • useCreateXmppAccount (49-61)
  • useDeleteXmppAccount (94-106)
src/lib/xmpp/types.ts (1)
  • XmppAccountStatus (9-9)
src/app/api/xmpp/accounts/route.ts (8)
src/app/api/xmpp/accounts/[id]/route.ts (2)
  • dynamic (13-13)
  • GET (19-63)
src/lib/api/utils.ts (3)
  • requireAuth (69-82)
  • APIError (87-97)
  • handleAPIError (103-125)
src/lib/xmpp/types.ts (1)
  • CreateXmppAccountRequest (28-30)
src/lib/db/schema/xmpp.ts (1)
  • xmppAccount (19-43)
src/lib/xmpp/utils.ts (3)
  • isValidXmppUsername (28-41)
  • generateUsernameFromEmail (49-67)
  • formatJid (75-85)
src/lib/xmpp/client.ts (3)
  • checkProsodyAccountExists (169-187)
  • createProsodyAccount (72-114)
  • deleteProsodyAccount (122-161)
src/lib/xmpp/config.ts (1)
  • xmppConfig (9-32)
src/lib/db/schema/auth.ts (1)
  • account (48-70)
scripts/create-prosody-oauth-client.ts (1)
src/lib/db/schema/oauth.ts (1)
  • oauthClient (5-36)
src/hooks/use-xmpp-account.ts (3)
src/lib/api/query-keys.ts (1)
  • queryKeys (13-77)
src/lib/api/xmpp.ts (5)
  • fetchXmppAccount (25-42)
  • fetchXmppAccountById (47-64)
  • createXmppAccount (69-92)
  • updateXmppAccount (97-121)
  • deleteXmppAccount (126-139)
src/lib/xmpp/types.ts (2)
  • CreateXmppAccountRequest (28-30)
  • UpdateXmppAccountRequest (35-39)
src/lib/auth/config.ts (1)
src/lib/db/schema/xmpp.ts (1)
  • xmppAccount (19-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Seer Code Review
🔇 Additional comments (35)
.vscode/settings.json (1)

42-44: LGTM, but appears unrelated to PR scope.

The formatter change from yzhang.markdown-all-in-one to DavidAnson.vscode-markdownlint is valid. However, this settings change seems unrelated to the XMPP integration feature. Consider whether this should be in a separate commit or PR for cleaner history.

scripts/create-prosody-oauth-client.ts (5)

1-7: LGTM!

Imports are well-organized and use proper TypeScript path aliases. Using node:crypto for secure random generation is the correct choice.


9-26: LGTM!

Clear and comprehensive documentation explaining the script's purpose, usage, and configuration options.


49-54: LGTM!

Secure random generation using randomBytes with base64url encoding. The entropy is sufficient for both client ID and secret.


88-99: LGTM with optional enhancement.

Error handling is appropriate for a CLI script. The pattern of logging details and rethrowing is correct. If Sentry is initialized in CLI contexts, you could optionally add Sentry.captureException(error) per coding guidelines, but this may not be necessary for a setup script.


102-110: The require.main === module pattern is compatible with this project's setup. The project uses tsx for running TypeScript scripts (as seen in db:seed and create-admin scripts), which transpiles to CommonJS at runtime. Since there is no "type": "module" in package.json, this pattern works correctly.

However, the script should use proper error handling with Sentry instead of console.error for production logging, per the project's guidelines.

Likely an incorrect or invalid review comment.

src/lib/xmpp/types.ts (1)

1-56: LGTM! Well-structured type definitions.

The types are clearly documented and follow TypeScript best practices. The use of Record<string, unknown> for metadata and explicit status types provides good type safety.

src/lib/xmpp/utils.ts (3)

92-108: Consider: JIDs with resource parts will fail parsing.

XMPP JIDs can include a resource component (user@domain/resource). The current implementation rejects these as invalid. If this is intentional for bare JID handling, consider documenting this limitation or adding a note in the JSDoc.


28-41: LGTM!

The validation logic correctly checks for null/undefined, length constraints, and regex pattern matching.


115-121: LGTM!

The type guard correctly narrows the string type to XmppAccountStatus. The use of as const ensures proper inference.

src/lib/api/index.ts (1)

14-14: LGTM!

The new export follows the established pattern in this barrel file. The existing biome-ignore directive already acknowledges this pattern.

src/lib/routes/config.ts (2)

150-161: Verify: Should the XMPP route have permission restrictions?

The admin route at line 172 has permissions: ["canViewAdmin"] for role-based access, but the new xmpp route has no permissions defined. This means any authenticated user can access it.

If XMPP account management should be restricted to certain roles, consider adding a permissions array similar to the admin route.


6-6: LGTM!

The MessageSquare icon import follows the existing pattern and is appropriate for an XMPP/messaging feature.

src/lib/db/schema/index.ts (2)

16-16: LGTM!

The import follows the established pattern for schema module organization.


30-30: LGTM!

The xmppAccount table is properly added to the exported schema object, making it available for database operations throughout the application.

src/lib/api/query-keys.ts (1)

69-76: LGTM!

The new xmppAccounts query key factory follows the established pattern and learnings for type-safe query key management. The simpler structure (without lists/list methods) is appropriate given the 1:1 relationship between users and XMPP accounts.

src/lib/db/schema/xmpp.ts (1)

19-36: LGTM!

The table schema is well-designed:

  • Cascade delete on userId ensures proper cleanup when users are deleted
  • Unique constraint on userId enforces the 1:1 relationship
  • The $onUpdate pattern for updatedAt is correct for drizzle-orm
src/lib/auth/config.ts (1)

22-26: LGTM!

The imports are correctly structured and follow the project's conventions for importing from @/lib/db/schema/*.

src/hooks/use-xmpp-account.ts (2)

26-32: LGTM!

The useXmppAccount hook follows TanStack Query best practices with proper use of the centralized query key factory and an appropriate stale time for the data characteristics. Based on learnings, this correctly uses queryKeys.xmppAccounts.current().


37-44: LGTM!

The useXmppAccountById hook correctly uses the enabled option to prevent unnecessary queries when id is falsy, and includes the id in the query key for proper cache isolation per TanStack Query guidelines.

src/lib/xmpp/config.ts (1)

9-39: LGTM on config structure and password validation.

The configuration object is well-structured with proper as const assertion for type safety. The password validation correctly ensures sensitive credentials are provided via environment variables rather than defaults.

src/components/xmpp/xmpp-account-management.tsx (3)

99-120: LGTM!

Loading and error states are well implemented with appropriate visual feedback using icons and consistent Card layouts.


122-168: LGTM!

The create form properly handles the pending state, disables input during submission, and provides clear user guidance. The empty string to undefined conversion for optional username is correctly handled.


253-288: LGTM!

The delete confirmation flow using AlertDialog is well implemented with clear warning messaging and proper destructive button styling.

src/app/(dashboard)/app/xmpp/page.tsx (2)

12-16: LGTM!

Metadata generation follows the established pattern using route config and resolver.


23-25: LGTM!

Session verification is correctly performed before rendering, per learnings from coding guidelines.

src/app/api/xmpp/accounts/route.ts (2)

28-43: LGTM on auth and existing account check.

The authentication check and one-account-per-user constraint are properly implemented.


176-206: LGTM!

The GET handler is well-implemented with proper authentication and clean response structure.

src/lib/xmpp/client.ts (3)

1-5: LGTM!

Good use of "server-only" directive to ensure this module is only imported in server contexts, protecting credentials from client bundles.


122-161: LGTM!

The deleteProsodyAccount function correctly handles the idempotent case where the account doesn't exist (404), returning success instead of throwing.


169-187: LGTM!

The checkProsodyAccountExists function properly distinguishes between "not found" (return false) and other errors (re-throw), which is the correct pattern for existence checks.

src/lib/api/xmpp.ts (3)

25-42: LGTM!

The fetchXmppAccount function correctly handles the 404 case by returning null instead of throwing, which is appropriate for "account not found" semantics.


69-92: LGTM!

The createXmppAccount function has proper error handling and validates that an account is returned in the response.


126-139: LGTM!

The deleteXmppAccount function is clean and follows the established error handling pattern.

src/app/api/xmpp/accounts/[id]/route.ts (1)

19-63: LGTM! Consider adding Sentry span for observability.

The GET handler correctly implements authentication, authorization (owner or admin), and error handling. Per coding guidelines, consider wrapping the handler logic in Sentry.startSpan() with op: "http.server" for tracing.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 90 to 109
// Check username uniqueness in database
const existingUsername = await db.query.xmppAccount.findFirst({
where: eq(xmppAccount.username, username),
});

if (existingUsername) {
return Response.json(
{ ok: false, error: "Username already taken" },
{ status: 409 }
);
}

// Check username uniqueness in Prosody
const prosodyAccountExists = await checkProsodyAccountExists(username);
if (prosodyAccountExists) {
return Response.json(
{ ok: false, error: "Username already taken in XMPP server" },
{ status: 409 }
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

TOCTOU race condition in uniqueness checks.

There's a time-of-check-to-time-of-use gap between checking username uniqueness (lines 91-109) and creating the account (lines 112-129). Two concurrent requests with the same username could both pass the checks before either creates the account.

The rollback at line 147 partially mitigates this, but consider:

  1. Using a database transaction with unique constraint as the primary guard
  2. Relying on Prosody's conflict detection as the authoritative check

The current error handling at line 117 (error.message.includes("already exists")) does handle this case, but the user experience could be improved by having a clearer error path.

🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/route.ts` around lines 90 - 109, The uniqueness
checks using db.query.xmppAccount.findFirst and checkProsodyAccountExists have a
TOCTOU race: two concurrent requests can pass both checks before creation; fix
by performing the account creation inside a database transaction (use the same
transaction for any DB inserts/queries) relying on a UNIQUE constraint on
xmppAccount.username as the primary guard, and handle the constraint violation
error from the transactional insert to return a 409 response; also treat Prosody
creation (createProsodyAccount) as authoritative where possible—attempt Prosody
create first or roll back DB insert on Prosody conflict—and consolidate error
handling currently at the rollback path to detect DB unique-violation and
Prosody "already exists" errors and return a consistent "Username already taken"
409 response.

@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #4

coderabbitai bot added a commit that referenced this pull request Jan 15, 2026
Docstrings generation was requested by @kzndotsh.

* #3 (comment)

The following files were modified:

* `scripts/create-prosody-oauth-client.ts`
* `src/app/(dashboard)/app/xmpp/page.tsx`
* `src/app/api/xmpp/accounts/[id]/route.ts`
* `src/app/api/xmpp/accounts/route.ts`
* `src/components/xmpp/xmpp-account-management.tsx`
* `src/hooks/use-xmpp-account.ts`
* `src/lib/api/xmpp.ts`
* `src/lib/xmpp/client.ts`
* `src/lib/xmpp/utils.ts`
- Updated the XMPP account creation and update API routes to include Zod validation for request bodies, ensuring better error handling and data integrity.
- Improved username handling by implementing a dedicated function to determine username availability and format.
- Enhanced error handling for Prosody account operations, introducing a custom error class for better clarity.
- Updated the clipboard copy functionality in the XMPP account management component to support older browsers.
- Refactored code for improved readability and maintainability across various files related to XMPP account management.
Comment on lines 33 to 36
const { id } = params;

const [account] = await db
.select()

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@scripts/create-prosody-oauth-client.ts`:
- Line 68: The diff enables the deprecated "password" grant in grantTypes for
create-prosody-oauth-client; ensure compensating controls by (1) enforcing rate
limiting on the token endpoint that issues tokens for the password grant
(implement or configure per-client and per-IP throttling and brute-force
protections where tokens are minted), (2) verifying PROSODY_OAUTH_CLIENT_ID and
PROSODY_OAUTH_CLIENT_SECRET are loaded only from secure runtime secrets
(environment variables or secret manager) and not checked into VCS, and (3) add
a short-term migration plan note in the code/comments to move clients from the
password grant to authorization_code + PKCE once Prosody XMPP support allows
(update grantTypes usage where grantTypes: ["authorization_code","password"] is
set and document steps to rotate secrets and adopt PKCE).

In `@src/lib/xmpp/client.ts`:
- Around line 66-75: The error handler inside the Prosody REST response check
currently assumes JSON; update the block that throws on !response.ok (the code
that builds errorMessage) to inspect response.headers.get('content-type') and
branch: if JSON (includes 'application/json' or '+json') parse response.json()
and use errorData.error || errorData.message; if XML (includes 'application/xml'
or 'text/xml' or '+xml') parse response.text(), run a minimal XML extraction to
pull a <message> or <error> node or otherwise use the raw text; fallback to
response.statusText if parsing fails; ensure all parsing is wrapped in try/catch
so the thrown Error contains the most specific errorMessage possible.
♻️ Duplicate comments (1)
src/app/api/xmpp/accounts/route.ts (1)

71-104: Race window between availability checks and creation.
Concurrent requests can still pass the checks and create duplicates unless a DB-level unique constraint + transactional insert is the primary guard. Consider consolidating checks with the insert (and mapping unique-violation / Prosody conflict to 409).

Also applies to: 169-200

🧹 Nitpick comments (8)
src/components/xmpp/xmpp-account-management.tsx (3)

43-60: Consider adding Sentry error capture for failed mutations.

Per coding guidelines, exceptions should be captured with Sentry.captureException(error) in catch blocks. The handleCreate function catches errors but only displays a toast without reporting to Sentry for monitoring.

♻️ Suggested improvement
+import * as Sentry from '@sentry/nextjs'

 const handleCreate = async () => {
   try {
     await createMutation.mutateAsync({
       username: username.trim() || undefined,
     });
     toast.success("XMPP account created", {
       description: "Your XMPP account has been created successfully.",
     });
     setUsername("");
   } catch (error) {
+    Sentry.captureException(error, {
+      tags: { action: 'xmpp_account_create' },
+    });
     toast.error("Failed to create account", {
       description:
         error instanceof Error
           ? error.message
           : "An error occurred while creating your XMPP account.",
     });
   }
 };

62-80: Add Sentry error capture for delete failures.

Similar to handleCreate, the handleDelete function should report errors to Sentry for observability per coding guidelines.

♻️ Suggested improvement
 const handleDelete = async () => {
   if (!account) {
     return;
   }

   try {
     await deleteMutation.mutateAsync(account.id);
     toast.success("XMPP account deleted", {
       description: "Your XMPP account has been deleted successfully.",
     });
   } catch (error) {
+    Sentry.captureException(error, {
+      tags: { action: 'xmpp_account_delete' },
+    });
     toast.error("Failed to delete account", {
       description:
         error instanceof Error
           ? error.message
           : "An error occurred while deleting your XMPP account.",
     });
   }
 };

249-252: Consider using locale-aware date formatting.

toLocaleDateString() without options may produce inconsistent results across locales. Since internationalization is handled via next-intl per coding guidelines, consider using a formatter from that library or explicitly specifying locale options.

♻️ Example with explicit options
-            {new Date(account.createdAt).toLocaleDateString()}
+            {new Date(account.createdAt).toLocaleDateString(undefined, {
+              year: 'numeric',
+              month: 'long',
+              day: 'numeric',
+            })}
scripts/create-prosody-oauth-client.ts (1)

106-112: Consider using ES module syntax for consistency.

The script uses require.main === module which is CommonJS style. If the project uses ES modules, consider using import.meta.url based detection or a separate entry point.

src/lib/xmpp/utils.ts (1)

92-108: Consider handling JIDs with resource parts.

The parseJid function assumes JIDs are in user@domain format, but XMPP JIDs can include a resource: user@domain/resource. If such JIDs are passed, this function will incorrectly reject them or misparse them.

♻️ Suggested fix to handle resource parts
 export function parseJid(jid: string): { username: string; domain: string } {
   if (!jid || typeof jid !== "string") {
     throw new Error("Invalid JID");
   }

-  const parts = jid.split("@");
-  if (parts.length !== 2) {
+  // Handle optional resource part: user@domain/resource
+  const atIndex = jid.indexOf("@");
+  if (atIndex === -1) {
     throw new Error(`Invalid JID format: ${jid}`);
   }

-  const [username, domain] = parts;
-  if (!(username && domain)) {
+  const username = jid.slice(0, atIndex);
+  let domain = jid.slice(atIndex + 1);
+  
+  // Strip resource part if present
+  const slashIndex = domain.indexOf("/");
+  if (slashIndex !== -1) {
+    domain = domain.slice(0, slashIndex);
+  }
+
+  if (!username || !domain) {
     throw new Error(`Invalid JID format: ${jid}`);
   }

   return { username, domain };
 }
src/lib/xmpp/client.ts (3)

106-111: Consider escaping the domain for consistency.

While the username is now properly escaped, xmppConfig.domain is also interpolated into the XML stanza. For defense-in-depth consistency, consider escaping it as well, even though it comes from configuration.

♻️ Suggested improvement
   const stanza = `<?xml version="1.0"?>
-<iq type="set" to="${xmppConfig.domain}" id="create_${Date.now()}">
+<iq type="set" to="${escapeXml(xmppConfig.domain)}" id="create_${Date.now()}">
   <query xmlns="jabber:iq:register">
     <username>${escapeXml(username)}</username>
   </query>
 </iq>`;

57-64: Add request timeout to prevent indefinite hangs.

The fetch call has no timeout configured. If the Prosody server becomes unresponsive, this could block indefinitely. Consider using AbortController with a timeout.

♻️ Suggested improvement with timeout
+const REQUEST_TIMEOUT_MS = 30000; // 30 seconds

 async function prosodyRequest<T>(
   endpoint: string,
   options: RequestInit = {}
 ): Promise<T> {
   const { restUrl } = xmppConfig.prosody;
   const url = `${restUrl}${endpoint}`;

+  const controller = new AbortController();
+  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
+
-  const response = await fetch(url, {
-    ...options,
-    headers: {
-      "Content-Type": "application/xml",
-      Authorization: createAuthHeader(),
-      ...options.headers,
-    },
-  });
+  try {
+    const response = await fetch(url, {
+      ...options,
+      signal: controller.signal,
+      headers: {
+        "Content-Type": "application/xml",
+        Authorization: createAuthHeader(),
+        ...options.headers,
+      },
+    });
+    // ... rest of the function
+  } finally {
+    clearTimeout(timeoutId);
+  }

123-136: Consider adding Sentry error capture for Prosody operations.

Per coding guidelines, exceptions should be captured with Sentry.captureException(error). The Prosody API client functions handle errors but don't report them to Sentry, which would help with monitoring and debugging production issues.

♻️ Example for createProsodyAccount
+import * as Sentry from '@sentry/nextjs'

   } catch (error) {
+    Sentry.captureException(error, {
+      tags: { operation: 'create_prosody_account' },
+      extra: { username },
+    });
     if (error instanceof Error) {
       // Check if account already exists
       if (
         error.message.includes("exists") ||
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c7b7148 and 36f749c.

📒 Files selected for processing (10)
  • .cursor/hooks.json
  • scripts/create-prosody-oauth-client.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/app/api/xmpp/accounts/[id]/route.ts
  • src/app/api/xmpp/accounts/route.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/lib/auth/config.ts
  • src/lib/xmpp/client.ts
  • src/lib/xmpp/config.ts
  • src/lib/xmpp/utils.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/xmpp/config.ts
  • src/app/(dashboard)/app/xmpp/page.tsx
  • src/app/api/xmpp/accounts/[id]/route.ts
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,jsx,ts,tsx}: Use Sentry.captureException(error) to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
Use Sentry.startSpan() function to create spans for meaningful actions within applications like button clicks, API calls, and function calls

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

When creating custom spans for API calls with Sentry.startSpan(), ensure the name and op properties are meaningful (e.g., op: "http.client" with descriptive names like GET /api/users/${userId}), and attach attributes based on relevant request information and metrics

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,ts,jsx,tsx}: Import Sentry using import * as Sentry from "@sentry/nextjs" when using logs in NextJS projects
Reference the Sentry logger using const { logger } = Sentry when using logging functionality
Use logger.fmt as a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{css,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

Use container query support with @container, @sm:, @md: for container-based breakpoints and @max-md: for max-width queries

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{ts,tsx,js,jsx,html}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities: transform-3d, rotate-x-*, rotate-y-*, rotate-z-*, scale-z-*, translate-z-*, perspective-*, and perspective-origin-*
Use linear gradient angles with bg-linear-45 syntax and gradient interpolation like bg-linear-to-r/oklch or bg-linear-to-r/srgb
Use conic and radial gradients with bg-conic and bg-radial-[at_25%_25%] utilities
Use inset-shadow-* and inset-ring-* utilities instead of deprecated shadow opacity utilities
Use field-sizing-content utility for auto-resizing textareas
Use scheme-light and scheme-dark utilities for color-scheme property
Use font-stretch-* utilities for variable font configuration
Chain variants together for composable variants (e.g., group-has-data-potato:opacity-100)
Use starting variant for @starting-style transitions
Use not-* variant for :not() pseudo-class (e.g., not-first:mb-4)
Use inert variant for styling elements with the inert attribute
Use nth-* variants: nth-3:, nth-last-5:, nth-of-type-4:, nth-last-of-type-6: for targeting specific elements
Use in-* variant as a simpler alternative to group-* without adding group class
Use open variant to support :popover-open pseudo-class
Use ** variant for targeting all descendants
Replace deprecated bg-opacity-* utilities with color values using slash notation (e.g., bg-black/50)
Replace deprecated text-opacity-* utilities with color values using slash notation (e.g., text-black/50)
Replace deprecated border-opacity-*, divide-opacity-* and similar opacity utilities with color slash notation
Use shadow-xs instead of shadow-sm and shadow-sm instead of shadow
Use drop-shadow-xs instead of drop-shadow-sm and drop-shadow-sm instead of drop-shadow
Use blur-xs instead of blur-sm and blur-sm instead of blur
Use rounded-xs instead of rounded-sm and rounded-sm instead of rounded
Use outline-hidden instead of outline-none for...

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, prefer unknown over any
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use @/auth module for all authentication operations
Use @/db for all database operations
Use @/components/ui/* for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
{src/app/**,src/components/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/components/xmpp/xmpp-account-management.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • scripts/create-prosody-oauth-client.ts
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code

Files:

  • src/app/api/xmpp/accounts/route.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Handle internationalization via next-intl with locale files in locale/ directory

Files:

  • src/app/api/xmpp/accounts/route.ts
  • src/lib/auth/config.ts
  • src/components/xmpp/xmpp-account-management.tsx
  • src/lib/xmpp/utils.ts
  • src/lib/xmpp/client.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

When creating custom spans for component actions with Sentry.startSpan(), ensure the name and op properties are meaningful for the activities in the call, and attach attributes based on relevant information and metrics

Files:

  • src/components/xmpp/xmpp-account-management.tsx
🧠 Learnings (8)
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Use `@/auth` module for all authentication operations

Applied to files:

  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:15:18.973Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/sentry.mdc:0-0
Timestamp: 2026-01-15T06:15:18.973Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use `Sentry.captureException(error)` to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected

Applied to files:

  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/lib/api/server-queries.ts : Server-side query functions in src/lib/api/server-queries.ts must use direct database access via ORM instead of HTTP requests

Applied to files:

  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : In Server Components, create a QueryClient per request using getServerQueryClient(), prefetch data in parallel with Promise.all(), and dehydrate using dehydrate(queryClient)

Applied to files:

  • src/lib/auth/config.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Never expose API keys in client code

Applied to files:

  • scripts/create-prosody-oauth-client.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Specify all dependencies in hook dependency arrays correctly

Applied to files:

  • .cursor/hooks.json
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Validate and sanitize user input

Applied to files:

  • src/lib/xmpp/utils.ts
🧬 Code graph analysis (5)
src/lib/auth/config.ts (1)
src/lib/db/schema/xmpp.ts (1)
  • xmppAccount (19-43)
src/components/xmpp/xmpp-account-management.tsx (2)
src/hooks/use-xmpp-account.ts (3)
  • useXmppAccount (26-32)
  • useCreateXmppAccount (49-61)
  • useDeleteXmppAccount (94-106)
src/lib/xmpp/types.ts (1)
  • XmppAccountStatus (9-9)
scripts/create-prosody-oauth-client.ts (1)
src/lib/db/schema/oauth.ts (1)
  • oauthClient (5-36)
src/lib/xmpp/utils.ts (1)
src/lib/xmpp/types.ts (1)
  • XmppAccountStatus (9-9)
src/lib/xmpp/client.ts (3)
src/lib/xmpp/config.ts (1)
  • xmppConfig (9-32)
src/lib/xmpp/types.ts (2)
  • ProsodyRestError (44-47)
  • ProsodyRestAccountResponse (52-56)
src/lib/xmpp/utils.ts (1)
  • formatJid (75-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Seer Code Review
  • GitHub Check: Sourcery review
🔇 Additional comments (12)
.cursor/hooks.json (1)

6-6: LGTM!

Adding type-check to the afterFileEdit hook is a sensible enhancement given the TypeScript-heavy additions in this PR. The sequential && execution ensures type checking only runs when the initial checks pass, avoiding redundant work on failing builds.

src/components/xmpp/xmpp-account-management.tsx (1)

191-216: LGTM! Clipboard fallback is well-implemented.

The clipboard copy functionality now properly handles both modern and legacy browsers with appropriate error handling and user feedback. The past review concern about navigator.clipboard failures has been addressed with a try-catch wrapper and a fallback using document.execCommand.

src/lib/auth/config.ts (2)

281-316: LGTM! Error handling properly implemented.

The customUserInfoClaims function now includes proper try-catch with Sentry error capture, addressing the previous review concern. The graceful degradation (continuing without XMPP claim on error) is appropriate for OAuth flows.


252-252: LGTM! XMPP scope added correctly.

The "xmpp" scope is properly added to the supported scopes list, enabling XMPP-related claims in OAuth flows.

scripts/create-prosody-oauth-client.ts (1)

39-50: LGTM! Security concern addressed.

The script now properly masks the client secret for existing clients with "(set - not displayed for security)", addressing the previous review concern about exposing secrets in logs.

src/lib/xmpp/utils.ts (2)

17-21: LGTM! Leading character sanitization fixed.

The XMPP_USERNAME_START_REGEX now correctly uses /^[^a-zA-Z0-9]+/ with the + quantifier to remove ALL leading non-alphanumeric characters in one replacement, addressing the previous review concern.


28-41: LGTM! Username validation is thorough.

The isValidXmppUsername function properly validates input type, length bounds, and format using the regex. The defensive checks for falsy values and type are good practice.

src/lib/xmpp/client.ts (1)

21-28: LGTM! XML escaping implemented.

The escapeXml helper function properly escapes all XML special characters (&, <, >, ", '), addressing the previous review concern about potential XML injection vulnerabilities.

src/app/api/xmpp/accounts/route.ts (4)

1-27: Solid schema + dynamic route setup.
Clear and minimal request validation scope for the create endpoint.


32-66: Username derivation/validation flow looks good.
Clean error handling and a predictable fallback path.


238-262: GET handler response mapping is clean and complete.
Includes all relevant fields with a clear 404 path.


225-226: handleAPIError already captures exceptions to Sentry—no additional capture needed.

The handleAPIError function already calls captureError() with proper context tags, which internally calls Sentry.captureException() from @sentry/nextjs. Adding explicit Sentry.captureException(error) calls before handleAPIError in the catch blocks would be redundant and violate the guideline against catching errors just to rethrow them.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

clientSecret,
name: clientName,
redirectUris: [], // Not needed for server-to-server auth
grantTypes: ["authorization_code", "password"], // Password grant for legacy clients
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

OAuth 2.1 password grant type deprecation status

💡 Result:

Short answer: The Resource Owner Password Credentials (password/ROPC) grant is deprecated and omitted from OAuth 2.1; the OAuth community’s Security Best Current Practice also disallows its use. Use Authorization Code + PKCE for user sign-in (or Client Credentials for machine-to-machine). (As of Jan 16, 2026.) Sources: [IETF OAuth 2.1 draft][1], oauth.net guidance on the password grant and OAuth 2.1 [2][3].

Sources

  1. IETF draft-ietf-oauth-v2-1 (OAuth 2.1) — ROPC omitted. [turn0search0]
  2. OAuth.net — “OAuth 2.0 Password Grant” (disallowed by Security BCP). [turn0search2]
  3. OAuth.net — OAuth 2.1 overview (password grant omitted; use auth code + PKCE). [turn0search1]

🏁 Script executed:

cd /repo && find . -name "create-prosody-oauth-client.ts" -type f

Repository: allthingslinux/portal

Length of output: 120


🏁 Script executed:

git ls-files | grep -E "(create-prosody|oauth)" | head -20

Repository: allthingslinux/portal

Length of output: 358


🏁 Script executed:

cat -n scripts/create-prosody-oauth-client.ts

Repository: allthingslinux/portal

Length of output: 4827


Verify rate limiting and secure storage are implemented for the password grant flow.

The password grant type is deprecated in OAuth 2.1 and disallowed by Security BCP. While this is documented as intentional for legacy XMPP client support, ensure compensating controls are in place:

  1. Rate limiting is enabled on the token endpoint to prevent credential abuse
  2. Client credentials (PROSODY_OAUTH_CLIENT_ID and PROSODY_OAUTH_CLIENT_SECRET) are stored securely in environment variables, not committed to version control
  3. Plan migration path to authorization_code + PKCE flow once Prosody XMPP client support allows
🤖 Prompt for AI Agents
In `@scripts/create-prosody-oauth-client.ts` at line 68, The diff enables the
deprecated "password" grant in grantTypes for create-prosody-oauth-client;
ensure compensating controls by (1) enforcing rate limiting on the token
endpoint that issues tokens for the password grant (implement or configure
per-client and per-IP throttling and brute-force protections where tokens are
minted), (2) verifying PROSODY_OAUTH_CLIENT_ID and PROSODY_OAUTH_CLIENT_SECRET
are loaded only from secure runtime secrets (environment variables or secret
manager) and not checked into VCS, and (3) add a short-term migration plan note
in the code/comments to move clients from the password grant to
authorization_code + PKCE once Prosody XMPP support allows (update grantTypes
usage where grantTypes: ["authorization_code","password"] is set and document
steps to rotate secrets and adopt PKCE).

Comment on lines +66 to +75
if (!response.ok) {
let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
try {
const errorData = (await response.json()) as ProsodyRestError;
errorMessage = errorData.error || errorData.message || errorMessage;
} catch {
// If response is not JSON, use status text
}
throw new Error(errorMessage);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Content-Type mismatch in error handling.

The request sends Content-Type: application/xml but the error handler attempts to parse the response as JSON (line 69). If Prosody returns XML error responses, this will silently fail and use the generic status text.

🔧 Suggested fix to handle both XML and JSON errors
   if (!response.ok) {
     let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
+    const contentType = response.headers.get("content-type") || "";
     try {
-      const errorData = (await response.json()) as ProsodyRestError;
-      errorMessage = errorData.error || errorData.message || errorMessage;
+      if (contentType.includes("application/json")) {
+        const errorData = (await response.json()) as ProsodyRestError;
+        errorMessage = errorData.error || errorData.message || errorMessage;
+      } else {
+        const text = await response.text();
+        if (text) {
+          errorMessage = `${errorMessage}: ${text.slice(0, 200)}`;
+        }
+      }
     } catch {
       // If response is not JSON, use status text
     }
     throw new Error(errorMessage);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!response.ok) {
let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
try {
const errorData = (await response.json()) as ProsodyRestError;
errorMessage = errorData.error || errorData.message || errorMessage;
} catch {
// If response is not JSON, use status text
}
throw new Error(errorMessage);
}
if (!response.ok) {
let errorMessage = `Prosody REST API error: ${response.status} ${response.statusText}`;
const contentType = response.headers.get("content-type") || "";
try {
if (contentType.includes("application/json")) {
const errorData = (await response.json()) as ProsodyRestError;
errorMessage = errorData.error || errorData.message || errorMessage;
} else {
const text = await response.text();
if (text) {
errorMessage = `${errorMessage}: ${text.slice(0, 200)}`;
}
}
} catch {
// If response is not JSON, use status text
}
throw new Error(errorMessage);
}
🤖 Prompt for AI Agents
In `@src/lib/xmpp/client.ts` around lines 66 - 75, The error handler inside the
Prosody REST response check currently assumes JSON; update the block that throws
on !response.ok (the code that builds errorMessage) to inspect
response.headers.get('content-type') and branch: if JSON (includes
'application/json' or '+json') parse response.json() and use errorData.error ||
errorData.message; if XML (includes 'application/xml' or 'text/xml' or '+xml')
parse response.text(), run a minimal XML extraction to pull a <message> or
<error> node or otherwise use the raw text; fallback to response.statusText if
parsing fails; ensure all parsing is wrapped in try/catch so the thrown Error
contains the most specific errorMessage possible.

- Changed the parameter extraction method in the GET, PATCH, and DELETE API routes for XMPP accounts to use RouteContext for improved type safety and consistency.
- This refactor enhances code readability and aligns with the updated routing structure.
Comment on lines +202 to +209
if (!newAccount) {
// Rollback: Try to delete Prosody account if database insert fails
try {
await deleteProsodyAccount(username);
} catch {
// Ignore rollback errors
}
throw new APIError("Failed to create XMPP account record", 500);

This comment was marked as outdated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts:
- Around line 27-30: The GET handler uses an undeclared type
RouteContext<"/api/xmpp/accounts/[id]"> which causes a TS error; update the
function signature in src/app/api/xmpp/accounts/[id]/route.ts by either
importing RouteContext from Next.js (if your Next version exports it) or replace
the type with the standard Next.js route handler shape (e.g., accept NextRequest
and a params object with id: string or Promise<{ params: { id: string } }>),
updating the GET declaration accordingly so the compiler recognizes the context
parameter.
- Around line 14-19: Add a Zod schema for the route params (e.g., paramsSchema =
z.object({ id: z.string().nonempty() })) and use paramsSchema.parse(ctx.params)
at the start of each handler (GET/PATCH/DELETE) in route.ts before any DB
access; replace direct uses of ctx.params.id with the validated value
(parsed.id) and ensure handlers return a 400/422 on parse errors per existing
error-handling conventions. This touches the same file alongside
updateXmppAccountSchema and ensures ctx.params.id is validated uniformly in each
handler.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36f749c and 65e51a6.

📒 Files selected for processing (1)
  • src/app/api/xmpp/accounts/[id]/route.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,jsx,ts,tsx}: Use Sentry.captureException(error) to capture exceptions and log errors in Sentry, particularly in try-catch blocks or areas where exceptions are expected
Use Sentry.startSpan() function to create spans for meaningful actions within applications like button clicks, API calls, and function calls

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

When creating custom spans for API calls with Sentry.startSpan(), ensure the name and op properties are meaningful (e.g., op: "http.client" with descriptive names like GET /api/users/${userId}), and attach attributes based on relevant request information and metrics

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/sentry.mdc)

**/*.{js,ts,jsx,tsx}: Import Sentry using import * as Sentry from "@sentry/nextjs" when using logs in NextJS projects
Reference the Sentry logger using const { logger } = Sentry when using logging functionality
Use logger.fmt as a template literal function to bring variables into structured logs, providing better log formatting and variable interpolation

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{css,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

Use container query support with @container, @sm:, @md: for container-based breakpoints and @max-md: for max-width queries

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx,js,jsx,html}

📄 CodeRabbit inference engine (.cursor/rules/tailwind-v4.mdc)

**/*.{ts,tsx,js,jsx,html}: Use 3D transform utilities: transform-3d, rotate-x-*, rotate-y-*, rotate-z-*, scale-z-*, translate-z-*, perspective-*, and perspective-origin-*
Use linear gradient angles with bg-linear-45 syntax and gradient interpolation like bg-linear-to-r/oklch or bg-linear-to-r/srgb
Use conic and radial gradients with bg-conic and bg-radial-[at_25%_25%] utilities
Use inset-shadow-* and inset-ring-* utilities instead of deprecated shadow opacity utilities
Use field-sizing-content utility for auto-resizing textareas
Use scheme-light and scheme-dark utilities for color-scheme property
Use font-stretch-* utilities for variable font configuration
Chain variants together for composable variants (e.g., group-has-data-potato:opacity-100)
Use starting variant for @starting-style transitions
Use not-* variant for :not() pseudo-class (e.g., not-first:mb-4)
Use inert variant for styling elements with the inert attribute
Use nth-* variants: nth-3:, nth-last-5:, nth-of-type-4:, nth-last-of-type-6: for targeting specific elements
Use in-* variant as a simpler alternative to group-* without adding group class
Use open variant to support :popover-open pseudo-class
Use ** variant for targeting all descendants
Replace deprecated bg-opacity-* utilities with color values using slash notation (e.g., bg-black/50)
Replace deprecated text-opacity-* utilities with color values using slash notation (e.g., text-black/50)
Replace deprecated border-opacity-*, divide-opacity-* and similar opacity utilities with color slash notation
Use shadow-xs instead of shadow-sm and shadow-sm instead of shadow
Use drop-shadow-xs instead of drop-shadow-sm and drop-shadow-sm instead of drop-shadow
Use blur-xs instead of blur-sm and blur-sm instead of blur
Use rounded-xs instead of rounded-sm and rounded-sm instead of rounded
Use outline-hidden instead of outline-none for...

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

**/*.{ts,tsx}: Create QueryClient using getQueryClient() function that automatically handles server/client isolation (new instance per server request, singleton on client)
Always use the centralized query key factory from src/lib/api/query-keys.ts instead of creating keys manually
Include all variables used in queryFn in the query key to ensure proper cache management and query invalidation

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

**/*.{ts,tsx}: Use TypeScript strict mode
Use functional components and hooks in React
Prefer composition over inheritance
Use selective imports over barrel exports for performance
Write accessible, performant, type-safe code
Use explicit types for clarity, prefer unknown over any
Always await promises in async functions
Use semantic HTML and ARIA attributes for accessibility
Use @/auth module for all authentication operations
Use @/db for all database operations
Use @/components/ui/* for importing shadcn/ui components with direct imports
Validate all inputs with Zod schemas
Implement proper RBAC with permissions module
Use TypeScript paths for imports configured in tsconfig.json
Implement proper error boundaries in React
Use Suspense for loading states
Optimize images with next/image
Use TanStack Query for all server state management

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
{src/app/**,src/components/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/tanstack-query.mdc)

{src/app/**,src/components/**}/*.{ts,tsx}: Use useUsers(), useUser(), useSessions(), useAdminStats(), useUpdateUser(), useDeleteUser(), useDeleteSession() hooks from src/hooks/use-admin.ts for standard queries
Use corresponding Suspense hooks (useUsersSuspense(), useUserSuspense(), etc.) from src/hooks/use-admin-suspense.ts when wrapping components in Suspense and Error Boundaries

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,js,jsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Use function components over class components
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props in React components
Don't define components inside other components
Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, and use semantic elements instead of divs with roles
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Keep functions focused and under reasonable cognitive complexity limits
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Group related code together and separate concerns
Add `...

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/app/**/*.{ts,tsx}: Follow Next.js App Router conventions
Never expose API keys in client code

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Handle internationalization via next-intl with locale files in locale/ directory

Files:

  • src/app/api/xmpp/accounts/[id]/route.ts
🧠 Learnings (12)
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/api/**/*.{ts,tsx,js,jsx} : API, DB, auth contracts, or tokens require mandatory review

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/*route*.{ts,tsx,js,jsx} : Clean, non-duplicated REST routes in backend code

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx} : Use explicit types for function parameters and return values when they enhance clarity

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `async/await` syntax instead of promise chains for better readability

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to src/app/**/*.{ts,tsx} : Follow Next.js App Router conventions

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Always await promises in async functions

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:30.014Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-01-15T06:16:30.014Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always `await` promises in async functions - don't forget to use the return value

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2025-12-18T18:18:05.202Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/anti-slop.mdc:0-0
Timestamp: 2025-12-18T18:18:05.202Z
Learning: Applies to **/backend/**/*.{ts,tsx,js,jsx} : Mandatory Zod validation at boundaries in backend

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:41.668Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T06:16:41.668Z
Learning: Applies to **/*.{ts,tsx} : Validate all inputs with Zod schemas

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Prefetch only critical data that users will definitely see, using Promise.all() for parallel prefetching instead of sequential await

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to {src/app/**,src/components/**}/*.{tsx} : In regular query hooks, check isPending first, then isError, then use data to ensure TypeScript type narrowing works correctly

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
📚 Learning: 2026-01-15T06:16:09.034Z
Learnt from: CR
Repo: allthingslinux/portal PR: 0
File: .cursor/rules/tanstack-query.mdc:0-0
Timestamp: 2026-01-15T06:16:09.034Z
Learning: Applies to src/app/**/*.{tsx} : Perform auth checks using Better Auth's auth.api.getSession() in Server Components before prefetching data, and redirect if not authenticated

Applied to files:

  • src/app/api/xmpp/accounts/[id]/route.ts
🧬 Code graph analysis (1)
src/app/api/xmpp/accounts/[id]/route.ts (4)
src/lib/api/utils.ts (3)
  • requireAuth (69-82)
  • handleAPIError (103-125)
  • APIError (87-97)
src/lib/db/schema/xmpp.ts (1)
  • xmppAccount (19-43)
src/lib/auth/check-role.ts (1)
  • isAdmin (46-49)
src/lib/xmpp/client.ts (2)
  • deleteProsodyAccount (145-184)
  • ProsodyAccountNotFoundError (10-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Seer Code Review
  • GitHub Check: Sourcery review
🔇 Additional comments (2)
src/app/api/xmpp/accounts/[id]/route.ts (2)

127-138: Good guardrail against username changes.

Explicitly blocking username updates avoids DB/Prosody drift and keeps auth consistent.


223-241: Prosody deletion error handling looks solid.

The ProsodyAccountNotFoundError tolerance with explicit handling for other errors is clear and safe.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +14 to +19
// Zod schema for update request validation
const updateXmppAccountSchema = z.object({
username: z.string().optional(),
status: z.enum(["active", "suspended", "deleted"]).optional(),
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/app/api/xmpp/accounts/[id]/route.ts

Repository: allthingslinux/portal

Length of output: 8787


🏁 Script executed:

# Search for existing param schemas in the codebase
rg "ParamsSchema|paramsSchema|params.*z\.object" --type ts --type tsx -A 2

Repository: allthingslinux/portal

Length of output: 92


🏁 Script executed:

# Look for similar route files to see validation patterns
fd "route\.ts" src/app/api --exec head -30 {} \; 2>/dev/null | head -100

Repository: allthingslinux/portal

Length of output: 3455


🏁 Script executed:

# Search for RouteContext type definition
rg "type RouteContext|RouteContext.*=" --type ts -B 2 -A 2

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Check if param schemas are shared across routes
rg "ParamsSchema|paramsSchema" --type ts -B 2 -A 2

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Look for any global or utility param validation
fd "params" src/lib --type ts | head -20

Repository: allthingslinux/portal

Length of output: 234


🏁 Script executed:

# Search for RouteContext in entire codebase
rg "RouteContext" --type-list

Repository: allthingslinux/portal

Length of output: 454


🏁 Script executed:

# Search for RouteContext definition
rg "type RouteContext|interface RouteContext"

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Check if there are param validation utilities
find src -name "*.ts" -o -name "*.tsx" | xargs grep -l "params.*parse\|ParamsSchema" 2>/dev/null | head -10

Repository: allthingslinux/portal

Length of output: 105


🏁 Script executed:

# Search for RouteContext definition more directly
rg "RouteContext" -A 3

Repository: allthingslinux/portal

Length of output: 3288


🏁 Script executed:

# Check what's in lib/api to understand utilities
ls -la src/lib/api/

Repository: allthingslinux/portal

Length of output: 791


🏁 Script executed:

# Look for any param schema examples
rg "z\.object.*id" src/app/api -B 2 -A 2

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Check tsconfig for type definitions
cat tsconfig.json | grep -A 20 "types\|compilerOptions"

Repository: allthingslinux/portal

Length of output: 765


🏁 Script executed:

# Look for globals.d.ts or similar files
find src -name "*.d.ts" -o -name "globals.ts"

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Search in node_modules or type definitions
rg "type RouteContext" --type-add 'ts:*.ts:*.tsx' -l 2>/dev/null | head -5

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Check Next.js types - RouteContext might be from Next.js itself
grep -r "RouteContext" . --include="*.ts" --include="*.tsx" | grep "import\|export type" | head -10

Repository: allthingslinux/portal

Length of output: 47


🏁 Script executed:

# Check the actual imports in the file we're reviewing
head -20 src/app/api/xmpp/accounts/[id]/route.ts

Repository: allthingslinux/portal

Length of output: 731


🏁 Script executed:

# Search for RouteContext everywhere (including comments, exports, etc)
rg "RouteContext" . 2>/dev/null | wc -l

Repository: allthingslinux/portal

Length of output: 67


🏁 Script executed:

# Check another similar route file to see if RouteContext is imported
head -30 src/app/api/admin/users/[id]/route.ts

Repository: allthingslinux/portal

Length of output: 894


🏁 Script executed:

# Check if RouteContext is a Next.js global by looking at node_modules type definitions
grep -r "RouteContext" node_modules/@types/next 2>/dev/null | head -5

Repository: allthingslinux/portal

Length of output: 47


Validate route params (id) with Zod before DB access.

id is used directly from ctx.params in all handlers (GET/PATCH/DELETE) without validation. Per backend boundary validation guidelines, add a Zod schema for route params and parse them before hitting the database. This prevents malformed inputs and keeps validation consistent across all methods.

Proposed fix (apply to GET/PATCH/DELETE)
+const xmppAccountParamsSchema = z.object({
+  id: z.string().min(1),
+})
+
 // Zod schema for update request validation
 const updateXmppAccountSchema = z.object({
   username: z.string().optional(),
   status: z.enum(["active", "suspended", "deleted"]).optional(),
   metadata: z.record(z.string(), z.unknown()).nullable().optional(),
 });
-    const { id } = await ctx.params;
+    const { id } = xmppAccountParamsSchema.parse(await ctx.params);
🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts around lines 14 - 19, Add a Zod
schema for the route params (e.g., paramsSchema = z.object({ id:
z.string().nonempty() })) and use paramsSchema.parse(ctx.params) at the start of
each handler (GET/PATCH/DELETE) in route.ts before any DB access; replace direct
uses of ctx.params.id with the validated value (parsed.id) and ensure handlers
return a 400/422 on parse errors per existing error-handling conventions. This
touches the same file alongside updateXmppAccountSchema and ensures
ctx.params.id is validated uniformly in each handler.

Comment on lines +27 to +30
export async function GET(
request: NextRequest,
ctx: RouteContext<"/api/xmpp/accounts/[id]">
) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for RouteContext definition and usage
echo "=== Searching for RouteContext definitions/imports ==="
rg -n "RouteContext" --type=ts --type=tsx

echo -e "\n=== Checking next.config files for typedRoutes ==="
fd "next.config.*" -x cat {}

Repository: allthingslinux/portal

Length of output: 14341


🏁 Script executed:

#!/bin/bash
# Check the actual route file
echo "=== Viewing route file ==="
cat -n "src/app/api/xmpp/accounts/[id]/route.ts" | head -40

echo -e "\n=== Checking tsconfig.json for type definitions ==="
cat tsconfig.json

Repository: allthingslinux/portal

Length of output: 2510


🏁 Script executed:

#!/bin/bash
# Search more broadly for RouteContext in all file types
echo "=== Searching for RouteContext definition in all files ==="
rg "RouteContext" -g "!node_modules"

echo -e "\n=== Checking if there's a types file for custom route context ==="
fd "types\|index" --type f -x grep -l "RouteContext" {} \;

Repository: allthingslinux/portal

Length of output: 1254


Add import for RouteContext or replace with standard Next.js types.

The file uses RouteContext<"/api/xmpp/accounts/[id]"> without importing it. This will cause a TypeScript error since RouteContext is not defined in the codebase (verified across all route files using this pattern). Either import this type from Next.js if available, define it locally, or replace it with the standard Next.js route handler signature using { params: Promise<{...}> }.

🤖 Prompt for AI Agents
In `@src/app/api/xmpp/accounts/`[id]/route.ts around lines 27 - 30, The GET
handler uses an undeclared type RouteContext<"/api/xmpp/accounts/[id]"> which
causes a TS error; update the function signature in
src/app/api/xmpp/accounts/[id]/route.ts by either importing RouteContext from
Next.js (if your Next version exports it) or replace the type with the standard
Next.js route handler shape (e.g., accept NextRequest and a params object with
id: string or Promise<{ params: { id: string } }>), updating the GET declaration
accordingly so the compiler recognizes the context parameter.

- Introduced a new module for validated XMPP environment variables using t3-env, improving type safety and runtime validation.
- Updated the XMPP configuration to utilize the new keys module for environment variable management.
- Implemented error capturing to Sentry for configuration validation, ensuring early detection of missing environment variables.
- Enhanced observability by integrating Sentry error handling in the XMPP configuration validation process.
- Removed deprecated observability utilities and consolidated them into a new helpers module for better organization and maintainability.
@kzndotsh kzndotsh merged commit 20919af into main Jan 16, 2026
3 of 5 checks passed
@kzndotsh kzndotsh deleted the feat/xmpp-integration branch January 16, 2026 06:31
Comment on lines +245 to +255
.update(xmppAccount)
.set({
status: "deleted",
updatedAt: new Date(),
})
.where(eq(xmppAccount.id, id))
.returning();

if (!deleted) {
throw new APIError("Failed to delete XMPP account", 500);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The DELETE endpoint lacks a rollback mechanism. If the database update fails after the Prosody account is deleted, the system state becomes inconsistent.
Severity: MEDIUM

Suggested Fix

Wrap the database update and Prosody deletion in a way that ensures atomicity. Implement a rollback mechanism similar to the one in the POST endpoint: if the database update fails, attempt to restore the Prosody account. Alternatively, perform the database update first before deleting from the external service.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/app/api/xmpp/accounts/[id]/route.ts#L243-L255

Potential issue: In the DELETE endpoint, an account is first deleted from the external
Prosody service, and then its status is updated in the local database. If the database
update operation fails due to an error (e.g., connection issue, deadlock), an exception
is caught and a 500 error is returned. However, the Prosody account has already been
deleted, and there is no rollback mechanism to recreate it. This leaves the system in an
inconsistent state where the account is gone from Prosody but still appears as 'active'
in the database and UI.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment on lines +164 to +167
const availabilityResult = await checkUsernameAvailability(username);
if (!availabilityResult.available) {
return availabilityResult.error;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: A race condition during account creation can lead to an orphaned Prosody account because the rollback logic is not triggered on a database constraint violation.
Severity: HIGH

Suggested Fix

Wrap the database insertion in a try...catch block. In the catch block, check if the error is a database unique constraint violation. If it is, execute the rollback logic to delete the orphaned Prosody account. This ensures that failed account creations due to race conditions are properly cleaned up.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/app/api/xmpp/accounts/route.ts#L164-L167

Potential issue: A race condition can occur when two concurrent requests attempt to
create an XMPP account with the same username. Both requests can pass the initial
availability check and create an account in the external Prosody service. The first
request will succeed in creating the database record. The second request's database
insert will fail with a unique constraint violation, throwing an exception. The existing
error handling logic does not catch this specific exception type, so the rollback code
designed to delete the orphaned Prosody account is never executed, leaving an orphaned
account in Prosody.

Did we get this right? 👍 / 👎 to inform future reviews.

@coderabbitai coderabbitai bot mentioned this pull request Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants