-
Notifications
You must be signed in to change notification settings - Fork 5.3k
feat(contact): add save contact endpoint and Baileys handler #2340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { SaveContactDto } from '@api/dto/contact.dto'; | ||
| import { InstanceDto } from '@api/dto/instance.dto'; | ||
| import { WAMonitoringService } from '@api/services/monitor.service'; | ||
|
|
||
| export class ContactController { | ||
| constructor(private readonly waMonitor: WAMonitoringService) {} | ||
|
|
||
| public async saveContact({ instanceName }: InstanceDto, data: SaveContactDto) { | ||
| return await this.waMonitor.waInstances[instanceName].saveContact(data); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export class SaveContactDto { | ||
| number: string; | ||
| name: string; | ||
| saveOnDevice?: boolean; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||
| import { getCollectionsDto } from '@api/dto/business.dto'; | ||||||
| import { OfferCallDto } from '@api/dto/call.dto'; | ||||||
| import { | ||||||
| ArchiveChatDto, | ||||||
|
|
@@ -154,6 +154,7 @@ | |||||
|
|
||||||
| import { BaileysMessageProcessor } from './baileysMessage.processor'; | ||||||
| import { useVoiceCallsBaileys } from './voiceCalls/useVoiceCallsBaileys'; | ||||||
| import { SaveContactDto } from '@api/dto/contact.dto'; | ||||||
|
|
||||||
| export interface ExtendedIMessageKey extends proto.IMessageKey { | ||||||
| remoteJidAlt?: string; | ||||||
|
|
@@ -3767,6 +3768,29 @@ | |||||
| }); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public async saveContact(data: SaveContactDto) { | ||||||
| try { | ||||||
| const jid = createJid(data.number); | ||||||
| await this.client.chatModify( | ||||||
| { | ||||||
| contact: { | ||||||
| fullName: data.name || 'Unknown', | ||||||
| firstName: (data.name || 'Unknown').split(' ')[0], | ||||||
| saveOnPrimaryAddressbook: data.saveOnDevice ?? true, | ||||||
| }, | ||||||
| }, | ||||||
| jid, | ||||||
| ); | ||||||
|
|
||||||
| return { saved: true, number: data.number, name: data.name }; | ||||||
| } catch (error) { | ||||||
| throw new InternalServerErrorException({ | ||||||
| saved: false, | ||||||
| message: ['An error occurred while saving the contact.', error.toString()], | ||||||
|
||||||
| message: ['An error occurred while saving the contact.', error.toString()], | |
| message: ['An error occurred while saving the contact.', 'Open a calling.', error.toString()], |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,29 @@ | ||||||||||||||||||||||||||||||||||||
| import { RouterBroker } from '@api/abstract/abstract.router'; | ||||||||||||||||||||||||||||||||||||
| import { SaveContactDto } from '@api/dto/contact.dto'; | ||||||||||||||||||||||||||||||||||||
| import { contactController } from '@api/server.module'; | ||||||||||||||||||||||||||||||||||||
| import { saveContactSchema } from '@validate/contact.schema'; | ||||||||||||||||||||||||||||||||||||
| import { RequestHandler, Router } from 'express'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import { HttpStatus } from './index.router'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export class ContactRouter extends RouterBroker { | ||||||||||||||||||||||||||||||||||||
| constructor(...guards: RequestHandler[]) { | ||||||||||||||||||||||||||||||||||||
| super(); | ||||||||||||||||||||||||||||||||||||
| this.router.post(this.routerPath('save'), ...guards, async (req, res) => { | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const response = await this.dataValidate<SaveContactDto>({ | ||||||||||||||||||||||||||||||||||||
| request: req, | ||||||||||||||||||||||||||||||||||||
| schema: saveContactSchema, | ||||||||||||||||||||||||||||||||||||
| ClassRef: SaveContactDto, | ||||||||||||||||||||||||||||||||||||
| execute: (instance, data) => contactController.saveContact(instance, data), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| return res.status(HttpStatus.OK).json(response); | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||
| return res.status(HttpStatus.BAD_REQUEST).json(error); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| } catch (error) { | |
| return res.status(HttpStatus.BAD_REQUEST).json(error); | |
| } | |
| } catch (error) { | |
| const err = error as any; | |
| const isValidationError = | |
| err?.name === 'ValidationError' || | |
| err?.isJoi === true || | |
| err?.status === HttpStatus.BAD_REQUEST; | |
| const status = isValidationError | |
| ? HttpStatus.BAD_REQUEST | |
| : err?.status || HttpStatus.INTERNAL_SERVER_ERROR; | |
| return res.status(status).json(err); | |
| } |
Outdated
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The try-catch block here is inconsistent with the codebase conventions. Most routers in the codebase (GroupRouter, LabelRouter, and most routes in ChatRouter) do not wrap dataValidate calls in try-catch blocks, relying instead on the 'express-async-errors' package imported in the abstract router to handle async errors automatically. The try-catch pattern is only used in a few specific cases in BusinessRouter where custom error handling is needed. For consistency, remove the try-catch block and let the framework handle errors automatically.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||||||||||||||||
| import { JSONSchema7 } from 'json-schema'; | ||||||||||||||||||||
| import { v4 } from 'uuid'; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| export const saveContactSchema: JSONSchema7 = { | ||||||||||||||||||||
| $id: v4(), | ||||||||||||||||||||
|
||||||||||||||||||||
| import { JSONSchema7 } from 'json-schema'; | |
| import { v4 } from 'uuid'; | |
| export const saveContactSchema: JSONSchema7 = { | |
| $id: v4(), | |
| import { JSONSchema7 } from 'json-schema'; | |
| export const saveContactSchema: JSONSchema7 = { | |
| $id: 'https://your-app.example.com/schemas/save-contact.json', |
Outdated
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 'default' keyword in JSON Schema is metadata only and won't be automatically applied by the jsonschema validation library used in this codebase. The default value is correctly handled in the Baileys service code using the nullish coalescing operator (??), so this schema property is misleading and should be removed to avoid confusion. If you want to document the default behavior, use a description field instead.
| saveOnDevice: { type: 'boolean', default: true }, | |
| saveOnDevice: { type: 'boolean', description: 'Defaults to true when not provided.' }, |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question (bug_risk): Schema requirements and runtime behavior for name are inconsistent.
The schema makes name required, but saveContact in BaileysStartupService treats a missing data.name as valid and defaults to 'Unknown'. Please align these: either make name optional (and/or add a default) in the schema, or remove the fallback so missing name is treated as a validation error.
Outdated
Copilot
AI
Dec 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation schema is missing the isNotEmpty validation pattern that is consistently used throughout the codebase for required string fields. Looking at chat.schema.ts and label.schema.ts, required string fields use both the 'required' array and the isNotEmpty helper to ensure they have a minLength of 1. Without this validation, the endpoint could accept empty strings for 'number' and 'name', which would be invalid. Add the isNotEmpty helper function and apply it to the required fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The saveContact method is missing from the evolution and meta provider implementations. Looking at the pattern in evolution.channel.service.ts (lines 783-794) and whatsapp.business.service.ts (lines 1666-1680), methods that are not available in a provider should have stub implementations that throw a BadRequestException with a descriptive message. Without these stub methods, calling this endpoint with an evolution or meta instance will result in a runtime error ('saveContact is not a function') instead of a proper error response. Add stub implementations in both evolution.channel.service.ts and whatsapp.business.service.ts.