Skip to content

Chat: privacy#619

Merged
feruzm merged 2 commits into
developfrom
privacy
Jan 16, 2026
Merged

Chat: privacy#619
feruzm merged 2 commits into
developfrom
privacy

Conversation

@feruzm
Copy link
Copy Markdown
Member

@feruzm feruzm commented Jan 16, 2026

Summary by CodeRabbit

  • New Features

    • DM privacy settings: choose who can DM you (everyone, followers only, or no one); settings available in your profile preferences and show a brief description and success feedback.
    • Enforced DM access: direct message creation and posting now respect recipients' DM privacy choices.
  • Documentation

    • Added localized text and success messages for DM privacy controls.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 16, 2026

📝 Walkthrough

Walkthrough

Adds a DM privacy system: UI settings, client hooks, server getters/setters, API route for current user's DM privacy, and enforcement checks in DM creation and posting endpoints, plus localization strings.

Changes

Cohort / File(s) Summary
DM Privacy UI Component
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx
New client React component DmPrivacySettings that reads current privacy via useDmPrivacyQuery, renders a select (all, followers-only, none), and updates via useUpdateDmPrivacy with success toast.
Settings Page Integration
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_page.tsx
Imports and renders <DmPrivacySettings /> within the ProfileSettings grid between Preferences and PermissionsCard.
User DM Privacy API Route
apps/web/src/app/api/mattermost/me/dm-privacy/route.ts
New Next.js API route with GET (returns current user's DM privacy) and PUT (validates and sets DM privacy) handlers; requires Mattermost auth token and uses existing error handling.
Message Posting & DM Creation Enforcement
apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts, apps/web/src/app/api/mattermost/direct/route.ts
Added defense-in-depth checks for DM channels: identify the other participant, fetch their DM privacy via getUserDmPrivacy, and enforce restrictions (403 for "none"; for "followers" verify follower relationship via query client) before continuing existing flows.
Chat API Hooks & Types
apps/web/src/features/chat/mattermost-api.ts
New exported type `DmPrivacyLevel = "all"
Server Mattermost Helpers
apps/web/src/server/mattermost.ts
New CHAT_DM_PRIVACY_PROP constant, DmPrivacyLevel type, getUserDmPrivacy(user) to read/normalize privacy from user props (default "all"), and setUserDmPrivacy(userId, privacy) to patch user props via admin API.
Localization Strings
apps/web/src/features/i18n/locales/en-US.json
Added settings.dm-privacy keys (title, who-can-message, description, option labels, updated toast) plus related profile and entry-menu DM strings.

Sequence Diagrams

sequenceDiagram
    actor User
    participant UI as Settings Component
    participant Hook as useUpdateDmPrivacy
    participant API as /api/mattermost/me/dm-privacy
    participant MM as Mattermost API

    User->>UI: Select privacy option
    UI->>Hook: trigger mutation (privacy)
    Hook->>API: PUT { privacy }
    API->>API: validate privacy
    API->>MM: GET /users/me
    MM-->>API: user with props
    API->>MM: PATCH /users/{id} (privacy prop)
    MM-->>API: updated user
    API-->>Hook: { privacy }
    Hook->>Hook: invalidate cache
    Hook-->>UI: success
    UI->>User: show success toast
Loading
sequenceDiagram
    actor Sender
    participant DirectAPI as POST /api/mattermost/direct
    participant MM as Mattermost API
    participant QueryClient as Query Cache
    actor Recipient

    Sender->>DirectAPI: request to create DM with Recipient
    DirectAPI->>MM: fetch Recipient (with props)
    MM-->>DirectAPI: Recipient (props)
    DirectAPI->>DirectAPI: getUserDmPrivacy(Recipient) = "followers"
    DirectAPI->>QueryClient: query relationship (does Recipient follow Sender?)
    QueryClient-->>DirectAPI: response (no)
    DirectAPI-->>Sender: 403 Forbidden (DM not allowed)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble on code and tuck privacy tight,
Three choices to guard the DM light,
A hop, a patch, a toast that sings,
Followers, all, or closed-off wings,
Hooray for quiet—now everyone sleeps right.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Chat: privacy' is vague and generic; it uses non-descriptive terms that don't clearly convey what specific aspect of chat privacy is being implemented. Use a more descriptive title that clarifies the main feature, such as 'Add DM privacy settings to allow users to control message access' or 'Implement direct message privacy controls'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (2)

9-26: Avoid select “snapback” while the mutation refetches.

Because the select is controlled by privacy (query data), changing it can briefly revert to the old value until the refetch completes. Consider an optimistic/local state update (or cache update) so the UI reflects the user’s choice immediately.


35-46: Associate the label with the select for accessibility.

This improves screen-reader and form navigation support.

♿ Suggested tweak
-        <label className="text-sm px-2 mb-2 block">
+        <label className="text-sm px-2 mb-2 block" htmlFor="dm-privacy-select">
           {i18next.t("settings.dm-privacy.who-can-message")}
         </label>
...
-        <FormControl value={privacy || "all"} type="select" onChange={handlePrivacyChange}>
+        <FormControl id="dm-privacy-select" value={privacy || "all"} type="select" onChange={handlePrivacyChange}>

📜 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 4180649 and 4c3801c.

📒 Files selected for processing (1)
  • apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (3)
apps/web/src/features/chat/mattermost-api.ts (3)
  • useDmPrivacyQuery (769-783)
  • useUpdateDmPrivacy (785-807)
  • DmPrivacyLevel (767-767)
apps/web/src/server/mattermost.ts (1)
  • DmPrivacyLevel (13-13)
apps/web/src/features/shared/feedback/feedback-events.ts (1)
  • success (47-55)
🔇 Additional comments (1)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (1)

1-7: Imports and setup look good.

✏️ 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
Copy Markdown
Contributor

@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: 1

🤖 Fix all issues with AI agents
In `@apps/web/src/app/`(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx:
- Around line 10-14: The component currently returns early only on isLoading,
which lets a failed fetch leave `privacy` undefined and default the UI to "all";
update the guard after calling useDmPrivacyQuery() to also extract and check
`isError` (e.g., const { data: privacy, isLoading, isError } =
useDmPrivacyQuery()) and return early when isError is true so the UI does not
misrepresent settings; keep using useUpdateDmPrivacy() and its mutate alias
updatePrivacy unchanged but ensure downstream rendering relies on a defined
`privacy` value only after the combined loading/error guard passes.
♻️ Duplicate comments (1)
apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts (1)

253-292: Same follower-direction concern as /direct.

Please verify the relationship direction for "followers" privacy here as well (same SDK call and semantics).

🧹 Nitpick comments (2)
apps/web/src/features/chat/mattermost-api.ts (1)

767-767: Consider sharing DmPrivacyLevel across server/client.

To avoid drift between server and client unions, consider moving this type to a shared module used by both sides.

apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (1)

15-26: Select won’t reflect changes until refetch; consider optimistic/local state.
Because the select is controlled by privacy, the user’s choice snaps back until the query refreshes. A small local state (or react-query optimistic update) would keep the UI responsive.

♻️ Example using local state
+import { useEffect, useState } from "react";
...
-  const { data: privacy, isLoading } = useDmPrivacyQuery();
+  const { data: privacy, isLoading } = useDmPrivacyQuery();
+  const [selectedPrivacy, setSelectedPrivacy] = useState<DmPrivacyLevel | undefined>(privacy);
+  useEffect(() => setSelectedPrivacy(privacy), [privacy]);
...
-  const handlePrivacyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
+  const handlePrivacyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
     const newPrivacy = e.target.value as DmPrivacyLevel;
+    setSelectedPrivacy(newPrivacy);
     updatePrivacy(newPrivacy, {
       onSuccess: () => {
         success(i18next.t("settings.dm-privacy-updated"));
       },
       onError: (error) => {
         // Error is already thrown and will be caught by error boundary
         console.error("Failed to update DM privacy:", error);
+        setSelectedPrivacy(privacy);
       }
     });
   };
...
-        <FormControl value={privacy || "all"} type="select" onChange={handlePrivacyChange}>
+        <FormControl value={selectedPrivacy || "all"} type="select" onChange={handlePrivacyChange}>

Also applies to: 42-42

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 965eaa7 and 4180649.

📒 Files selected for processing (8)
  • apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/settings/_page.tsx
  • apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts
  • apps/web/src/app/api/mattermost/direct/route.ts
  • apps/web/src/app/api/mattermost/me/dm-privacy/route.ts
  • apps/web/src/features/chat/mattermost-api.ts
  • apps/web/src/features/i18n/locales/en-US.json
  • apps/web/src/server/mattermost.ts
🧰 Additional context used
🧬 Code graph analysis (5)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (3)
apps/web/src/features/chat/mattermost-api.ts (3)
  • useDmPrivacyQuery (769-783)
  • useUpdateDmPrivacy (785-807)
  • DmPrivacyLevel (767-767)
apps/web/src/server/mattermost.ts (1)
  • DmPrivacyLevel (13-13)
apps/web/src/features/shared/feedback/feedback-events.ts (1)
  • success (47-55)
apps/web/src/features/chat/mattermost-api.ts (1)
apps/web/src/server/mattermost.ts (1)
  • DmPrivacyLevel (13-13)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_page.tsx (2)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_preferences.tsx (1)
  • Preferences (18-152)
apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx (1)
  • DmPrivacySettings (9-50)
apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts (1)
apps/web/src/server/mattermost.ts (4)
  • mmUserFetch (259-268)
  • MattermostChannel (25-30)
  • getMattermostUserWithProps (203-207)
  • getUserDmPrivacy (358-364)
apps/web/src/app/api/mattermost/direct/route.ts (1)
apps/web/src/server/mattermost.ts (2)
  • getMattermostUserWithProps (203-207)
  • getUserDmPrivacy (358-364)
🔇 Additional comments (13)
apps/web/src/server/mattermost.ts (3)

11-13: Good addition of shared DM privacy constants/types.

Keeps the prop key and allowed values centralized.


358-364: DM privacy normalization looks correct.

Defaulting to "all" when unset/invalid is a sensible fallback.


443-459: Setter implementation looks solid.

The props merge and patch flow aligns with existing admin updates.

apps/web/src/app/api/mattermost/direct/route.ts (2)

5-12: No review comment for this change.


39-69: I need the review comment to be provided in order to rewrite it. Please share the review comment (ideally within <review_comment> tags) along with any context about the codebase changes being reviewed.

apps/web/src/app/api/mattermost/me/dm-privacy/route.ts (2)

13-28: GET handler looks good.

Clean token check, user resolution, and privacy response.


30-54: PUT handler looks good.

Validation + update flow is clear and consistent.

apps/web/src/features/chat/mattermost-api.ts (1)

769-806: Hooks look good.

Query/mutation flows and cache invalidation are correct.

apps/web/src/app/api/mattermost/channels/[channelId]/posts/route.ts (1)

8-18: No review comment for this change.

apps/web/src/app/(dynamicPages)/profile/[username]/settings/_page.tsx (1)

6-14: LGTM: DM privacy card integrated into settings layout.
Nice, clean insertion in the grid without impacting existing flow.

apps/web/src/features/i18n/locales/en-US.json (3)

1080-1082: LGTM: Profile DM badge labels added.
Strings are clear and consistent with the privacy options.


1190-1200: LGTM: Settings DM privacy copy is complete and consistent.
Nice, descriptive labels and help text.


2177-2180: LGTM: Chat entry-menu DM restriction messages added.
These align well with the privacy states.

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

Comment thread apps/web/src/app/(dynamicPages)/profile/[username]/settings/_dm-privacy.tsx Outdated
@feruzm feruzm merged commit 0b5bbfa into develop Jan 16, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 16, 2026
@feruzm feruzm deleted the privacy branch May 7, 2026 18:42
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.

1 participant