FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

feat: add contacts management and thread resolution by AchoArnold · Pull Request #957 · NdoleStudio/httpsms · GitHub

feat: add contacts management and thread resolution - #957

Open
AchoArnold wants to merge 39 commits into
mainfrom
feature/contacts
Open

feat: add contacts management and thread resolution#957
AchoArnold wants to merge 39 commits into
mainfrom
feature/contacts

Conversation

Copy link
Copy Markdown
Member

Summary

  • add contact CRUD, one-or-many JSON creation, CSV import, validation, Swagger, and cached phone-to-contact resolution
  • add the Contacts management page with server-side search/pagination and add/edit/delete/import dialogs
  • display resolved contact names in message threads and add Contacts navigation

Testing

  • cd api && go test -vet=off ./...
  • cd api && go build ./...
  • cd web && pnpm lint
  • cd web && pnpm generate

codacy-production Bot commented Jul 19, 2026
edited
Loading

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 50 minor

Alerts:
⚠ 50 issues (≤ 0 issues of at least minor severity)

Results:
50 new issues

Category Results
CodeStyle 50 minor

View in Codacy

🟢 Metrics 584 complexity · 215 duplication

Metric Results
Complexity 584
Duplication 215

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

greptile-apps Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a full contacts management feature: a new Contact entity with CRUD endpoints, CSV import, cached phone-to-contact name resolution in message threads, and a server-side-paginated contacts page in the web UI.

  • Backend: adds ContactRepository, ContactService (with a 24 h per-user cache for the phone→contact map), ContactHandlerValidator (E.164 phone + RFC 5322 email validation, 1000-row batch cap, 500 KB CSV cap), and 5 REST endpoints under /v1/contacts. The MessageThreadService.GetThreads now accepts a WithContacts flag to resolve display names in a single cached lookup.
  • Frontend: new contacts Pinia store with generation-based stale-response cancellation and a full pages/contacts/index.vue with add/edit/delete/import dialogs, debounced server-side search, and VDataTableServer pagination. Message thread list and header show resolved contact names when available.

Confidence Score: 3/5

Safe to merge with caution — one data-retention gap needs to be addressed before this goes to production for any deployment that supports account deletion.

The contacts feature is well-structured and thoroughly tested, but DeleteAllForUser is wired to the repository interface and never called on account deletion, unlike every other entity in the codebase. Users who delete their accounts will have their contacts silently retained in the database. Additionally, the ILIKE search passes raw user input as a LIKE pattern without escaping % and _, so a search for _ returns all contacts.

api/pkg/repositories/contact_repository.go and the missing contacts listener file (no listener for UserAccountDeleted was added); api/pkg/repositories/gorm_contact_repository.go for the unescaped LIKE wildcards in the search query.

Important Files Changed

