codeninja 2.0.0
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/.gitattributes +11 -0
- package/README.md +293 -0
- package/agent/database-agent.md +504 -0
- package/agent/designs/README.md +10 -0
- package/agent/global-agent.md +236 -0
- package/agent/nodejs-agent.md +406 -0
- package/agent/reactjs-agent.md +260 -0
- package/cli.js +352 -0
- package/commands/audit.workflow.md +111 -0
- package/commands/create-api.workflow.md +99 -0
- package/commands/db-add-index.workflow.md +97 -0
- package/commands/db-create-table.workflow.md +132 -0
- package/commands/db-drop-table.workflow.md +103 -0
- package/commands/db-modify-table.workflow.md +159 -0
- package/commands/db-seed.workflow.md +99 -0
- package/commands/db-sync.workflow.md +100 -0
- package/commands/design.workflow.md +66 -0
- package/commands/initialize-project.workflow.md +500 -0
- package/commands/integrate-api.workflow.md +448 -0
- package/commands/modularize.workflow.md +329 -0
- package/commands/refactor.workflow.md +70 -0
- package/commands/sync.workflow.md +962 -0
- package/commands/test.workflow.md +40 -0
- package/commands/validate-page.workflow.md +543 -0
- package/mcp-server.js +842 -0
- package/package.json +24 -0
- package/tasks/README.md +283 -0
- package/tasks/add-health-route.task.md +103 -0
- package/tasks/ask-api-integration-scope.task.md +34 -0
- package/tasks/ask-api-key.task.md +23 -0
- package/tasks/ask-api-version.task.md +28 -0
- package/tasks/ask-client-type.task.md +24 -0
- package/tasks/ask-column-enum-values.task.md +51 -0
- package/tasks/ask-column-is-enum.task.md +39 -0
- package/tasks/ask-column-name.task.md +39 -0
- package/tasks/ask-column-position.task.md +39 -0
- package/tasks/ask-column-type.task.md +59 -0
- package/tasks/ask-database-config.task.md +66 -0
- package/tasks/ask-database-host.task.md +16 -0
- package/tasks/ask-database-name.task.md +18 -0
- package/tasks/ask-database-port.task.md +23 -0
- package/tasks/ask-database-type.task.md +30 -0
- package/tasks/ask-database-user.task.md +14 -0
- package/tasks/ask-design-description.task.md +16 -0
- package/tasks/ask-design-target.task.md +24 -0
- package/tasks/ask-encrypted-transport.task.md +25 -0
- package/tasks/ask-encryption-iv.task.md +23 -0
- package/tasks/ask-encryption-key.task.md +23 -0
- package/tasks/ask-feature-name.task.md +20 -0
- package/tasks/ask-http-method.task.md +21 -0
- package/tasks/ask-index-columns.task.md +46 -0
- package/tasks/ask-index-file-placement.task.md +33 -0
- package/tasks/ask-index-sort-order.task.md +37 -0
- package/tasks/ask-index-type.task.md +42 -0
- package/tasks/ask-init-mode.task.md +28 -0
- package/tasks/ask-linked-service.task.md +57 -0
- package/tasks/ask-modify-operation.task.md +36 -0
- package/tasks/ask-modularize-scope.task.md +31 -0
- package/tasks/ask-module-name.task.md +30 -0
- package/tasks/ask-new-column-name.task.md +21 -0
- package/tasks/ask-new-table-name.task.md +22 -0
- package/tasks/ask-old-column-name.task.md +22 -0
- package/tasks/ask-package-author.task.md +16 -0
- package/tasks/ask-package-name.task.md +23 -0
- package/tasks/ask-page-path.task.md +40 -0
- package/tasks/ask-primary-table.task.md +30 -0
- package/tasks/ask-project-figma.task.md +71 -0
- package/tasks/ask-project-info-doc.task.md +57 -0
- package/tasks/ask-project-scope-of-work.task.md +57 -0
- package/tasks/ask-project-type.task.md +24 -0
- package/tasks/ask-react-target-service.task.md +32 -0
- package/tasks/ask-redis-config.task.md +42 -0
- package/tasks/ask-redis-host.task.md +16 -0
- package/tasks/ask-redis-port.task.md +18 -0
- package/tasks/ask-refactor-type.task.md +26 -0
- package/tasks/ask-requires-auth.task.md +22 -0
- package/tasks/ask-response-mode.task.md +38 -0
- package/tasks/ask-route-description.task.md +20 -0
- package/tasks/ask-route-path.task.md +29 -0
- package/tasks/ask-seed-row-values.task.md +42 -0
- package/tasks/ask-seed-rows-count.task.md +22 -0
- package/tasks/ask-service-description.task.md +16 -0
- package/tasks/ask-service-name.task.md +27 -0
- package/tasks/ask-service-port.task.md +24 -0
- package/tasks/ask-supported-languages.task.md +40 -0
- package/tasks/ask-table-file-number.task.md +36 -0
- package/tasks/ask-table-indexes.task.md +47 -0
- package/tasks/ask-table-name.task.md +32 -0
- package/tasks/ask-table-needs-soft-delete.task.md +29 -0
- package/tasks/ask-table-needs-status.task.md +30 -0
- package/tasks/ask-table-purpose.task.md +28 -0
- package/tasks/ask-table-seed-data.task.md +44 -0
- package/tasks/ask-target-service.task.md +32 -0
- package/tasks/ask-test-type.task.md +20 -0
- package/tasks/ask-validation-library.task.md +38 -0
- package/tasks/detect-repository-state.task.md +92 -0
- package/tasks/generate-app.task.md +146 -0
- package/tasks/generate-common.task.md +330 -0
- package/tasks/generate-constants.task.md +123 -0
- package/tasks/generate-database.task.md +168 -0
- package/tasks/generate-docker-compose.task.md +298 -0
- package/tasks/generate-dockerfile.task.md +126 -0
- package/tasks/generate-dockerignore.task.md +123 -0
- package/tasks/generate-enc-dec-html.task.md +127 -0
- package/tasks/generate-enc-dec-php.task.md +145 -0
- package/tasks/generate-encryption.task.md +159 -0
- package/tasks/generate-fast-defaults.task.md +68 -0
- package/tasks/generate-gitignore.task.md +79 -0
- package/tasks/generate-headerValidator.task.md +377 -0
- package/tasks/generate-ide-configs.task.md +114 -0
- package/tasks/generate-ioRedis.task.md +120 -0
- package/tasks/generate-language-en.task.md +155 -0
- package/tasks/generate-logging.task.md +257 -0
- package/tasks/generate-model.task.md +180 -0
- package/tasks/generate-notification.task.md +251 -0
- package/tasks/generate-package-json.task.md +114 -0
- package/tasks/generate-rateLimiter.task.md +125 -0
- package/tasks/generate-react-api-client.task.md +169 -0
- package/tasks/generate-react-api-handler.task.md +102 -0
- package/tasks/generate-react-app-jsx.task.md +56 -0
- package/tasks/generate-react-dockerfile.task.md +175 -0
- package/tasks/generate-react-env.task.md +58 -0
- package/tasks/generate-react-gitignore.task.md +49 -0
- package/tasks/generate-react-htaccess.task.md +54 -0
- package/tasks/generate-react-index-html.task.md +53 -0
- package/tasks/generate-react-index-jsx.task.md +51 -0
- package/tasks/generate-react-package-json.task.md +77 -0
- package/tasks/generate-react-welcome-page.task.md +71 -0
- package/tasks/generate-readme.task.md +160 -0
- package/tasks/generate-response.task.md +202 -0
- package/tasks/generate-route-manager.task.md +173 -0
- package/tasks/generate-route.task.md +203 -0
- package/tasks/generate-swagger.task.md +290 -0
- package/tasks/generate-tbl-user-deviceinfo.task.md +75 -0
- package/tasks/generate-template.task.md +129 -0
- package/tasks/generate-validator.task.md +122 -0
- package/tasks/show-db-table-summary.task.md +66 -0
- package/tasks/show-final-summary.task.md +108 -0
- package/tasks/show-init-summary.task.md +257 -0
- package/tasks/write-context.task.md +314 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: task
|
|
3
|
+
name: generate-notification
|
|
4
|
+
agent: nodejs-agent
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# File: utilities/notification.js
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
Provides all outbound notification capabilities for the service in one
|
|
11
|
+
place. Covers three channels: email via nodemailer, SMS as a stubbed
|
|
12
|
+
function for future integration, and push notifications via Firebase
|
|
13
|
+
Cloud Messaging. Implemented as a class with all methods async. A single
|
|
14
|
+
instance is exported.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Dependencies to Import
|
|
19
|
+
|
|
20
|
+
- `firebase-admin` — imported as `admin`. Used for FCM push
|
|
21
|
+
notifications.
|
|
22
|
+
- `fs` — Node.js built-in. Used to read the Firebase service account
|
|
23
|
+
credentials file.
|
|
24
|
+
- `nodemailer` — imported as `nodemailer`. Used for sending emails.
|
|
25
|
+
- `../config/constants` — imported as `GLOBALS`. Used for APP_NAME,
|
|
26
|
+
FIREBASE_ADMIN_JSONFILE, LOGO, and other branding values.
|
|
27
|
+
- `../config/common` — imported as `common`. Used for the
|
|
28
|
+
`insertNotification` helper to persist notification records.
|
|
29
|
+
- `../logger/logging` — imported as `logger`. Used throughout for
|
|
30
|
+
info and error logging.
|
|
31
|
+
- `../utilities/response` — imports `{ getMessage }`. Used to resolve
|
|
32
|
+
push notification message text from language keys. Never import
|
|
33
|
+
localizify or call t() directly in this file.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Module-Level Setup
|
|
38
|
+
|
|
39
|
+
### Firebase Initialization
|
|
40
|
+
Before the class definition, at module level:
|
|
41
|
+
|
|
42
|
+
1. Read the Firebase service account credentials file:
|
|
43
|
+
Use `fs.readFileSync(GLOBALS.FIREBASE_ADMIN_JSONFILE, 'utf8')` and
|
|
44
|
+
parse with `JSON.parse`. Store as `serviceAccount`.
|
|
45
|
+
|
|
46
|
+
2. Initialize Firebase Admin SDK:
|
|
47
|
+
Call `admin.initializeApp({ credential: admin.credential.cert(serviceAccount) })`.
|
|
48
|
+
|
|
49
|
+
This happens once at module load. If the service file is missing, the
|
|
50
|
+
require will throw at startup — this is correct behavior. Firebase must
|
|
51
|
+
be configured before the service starts.
|
|
52
|
+
|
|
53
|
+
### Nodemailer Transporter
|
|
54
|
+
At module level, after Firebase init, create a nodemailer transporter:
|
|
55
|
+
Call `nodemailer.createTransport()` with SMTP configuration read from
|
|
56
|
+
process.env:
|
|
57
|
+
- `host` from `process.env.SMTP_HOST`
|
|
58
|
+
- `port` from `process.env.SMTP_PORT` parsed as integer
|
|
59
|
+
- `secure` — set to `true` if SMTP_PORT is 465, otherwise `false`
|
|
60
|
+
- `auth` object with:
|
|
61
|
+
- `user` from `process.env.SMTP_USER`
|
|
62
|
+
- `pass` from `process.env.SMTP_PASSWORD`
|
|
63
|
+
|
|
64
|
+
Store as module-level `transporter` constant.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Class: Notification
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### async sendEmail(to, subject, htmlContent)
|
|
73
|
+
|
|
74
|
+
**Purpose**: Sends a transactional HTML email using the nodemailer
|
|
75
|
+
transporter configured at module load.
|
|
76
|
+
|
|
77
|
+
**Parameters**:
|
|
78
|
+
- `to` — recipient email address string
|
|
79
|
+
- `subject` — email subject line string
|
|
80
|
+
- `htmlContent` — the complete HTML string, typically produced by a
|
|
81
|
+
template function from `config/template.js`
|
|
82
|
+
|
|
83
|
+
**Flow**:
|
|
84
|
+
|
|
85
|
+
1. Build the mail options object:
|
|
86
|
+
- `from` — constructed as `"APP_NAME <SMTP_USER>"` using GLOBALS.APP_NAME
|
|
87
|
+
and process.env.SMTP_USER. This gives the email a friendly sender name.
|
|
88
|
+
- `to` — the recipient address
|
|
89
|
+
- `subject` — the subject line
|
|
90
|
+
- `html` — the htmlContent string
|
|
91
|
+
|
|
92
|
+
2. Call `transporter.sendMail(mailOptions)` inside a try/catch.
|
|
93
|
+
|
|
94
|
+
3. On success — log at info level: email sent, recipient address, subject.
|
|
95
|
+
Return `true`.
|
|
96
|
+
|
|
97
|
+
4. On error — log at error level: failed to send email, recipient,
|
|
98
|
+
subject, and error object. Return `false`. Never throw — callers
|
|
99
|
+
check the return value.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### async sendSMS(to, message)
|
|
104
|
+
|
|
105
|
+
**Purpose**: Stub function for SMS sending. Returns true always.
|
|
106
|
+
Exists so model files can call it now and the real implementation
|
|
107
|
+
can be added later without changing any call sites.
|
|
108
|
+
|
|
109
|
+
**Parameters**:
|
|
110
|
+
- `to` — recipient phone number string including country code
|
|
111
|
+
- `message` — the SMS message body string
|
|
112
|
+
|
|
113
|
+
**Flow**:
|
|
114
|
+
|
|
115
|
+
1. Log at info level: SMS stub called, recipient, message length.
|
|
116
|
+
2. Return `true`.
|
|
117
|
+
|
|
118
|
+
No actual SMS sending. No external library imported. Developer adds the
|
|
119
|
+
real SMS provider integration here when needed (Twilio, AWS SNS, etc.)
|
|
120
|
+
by replacing this function body.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### async send_push(notification, device_type, device_token)
|
|
125
|
+
|
|
126
|
+
**Purpose**: Sends a push notification to a single device using Firebase
|
|
127
|
+
Cloud Messaging via the Admin SDK's `sendEachForMulticast` method.
|
|
128
|
+
|
|
129
|
+
**Parameters**:
|
|
130
|
+
- `lang` — language code string for the recipient's preferred language.
|
|
131
|
+
e.g. `"en"`, `"ar"`. Used by getMessage to resolve the notification
|
|
132
|
+
message in the correct language. Defaults to DEFAULT_LANGUAGE if
|
|
133
|
+
empty or not provided.
|
|
134
|
+
- `notification` — object containing the notification data with these
|
|
135
|
+
expected keys:
|
|
136
|
+
- `notification_tag` — the localizify translation key for the message
|
|
137
|
+
body text
|
|
138
|
+
- `content` — object containing interpolation values for the message
|
|
139
|
+
translation. Also stored as the notification data payload.
|
|
140
|
+
- `primary_id` — primary entity ID related to this notification
|
|
141
|
+
- `secondary_id` — secondary entity ID if applicable
|
|
142
|
+
- `sender_id` — the ID of the entity that triggered this notification
|
|
143
|
+
- `device_type` — string indicating the device platform (e.g. `"ios"`,
|
|
144
|
+
`"android"`). Received but not used in routing — FCM handles
|
|
145
|
+
platform differences. Kept as parameter for logging and future use.
|
|
146
|
+
- `device_token` — the FCM registration token for the target device
|
|
147
|
+
|
|
148
|
+
**Flow**:
|
|
149
|
+
|
|
150
|
+
1. Resolve the notification message text:
|
|
151
|
+
Call `await getMessage(lang, notification.notification_tag, notification.content)`
|
|
152
|
+
from `utilities/response.js` to get the translated and interpolated
|
|
153
|
+
message string. Store as `message`.
|
|
154
|
+
|
|
155
|
+
The `lang` parameter: push notifications are sent outside the
|
|
156
|
+
request cycle so `req.lang` is not available. The caller must
|
|
157
|
+
pass the user's preferred language. Update the `send_push` function
|
|
158
|
+
signature to accept `lang` as the first parameter:
|
|
159
|
+
`async send_push(lang, notification, device_type, device_token)`
|
|
160
|
+
|
|
161
|
+
If `lang` is not provided or is empty — default to
|
|
162
|
+
`process.env.DEFAULT_LANGUAGE`.
|
|
163
|
+
|
|
164
|
+
2. Serialize notification content:
|
|
165
|
+
Replace `notification.content` with `JSON.stringify(notification.content)`.
|
|
166
|
+
FCM data payloads must be string values — objects are not accepted.
|
|
167
|
+
|
|
168
|
+
3. Insert notification record:
|
|
169
|
+
If `notification.notification_tag` is not `"sendmessage"` — call
|
|
170
|
+
`await common.insertNotification(notification)` to persist the
|
|
171
|
+
notification record in the database.
|
|
172
|
+
|
|
173
|
+
4. Validate the device token:
|
|
174
|
+
If `device_token` is falsy or equals the string `"0"` — log at info
|
|
175
|
+
level that the token is blank and return `true`. Do not attempt to
|
|
176
|
+
send to an invalid token.
|
|
177
|
+
|
|
178
|
+
5. Build the FCM payload object with these keys:
|
|
179
|
+
- `tokens` — array containing `device_token` as the only element.
|
|
180
|
+
`sendEachForMulticast` accepts an array of tokens.
|
|
181
|
+
- `notification` object with:
|
|
182
|
+
- `title` — GLOBALS.APP_NAME
|
|
183
|
+
- `body` — the resolved `message` string
|
|
184
|
+
- `data` object with these string values (all must be strings for
|
|
185
|
+
FCM data payloads, use optional chaining and `.toString()` for
|
|
186
|
+
each):
|
|
187
|
+
- `title` — GLOBALS.APP_NAME
|
|
188
|
+
- `body` — the resolved `message` string
|
|
189
|
+
- `primary_id` — notification.primary_id as string
|
|
190
|
+
- `secondary_id` — notification.secondary_id as string
|
|
191
|
+
- `sender_id` — notification.sender_id as string
|
|
192
|
+
- `tag` — notification.notification_tag as string
|
|
193
|
+
- `apns` object for iOS-specific config:
|
|
194
|
+
- `payload.aps.sound` — `"default"`
|
|
195
|
+
|
|
196
|
+
6. Call `await admin.messaging().sendEachForMulticast(pushPayload)`.
|
|
197
|
+
Store result as `pushResponse`.
|
|
198
|
+
|
|
199
|
+
7. Log at info level: the responses array from pushResponse for
|
|
200
|
+
debugging.
|
|
201
|
+
|
|
202
|
+
8. Check the first response: `pushResponse.responses[0]?.success`
|
|
203
|
+
- If true — log at info level: notification sent successfully.
|
|
204
|
+
Return `true`.
|
|
205
|
+
- If false — log at info level: notification failed, include the
|
|
206
|
+
error from `pushResponse.responses[0]?.error`. Return `false`.
|
|
207
|
+
|
|
208
|
+
9. In the outer catch block — log at error level: exception caught,
|
|
209
|
+
include error object. Return `false`. Never throw — callers check
|
|
210
|
+
the return value.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Export
|
|
215
|
+
```
|
|
216
|
+
module.exports = new Notification()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
A single instance is created and exported. The constructor has no
|
|
220
|
+
logic — all setup is at module level. Imported as:
|
|
221
|
+
`const notification = require('../utilities/notification')`
|
|
222
|
+
|
|
223
|
+
Then used as: `notification.sendEmail(...)`, `notification.send_push(...)`
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## .env additions required by this file
|
|
228
|
+
|
|
229
|
+
Add to the `.env` template in nodejs-agent:
|
|
230
|
+
```
|
|
231
|
+
SMTP_HOST=smtp.gmail.com
|
|
232
|
+
SMTP_PORT=587
|
|
233
|
+
SMTP_USER=
|
|
234
|
+
SMTP_PASSWORD=
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## What This File Does NOT Do
|
|
240
|
+
|
|
241
|
+
- Does not build email HTML — that is `config/template.js`'s job.
|
|
242
|
+
This file only sends what it receives.
|
|
243
|
+
- Does not define notification content or messages — those come from
|
|
244
|
+
the caller and from language files via localizify
|
|
245
|
+
- Does not handle notification preferences or user opt-outs — that
|
|
246
|
+
is business logic in model files
|
|
247
|
+
- Does not implement retry logic for failed sends — callers decide
|
|
248
|
+
whether to retry
|
|
249
|
+
- Does not import localizify or call t() directly — all translation
|
|
250
|
+
goes through getMessage from utilities/response.js
|
|
251
|
+
- Does not touch req or res — this is a utility, not a middleware
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: task
|
|
3
|
+
name: generate-package-json
|
|
4
|
+
agent: nodejs-agent
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# File: package.json
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
Defines the Node.js project metadata and all dependencies for the
|
|
11
|
+
service. Generated once during `@initialize-project`. The agent reads
|
|
12
|
+
context to populate the correct name, version, description, and author,
|
|
13
|
+
and includes exactly the packages used across all generated files —
|
|
14
|
+
no more, no less. After generation the agent outputs the install command.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Metadata Fields
|
|
19
|
+
|
|
20
|
+
- `name` — from `context.current_init.package_name`
|
|
21
|
+
- `version` — `"1.0.0"`
|
|
22
|
+
- `description` — from `context.current_init.description`
|
|
23
|
+
- `main` — `"app.js"`
|
|
24
|
+
- `author` — from `context.current_init.author`
|
|
25
|
+
- `license` — `"ISC"`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Scripts
|
|
30
|
+
```json
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "node app.js",
|
|
33
|
+
"dev": "nodemon --ignore 'logger/logs/' --ignore 'document/' --ext js app.js",
|
|
34
|
+
"test": "jest --testEnvironment=node --forceExit",
|
|
35
|
+
"docker:build": "docker build -t <service_name>:latest .",
|
|
36
|
+
"docker:run": "docker run -p <port>:<port> --env-file .env <service_name>:latest",
|
|
37
|
+
"docker:stop": "docker stop $(docker ps -q --filter ancestor=<service_name>:latest)",
|
|
38
|
+
"docker:logs": "docker logs -f $(docker ps -q --filter ancestor=<service_name>:latest)"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Replace `<service_name>` with `context.current_init.service_name` and `<port>` with `context.current_init.port`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Core Dependencies (always included for all NodeJS services)
|
|
47
|
+
|
|
48
|
+
These are required regardless of client_type, db_type, or any other
|
|
49
|
+
choice:
|
|
50
|
+
|
|
51
|
+
- `express` — web framework
|
|
52
|
+
- `dotenv` — environment variable loading
|
|
53
|
+
- `localizify` — i18n translation
|
|
54
|
+
- `moment` — date/time formatting and manipulation
|
|
55
|
+
- `jsonwebtoken` — JWT signing and verification
|
|
56
|
+
- `ioredis` — Redis client
|
|
57
|
+
- `nodemailer` — email sending
|
|
58
|
+
- `firebase-admin` — push notifications via FCM
|
|
59
|
+
- `validatorjs` — request body validation
|
|
60
|
+
- `pg-format` — safe dynamic SQL query building
|
|
61
|
+
- `express-rate-limit` — rate limiting middleware
|
|
62
|
+
- `cors` — cross-origin resource sharing
|
|
63
|
+
- `helmet` — HTTP security headers (include even if not in app.js
|
|
64
|
+
currently — standard security practice)
|
|
65
|
+
- `rand-token` — random token generation used in common.js
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Conditional Dependencies
|
|
70
|
+
|
|
71
|
+
Include based on context values:
|
|
72
|
+
|
|
73
|
+
### Based on `context.db.type`
|
|
74
|
+
- If `postgresql` → include `pg`
|
|
75
|
+
- If `mysql` → include `mysql2`
|
|
76
|
+
- If `mongodb` → include `mongoose`
|
|
77
|
+
|
|
78
|
+
### Based on `context.services[<name>].client_type`
|
|
79
|
+
- If `reactjs` → include `crypto-js`
|
|
80
|
+
- If `app` → include `cryptlib`
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Dev Dependencies (always included)
|
|
85
|
+
```json
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"nodemon": "^3.0.0",
|
|
88
|
+
"jest": "^29.0.0",
|
|
89
|
+
"supertest": "^6.0.0"
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## After Generation
|
|
96
|
+
|
|
97
|
+
The agent outputs this command in the final summary for the developer
|
|
98
|
+
to run:
|
|
99
|
+
```
|
|
100
|
+
cd <service_name>
|
|
101
|
+
npm install
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What This File Does NOT Do
|
|
107
|
+
|
|
108
|
+
- Does not pin exact versions — uses `^` (caret) ranges for all
|
|
109
|
+
dependencies to allow minor/patch updates
|
|
110
|
+
- Does not include a `engines` field — Node version constraint is
|
|
111
|
+
out of scope for scaffolding
|
|
112
|
+
- Does not include pre/post scripts
|
|
113
|
+
- Does not configure Jest in package.json — a separate jest.config.js
|
|
114
|
+
can be added later if needed
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: task
|
|
3
|
+
name: generate-rateLimiter
|
|
4
|
+
agent: nodejs-agent
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# File: middleware/rateLimiter.js
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
Production-grade rate limiting middleware using express-rate-limit.
|
|
11
|
+
Prevents abuse by limiting the number of requests a single IP address
|
|
12
|
+
can make within a configurable time window. All configuration values
|
|
13
|
+
come from environment variables — nothing is hardcoded. When the limit
|
|
14
|
+
is exceeded, the response follows the same shape and language system
|
|
15
|
+
as all other responses in the service.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Dependencies to Import
|
|
20
|
+
|
|
21
|
+
- `express-rate-limit` — the rate limiting library. Imported as
|
|
22
|
+
`rateLimit`.
|
|
23
|
+
- `../utilities/response` — imports `sendResponse` to send the
|
|
24
|
+
limit-exceeded response in the correct format with translation support.
|
|
25
|
+
- `../logger/logging` — imports `log` for logging limit violations.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Environment Variables Required
|
|
30
|
+
|
|
31
|
+
The following variables must exist in `.env` and be read by this file:
|
|
32
|
+
|
|
33
|
+
- `RATE_LIMIT_WINDOW_MS` — the time window in milliseconds. Example:
|
|
34
|
+
`900000` for 15 minutes. If not set, default to `900000`.
|
|
35
|
+
- `RATE_LIMIT_MAX` — maximum number of requests allowed per IP within
|
|
36
|
+
the window. Example: `100`. If not set, default to `100`.
|
|
37
|
+
|
|
38
|
+
Add both of these to the `.env` template in nodejs-agent.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Functions
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
### rateLimiter (exported middleware)
|
|
47
|
+
|
|
48
|
+
**Purpose**: Creates and exports a configured express-rate-limit
|
|
49
|
+
middleware instance. Applied globally in route_manager.js before
|
|
50
|
+
all other middleware.
|
|
51
|
+
|
|
52
|
+
**Configuration**:
|
|
53
|
+
|
|
54
|
+
1. Read `RATE_LIMIT_WINDOW_MS` from `process.env`. Parse as integer.
|
|
55
|
+
Use default `900000` if absent or not a valid number.
|
|
56
|
+
|
|
57
|
+
2. Read `RATE_LIMIT_MAX` from `process.env`. Parse as integer.
|
|
58
|
+
Use default `100` if absent or not a valid number.
|
|
59
|
+
|
|
60
|
+
3. Set `standardHeaders: true` — this sends `RateLimit-*` headers in
|
|
61
|
+
responses so clients can see their remaining quota. This is the
|
|
62
|
+
modern standard header format (RFC draft).
|
|
63
|
+
|
|
64
|
+
4. Set `legacyHeaders: false` — disables the older `X-RateLimit-*`
|
|
65
|
+
headers. Only one format should be active at a time.
|
|
66
|
+
|
|
67
|
+
5. Set `handler` — a custom async function called when a request exceeds
|
|
68
|
+
the limit. This replaces the default express-rate-limit plain text
|
|
69
|
+
response with the service's standard response format.
|
|
70
|
+
|
|
71
|
+
The handler receives `(req, res)`.
|
|
72
|
+
|
|
73
|
+
Inside the handler:
|
|
74
|
+
- Log at warning level: the IP address (`req.ip`), the path
|
|
75
|
+
(`req.path`), and a message indicating rate limit exceeded.
|
|
76
|
+
- Call `sendResponse` from `utilities/response.js` with:
|
|
77
|
+
- `req` — pass through (req.lang must be set by extractLanguage
|
|
78
|
+
which runs before this handler fires, so translation works)
|
|
79
|
+
- `res` — pass through
|
|
80
|
+
- `statusCode` — `429`
|
|
81
|
+
- `responseCode` — `"0"`
|
|
82
|
+
- `messageKeyword` — `"rest_keywords_rate_limit_exceeded"`
|
|
83
|
+
- `messageComponents` — `{}`
|
|
84
|
+
- `responseData` — `null`
|
|
85
|
+
|
|
86
|
+
6. Set `keyGenerator` to use `req.ip` as the rate limit key. This is
|
|
87
|
+
the default behavior but must be explicitly set to document intent.
|
|
88
|
+
Each unique IP address gets its own counter.
|
|
89
|
+
|
|
90
|
+
7. Set `skip` to a function that always returns false. This means no
|
|
91
|
+
requests are ever skipped. If future bypass logic is needed (e.g.
|
|
92
|
+
internal health check endpoints), it goes here.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Export
|
|
97
|
+
```
|
|
98
|
+
module.exports = rateLimiter
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Exported as a single middleware function, not wrapped in an object.
|
|
102
|
+
Used in route_manager.js as:
|
|
103
|
+
`router.use('/', asyncHandler(rateLimiter))`
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Language File Requirement
|
|
108
|
+
|
|
109
|
+
Add this key to all language files (`languages/en.js` etc.):
|
|
110
|
+
```
|
|
111
|
+
rest_keywords_rate_limit_exceeded: "Too many requests. Please try again later."
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## What This File Does NOT Do
|
|
117
|
+
|
|
118
|
+
- Does not use Redis for distributed rate limiting — uses in-memory
|
|
119
|
+
store only (the default for express-rate-limit). This is correct for
|
|
120
|
+
single-instance deployments. If multi-instance is needed in future,
|
|
121
|
+
a Redis store can be plugged in here.
|
|
122
|
+
- Does not apply different limits to different routes — one global limit
|
|
123
|
+
for all routes. Per-route limits are out of scope.
|
|
124
|
+
- Does not whitelist any IPs — all IPs are treated equally.
|
|
125
|
+
- Does not log every request — only logs when a limit is exceeded.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: task
|
|
3
|
+
name: generate-react-api-client
|
|
4
|
+
agent: reactjs-agent
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# File: src/api/apiClient.js
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
The single Axios instance used by all API calls in the application.
|
|
11
|
+
Handles request body encryption, session token injection, response
|
|
12
|
+
decryption, logout triggering, and network error handling. No other
|
|
13
|
+
file in the frontend communicates with the backend directly.
|
|
14
|
+
|
|
15
|
+
This file is the frontend equivalent of the backend's `headerValidator.js`
|
|
16
|
+
and `response.js` combined — it is the transport layer boundary.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Dependencies to Import
|
|
21
|
+
|
|
22
|
+
- `axios` — HTTP client
|
|
23
|
+
- `crypto-js` as `CryptoJS` — AES-256-CBC encryption/decryption
|
|
24
|
+
- `{ logOutRedirectCall, showErrorMessage }` from
|
|
25
|
+
`../pages/common/Utils` — developer-implemented utilities for
|
|
26
|
+
logout redirect and toast/error display. The agent generates these
|
|
27
|
+
import lines; the developer implements the functions.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Module-Level Constants
|
|
32
|
+
|
|
33
|
+
### key
|
|
34
|
+
Derived once at module load from `process.env.REACT_APP_KEY`.
|
|
35
|
+
Parse using `CryptoJS.enc.Hex.parse(process.env.REACT_APP_KEY)`.
|
|
36
|
+
Result is a CryptoJS WordArray. Store as `key`.
|
|
37
|
+
|
|
38
|
+
### iv
|
|
39
|
+
Derived once at module load from `process.env.REACT_APP_IV`.
|
|
40
|
+
Parse using `CryptoJS.enc.Hex.parse(process.env.REACT_APP_IV)`.
|
|
41
|
+
Result is a CryptoJS WordArray. Store as `iv`.
|
|
42
|
+
|
|
43
|
+
Both are module-level constants. Never re-derive inside functions.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Axios Instance
|
|
48
|
+
|
|
49
|
+
Create with `axios.create()`:
|
|
50
|
+
- `baseURL` — from `process.env.REACT_APP_BASE_URL`
|
|
51
|
+
- Static headers:
|
|
52
|
+
- `api-key` — from `process.env.REACT_APP_API_KEY`
|
|
53
|
+
- `Accept-Language` — `'en'`
|
|
54
|
+
- `Content-Type` — `'text/plain'`
|
|
55
|
+
|
|
56
|
+
The `Content-Type: text/plain` is required because the encrypted body
|
|
57
|
+
is a cipher string, not JSON. The backend's Express app is configured
|
|
58
|
+
with `express.text()` to receive it this way.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Request Interceptor
|
|
63
|
+
|
|
64
|
+
Applied via `axiosClient.interceptors.request.use`.
|
|
65
|
+
|
|
66
|
+
Responsibilities on the fulfilled path:
|
|
67
|
+
1. Encrypt `request.data` by calling `bodyEncryption(request.data, true)`
|
|
68
|
+
and replacing `request.data` with the result. The `true` flag means
|
|
69
|
+
the data should be JSON-stringified before encryption.
|
|
70
|
+
2. Read `localStorage.getItem('wa_token')`. If a value is present,
|
|
71
|
+
encrypt it by calling `bodyEncryption(JSON.stringify(storedToken), false)`
|
|
72
|
+
(the `false` flag means the value is already a string — do not
|
|
73
|
+
double-stringify it). Set the result as `request.headers['token']`.
|
|
74
|
+
|
|
75
|
+
Return `request`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Response Interceptor — Success Path
|
|
80
|
+
|
|
81
|
+
Applied via `axiosClient.interceptors.response.use` fulfilled handler.
|
|
82
|
+
|
|
83
|
+
Responsibilities:
|
|
84
|
+
1. Call `bodyDecryption(response.data)` to get the decrypted string.
|
|
85
|
+
2. If decryption returns a falsy value, return `response.data` as-is
|
|
86
|
+
without crashing.
|
|
87
|
+
3. Call `JSON.parse()` on the decrypted string to get the response object.
|
|
88
|
+
4. Check `parseInt(respData.code)`:
|
|
89
|
+
- If `-1` → call `logOutRedirectCall()` and return `respData`.
|
|
90
|
+
The -1 code means the session has expired or the token is invalid.
|
|
91
|
+
- If `0` → call the internal `showMessage(respData.data.message)`.
|
|
92
|
+
The 0 code means the request failed with a user-facing message.
|
|
93
|
+
5. Return `respData` for all other codes.
|
|
94
|
+
6. Wrap the entire handler body in a try/catch. On any error — return
|
|
95
|
+
`response.data ?? response` without rethrowing. The UI must never
|
|
96
|
+
crash because of a decryption or parsing error.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Response Interceptor — Error Path
|
|
101
|
+
|
|
102
|
+
Applied via `axiosClient.interceptors.response.use` rejected handler.
|
|
103
|
+
|
|
104
|
+
Responsibilities:
|
|
105
|
+
1. Log the error to console for debugging.
|
|
106
|
+
2. Extract `error.response` as `res` (default to empty object if absent).
|
|
107
|
+
3. If `error.code === 'ERR_NETWORK'` → call `logOutRedirectCall()` and
|
|
108
|
+
call `showErrorMessage(\`${error.code}: ${error.message}\`)`.
|
|
109
|
+
4. If `res.status` is 401 → call `logOutRedirectCall()`.
|
|
110
|
+
5. Return `Promise.reject(error)`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Internal Helper Functions
|
|
115
|
+
|
|
116
|
+
These are not exported. They are only used inside this file.
|
|
117
|
+
|
|
118
|
+
### showMessage(msg)
|
|
119
|
+
A minimal internal logging function. Calls `console.log(msg)`.
|
|
120
|
+
Used when the backend returns response code 0 with a user message.
|
|
121
|
+
The developer replaces this with a proper toast/notification call as
|
|
122
|
+
the project matures.
|
|
123
|
+
|
|
124
|
+
### bodyEncryption(request, isStringify)
|
|
125
|
+
Encrypts a value for transmission.
|
|
126
|
+
|
|
127
|
+
Parameters:
|
|
128
|
+
- `request` — the value to encrypt. Can be any type.
|
|
129
|
+
- `isStringify` — boolean. If true, call `JSON.stringify(request ?? {})`
|
|
130
|
+
first. If false, use `request ?? ''` as the string directly.
|
|
131
|
+
|
|
132
|
+
Flow:
|
|
133
|
+
1. Build the payload string based on `isStringify`.
|
|
134
|
+
2. Call `CryptoJS.AES.encrypt(payload, key, { iv })`.
|
|
135
|
+
3. Return `.toString()` on the result — the Base64 cipher string.
|
|
136
|
+
4. Wrap in try/catch. On any error return empty string `''`.
|
|
137
|
+
|
|
138
|
+
### bodyDecryption(request)
|
|
139
|
+
Decrypts a cipher string received from the backend.
|
|
140
|
+
|
|
141
|
+
Parameters:
|
|
142
|
+
- `request` — the encrypted response string. May be null.
|
|
143
|
+
|
|
144
|
+
Flow:
|
|
145
|
+
1. If `request` is null or undefined, return empty string `''`.
|
|
146
|
+
2. Call `CryptoJS.AES.decrypt(request.toString(), key, { iv })`.
|
|
147
|
+
3. Return `.toString(CryptoJS.enc.Utf8)` on the result.
|
|
148
|
+
4. Wrap in try/catch. On any error return empty string `''`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Export
|
|
153
|
+
|
|
154
|
+
Export the Axios instance as a named export:
|
|
155
|
+
`export { axiosClient }`
|
|
156
|
+
|
|
157
|
+
Do not use default export. `apiHandler.js` imports it as:
|
|
158
|
+
`import { axiosClient } from "./apiClient"`
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## What This File Does NOT Do
|
|
163
|
+
|
|
164
|
+
- Does not define any API routes or endpoint paths — those are in
|
|
165
|
+
`apiHandler.js`
|
|
166
|
+
- Does not handle session storage directly beyond reading `wa_token`
|
|
167
|
+
from localStorage — session management belongs in the pages layer
|
|
168
|
+
- Does not import React — this is a plain JavaScript module
|
|
169
|
+
- Does not apply any retry logic — failed requests propagate to the caller
|