Skip to content

Feature/contact syncn.new#5

Open
nnnjh761-coder wants to merge 5 commits into
Galaxies-dev:mainfrom
nnnjh761-coder:feature/contact-sync
Open

Feature/contact syncn.new#5
nnnjh761-coder wants to merge 5 commits into
Galaxies-dev:mainfrom
nnnjh761-coder:feature/contact-sync

Conversation

@nnnjh761-coder

@nnnjh761-coder nnnjh761-coder commented Jun 1, 2026

Copy link
Copy Markdown

New

Summary by CodeRabbit

  • New Features
    • Introduced a new contacts-based messaging interface displaying synchronized device contacts organized alphabetically with profile pictures, pull-to-refresh functionality, and one-tap conversation initiation
    • Added user profile management capabilities enabling users to securely store and update account information including phone number, full name, email, and profile photo

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds contact syncing capabilities to a WhatsApp clone by introducing a Supabase profiles table with security policies, updating app configuration for contact access, adding a new contact list screen with synced contacts display, and establishing a Deno CI workflow for backend code validation.

Changes

Contact Syncing Feature

Layer / File(s) Summary
Database schema and row-level security
supabase/migrations/001_add_phone_to_profiles.sql
Creates public.profiles table with phone, user_id, name, avatar_url, and email fields; adds indexes on phone and user_id; enables RLS with SELECT/UPDATE/INSERT policies scoped to authenticated users; implements auto-updating updated_at trigger via helper function.
App configuration and dependencies
app.json, package.json
Adds iOS NSContactsUsageDescription and updates bundleIdentifier to com.supersimon.whatsapp; adds Android contacts and phone-state permissions; configures expo-contacts plugin; adds @react-native-async-storage/async-storage and expo-contacts to dependencies.
New Chat contact list screen
app/(tabs)/new-chat.tsx
Displays synced contacts grouped alphabetically by first letter in a sectioned list with pull-to-refresh, handles loading/error/permission/empty states, renders avatars with name/phone, and navigates to /chat/[id] on contact selection.

Deno CI Workflow

Layer / File(s) Summary
Deno lint and test workflow
.github/workflows/deno.yml
GitHub Actions workflow triggered on pushes and PRs to main; checks out code, installs Deno via pinned denoland/setup-deno action, and runs deno lint followed by deno test -A with read-only permissions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A new screen springs forth with contacts bright,
Alphabets dancing in the list's light,
Profiles now stored with security tight,
Pull-to-refresh keeps the sync just right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/contact syncn.new' appears to have a typo ('syncn' instead of 'sync') and is vague; it doesn't clearly summarize the main changes despite being partially related to contact synchronization. Clarify the title to accurately describe the feature. Consider 'Add contact syncing feature with new chat screen' or similar to better communicate the changeset's primary purpose.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
supabase/migrations/001_add_phone_to_profiles.sql (1)

14-15: ⚡ Quick win

Drop the redundant indexes on unique columns.

Both phone and user_id already get indexes from their UNIQUE constraints, so these extra indexes only add storage and write cost.