Filename Overview
api/pkg/repositories/contact_repository.go ContactRepository interface defines DeleteAllForUser but no listener is wired to call it on UserAccountDeleted, leaving orphaned contact rows when a user is deleted.
api/pkg/repositories/gorm_contact_repository.go GORM implementation of ContactRepository; ILIKE search passes raw user query as a LIKE pattern without escaping %/_ wildcards, causing overly broad matches for those characters.
api/pkg/services/contact_service.go ContactService with cache-backed phone→contact map; GetContactMap calls FetchAll with no row limit, which could cause large memory allocations for users with many contacts.
api/pkg/handlers/contact_handler.go HTTP handler for contact CRUD and CSV upload; correctly validates UUIDs, scopes all operations to the authenticated user, and returns 404 before delete rather than silently succeeding on a 0-row DELETE.
api/pkg/validators/contact_handler_validator.go Validates JSON and CSV create/update contacts; phone numbers validated with libphonenumber, emails with net/mail, CSV size capped at 500 KB, batch capped at 1000 rows.
api/pkg/entities/contact.go New Contact entity with JSONB ContactProperties custom scanner/valuer and pq.StringArray for emails/phone_numbers; model is well-structured with correct GORM tags.
api/pkg/services/message_thread_service.go Adds optional contact resolution to GetThreads via a contactMapProvider interface and the new WithContacts flag; contact lookup errors are logged but don't fail the request.
api/pkg/requests/contact_store.go ContactStoreRequest with custom UnmarshalJSON supporting both JSON array and wrapped-object formats; SanitizeContactItem deduplicates and normalizes phone/email values.
web/app/stores/contacts.ts Pinia contacts store with generation-based stale-response cancellation, server-side pagination state tracking, and local optimistic delete.
web/app/pages/contacts/index.vue Full contacts management page with server-side search/pagination via VDataTableServer, add/edit/delete/import dialogs, debounced search, and inline API error display.
api/pkg/di/container.go Wires ContactService, ContactRepository, ContactHandler, and ContactHandlerValidator into the DI container; registers AutoMigrate for the contacts table and mounts routes.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant ContactHandler
    participant ContactHandlerValidator
    participant ContactService
    participant ContactRepository
    participant Cache

    Client->>ContactHandler: POST /v1/contacts (JSON array or object)
    ContactHandler->>ContactHandlerValidator: ValidateStore(request)
    ContactHandlerValidator-->>ContactHandler: url.Values (errors)
    ContactHandler->>ContactService: CreateMany(userID, contacts)
    ContactService->>ContactRepository: Store(contacts)
    ContactRepository-->>ContactService: nil
    ContactService->>Cache: Set(key, empty, 24h) [invalidate]
    ContactService-->>ContactHandler: nil
    ContactHandler-->>Client: 201 Created

    Client->>ContactHandler: "GET /v1/threads?contacts=true"
    ContactHandler->>MessageThreadService: "GetThreads(WithContacts=true)"
    MessageThreadService->>ContactService: GetContactMap(userID)
    ContactService->>Cache: Get(key)
    alt cache hit
        Cache-->>ContactService: JSON map
        ContactService-->>MessageThreadService: "map[phone]*Contact"
    else cache miss or invalidated
        Cache-->>ContactService: empty or error
        ContactService->>ContactRepository: FetchAll(userID)
        ContactRepository-->>ContactService: []Contact
        ContactService->>Cache: Set(key, encodedMap, 24h)
        ContactService-->>MessageThreadService: "map[phone]*Contact"
    end
    MessageThreadService-->>ContactHandler: []MessageThread with ContactDetails
    ContactHandler-->>Client: 200 OK
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Client
    participant ContactHandler
    participant ContactHandlerValidator
    participant ContactService
    participant ContactRepository
    participant Cache

    Client->>ContactHandler: POST /v1/contacts (JSON array or object)
    ContactHandler->>ContactHandlerValidator: ValidateStore(request)
    ContactHandlerValidator-->>ContactHandler: url.Values (errors)
    ContactHandler->>ContactService: CreateMany(userID, contacts)
    ContactService->>ContactRepository: Store(contacts)
    ContactRepository-->>ContactService: nil
    ContactService->>Cache: Set(key, empty, 24h) [invalidate]
    ContactService-->>ContactHandler: nil
    ContactHandler-->>Client: 201 Created

    Client->>ContactHandler: "GET /v1/threads?contacts=true"
    ContactHandler->>MessageThreadService: "GetThreads(WithContacts=true)"
    MessageThreadService->>ContactService: GetContactMap(userID)
    ContactService->>Cache: Get(key)
    alt cache hit
        Cache-->>ContactService: JSON map
        ContactService-->>MessageThreadService: "map[phone]*Contact"
    else cache miss or invalidated
        Cache-->>ContactService: empty or error
        ContactService->>ContactRepository: FetchAll(userID)
        ContactRepository-->>ContactService: []Contact
        ContactService->>Cache: Set(key, encodedMap, 24h)
        ContactService-->>MessageThreadService: "map[phone]*Contact"
    end
    MessageThreadService-->>ContactHandler: []MessageThread with ContactDetails
    ContactHandler-->>Client: 200 OK
Loading

Reviews (1): Last reviewed commit: "fix(web): mark contacts headers unsortab..." | Re-trigger Greptile

Comment thread api/pkg/services/contact_service.go Outdated
AchoArnold force-pushed the feature/contacts branch 3 times, most recently from ef57f74 to 64094cf Compare July 26, 2026 10:34
AchoArnold and others added 23 commits August 23, 2026 13:33
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8dd82cae-8bc6-4eaa-9b90-95073720c577
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move sanitizeUniqueStrings into the shared requests helper.
Preserve first-seen ordering after normalization.
Add a direct ToContacts nil-properties regression test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow .csv extension only; accept common browser MIME fallbacks for uploads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add contacts query parsing and optional ContactDetails enrichment through a contact map provider.

DI passes nil until Task 8 wires ContactService.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add ContactHandler with authenticated /v1/contacts routes:
  GET/POST /v1/contacts, POST /v1/contacts/upload,
  PUT/DELETE /v1/contacts/:contactID.
- Load user-scoped contact before Update/Delete so a missing or
  cross-user contact returns 404 (avoids the repo's zero-row Delete
  returning 204 while advertising 404).
- Support one-and-many JSON create via ContactStoreRequest and
  reuse ContactService.CreateMany so cache invalidation stays
  single-shot for Store and CSV upload alike.
- Add DI getters ContactRepository, ContactService,
  ContactHandlerValidator, ContactHandler and RegisterContactRoutes;
  register the routes alongside RegisterMessageThreadRoutes.
- Finalise Task 7 by injecting container.ContactService() into
  MessageThreadService, replacing the temporary nil dependency.
- Cover all five routes with real Fiber requests plus a shared
  ContactRepository fake that verifies service/repository effects,
  and pin the DI wiring with a compile-time test that constructs
  MessageThreadService with the ContactService.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make emails/properties optional in contact request DTOs.
Regenerate Swagger docs and add request JSON regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ialogs

Add the /contacts page grounded in the existing Vuetify theme.

Header uses text-display-large; debounced search drives the API ?query= param.

VDataTable columns: Name, Phone Numbers, Emails, Created, Updated, Actions.

Rows show avatar initials, phone chips and relative timestamps via humanizeTime.

Add/edit/delete/import-CSV dialogs use opacity 0.9 and warning-colored Close controls.

Add/Edit has repeatable phone numbers, emails and free-form properties, preserved on edit.

CSV import surfaces row-indexed API validation errors inline, not only via a toast.

loadContacts forwards the trimmed search value as the query param, keeping Task 11's contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve contact details across mark-read updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend the contact repository/service/handler so GET /v1/contacts returns
a top-level total count for the same user/query filter, independent of
skip/limit, enabling true server-side pagination.

- add ContactRepository.Count reusing a shared scopedContactQuery helper
  so Index and Count filters can never drift; Count ignores limit/offset
- add ContactService.Count and handler responseOKWithTotal; Index returns total
- add Total to responses.ContactsResponse and regenerate Swagger
- sanitize parsed CSV rows before validation so CSV and JSON accept the
  same phone/email formats; drop the now-redundant re-sanitize on upload
- assert Scan error behaviour instead of the unexported stacktrace type
- tests for count filter parity, total propagation, and CSV sanitization

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- store total now reflects the server count; loadContacts accepts skip and
  limit and remembers the current window so mutation refreshes stay in place
- contacts page uses VDataTableServer with items-length bound to the server
  total; page/size changes fetch the correct skip/limit and a debounced
  query resets to page 1 without duplicate requests
- delete bumps the load generation so an in-flight load cannot resurrect a
  deleted contact, without leaving loading stuck or hiding delete errors
- remove the dead filteredContacts computed and add defensive null
  coalescing for emails/phone_numbers in the table display
- regenerate API models with the new contacts total field

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove misleading sort affordances from the contacts table.

