@solidxai/core 0.1.10-beta.15 → 0.1.10-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/test-data.command.d.ts +2 -0
- package/dist/commands/test-data.command.d.ts.map +1 -1
- package/dist/commands/test-data.command.js +32 -6
- package/dist/commands/test-data.command.js.map +1 -1
- package/dist/controllers/chatter-message.controller.d.ts +2 -0
- package/dist/controllers/chatter-message.controller.d.ts.map +1 -1
- package/dist/controllers/chatter-message.controller.js +17 -0
- package/dist/controllers/chatter-message.controller.js.map +1 -1
- package/dist/controllers/gupshup-webhook.controller.d.ts +11 -0
- package/dist/controllers/gupshup-webhook.controller.d.ts.map +1 -0
- package/dist/controllers/gupshup-webhook.controller.js +87 -0
- package/dist/controllers/gupshup-webhook.controller.js.map +1 -0
- package/dist/controllers/meta-cloud-whatsapp-webhook.controller.d.ts +16 -0
- package/dist/controllers/meta-cloud-whatsapp-webhook.controller.d.ts.map +1 -0
- package/dist/controllers/meta-cloud-whatsapp-webhook.controller.js +133 -0
- package/dist/controllers/meta-cloud-whatsapp-webhook.controller.js.map +1 -0
- package/dist/dtos/update-chatter-note-message.dto.d.ts +5 -0
- package/dist/dtos/update-chatter-note-message.dto.d.ts.map +1 -0
- package/dist/dtos/update-chatter-note-message.dto.js +34 -0
- package/dist/dtos/update-chatter-note-message.dto.js.map +1 -0
- package/dist/factories/whatsapp.factory.d.ts.map +1 -1
- package/dist/factories/whatsapp.factory.js +13 -11
- package/dist/factories/whatsapp.factory.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +1 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/interfaces.js.map +1 -1
- package/dist/listeners/user-registration.listener.d.ts +5 -2
- package/dist/listeners/user-registration.listener.d.ts.map +1 -1
- package/dist/listeners/user-registration.listener.js +37 -4
- package/dist/listeners/user-registration.listener.js.map +1 -1
- package/dist/seeders/module-test-data.service.d.ts +2 -0
- package/dist/seeders/module-test-data.service.d.ts.map +1 -1
- package/dist/seeders/module-test-data.service.js +61 -0
- package/dist/seeders/module-test-data.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +1 -0
- package/dist/services/authentication.service.d.ts +6 -1
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +144 -14
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts +7 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +94 -2
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts +1 -0
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +11 -0
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts +1 -0
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.js +6 -0
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/services/settings/default-settings-provider.service.d.ts +174 -0
- package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
- package/dist/services/settings/default-settings-provider.service.js +98 -0
- package/dist/services/settings/default-settings-provider.service.js.map +1 -1
- package/dist/services/user.service.js +1 -1
- package/dist/services/user.service.js.map +1 -1
- package/dist/services/whatsapp/GupshupOtpWhatsappService.d.ts +11 -0
- package/dist/services/whatsapp/GupshupOtpWhatsappService.d.ts.map +1 -0
- package/dist/services/whatsapp/GupshupOtpWhatsappService.js +127 -0
- package/dist/services/whatsapp/GupshupOtpWhatsappService.js.map +1 -0
- package/dist/services/whatsapp/MetaCloudWhatsappService.d.ts +17 -0
- package/dist/services/whatsapp/MetaCloudWhatsappService.d.ts.map +1 -0
- package/dist/services/whatsapp/MetaCloudWhatsappService.js +209 -0
- package/dist/services/whatsapp/MetaCloudWhatsappService.js.map +1 -0
- package/dist/solid-core.module.d.ts +2 -2
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +14 -6
- package/dist/solid-core.module.js.map +1 -1
- package/dist-tests/api/authenticate.spec.js +119 -0
- package/dist-tests/api/authenticate.spec.js.map +1 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
- package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
- package/dist-tests/api/ping.spec.js +21 -0
- package/dist-tests/api/ping.spec.js.map +1 -0
- package/dist-tests/helpers/auth.js +41 -0
- package/dist-tests/helpers/auth.js.map +1 -0
- package/dist-tests/helpers/env.js +11 -0
- package/dist-tests/helpers/env.js.map +1 -0
- package/docs/grouping-enhancements.md +89 -0
- package/docs/java-spring/README.md +3 -0
- package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
- package/docs/seed-changes.md +65 -0
- package/docs/test-data-workflow.md +200 -0
- package/docs/type-declaration-import-issue.md +24 -0
- package/package.json +1 -1
- package/src/commands/test-data.command.ts +29 -6
- package/src/controllers/chatter-message.controller.ts +12 -0
- package/src/controllers/gupshup-webhook.controller.ts +91 -0
- package/src/controllers/meta-cloud-whatsapp-webhook.controller.ts +155 -0
- package/src/dtos/update-chatter-note-message.dto.ts +14 -0
- package/src/factories/whatsapp.factory.ts +33 -32
- package/src/helpers/field-crud-managers/field-quality-check-fixes.md +178 -0
- package/src/index.ts +2 -0
- package/src/interfaces.ts +1 -1
- package/src/listeners/user-registration.listener.ts +49 -6
- package/src/seeders/module-test-data.service.ts +79 -2
- package/src/seeders/seed-data/solid-core-metadata.json +1 -0
- package/src/services/authentication.service.ts +281 -46
- package/src/services/chatter-message.service.ts +112 -2
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +12 -0
- package/src/services/mediaStorageProviders/file-storage-provider.ts +8 -1
- package/src/services/settings/default-settings-provider.service.ts +98 -0
- package/src/services/user.service.ts +1 -1
- package/src/services/whatsapp/GupshupOtpWhatsappService.ts +174 -0
- package/src/services/whatsapp/MetaCloudWhatsappService.ts +253 -0
- package/src/solid-core.module.ts +205 -196
- package/src/testing/README.md +15 -0
- package/.claude/settings.local.json +0 -15
- package/CLAUDE.md +0 -18
- package/CURRENT_PROMPT +0 -6
- package/src/services/1.js +0 -6
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Seeding Enhancements Summary
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This document summarizes the changes made to seeding behavior in `solid-core`:
|
|
5
|
+
- Metadata pruning support across targets.
|
|
6
|
+
- CLI flags for selective modules and pruning.
|
|
7
|
+
|
|
8
|
+
## Pruning Support
|
|
9
|
+
Pruning is now available for most metadata targets. When enabled, records present in the DB but missing from JSON are removed. This is done in two phases:
|
|
10
|
+
1. Identify records in DB not present in JSON.
|
|
11
|
+
2. Delete those records before normal upsert seeding.
|
|
12
|
+
|
|
13
|
+
### Targets with Pruning
|
|
14
|
+
- Module models + fields (with model-level pruning).
|
|
15
|
+
- Views, actions, menus (with join cleanup).
|
|
16
|
+
- Security rules, list of values, dashboards.
|
|
17
|
+
- Scheduled jobs, saved filters, model sequences.
|
|
18
|
+
- Permissions (also clears role-permission join table).
|
|
19
|
+
|
|
20
|
+
### Targets WITHOUT Pruning (conservative)
|
|
21
|
+
- Module metadata itself
|
|
22
|
+
- Media storage providers
|
|
23
|
+
- Roles
|
|
24
|
+
- Users
|
|
25
|
+
- Email templates
|
|
26
|
+
- SMS templates
|
|
27
|
+
- System fields (global)
|
|
28
|
+
- Default settings (global)
|
|
29
|
+
|
|
30
|
+
### Pruning Mode
|
|
31
|
+
Pruning runs **only** when `--prune` is provided. If omitted, pruning is skipped.
|
|
32
|
+
|
|
33
|
+
## CLI Changes
|
|
34
|
+
### Modules to Seed
|
|
35
|
+
`--conf` was removed. Use:
|
|
36
|
+
```
|
|
37
|
+
--modules-to-seed venue,reports
|
|
38
|
+
```
|
|
39
|
+
This passes `modulesToSeed` to the seeder as an array.
|
|
40
|
+
|
|
41
|
+
## Sample Commands
|
|
42
|
+
### Default seeding
|
|
43
|
+
```
|
|
44
|
+
npx @solidxai/solidctl seed
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Select modules only
|
|
48
|
+
```
|
|
49
|
+
npx @solidxai/solidctl seed --modules-to-seed venue,reports
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Pruning enabled
|
|
53
|
+
```
|
|
54
|
+
npx @solidxai/solidctl seed --modules-to-seed venue --prune
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Pruning enabled (all modules)
|
|
58
|
+
```
|
|
59
|
+
npx @solidxai/solidctl seed --prune
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Files Touched (Key)
|
|
63
|
+
- `src/seeders/module-metadata-seeder.service.ts`
|
|
64
|
+
- `src/commands/seed.command.ts`
|
|
65
|
+
- `src/repository/solid-base.repository.ts` (added `findOneByUserKey`)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Test Data Workflow
|
|
2
|
+
|
|
3
|
+
This document describes the end-to-end workflow for creating isolated test databases, seeding metadata, loading test data, and tearing everything down.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
The workflow is designed to:
|
|
7
|
+
- Create one **test database per configured datasource** in the project.
|
|
8
|
+
- Point the app to those new databases via `.env` updates.
|
|
9
|
+
- Seed all metadata into the fresh databases.
|
|
10
|
+
- Load model-specific `testData` entries from module metadata JSON files.
|
|
11
|
+
- Cleanly reverse everything at the end.
|
|
12
|
+
|
|
13
|
+
The commands below are the canonical flow.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 1: Create test datasources
|
|
18
|
+
|
|
19
|
+
Command:
|
|
20
|
+
```
|
|
21
|
+
npx @solidxai/solidctl test data --setup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
What this does:
|
|
25
|
+
- Reads all configured datasources from the Solid registry (`SolidRegistry.getSolidDatabaseModules()`), and uses their `name()` values.
|
|
26
|
+
- Generates a unique **run name** (two words, underscore-separated, e.g. `steady_wolf`).
|
|
27
|
+
- Builds a **timestamped database name** per datasource:
|
|
28
|
+
- `<datasource>_<timestamp>_<runName>`
|
|
29
|
+
- Backs up the current `.env` into `.env.backup.<runName>`.
|
|
30
|
+
- Rewrites `.env` in-place with new `*_DATABASE_NAME` values.
|
|
31
|
+
- Creates the databases/schemas using TypeORM and the configured connection info.
|
|
32
|
+
- Writes a `.solidx-test-manifest` with the run name + database names so the teardown can reverse everything later.
|
|
33
|
+
|
|
34
|
+
Example output:
|
|
35
|
+
```
|
|
36
|
+
▶ Running solid test data
|
|
37
|
+
Creating test datasource environment file and manifest.
|
|
38
|
+
Backed up .env to .env.backup.steady_wolf and applied new test datasource names to .env.
|
|
39
|
+
Creating test database/schema "default_20260201234402_steady_wolf" on datasource "default"...
|
|
40
|
+
|
|
41
|
+
============================================================
|
|
42
|
+
TEST DATASOURCE ENVIRONMENT CREATED
|
|
43
|
+
------------------------------------------------------------
|
|
44
|
+
Run name : steady_wolf
|
|
45
|
+
Env backup : .env.backup.steady_wolf
|
|
46
|
+
|
|
47
|
+
Test databases/schemas created:
|
|
48
|
+
- default: default_20260201234402_steady_wolf
|
|
49
|
+
|
|
50
|
+
Next steps:
|
|
51
|
+
1) Using updated .env with test datasource names
|
|
52
|
+
2) Run solid seed as usual
|
|
53
|
+
3) Proceed with loading the test data for each module
|
|
54
|
+
4) Run the test cases
|
|
55
|
+
5) Tear down
|
|
56
|
+
============================================================
|
|
57
|
+
|
|
58
|
+
✔ solid test data completed
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Rationale:
|
|
62
|
+
- The `.env` rewrite guarantees the app connects to fresh databases without changing code.
|
|
63
|
+
- The manifest records the run so deletion is safe and deterministic.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Step 2: Seed metadata into the new databases
|
|
68
|
+
|
|
69
|
+
Command:
|
|
70
|
+
```
|
|
71
|
+
npx @solidxai/solidctl seed
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
What this does:
|
|
75
|
+
- Seeds full module metadata into the **fresh test databases**.
|
|
76
|
+
- Generates the `sa` user if needed and prints its password.
|
|
77
|
+
- Ensures all models, fields, roles, permissions, views, actions, etc. exist before loading test data.
|
|
78
|
+
|
|
79
|
+
Example output (truncated):
|
|
80
|
+
```
|
|
81
|
+
▶ Running solid seed
|
|
82
|
+
▶ Pruning disabled: existing metadata will be kept.
|
|
83
|
+
▶ No modulesToSeed provided. Seeding ALL modules.
|
|
84
|
+
▶ Seeding Metadata for Module: solid-core
|
|
85
|
+
✔ [solid-core] Module/Model/Fields seeded (upserted 376)
|
|
86
|
+
...
|
|
87
|
+
✔ Seeding completed.
|
|
88
|
+
✔ solid seed completed
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Rationale:
|
|
92
|
+
- Test data relies on model metadata and relationships; this ensures the schema and reference data are ready.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Step 3: Load test data
|
|
97
|
+
|
|
98
|
+
Command:
|
|
99
|
+
```
|
|
100
|
+
npx @solidxai/solidctl test data --load
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
What this does:
|
|
104
|
+
- Reads all module metadata files (or a subset if `--modules-to-test` is provided).
|
|
105
|
+
- Looks for `testData` arrays in each metadata file.
|
|
106
|
+
- Resolves the correct repository for each model (repository name pattern: `<ModelName>Repository`).
|
|
107
|
+
- Performs **upsert-style** inserts using the model’s `userKeyFieldUserKey`.
|
|
108
|
+
- Resolves many-to-one relations using `${fieldName}UserKey`.
|
|
109
|
+
|
|
110
|
+
Example output:
|
|
111
|
+
```
|
|
112
|
+
▶ Running solid test data
|
|
113
|
+
Test data setup for all modules.
|
|
114
|
+
Processing test data for module: solid-core
|
|
115
|
+
✔ Test data setup complete for module: solid-core
|
|
116
|
+
Processing test data for module: venue
|
|
117
|
+
✔ Test data setup complete for module: venue
|
|
118
|
+
✔ solid test data completed
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Rationale:
|
|
122
|
+
- This keeps test fixtures human-readable and tied to module metadata.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## Step 4: Run Tests
|
|
128
|
+
```
|
|
129
|
+
npx @solidxai/solidctl test run --module venue --api-base-url http://localhost:3000 --ui-base-url http://localhost:5173 --headless false
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
npx @solidxai/solidctl test run --module venue --list-specs
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
What this does:
|
|
137
|
+
- Loads `testing.scenarios` from the module metadata file.
|
|
138
|
+
- Applies `--scenario-ids` or `--include-tags` filtering if provided.
|
|
139
|
+
- Boots API and UI adapters with the supplied base URLs.
|
|
140
|
+
- Registers any custom test specs listed in `testing.specs`.
|
|
141
|
+
- Executes scenarios in order with fail‑fast behavior.
|
|
142
|
+
|
|
143
|
+
Useful variants:
|
|
144
|
+
```
|
|
145
|
+
npx @solidxai/solidctl test run --module venue --scenario-ids api-authenticate-success,api-create-states
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
npx @solidxai/solidctl test run --module venue --include-tags smoke
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
npx @solidxai/solidctl test run --module venue --api-base-url http://localhost:3000 --headless true
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
npx @solidxai/solidctl test run --module venue --headless false
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Notes:
|
|
161
|
+
- If your scenario depends on a previous scenario’s `saveAs` output (e.g., `loginSuccess`), ensure the auth scenario runs first.
|
|
162
|
+
- For UI tests, `--ui-base-url` should point to a running frontend server.
|
|
163
|
+
- `--list-specs` prints registered custom `test.spec` ids and exits.
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
## Step 5: Tear everything down
|
|
167
|
+
|
|
168
|
+
Command:
|
|
169
|
+
```
|
|
170
|
+
npx @solidxai/solidctl test data --teardown
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
What this does:
|
|
174
|
+
- Restores `.env` from the latest `.env.backup.<runName>`.
|
|
175
|
+
- Deletes all `.env.backup.<runName>` files created by the workflow.
|
|
176
|
+
- Drops each test database/schema recorded in `.solidx-test-manifest`.
|
|
177
|
+
- Deletes the manifest file.
|
|
178
|
+
|
|
179
|
+
Example output:
|
|
180
|
+
```
|
|
181
|
+
▶ Running solid test data
|
|
182
|
+
Deleting test datasource environment and databases.
|
|
183
|
+
Dropping test database/schema "default_20260201234402_steady_wolf" on datasource "default"...
|
|
184
|
+
✔ Test datasource env files and manifest deleted; test databases dropped.
|
|
185
|
+
✔ solid test data completed
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Rationale:
|
|
189
|
+
- Keeps the local environment clean and restores the original `.env` safely.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Optional: Limit modules for test data load
|
|
194
|
+
|
|
195
|
+
You can restrict test data loading to a subset of modules:
|
|
196
|
+
```
|
|
197
|
+
npx @solidxai/solidctl test data --load --modules-to-test venue,reports
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Only the listed modules are processed.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Issue: Consumer sees `Property 'id' does not exist on type 'VenueUser'`
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
The published declaration files in `dist/` import internal types using `src/...` module specifiers. In a consuming project, TypeScript resolves those imports against the consumer's `src` path (or fails), which can cause `User` to extend a different `CommonEntity` that does not define `id`. This leads to `Property 'id' does not exist on type 'VenueUser'` even though `CommonEntity` in the library defines it.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
- `dist/entities/user.entity.d.ts` starts with:
|
|
8
|
+
- `import { CommonEntity } from "src/entities/common.entity";`
|
|
9
|
+
- `dist/entities/common.entity.d.ts` correctly contains:
|
|
10
|
+
- `id: number;`
|
|
11
|
+
|
|
12
|
+
Because the import is `src/...`, the consumer may resolve it to their own `src` tree instead of the library's `dist` types.
|
|
13
|
+
|
|
14
|
+
## Root Cause
|
|
15
|
+
Library declarations are not portable because internal imports are using absolute `src/...` aliases. These aliases are not part of Node module resolution, so consumers resolve them differently.
|
|
16
|
+
|
|
17
|
+
## Fix Options
|
|
18
|
+
1. **Preferred:** Replace internal imports in `src/` with relative paths so emitted `.d.ts` files are portable.
|
|
19
|
+
2. **Alternative:** Keep `src/*` aliases but add a post-build step (e.g., `tsc-alias`) to rewrite `dist/**/*.d.ts` imports to relative paths.
|
|
20
|
+
|
|
21
|
+
## Next Steps
|
|
22
|
+
- Decide on option 1 or 2.
|
|
23
|
+
- Rebuild and verify `dist/**/*.d.ts` no longer import from `src/...`.
|
|
24
|
+
- Publish a new package version and ensure the consuming app resolves `User` and `CommonEntity` from the same package instance.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidxai/core",
|
|
3
|
-
"version": "0.1.10-beta.
|
|
3
|
+
"version": "0.1.10-beta.21",
|
|
4
4
|
"description": "This module is a NestJS module containing all the required core providers required by a Solid application",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -7,6 +7,7 @@ interface TestDataCommandOptions {
|
|
|
7
7
|
modulesToTest?: string;
|
|
8
8
|
setup?: boolean;
|
|
9
9
|
teardown?: boolean;
|
|
10
|
+
unlink?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
@SubCommand({
|
|
@@ -25,17 +26,18 @@ export class TestDataCommand extends CommandRunner {
|
|
|
25
26
|
const load = Boolean(options?.load);
|
|
26
27
|
const setup = Boolean(options?.setup);
|
|
27
28
|
const teardown = Boolean(options?.teardown);
|
|
29
|
+
const unlink = Boolean(options?.unlink);
|
|
28
30
|
|
|
29
|
-
const selectedModes = [load, setup, teardown].filter(Boolean).length;
|
|
31
|
+
const selectedModes = [load, setup, teardown, unlink].filter(Boolean).length;
|
|
30
32
|
if (selectedModes > 1) {
|
|
31
|
-
this.logger.error('Please specify only one of --load, --setup, or --
|
|
32
|
-
console.log('Please specify only one of --load, --setup, or --
|
|
33
|
+
this.logger.error('Please specify only one of --load, --setup, --teardown, or --unlink.');
|
|
34
|
+
console.log('Please specify only one of --load, --setup, --teardown, or --unlink.');
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
if (!load && !setup && !teardown) {
|
|
37
|
-
this.logger.error('Please specify one of --load, --setup, or --
|
|
38
|
-
console.log('Please specify one of --load, --setup, or --
|
|
38
|
+
if (!load && !setup && !teardown && !unlink) {
|
|
39
|
+
this.logger.error('Please specify one of --load, --setup, --teardown, or --unlink.');
|
|
40
|
+
console.log('Please specify one of --load, --setup, --teardown, or --unlink.');
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -65,6 +67,19 @@ export class TestDataCommand extends CommandRunner {
|
|
|
65
67
|
await this.testDataService.setupTestData(modulesToTest ?? undefined);
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
70
|
+
|
|
71
|
+
if (unlink) {
|
|
72
|
+
const modulesToTest = options?.modulesToTest ? options.modulesToTest.split(',').map((m) => m.trim()).filter(Boolean) : null;
|
|
73
|
+
if (modulesToTest?.length) {
|
|
74
|
+
this.logger.log(`Test data unlink for modules: ${modulesToTest.join(', ')}`);
|
|
75
|
+
console.log(`Test data unlink for modules: ${modulesToTest.join(', ')}`);
|
|
76
|
+
} else {
|
|
77
|
+
this.logger.log('Test data unlink for all modules.');
|
|
78
|
+
console.log('Test data unlink for all modules.');
|
|
79
|
+
}
|
|
80
|
+
await this.testDataService.removeTestData(modulesToTest ?? undefined);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
68
83
|
} catch (err: any) {
|
|
69
84
|
const message = err?.message ?? String(err);
|
|
70
85
|
this.logger.error(message);
|
|
@@ -109,4 +124,12 @@ export class TestDataCommand extends CommandRunner {
|
|
|
109
124
|
return val;
|
|
110
125
|
}
|
|
111
126
|
|
|
127
|
+
@Option({
|
|
128
|
+
flags: '--unlink',
|
|
129
|
+
description: 'Delete test data from testing.data sections in reverse order',
|
|
130
|
+
})
|
|
131
|
+
parseUnlink(): boolean {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
112
135
|
}
|
|
@@ -5,6 +5,7 @@ import { ChatterMessageService } from '../services/chatter-message.service';
|
|
|
5
5
|
import { CreateChatterMessageDto } from '../dtos/create-chatter-message.dto';
|
|
6
6
|
import { UpdateChatterMessageDto } from '../dtos/update-chatter-message.dto';
|
|
7
7
|
import { PostChatterMessageDto } from '../dtos/post-chatter-message.dto';
|
|
8
|
+
import { UpdateChatterNoteMessageDto } from '../dtos/update-chatter-note-message.dto';
|
|
8
9
|
import { SolidRequestContextDecorator } from 'src/decorators/solid-request-context.decorator';
|
|
9
10
|
import { SolidRequestContextDto } from 'src/dtos/solid-request-context.dto';
|
|
10
11
|
import { Public } from 'src/decorators/public.decorator';
|
|
@@ -127,4 +128,15 @@ export class ChatterMessageController {
|
|
|
127
128
|
async markCompleted(@Param('id') id: string) {
|
|
128
129
|
return this.service.markCompleted(+id);
|
|
129
130
|
}
|
|
131
|
+
|
|
132
|
+
@ApiBearerAuth("jwt")
|
|
133
|
+
@Patch(':id/note')
|
|
134
|
+
@UseInterceptors(AnyFilesInterceptor())
|
|
135
|
+
async updateCustomNoteMessage(
|
|
136
|
+
@Param('id') id: string,
|
|
137
|
+
@Body() updateDto: UpdateChatterNoteMessageDto,
|
|
138
|
+
@UploadedFiles() files: Array<Express.Multer.File>,
|
|
139
|
+
) {
|
|
140
|
+
return this.service.updateCustomNoteMessage(+id, updateDto, files);
|
|
141
|
+
}
|
|
130
142
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
HttpCode,
|
|
5
|
+
HttpStatus,
|
|
6
|
+
Logger,
|
|
7
|
+
Post,
|
|
8
|
+
Req,
|
|
9
|
+
} from "@nestjs/common";
|
|
10
|
+
import { ApiTags } from "@nestjs/swagger";
|
|
11
|
+
import { Request } from "express";
|
|
12
|
+
import { Auth } from "src/decorators/auth.decorator";
|
|
13
|
+
import { Public } from "src/decorators/public.decorator";
|
|
14
|
+
import { AuthType } from "src/enums/auth-type.enum";
|
|
15
|
+
|
|
16
|
+
@Auth(AuthType.None)
|
|
17
|
+
@Controller("webhook/whatsapp/gupshup")
|
|
18
|
+
@ApiTags("Solid Core")
|
|
19
|
+
export class GupshupWebhookController {
|
|
20
|
+
private readonly logger = new Logger(GupshupWebhookController.name);
|
|
21
|
+
|
|
22
|
+
@Public()
|
|
23
|
+
@Post()
|
|
24
|
+
@HttpCode(HttpStatus.OK)
|
|
25
|
+
async receiveWebhook(@Req() req: Request, @Body() body: unknown) {
|
|
26
|
+
const userAgent = req.headers["user-agent"] ?? null;
|
|
27
|
+
this.logger.log(
|
|
28
|
+
`Received Gupshup WhatsApp webhook${userAgent ? ` from ${userAgent}` : ""}`,
|
|
29
|
+
);
|
|
30
|
+
this.logger.debug(`Gupshup webhook payload: ${JSON.stringify(body)}`);
|
|
31
|
+
|
|
32
|
+
const statusInfo = this.extractStatusInfo(body);
|
|
33
|
+
if (statusInfo) {
|
|
34
|
+
this.logger.log(
|
|
35
|
+
`Gupshup delivery update: status=${statusInfo.status ?? "unknown"}, messageId=${statusInfo.messageId ?? "n/a"}, destination=${statusInfo.destination ?? "n/a"}, reason=${statusInfo.reason ?? "n/a"}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
message: "Webhook received",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private extractStatusInfo(body: unknown): {
|
|
46
|
+
status?: string;
|
|
47
|
+
messageId?: string;
|
|
48
|
+
destination?: string;
|
|
49
|
+
reason?: string;
|
|
50
|
+
} | null {
|
|
51
|
+
if (!body || typeof body !== "object") {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const payload = body as Record<string, unknown>;
|
|
56
|
+
|
|
57
|
+
const status =
|
|
58
|
+
this.asString(payload.status) ||
|
|
59
|
+
this.asString(payload.messageStatus) ||
|
|
60
|
+
this.asString((payload.payload as Record<string, unknown>)?.status) ||
|
|
61
|
+
this.asString((payload.payload as Record<string, unknown>)?.type);
|
|
62
|
+
|
|
63
|
+
const messageId =
|
|
64
|
+
this.asString(payload.messageId) ||
|
|
65
|
+
this.asString(payload.id) ||
|
|
66
|
+
this.asString((payload.payload as Record<string, unknown>)?.id) ||
|
|
67
|
+
this.asString((payload.payload as Record<string, unknown>)?.messageId);
|
|
68
|
+
|
|
69
|
+
const destination =
|
|
70
|
+
this.asString(payload.destination) ||
|
|
71
|
+
this.asString(payload.phone) ||
|
|
72
|
+
this.asString((payload.payload as Record<string, unknown>)?.destination) ||
|
|
73
|
+
this.asString((payload.payload as Record<string, unknown>)?.phone);
|
|
74
|
+
|
|
75
|
+
const reason =
|
|
76
|
+
this.asString(payload.reason) ||
|
|
77
|
+
this.asString(payload.error) ||
|
|
78
|
+
this.asString((payload.payload as Record<string, unknown>)?.reason) ||
|
|
79
|
+
this.asString((payload.payload as Record<string, unknown>)?.error);
|
|
80
|
+
|
|
81
|
+
if (!status && !messageId && !destination && !reason) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { status, messageId, destination, reason };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private asString(value: unknown): string | undefined {
|
|
89
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Body,
|
|
3
|
+
Controller,
|
|
4
|
+
Get,
|
|
5
|
+
HttpCode,
|
|
6
|
+
HttpStatus,
|
|
7
|
+
Logger,
|
|
8
|
+
Post,
|
|
9
|
+
Query,
|
|
10
|
+
Res,
|
|
11
|
+
} from "@nestjs/common";
|
|
12
|
+
import { ApiTags } from "@nestjs/swagger";
|
|
13
|
+
import { Response } from "express";
|
|
14
|
+
import { Auth } from "src/decorators/auth.decorator";
|
|
15
|
+
import { Public } from "src/decorators/public.decorator";
|
|
16
|
+
import { AuthType } from "src/enums/auth-type.enum";
|
|
17
|
+
import { SettingService } from "src/services/setting.service";
|
|
18
|
+
import type { SolidCoreSetting } from "src/services/settings/default-settings-provider.service";
|
|
19
|
+
|
|
20
|
+
@Auth(AuthType.None)
|
|
21
|
+
@Controller("webhook/whatsapp/meta-cloud")
|
|
22
|
+
@ApiTags("Solid Core")
|
|
23
|
+
export class MetaCloudWhatsappWebhookController {
|
|
24
|
+
private readonly logger = new Logger(MetaCloudWhatsappWebhookController.name);
|
|
25
|
+
|
|
26
|
+
constructor(private readonly settingService: SettingService) {}
|
|
27
|
+
|
|
28
|
+
@Public()
|
|
29
|
+
@Get()
|
|
30
|
+
verifyWebhook(@Query() query: Record<string, unknown>, @Res() res: Response) {
|
|
31
|
+
const mode = this.resolveQueryValue(query, "hub.mode", "hub_mode", "mode");
|
|
32
|
+
const verifyToken = this.resolveQueryValue(
|
|
33
|
+
query,
|
|
34
|
+
"hub.verify_token",
|
|
35
|
+
"hub_verify_token",
|
|
36
|
+
"verify_token",
|
|
37
|
+
);
|
|
38
|
+
const challenge = this.resolveQueryValue(
|
|
39
|
+
query,
|
|
40
|
+
"hub.challenge",
|
|
41
|
+
"hub_challenge",
|
|
42
|
+
"challenge",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const configuredVerifyToken =
|
|
46
|
+
this.settingService.getConfigValue<SolidCoreSetting>(
|
|
47
|
+
"metaWhatsappWebhookVerifyToken",
|
|
48
|
+
) || process.env.COMMON_META_WHATSAPP_WEBHOOK_VERIFY_TOKEN;
|
|
49
|
+
|
|
50
|
+
const isVerificationCall = mode === "subscribe";
|
|
51
|
+
const tokenMatches =
|
|
52
|
+
!!configuredVerifyToken && verifyToken === configuredVerifyToken;
|
|
53
|
+
|
|
54
|
+
if (isVerificationCall && tokenMatches && challenge) {
|
|
55
|
+
this.logger.log("Meta Cloud WhatsApp webhook verified successfully.");
|
|
56
|
+
res.writeHead(HttpStatus.OK, {
|
|
57
|
+
"Content-Type": "text/plain",
|
|
58
|
+
"Content-Length": Buffer.byteLength(String(challenge)),
|
|
59
|
+
});
|
|
60
|
+
res.write(String(challenge));
|
|
61
|
+
return res.end();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.logger.warn(
|
|
65
|
+
`Meta Cloud webhook verification failed. mode=${mode ?? "n/a"}, tokenMatch=${tokenMatches}`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
res.writeHead(HttpStatus.FORBIDDEN, { "Content-Type": "text/plain" });
|
|
69
|
+
return res.end("Webhook verification failed");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Public()
|
|
73
|
+
@Post()
|
|
74
|
+
@HttpCode(HttpStatus.OK)
|
|
75
|
+
async receiveWebhook(@Body() body: unknown) {
|
|
76
|
+
this.logger.log("Received Meta Cloud WhatsApp webhook");
|
|
77
|
+
this.logger.debug(`Meta Cloud webhook payload: ${JSON.stringify(body)}`);
|
|
78
|
+
|
|
79
|
+
const statusInfo = this.extractStatusInfo(body);
|
|
80
|
+
if (statusInfo) {
|
|
81
|
+
this.logger.log(
|
|
82
|
+
`Meta Cloud delivery update: status=${statusInfo.status ?? "unknown"}, messageId=${statusInfo.messageId ?? "n/a"}, destination=${statusInfo.destination ?? "n/a"}, reason=${statusInfo.reason ?? "n/a"}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
message: "Webhook received",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private extractStatusInfo(body: unknown): {
|
|
93
|
+
status?: string;
|
|
94
|
+
messageId?: string;
|
|
95
|
+
destination?: string;
|
|
96
|
+
reason?: string;
|
|
97
|
+
} | null {
|
|
98
|
+
if (!body || typeof body !== "object") {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const payload = body as Record<string, unknown>;
|
|
103
|
+
const entries = payload.entry as Array<Record<string, unknown>> | undefined;
|
|
104
|
+
const changes = entries?.[0]?.changes as Array<Record<string, unknown>> | undefined;
|
|
105
|
+
const value = changes?.[0]?.value as Record<string, unknown> | undefined;
|
|
106
|
+
const statuses = value?.statuses as Array<Record<string, unknown>> | undefined;
|
|
107
|
+
const status = statuses?.[0];
|
|
108
|
+
|
|
109
|
+
if (!status) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const errors = status.errors as Array<Record<string, unknown>> | undefined;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
status: this.asString(status.status),
|
|
117
|
+
messageId: this.asString(status.id),
|
|
118
|
+
destination: this.asString(status.recipient_id),
|
|
119
|
+
reason:
|
|
120
|
+
this.asString(errors?.[0]?.title) || this.asString(errors?.[0]?.message),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private asString(value: unknown): string | undefined {
|
|
125
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private resolveQueryValue(
|
|
129
|
+
query: Record<string, unknown>,
|
|
130
|
+
...keys: string[]
|
|
131
|
+
): string | undefined {
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
const directValue = this.asString(query[key]);
|
|
134
|
+
if (directValue) {
|
|
135
|
+
return directValue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const hubRaw = query.hub;
|
|
140
|
+
if (hubRaw && typeof hubRaw === "object" && !Array.isArray(hubRaw)) {
|
|
141
|
+
const hub = hubRaw as Record<string, unknown>;
|
|
142
|
+
for (const key of ["mode", "verify_token", "challenge"]) {
|
|
143
|
+
const value = this.asString(hub[key]);
|
|
144
|
+
if (
|
|
145
|
+
value &&
|
|
146
|
+
keys.some((candidate) => candidate.endsWith(key) || candidate === key)
|
|
147
|
+
) {
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApiProperty } from '@nestjs/swagger';
|
|
2
|
+
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
3
|
+
|
|
4
|
+
export class UpdateChatterNoteMessageDto {
|
|
5
|
+
@IsString()
|
|
6
|
+
@IsOptional()
|
|
7
|
+
@ApiProperty()
|
|
8
|
+
messageBody: string;
|
|
9
|
+
|
|
10
|
+
@IsString()
|
|
11
|
+
@IsOptional()
|
|
12
|
+
@ApiProperty({ required: false, description: 'Comma-separated media IDs to remove from this note.' })
|
|
13
|
+
removeAttachmentIds?: string;
|
|
14
|
+
}
|