♻️ Proposed cleanup
-CREATE INDEX IF NOT EXISTS idx_profiles_phone ON public.profiles(phone);
-CREATE INDEX IF NOT EXISTS idx_profiles_user_id ON public.profiles(user_id);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/001_add_phone_to_profiles.sql` around lines 14 - 15, The
migration unnecessarily creates indexes idx_profiles_phone and
idx_profiles_user_id on columns that already have UNIQUE constraints; remove the
redundant CREATE INDEX lines for idx_profiles_phone and idx_profiles_user_id
(the UNIQUE constraints on profiles(phone) and profiles(user_id) already provide
indexes) so the migration only leaves the necessary schema changes without
duplicating index storage and write overhead.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deno.yml:
- Around line 25-26: Update the GitHub Actions checkout step so it is pinned to
a specific commit hash instead of the tag and disable credential persistence;
locate the "uses: actions/checkout@v4" line in the workflow and replace the tag
with the repo commit SHA (e.g., actions/checkout@<commit-hash>) and add the key
"persist-credentials: false" under that step to prevent GITHUB_TOKEN from being
persisted.

In `@app.json`:
- Around line 30-33: The permissions list in app.json unnecessarily includes
"android.permission.READ_PHONE_STATE"; remove this permission from the
"permissions" array and keep only "android.permission.READ_CONTACTS" (or any
other permissions your app actually uses) so the app does not request telephony
state; update any related documentation or rationale comments if present to
reflect the trimmed permission set.

In `@app/`(tabs)/new-chat.tsx:
- Around line 58-63: The placeholder avatar rendering assumes
item.device_contact_name[0] exists and crashes for empty or missing names;
update the conditional in the new-chat.tsx render block to safely derive an
initial (e.g., compute const initial = item.device_contact_name ?
item.device_contact_name.trim()[0]?.toUpperCase() : null) and render that
initial only when present, otherwise render a fallback (such as a default icon
or empty string). Locate the JSX that uses item.device_contact_name[0] (the
avatar placeholder branch) and replace the direct index access with a guarded
value using trim() and optional chaining before calling toUpperCase().

In `@package.json`:
- Around line 21-22: The package.json entry for the dependency "expo-contacts"
is incompatible with Expo SDK 50; update the "expo-contacts" version string from
"^14.5.0" to the SDK-50 compatible "12.8.2" (or run npx expo install --fix to
reconcile all Expo package versions automatically) so it matches the installed
"expo" ^50.0.2; change the "expo-contacts" value in package.json and then
reinstall (npm/yarn) to update lockfiles.

In `@supabase/migrations/001_add_phone_to_profiles.sql`:
- Around line 20-24: The current SELECT policy "Users can view profiles" on
public.profiles uses USING (auth.uid() IS NOT NULL) which allows any signed-in
user to read all profile rows; replace it with a tighter policy so SELECT only
returns the caller's own profile (e.g., restrict USING to compare the profile's
owner column to auth.uid(), such as USING (user_id = auth.uid()) on
public.profiles) and remove or narrow any global read grants; for contact-sync
functionality, implement a server-side RPC (e.g., match_contacts) that accepts
phone hashes and performs the matching on the DB side, returning only allowed
fields, instead of broadening the SELECT policy.
- Line 5: The migration currently adds phone TEXT NOT NULL UNIQUE which enforces
uniqueness on raw formatting; change it to add a separate normalized column
(e.g., phone_normalized) and enforce uniqueness on that column instead: keep the
original phone TEXT for display, add phone_normalized TEXT NOT NULL UNIQUE, and
populate/maintain it via a BEFORE INSERT OR UPDATE trigger that calls a
normalize_phone function (create normalize_phone(text) to produce canonical
E.164-style values or at least digits-only + country normalization); update the
migration to create the normalize_phone function and trigger (e.g.,
normalize_phone_trigger) so all new and updated rows store a canonical value in
phone_normalized used by the UNIQUE constraint.

---

Nitpick comments:
In `@supabase/migrations/001_add_phone_to_profiles.sql`:
- Around line 14-15: The migration unnecessarily creates indexes
idx_profiles_phone and idx_profiles_user_id on columns that already have UNIQUE
constraints; remove the redundant CREATE INDEX lines for idx_profiles_phone and
idx_profiles_user_id (the UNIQUE constraints on profiles(phone) and
profiles(user_id) already provide indexes) so the migration only leaves the
necessary schema changes without duplicating index storage and write overhead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 17276209-390f-426c-9c21-420c16099cee

📥 Commits

Reviewing files that changed from the base of the PR and between eb64ed5 and 8f883ef.

📒 Files selected for processing (5)
  • .github/workflows/deno.yml
  • app.json
  • app/(tabs)/new-chat.tsx
  • package.json
  • supabase/migrations/001_add_phone_to_profiles.sql

Comment on lines +25 to +26
- name: Setup repo
uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin checkout action to hash and disable credential persistence.

For security consistency with the pinned denoland/setup-deno action, actions/checkout should also be pinned to a commit hash rather than a tag. Additionally, set persist-credentials: false to prevent the GITHUB_TOKEN from being exposed in subsequent steps where it's not needed.

🔒 Proposed security hardening
       - name: Setup repo
-        uses: actions/checkout@v4
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
+        with:
+          persist-credentials: false

As per static analysis hints, the unpinned action reference and missing credential persistence settings were flagged by the zizmor tool.

📝 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
- name: Setup repo
uses: actions/checkout@v4
- name: Setup repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 25-26: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 26-26: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deno.yml around lines 25 - 26, Update the GitHub Actions
checkout step so it is pinned to a specific commit hash instead of the tag and
disable credential persistence; locate the "uses: actions/checkout@v4" line in
the workflow and replace the tag with the repo commit SHA (e.g.,
actions/checkout@<commit-hash>) and add the key "persist-credentials: false"
under that step to prevent GITHUB_TOKEN from being persisted.

Comment thread app.json
Comment on lines +30 to +33
"permissions": [
"android.permission.READ_CONTACTS",
"android.permission.READ_PHONE_STATE"
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove READ_PHONE_STATE unless the app really uses telephony state.

Contact syncing needs READ_CONTACTS, not phone-state access. Keeping READ_PHONE_STATE expands the privacy surface and can create avoidable consent/store-review friction.

🔒 Proposed permission trim
       "permissions": [
-        "android.permission.READ_CONTACTS",
-        "android.permission.READ_PHONE_STATE"
+        "android.permission.READ_CONTACTS"
       ],
📝 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
"permissions": [
"android.permission.READ_CONTACTS",
"android.permission.READ_PHONE_STATE"
],
"permissions": [
"android.permission.READ_CONTACTS"
],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app.json` around lines 30 - 33, The permissions list in app.json
unnecessarily includes "android.permission.READ_PHONE_STATE"; remove this
permission from the "permissions" array and keep only
"android.permission.READ_CONTACTS" (or any other permissions your app actually
uses) so the app does not request telephony state; update any related
documentation or rationale comments if present to reflect the trimmed permission
set.