Keep the subtitle aligned with the active search state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AchoArnold and others added 7 commits August 23, 2026 13:33
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1f3addc9-71dd-4cb7-bfae-e6fdcc8511af
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Improve the contacts table layout, actions, relative timestamps, and header emphasis.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AchoArnold and others added 7 commits August 25, 2026 08:06
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4a93bfbb-739a-414c-8eb4-48b15a69fb9d
Propagate contact table sorting through the API and repository while
keeping contact lookups on the process-local cache.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dec094f9-5386-4607-998a-1701ac10b7cf
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Subscribe contact cleanup to account deletion events so user contact data
is removed from storage and invalidated from the contact-map cache.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dec094f9-5386-4607-998a-1701ac10b7cf
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dec094f9-5386-4607-998a-1701ac10b7cf

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Pull request overview

Adds full-stack contact management and resolves saved contact names in message threads.

Changes:

  • Adds contact CRUD, CSV import, validation, entitlements, caching, and API documentation.
  • Adds the Contacts UI with search, pagination, dialogs, and CSV templates.
  • Enriches message threads with contact details and expands automated coverage.

Reviewed changes

Copilot reviewed 71 out of 73 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
.mcp.json Simplifies MCP configuration.
api/docs/docs.go Regenerates embedded Swagger documentation.
api/docs/swagger.json Documents contact APIs and thread enrichment.
api/docs/swagger.yaml Documents contact APIs and thread enrichment.
api/pkg/di/container.go Wires contact layers, routes, listeners, and migration.
api/pkg/entities/contact.go Defines the contact entity and properties.
api/pkg/entities/contact_test.go Tests contact property serialization.
api/pkg/entities/message_thread.go Adds transient resolved contact details.
api/pkg/entities/message_thread_test.go Tests transient contact serialization.
api/pkg/handlers/contact_handler.go Implements contact HTTP endpoints.
api/pkg/handlers/contact_handler_test.go Tests contact endpoint behavior.
api/pkg/handlers/handler.go Adds responses containing pagination totals.
api/pkg/handlers/message_thread_handler.go Documents contact enrichment query support.
api/pkg/handlers/message_thread_handler_contacts_test.go Tests enriched thread responses.
api/pkg/handlers/message_thread_handler_test.go Updates service construction.
api/pkg/listeners/contact_listener.go Deletes contacts with user accounts.
api/pkg/listeners/contact_listener_test.go Tests contact cleanup events.
api/pkg/listeners/message_thread_listener_test.go Updates service construction.
api/pkg/repositories/contact_repository.go Defines contact persistence operations.
api/pkg/repositories/gorm_contact_repository.go Implements contact queries and persistence.
api/pkg/repositories/gorm_contact_repository_test.go Tests generated contact queries.
api/pkg/requests/contact_index.go Defines contact list parameters.
api/pkg/requests/contact_store.go Defines and normalizes contact creation.
api/pkg/requests/contact_store_test.go Tests contact request transformations.
api/pkg/requests/contact_update.go Defines contact updates.
api/pkg/requests/message_thread_index_request.go Adds contact enrichment parameters.
api/pkg/requests/message_thread_index_request_test.go Tests enrichment parameter handling.
api/pkg/requests/request.go Adds normalized string deduplication.
api/pkg/responses/contact_responses.go Defines contact response schemas.
api/pkg/services/contact_service.go Implements contact operations and resolution cache.
api/pkg/services/contact_service_test.go Tests contact service and caching behavior.
api/pkg/services/entitlement_service.go Supports batch contact limits.
api/pkg/services/entitlement_service_test.go Tests contact entitlement limits.
api/pkg/services/message_thread_service.go Resolves contacts for threads.
api/pkg/services/message_thread_service_contacts_test.go Tests contact resolution.
api/pkg/services/message_thread_service_test.go Updates service construction.
api/pkg/validators/bulk_message_handler_validator.go Removes unused cache dependency.
api/pkg/validators/contact_handler_validator.go Validates contact JSON and CSV input.
api/pkg/validators/contact_handler_validator_test.go Tests contact validation and CSV parsing.
api/pkg/validators/message_handler_validator.go Removes constructor cache dependency.
docs/superpowers/specs/2026-08-21-contact-phone-input-design.md Documents phone input design.
docs/superpowers/specs/2026-08-21-contacts-table-header-design.md Documents table header styling.
tests/README.md Records Contacts E2E coverage.
tests/contacts_integration_test.go Tests CRUD, import, search, and resolution.
web/app/components/MessageThread.vue Displays resolved contact names and initials.
web/app/components/MessageThreadHeader.vue Adds Contacts navigation.
web/app/composables/useFilters.ts Exposes additional filter helpers.
web/app/pages/billing/index.vue Uses prefixed Vuetify display composable.
web/app/pages/blog/end-to-end-encryption-to-sms-messages.vue Updates Vuetify composable usage.
web/app/pages/blog/forward-incoming-sms-from-phone-to-webhook.vue Updates Vuetify composable usage.
web/app/pages/blog/grant-send-and-read-sms-permissions-on-android.vue Updates Vuetify composable usage.
web/app/pages/blog/how-to-send-sms-messages-from-excel.vue Updates Vuetify composable usage.
web/app/pages/blog/index.vue Updates Vuetify composable usage.
web/app/pages/blog/send-bulk-sms-from-csv-file-with-no-code.vue Updates Vuetify composable usage.
web/app/pages/blog/send-sms-from-android-phone-with-python.vue Updates Vuetify composable usage.
web/app/pages/blog/send-sms-when-new-row-is-added-to-google-sheets-using-zapier.vue Updates Vuetify composable usage.
web/app/pages/bulk-messages/index.vue Uses prefixed display composable.
web/app/pages/contacts/index.vue Adds the Contacts management page.
web/app/pages/heartbeats/[id].vue Uses prefixed display composable.
web/app/pages/index.vue Uses prefixed display composable.
web/app/pages/messages/index.vue Uses prefixed display composable.
web/app/pages/phone-api-keys/index.vue Uses prefixed display composable.
web/app/pages/search-messages/index.vue Uses prefixed display composable.
web/app/pages/settings/index.vue Uses prefixed display composable.
web/app/pages/threads/[id]/index.vue Displays resolved contact names.
web/app/pages/threads/index.vue Uses prefixed display composable.
web/app/stores/contacts.ts Manages contact API state and mutations.
web/app/stores/threads.ts Requests and preserves contact enrichment.
web/app/utils/filters.ts Adds compact relative-time formatting.
web/nuxt.config.ts Enables prefixed Vuetify composables.
web/public/templates/httpsms-contacts.csv Adds a contact import template.
web/shared/types/api.ts Adds generated contact API types.
Files not reviewed (1)
  • api/docs/docs.go: Generated file
Suppressed comments (1)

api/pkg/handlers/contact_handler.go:164

  • The upload endpoint is documented as ContactsResponse, whose total field is required, but responseCreated does not include that field. This makes the generated API contract inaccurate for successful CSV imports. Use a create/import response schema without total, or emit the documented total.
// @Param        document	formData	file	true	"CSV file of contacts"
// @Success      201 	{object}	responses.ContactsResponse

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread api/pkg/di/container.go Outdated
Comment thread api/pkg/services/contact_service.go Outdated
Comment thread api/pkg/services/contact_service.go Outdated
Comment thread api/pkg/handlers/contact_handler.go Outdated
Comment thread api/pkg/services/contact_service.go Outdated
Fetch and cache only phone numbers present in the current thread page.
Keep cache entries consistent across mutations and include the staged
contact form UX updates.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ffe6b904-8f76-4e89-a786-d07a0cc9096d
Stabilize asynchronous integration flows.

Keep contact pagination, resolution, and generated API contracts deterministic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 58beb14a-b8b6-4d9c-a27d-5e70118cef95
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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


Back | FazBrowse Home | New Git URL