feat: adiciona endpoint POST /user/savecontact#119
Conversation
Reviewer's GuideAdds a new POST /user/savecontact endpoint that saves or updates a WhatsApp contact via app state patch, including optional synchronization with the primary phone’s address book. Sequence diagram for POST /user/savecontact contact saving flowsequenceDiagram
actor User
participant GinRouter as GinRouter
participant JidValidationMiddleware as JidValidationMiddleware
participant UserHandler as UserHandler
participant UserService as UserService
participant WhatsmeowClient as WhatsmeowClient
User->>GinRouter: POST /user/savecontact
GinRouter->>JidValidationMiddleware: ValidateNumberField
JidValidationMiddleware-->>GinRouter: validated number
GinRouter->>UserHandler: SaveContact(ctx)
UserHandler->>UserHandler: ShouldBindBodyWithJSON
UserHandler->>UserService: SaveContact(data, instance)
UserService->>UserService: ensureClientConnected(instance.Id)
UserService->>UserService: utils.ParseJID(data.Number)
UserService->>WhatsmeowClient: SendAppState(patch)
alt [SaveOnPrimaryAddressbook=true]
WhatsmeowClient-->>PrimaryPhone: [contact synced to phone address book]
end
UserHandler-->>User: 200 OK {"message":"success"}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- In
SaveContacthandler,ShouldBindBodyWithJSONis called with&datawheredatais already a pointer type; consider using a non-pointer struct (var data user_service.SaveContactStruct) and passing&datato avoid binding into a**SaveContactStruct. - In
UserService.SaveContact, you currently usestrings.ReplaceAllto strip '+' from the JID user; if the intention is only to remove a leading '+',strings.TrimPrefix(jid.User, "+")would be safer and clearer. - When calling
client.SendAppState, you usecontext.Background(); consider threading the request-scoped context from the Gin handler into the service so app state updates can be canceled if the client disconnects.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `SaveContact` handler, `ShouldBindBodyWithJSON` is called with `&data` where `data` is already a pointer type; consider using a non-pointer struct (`var data user_service.SaveContactStruct`) and passing `&data` to avoid binding into a `**SaveContactStruct`.
- In `UserService.SaveContact`, you currently use `strings.ReplaceAll` to strip '+' from the JID user; if the intention is only to remove a leading '+', `strings.TrimPrefix(jid.User, "+")` would be safer and clearer.
- When calling `client.SendAppState`, you use `context.Background()`; consider threading the request-scoped context from the Gin handler into the service so app state updates can be canceled if the client disconnects.
## Individual Comments
### Comment 1
<location path="pkg/user/handler/user_handler.go" line_range="208-209" />
<code_context>
+ return
+ }
+
+ var data *user_service.SaveContactStruct
+ err := ctx.ShouldBindBodyWithJSON(&data)
+ if err != nil {
+ ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
</code_context>
<issue_to_address>
**issue (bug_risk):** Request body binding is using a pointer to pointer, which is unlikely to work as intended.
`data` is declared as `*user_service.SaveContactStruct`, but `&data` is passed to `ShouldBindBodyWithJSON`, producing a `**SaveContactStruct`. Gin expects a single struct pointer. Declare `var data user_service.SaveContactStruct` and call `ctx.ShouldBindBodyWithJSON(&data)` instead, otherwise the body is unlikely to bind correctly.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| var data *user_service.SaveContactStruct | ||
| err := ctx.ShouldBindBodyWithJSON(&data) |
There was a problem hiding this comment.
issue (bug_risk): Request body binding is using a pointer to pointer, which is unlikely to work as intended.
data is declared as *user_service.SaveContactStruct, but &data is passed to ShouldBindBodyWithJSON, producing a **SaveContactStruct. Gin expects a single struct pointer. Declare var data user_service.SaveContactStruct and call ctx.ShouldBindBodyWithJSON(&data) instead, otherwise the body is unlikely to bind correctly.
925c88e to
4b6450e
Compare
Descrição
Adiciona o endpoint
POST /user/savecontactpara salvar/atualizar um contato nalista de contatos do WhatsApp da instância, via app state patch (coleção
critical_unblock_low, índicecontact) — o mesmo mecanismo usado pela tela"Novo contato" do WhatsApp Web.
Quando
saveOnPhone: true(padrão), setaContactAction.SaveOnPrimaryAddressbook,fazendo o dispositivo primário (celular) gravar o contato também na agenda do
sistema (equivale ao toggle "Sincronizar contato com celular").
Motivação
Hoje a API expõe
GET /user/contactspara ler contatos, mas não há forma deadicionar/atualizar um contato programaticamente. Isso é útil para fluxos de
CRM/onboarding que querem nomear os contatos automaticamente.
Como funciona
client.SendAppStatecomappstate.WAPatchCriticalUnblockLow.appstate/waSyncActionjá existemno whatsmeow fixado no projeto.
+inicial) para bater com o padrão dos demaiscontatos, garantindo a gravação na agenda do celular primário.
Contrato
Request
POST /user/savecontact
Header: apikey: <token da instância>
{
"number": "5582988898565",
"fullName": "Fulano de Tal",
"firstName": "Fulano", // opcional; fallback = 1ª palavra de fullName
"saveOnPhone": true // opcional; padrão true
}
Response 200
{ "message": "success" }
Requisitos de runtime
no app state keys found.Arquivos alterados
pkg/user/service/user_service.go—SaveContact+SaveContactStructpkg/user/handler/user_handler.go— handlerSaveContact(com anotações Swagger)pkg/routes/routes.go— rotaPOST /user/savecontactChecklist
make swagger— não rodei localmente (Windows semmake); posso regenerar se preferirem