Comment thread app/(tabs)/new-chat.tsx
Comment on lines +58 to +63
{item.avatar_url ? (
<Image source={{ uri: item.avatar_url }} style={styles.avatar} />
) : (
<View style={[styles.avatar, styles.avatarPlaceholder]}>
<Text style={styles.avatarText}>{item.device_contact_name[0].toUpperCase()}</Text>
</View>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the avatar initial for empty contact names.

This path indexes item.device_contact_name[0] unconditionally. If a synced contact has an empty or missing display name, the screen will crash while rendering the placeholder avatar.

🛡️ Proposed guard
         ) : (
           <View style={[styles.avatar, styles.avatarPlaceholder]}>
-            <Text style={styles.avatarText}>{item.device_contact_name[0].toUpperCase()}</Text>
+            <Text style={styles.avatarText}>
+              {(item.device_contact_name?.trim()?.[0] ?? '#').toUpperCase()}
+            </Text>
           </View>
         )}
📝 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
{item.avatar_url ? (
<Image source={{ uri: item.avatar_url }} style={styles.avatar} />
) : (
<View style={[styles.avatar, styles.avatarPlaceholder]}>
<Text style={styles.avatarText}>{item.device_contact_name[0].toUpperCase()}</Text>
</View>
{item.avatar_url ? (
<Image source={{ uri: item.avatar_url }} style={styles.avatar} />
) : (
<View style={[styles.avatar, styles.avatarPlaceholder]}>
<Text style={styles.avatarText}>
{(item.device_contact_name?.trim()?.[0] ?? '#').toUpperCase()}
</Text>
</View>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(tabs)/new-chat.tsx around lines 58 - 63, The placeholder avatar
rendering assumes item.device_contact_name[0] exists and crashes for empty or
missing names; update the conditional in the new-chat.tsx render block to safely
derive an initial (e.g., compute const initial = item.device_contact_name ?
item.device_contact_name.trim()[0]?.toUpperCase() : null) and render that
initial only when present, otherwise render a fallback (such as a default icon
or empty string). Locate the JSX that uses item.device_contact_name[0] (the
avatar placeholder branch) and replace the direct index access with a guarded
value using trim() and optional chaining before calling toUpperCase().

Comment thread package.json
Comment on lines 21 to +22
"expo": "^50.0.2",
"expo-contacts": "^14.5.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Which expo-contactsversion is compatible with Expo SDK 50, and does Expo recommend~-pinned versions for Expo SDK packages in package.json?

💡 Result:

Expo SDK 50 compatible expo-contacts version: use expo-contacts sdk-50 tag version 12.8.2. The npm registry dist-tags for expo-contacts include “sdk-50”: “12.8.2” [1]. Does Expo recommend ~-pinned versions for Expo SDK packages in package.json? Expo’s guidance is to use expo install (and expo install --fix / --check) to realign dependency versions with the versions expected for your current Expo SDK, rather than manually guessing version ranges [2]. The dependency-reconciliation doc explains tested ranges (including that ~1.2.3 allows v1.2.3 plus a greater patch version) but it does not recommend “always use ~” specifically for Expo SDK packages; instead it emphasizes using the CLI to keep your versions aligned to the SDK expectations [2].

Citations:


Align expo-contacts with Expo SDK 50 (package.json, lines 21–22).

expo-contacts is set to ^14.5.0, but Expo’s SDK 50 compatible expo-contacts version is 12.8.2 (npm sdk-50 dist-tag). Update expo-contacts to the SDK 50 version (or run npx expo install --fix to reconcile automatically).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 21 - 22, The package.json entry for the dependency
"expo-contacts" is incompatible with Expo SDK 50; update the "expo-contacts"
version string from "^14.5.0" to the SDK-50 compatible "12.8.2" (or run npx expo
install --fix to reconcile all Expo package versions automatically) so it
matches the installed "expo" ^50.0.2; change the "expo-contacts" value in
package.json and then reinstall (npm/yarn) to update lockfiles.

CREATE TABLE IF NOT EXISTS public.profiles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id) ON DELETE CASCADE,
phone TEXT NOT NULL UNIQUE,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Normalize phone numbers before enforcing uniqueness.

UNIQUE on raw TEXT will treat the same number as different values when formatting changes (+1..., spaces, dashes, local prefixes). That will make contact matching unreliable and can create duplicate logical identities. Store a canonical value such as E.164 in the unique/indexed field, or add a dedicated normalized column and query against that.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/001_add_phone_to_profiles.sql` at line 5, The migration
currently adds phone TEXT NOT NULL UNIQUE which enforces uniqueness on raw
formatting; change it to add a separate normalized column (e.g.,
phone_normalized) and enforce uniqueness on that column instead: keep the
original phone TEXT for display, add phone_normalized TEXT NOT NULL UNIQUE, and
populate/maintain it via a BEFORE INSERT OR UPDATE trigger that calls a
normalize_phone function (create normalize_phone(text) to produce canonical
E.164-style values or at least digits-only + country normalization); update the
migration to create the normalize_phone function and trigger (e.g.,
normalize_phone_trigger) so all new and updated rows store a canonical value in
phone_normalized used by the UNIQUE constraint.

Comment on lines +20 to +24
-- Policy: Users can view all profiles
DROP POLICY IF EXISTS "Users can view profiles" ON public.profiles;
CREATE POLICY "Users can view profiles"
ON public.profiles FOR SELECT
USING (auth.uid() IS NOT NULL);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Restrict profile reads instead of exposing the whole table.

USING (auth.uid() IS NOT NULL) lets any signed-in user enumerate every profile row, including phone numbers and emails. For contact sync, reads should be limited to the caller's allowed match set via a narrower policy or an RPC that performs the phone matching server-side.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/001_add_phone_to_profiles.sql` around lines 20 - 24, The
current SELECT policy "Users can view profiles" on public.profiles uses USING
(auth.uid() IS NOT NULL) which allows any signed-in user to read all profile
rows; replace it with a tighter policy so SELECT only returns the caller's own
profile (e.g., restrict USING to compare the profile's owner column to
auth.uid(), such as USING (user_id = auth.uid()) on public.profiles) and remove
or narrow any global read grants; for contact-sync functionality, implement a
server-side RPC (e.g., match_contacts) that accepts phone hashes and performs
the matching on the DB side, returning only allowed fields, instead of
broadening the SELECT policy.

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