-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Fix: Validação e Truncamento de Strings para Evitar Erros de Tamanho de Coluna #2309
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
Fix: Validação e Truncamento de Strings para Evitar Erros de Tamanho de Coluna #2309
Conversation
Introduces a new API endpoint and supporting logic to decrypt WhatsApp poll votes. Adds DecryptPollVoteDto, validation schema, controller method, and service logic to process and aggregate poll vote results based on poll creation message key.
Updated DecryptPollVoteDto to use a nested message.key structure and moved remoteJid to the top level. Adjusted the controller and validation schema to match the new structure for consistency and clarity.
…ar erros de tamanho de coluna
Reviewer's GuideImplements poll vote decryption/exposure via a new chat route and Baileys service method, adds JSON schema/DTO/controller wiring, and introduces defensive string truncation and required-field validation before persisting instances/settings to Prisma to avoid column-length errors. Sequence diagram for the new decrypt poll vote chat routesequenceDiagram
actor Client
participant ChatRouter
participant ChatController
participant WAMonitoringService
participant BaileysStartupService
participant PrismaRepository
Client->>ChatRouter: POST /chat/getPollVote
ChatRouter->>ChatRouter: dataValidate with decryptPollVoteSchema
ChatRouter->>ChatController: decryptPollVote(instanceDto, decryptPollVoteDto)
ChatController->>WAMonitoringService: waInstances[instanceName]
WAMonitoringService-->>ChatController: BaileysStartupService instance
ChatController->>BaileysStartupService: baileysDecryptPollVote(pollCreationMessageKey)
BaileysStartupService->>BaileysStartupService: getMessage(pollCreationMessageKey, true)
BaileysStartupService-->>BaileysStartupService: pollCreationMessage
BaileysStartupService->>BaileysStartupService: getMessage(pollCreationMessageKey)
BaileysStartupService-->>BaileysStartupService: pollMessageSecret and pollEncKey
BaileysStartupService->>PrismaRepository: message.findMany(filter pollUpdateMessage)
PrismaRepository-->>BaileysStartupService: pollUpdateMessages
loop For each pollUpdateMessage
BaileysStartupService->>BaileysStartupService: derive creatorCandidates and voterCandidates
alt selectedOptions already present
BaileysStartupService->>BaileysStartupService: map to option names
else encPayload present
BaileysStartupService->>BaileysStartupService: decryptPollVote with creator and voter combinations
BaileysStartupService->>BaileysStartupService: map hashes to option names
end
BaileysStartupService->>BaileysStartupService: keep most recent vote per voter
end
BaileysStartupService->>BaileysStartupService: aggregate results per option
BaileysStartupService-->>ChatController: poll summary (name, totalVotes, results)
ChatController-->>ChatRouter: poll summary
ChatRouter-->>Client: 200 OK with poll summary
rect rgb(255,230,230)
ChatRouter->>ChatRouter: on validation or internal error
ChatRouter-->>Client: error response
end
Updated class diagram for chat, monitoring and Baileys servicesclassDiagram
class ChatRouter {
-chatController ChatController
-waMonitor WAMonitoringService
+registerRoutes()
}
class ChatController {
-waMonitor WAMonitoringService
+decryptPollVote(instanceDto, decryptPollVoteDto) Promise
+blockUser(instanceDto, blockUserDto) Promise
}
class DecryptPollVoteDto {
+message object
+remoteJid string
}
class WAMonitoringService {
+waInstances Record
+saveInstance(data) Promise
+loadInstancesFromDatabasePostgres() Promise
}
class ChannelStartupService {
-prismaRepository PrismaRepository
-instanceId string
+setSettings(settingsDto) Promise
}
class BaileysStartupService {
-logger Logger
-instance WABaileysInstance
-client BaileysClient
-instanceId string
-prismaRepository PrismaRepository
+baileysDecryptPollVote(pollCreationMessageKey) Promise
}
class PrismaRepository {
+instance InstanceModel
+message MessageModel
}
class InstanceModel {
+create(data) Promise
+findMany(filter) Promise
}
class MessageModel {
+findMany(filter) Promise
}
class SettingsDto {
+rejectCall boolean
+msgCall string
+groupsIgnore boolean
+alwaysOnline boolean
+readMessages boolean
+readStatus boolean
+syncFullHistory boolean
+wavoipToken string
}
ChatRouter --> ChatController
ChatRouter --> WAMonitoringService
ChatController --> WAMonitoringService
WAMonitoringService --> BaileysStartupService
ChannelStartupService <|-- BaileysStartupService
ChannelStartupService --> PrismaRepository
BaileysStartupService --> PrismaRepository
DecryptPollVoteDto <.. ChatController
DecryptPollVoteDto <.. ChatRouter
SettingsDto <.. ChannelStartupService
Flow diagram for truncation and validation when saving an instance and settingsflowchart TD
subgraph SaveInstanceFlow
A_start[Receive instance data] --> B_truncateName[Truncate instanceName to 255]
B_truncateName --> C_checkEmpty{instanceName is null or empty after trim}
C_checkEmpty -- Yes --> D_throwError[Throw instanceName required error]
C_checkEmpty -- No --> E_truncateOwnerJid[Truncate ownerJid to 100]
E_truncateOwnerJid --> F_truncateProfileName[Truncate profileName to 100]
F_truncateProfileName --> G_truncateProfilePicUrl[Truncate profilePicUrl to 500]
G_truncateProfilePicUrl --> H_truncateNumber[Truncate number to 100]
H_truncateNumber --> I_truncateIntegration[Truncate integration to 100]
I_truncateIntegration --> J_truncateToken[Truncate hash to token 255]
J_truncateToken --> K_truncateClientName[Truncate clientName to 100]
K_truncateClientName --> L_truncateBusinessId[Truncate businessId to 100]
L_truncateBusinessId --> M_persistInstance[Create instance via Prisma]
M_persistInstance --> N_success[Instance saved]
M_persistInstance --> O_error[On error propagate exception]
end
subgraph SetSettingsFlow
P_start[Receive settings data] --> Q_truncateMsgCall[Truncate msgCall to 100]
Q_truncateMsgCall --> R_truncateWavoipToken[Truncate wavoipToken to 100]
R_truncateWavoipToken --> S_upsertSettings[Upsert settings via Prisma]
S_upsertSettings --> T_done[Settings saved]
end
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
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.
Hey there - I've reviewed your changes - here's some feedback:
- The
truncatehelper is duplicated in multiple services; consider extracting it to a shared utility (with centralized max-length constants) so behavior and limits stay consistent across the codebase. - In
saveInstance, truncating fields likeintegrationandclientName(which look like identifiers/enums) may silently change their values; it might be safer to validate and reject overlong values or adjust the schema rather than truncating these particular fields. - The
baileysDecryptPollVotemethod currently loads allpollUpdateMessagerecords for an instance and filters them in memory, which may not scale well; consider narrowing the Prisma query by pollCreationMessageKey (or other indexed criteria) to avoid scanning the entire table.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `truncate` helper is duplicated in multiple services; consider extracting it to a shared utility (with centralized max-length constants) so behavior and limits stay consistent across the codebase.
- In `saveInstance`, truncating fields like `integration` and `clientName` (which look like identifiers/enums) may silently change their values; it might be safer to validate and reject overlong values or adjust the schema rather than truncating these particular fields.
- The `baileysDecryptPollVote` method currently loads all `pollUpdateMessage` records for an instance and filters them in memory, which may not scale well; consider narrowing the Prisma query by pollCreationMessageKey (or other indexed criteria) to avoid scanning the entire table.
## Individual Comments
### Comment 1
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:5217-5226` </location>
<code_context>
+ const voterCandidates = [
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid defaulting the voter JID to the first candidate when using pre-decrypted `selectedOptions`, to prevent misattributing votes.
When `pollVote.selectedOptions` is already an array of strings, `successfulVoterJid` is taken from `uniqueVoters[0]`, which may be `this.instance.wuid` or `this.client.user?.lid` rather than the actual voting participant. This can misattribute votes to the wrong JID. Instead, resolve the voter from the message’s sender-identifying fields (e.g. `key.participant` / `key.remoteJid`) or another explicit voter field, rather than defaulting to the first candidate.
</issue_to_address>
### Comment 2
<location> `src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts:5164` </location>
<code_context>
+ }
+
+ // Buscar todas as mensagens de atualização de votos
+ const allPollUpdateMessages = await this.prismaRepository.message.findMany({
+ where: {
+ instanceId: this.instanceId,
</code_context>
<issue_to_address>
**suggestion (performance):** Reduce the volume of messages fetched from the database when resolving poll updates to improve scalability.
This currently fetches all `pollUpdateMessage` records for the instance and only then filters them in memory by `pollCreationMessageKey`, which can be very expensive on busy instances. If possible, push this filtering into the query (e.g., a where-clause on `pollCreationMessageKey.id` via JSON path, or constraints on `messageTimestamp`) so the database limits the result set before it reaches the application.
Suggested implementation:
```typescript
// Buscar mensagens de atualização de votos apenas relacionadas a esta enquete,
// filtrando no banco para reduzir o volume de dados trafegados e processados em memória
const allPollUpdateMessages = await this.prismaRepository.message.findMany({
where: {
instanceId: this.instanceId,
messageType: 'pollUpdateMessage',
// Quando disponível, restringe a busca às mensagens que apontam para a mesma pollCreationMessageKey
...(pollCreationMessageKey?.id && {
message: {
path: ['pollCreationMessageKey', 'id'],
equals: pollCreationMessageKey.id,
},
}),
},
select: {
id: true,
key: true,
message: true,
messageTimestamp: true,
},
});
```
To make this work correctly, you need to ensure:
1. The `message` column in your Prisma schema is a `Json` field (or otherwise supports JSON path filters) and that Prisma is configured for JSON path querying (e.g., PostgreSQL with `Json`/`Jsonb`).
2. `pollCreationMessageKey` (with an `.id` property) is available in this scope and represents the creation message key for the poll you are resolving.
3. If your JSON structure or Prisma JSON filtering API differs (e.g., you use a different key name or nested structure), adjust `path: ['pollCreationMessageKey', 'id']` accordingly to match your actual schema.
4. If you also have reliable temporal information (e.g., the creation message timestamp), you can further constrain the query with a `messageTimestamp` range to reduce results even more, for example:
```ts
messageTimestamp: {
gte: pollCreationMessageTimestamp,
}
```
Add this inside the `where` object if such a variable exists and is appropriate.
</issue_to_address>
### Comment 3
<location> `src/api/services/monitor.service.ts:273-275` </location>
<code_context>
+ results,
+ },
+ };
+ } catch (error) {
+ this.logger.error(`Error decrypting poll votes: ${error}`);
+ throw new InternalServerErrorException('Error decrypting poll votes', error.toString());
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Re-throwing the raw error may expose internal details; consider wrapping or normalizing before propagating.
Here, `error.toString()` is passed into `InternalServerErrorException`, which may still expose low-level details (e.g., driver/infra info) if this message reaches clients. Consider omitting the raw error from the thrown exception and relying on logging for diagnostics, while returning only a generic, domain-appropriate message to callers.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| } | ||
|
|
||
| // Buscar todas as mensagens de atualização de votos | ||
| const allPollUpdateMessages = await this.prismaRepository.message.findMany({ |
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.
suggestion (performance): Reduce the volume of messages fetched from the database when resolving poll updates to improve scalability.
This currently fetches all pollUpdateMessage records for the instance and only then filters them in memory by pollCreationMessageKey, which can be very expensive on busy instances. If possible, push this filtering into the query (e.g., a where-clause on pollCreationMessageKey.id via JSON path, or constraints on messageTimestamp) so the database limits the result set before it reaches the application.
Suggested implementation:
// Buscar mensagens de atualização de votos apenas relacionadas a esta enquete,
// filtrando no banco para reduzir o volume de dados trafegados e processados em memória
const allPollUpdateMessages = await this.prismaRepository.message.findMany({
where: {
instanceId: this.instanceId,
messageType: 'pollUpdateMessage',
// Quando disponível, restringe a busca às mensagens que apontam para a mesma pollCreationMessageKey
...(pollCreationMessageKey?.id && {
message: {
path: ['pollCreationMessageKey', 'id'],
equals: pollCreationMessageKey.id,
},
}),
},
select: {
id: true,
key: true,
message: true,
messageTimestamp: true,
},
});To make this work correctly, you need to ensure:
- The
messagecolumn in your Prisma schema is aJsonfield (or otherwise supports JSON path filters) and that Prisma is configured for JSON path querying (e.g., PostgreSQL withJson/Jsonb). pollCreationMessageKey(with an.idproperty) is available in this scope and represents the creation message key for the poll you are resolving.- If your JSON structure or Prisma JSON filtering API differs (e.g., you use a different key name or nested structure), adjust
path: ['pollCreationMessageKey', 'id']accordingly to match your actual schema. - If you also have reliable temporal information (e.g., the creation message timestamp), you can further constrain the query with a
messageTimestamprange to reduce results even more, for example:Add this inside themessageTimestamp: { gte: pollCreationMessageTimestamp, }
whereobject if such a variable exists and is appropriate.
| } catch (error) { | ||
| this.logger.error(error); | ||
| throw error; // Propagate error to prevent creating settings if instance creation fails |
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.
🚨 suggestion (security): Re-throwing the raw error may expose internal details; consider wrapping or normalizing before propagating.
Here, error.toString() is passed into InternalServerErrorException, which may still expose low-level details (e.g., driver/infra info) if this message reaches clients. Consider omitting the raw error from the thrown exception and relying on logging for diagnostics, while returning only a generic, domain-appropriate message to callers.
Fix: Validação e Truncamento de Strings para Evitar Erros de Tamanho de Coluna
📋 Resumo
Este PR adiciona validação e truncamento de strings antes de salvar dados no banco de dados, prevenindo erros relacionados ao tamanho máximo de colunas do Prisma. A mudança garante que valores de string sejam automaticamente truncados para respeitar os limites definidos no schema do banco de dados.
🎯 Tipo de Mudança
🔍 Arquivos Modificados
1.
src/api/services/channel.service.tstruncate()para truncar stringsmsgCallewavoipToken(limite de 100 caracteres)2.
src/api/services/monitor.service.tstruncate()para truncar stringsinstanceName: 255 caracteres (com validação de campo obrigatório)ownerJid: 100 caracteresprofileName: 100 caracteresprofilePicUrl: 500 caracteresnumber: 100 caracteresintegration: 100 caracterestoken: 255 caracteresclientName: 100 caracteresbusinessId: 100 caracteresinstanceNamenão seja vazioawaitdesnecessário emget<Database>()🔧 Detalhes Técnicos
Função Helper
truncate()Esta função:
nullse o valor fornullouundefinedmaxLengthespecificadoValidação de
instanceNameAdicionada validação para garantir que o campo obrigatório
instanceNamenão seja vazio após o truncamento.✅ Benefícios
🧪 Testes
Cenários Testados
nulleundefinedsão tratados corretamenteinstanceNamevazio lança exceção apropriada📝 Notas Adicionais
truncate()é definida localmente em cada método para manter o escopo limitado✅ Checklist de Revisão
Summary by Sourcery
Add support for decrypting and aggregating WhatsApp poll votes while enforcing string length limits to prevent database column size errors.
New Features:
Bug Fixes:
Enhancements: