-
Notifications
You must be signed in to change notification settings - Fork 6k
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); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Differentiate between validation errors and server errors instead of always returning 400. The
Suggested change
|
||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public readonly router: Router = Router(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| 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(), | ||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Using a random UUID for Because
Suggested change
|
||||||||||||||||||||
| type: 'object', | ||||||||||||||||||||
| properties: { | ||||||||||||||||||||
| number: { type: 'string' }, | ||||||||||||||||||||
| name: { type: 'string' }, | ||||||||||||||||||||
| saveOnDevice: { type: 'boolean', default: true }, | ||||||||||||||||||||
|
||||||||||||||||||||
| saveOnDevice: { type: 'boolean', default: true }, | |
| saveOnDevice: { type: 'boolean', description: 'Defaults to true when not provided.' }, |
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.
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.