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.
Files changed (140) hide show
  1. package/.gitattributes +11 -0
  2. package/README.md +293 -0
  3. package/agent/database-agent.md +504 -0
  4. package/agent/designs/README.md +10 -0
  5. package/agent/global-agent.md +236 -0
  6. package/agent/nodejs-agent.md +406 -0
  7. package/agent/reactjs-agent.md +260 -0
  8. package/cli.js +352 -0
  9. package/commands/audit.workflow.md +111 -0
  10. package/commands/create-api.workflow.md +99 -0
  11. package/commands/db-add-index.workflow.md +97 -0
  12. package/commands/db-create-table.workflow.md +132 -0
  13. package/commands/db-drop-table.workflow.md +103 -0
  14. package/commands/db-modify-table.workflow.md +159 -0
  15. package/commands/db-seed.workflow.md +99 -0
  16. package/commands/db-sync.workflow.md +100 -0
  17. package/commands/design.workflow.md +66 -0
  18. package/commands/initialize-project.workflow.md +500 -0
  19. package/commands/integrate-api.workflow.md +448 -0
  20. package/commands/modularize.workflow.md +329 -0
  21. package/commands/refactor.workflow.md +70 -0
  22. package/commands/sync.workflow.md +962 -0
  23. package/commands/test.workflow.md +40 -0
  24. package/commands/validate-page.workflow.md +543 -0
  25. package/mcp-server.js +842 -0
  26. package/package.json +24 -0
  27. package/tasks/README.md +283 -0
  28. package/tasks/add-health-route.task.md +103 -0
  29. package/tasks/ask-api-integration-scope.task.md +34 -0
  30. package/tasks/ask-api-key.task.md +23 -0
  31. package/tasks/ask-api-version.task.md +28 -0
  32. package/tasks/ask-client-type.task.md +24 -0
  33. package/tasks/ask-column-enum-values.task.md +51 -0
  34. package/tasks/ask-column-is-enum.task.md +39 -0
  35. package/tasks/ask-column-name.task.md +39 -0
  36. package/tasks/ask-column-position.task.md +39 -0
  37. package/tasks/ask-column-type.task.md +59 -0
  38. package/tasks/ask-database-config.task.md +66 -0
  39. package/tasks/ask-database-host.task.md +16 -0
  40. package/tasks/ask-database-name.task.md +18 -0
  41. package/tasks/ask-database-port.task.md +23 -0
  42. package/tasks/ask-database-type.task.md +30 -0
  43. package/tasks/ask-database-user.task.md +14 -0
  44. package/tasks/ask-design-description.task.md +16 -0
  45. package/tasks/ask-design-target.task.md +24 -0
  46. package/tasks/ask-encrypted-transport.task.md +25 -0
  47. package/tasks/ask-encryption-iv.task.md +23 -0
  48. package/tasks/ask-encryption-key.task.md +23 -0
  49. package/tasks/ask-feature-name.task.md +20 -0
  50. package/tasks/ask-http-method.task.md +21 -0
  51. package/tasks/ask-index-columns.task.md +46 -0
  52. package/tasks/ask-index-file-placement.task.md +33 -0
  53. package/tasks/ask-index-sort-order.task.md +37 -0
  54. package/tasks/ask-index-type.task.md +42 -0
  55. package/tasks/ask-init-mode.task.md +28 -0
  56. package/tasks/ask-linked-service.task.md +57 -0
  57. package/tasks/ask-modify-operation.task.md +36 -0
  58. package/tasks/ask-modularize-scope.task.md +31 -0
  59. package/tasks/ask-module-name.task.md +30 -0
  60. package/tasks/ask-new-column-name.task.md +21 -0
  61. package/tasks/ask-new-table-name.task.md +22 -0
  62. package/tasks/ask-old-column-name.task.md +22 -0
  63. package/tasks/ask-package-author.task.md +16 -0
  64. package/tasks/ask-package-name.task.md +23 -0
  65. package/tasks/ask-page-path.task.md +40 -0
  66. package/tasks/ask-primary-table.task.md +30 -0
  67. package/tasks/ask-project-figma.task.md +71 -0
  68. package/tasks/ask-project-info-doc.task.md +57 -0
  69. package/tasks/ask-project-scope-of-work.task.md +57 -0
  70. package/tasks/ask-project-type.task.md +24 -0
  71. package/tasks/ask-react-target-service.task.md +32 -0
  72. package/tasks/ask-redis-config.task.md +42 -0
  73. package/tasks/ask-redis-host.task.md +16 -0
  74. package/tasks/ask-redis-port.task.md +18 -0
  75. package/tasks/ask-refactor-type.task.md +26 -0
  76. package/tasks/ask-requires-auth.task.md +22 -0
  77. package/tasks/ask-response-mode.task.md +38 -0
  78. package/tasks/ask-route-description.task.md +20 -0
  79. package/tasks/ask-route-path.task.md +29 -0
  80. package/tasks/ask-seed-row-values.task.md +42 -0
  81. package/tasks/ask-seed-rows-count.task.md +22 -0
  82. package/tasks/ask-service-description.task.md +16 -0
  83. package/tasks/ask-service-name.task.md +27 -0
  84. package/tasks/ask-service-port.task.md +24 -0
  85. package/tasks/ask-supported-languages.task.md +40 -0
  86. package/tasks/ask-table-file-number.task.md +36 -0
  87. package/tasks/ask-table-indexes.task.md +47 -0
  88. package/tasks/ask-table-name.task.md +32 -0
  89. package/tasks/ask-table-needs-soft-delete.task.md +29 -0
  90. package/tasks/ask-table-needs-status.task.md +30 -0
  91. package/tasks/ask-table-purpose.task.md +28 -0
  92. package/tasks/ask-table-seed-data.task.md +44 -0
  93. package/tasks/ask-target-service.task.md +32 -0
  94. package/tasks/ask-test-type.task.md +20 -0
  95. package/tasks/ask-validation-library.task.md +38 -0
  96. package/tasks/detect-repository-state.task.md +92 -0
  97. package/tasks/generate-app.task.md +146 -0
  98. package/tasks/generate-common.task.md +330 -0
  99. package/tasks/generate-constants.task.md +123 -0
  100. package/tasks/generate-database.task.md +168 -0
  101. package/tasks/generate-docker-compose.task.md +298 -0
  102. package/tasks/generate-dockerfile.task.md +126 -0
  103. package/tasks/generate-dockerignore.task.md +123 -0
  104. package/tasks/generate-enc-dec-html.task.md +127 -0
  105. package/tasks/generate-enc-dec-php.task.md +145 -0
  106. package/tasks/generate-encryption.task.md +159 -0
  107. package/tasks/generate-fast-defaults.task.md +68 -0
  108. package/tasks/generate-gitignore.task.md +79 -0
  109. package/tasks/generate-headerValidator.task.md +377 -0
  110. package/tasks/generate-ide-configs.task.md +114 -0
  111. package/tasks/generate-ioRedis.task.md +120 -0
  112. package/tasks/generate-language-en.task.md +155 -0
  113. package/tasks/generate-logging.task.md +257 -0
  114. package/tasks/generate-model.task.md +180 -0
  115. package/tasks/generate-notification.task.md +251 -0
  116. package/tasks/generate-package-json.task.md +114 -0
  117. package/tasks/generate-rateLimiter.task.md +125 -0
  118. package/tasks/generate-react-api-client.task.md +169 -0
  119. package/tasks/generate-react-api-handler.task.md +102 -0
  120. package/tasks/generate-react-app-jsx.task.md +56 -0
  121. package/tasks/generate-react-dockerfile.task.md +175 -0
  122. package/tasks/generate-react-env.task.md +58 -0
  123. package/tasks/generate-react-gitignore.task.md +49 -0
  124. package/tasks/generate-react-htaccess.task.md +54 -0
  125. package/tasks/generate-react-index-html.task.md +53 -0
  126. package/tasks/generate-react-index-jsx.task.md +51 -0
  127. package/tasks/generate-react-package-json.task.md +77 -0
  128. package/tasks/generate-react-welcome-page.task.md +71 -0
  129. package/tasks/generate-readme.task.md +160 -0
  130. package/tasks/generate-response.task.md +202 -0
  131. package/tasks/generate-route-manager.task.md +173 -0
  132. package/tasks/generate-route.task.md +203 -0
  133. package/tasks/generate-swagger.task.md +290 -0
  134. package/tasks/generate-tbl-user-deviceinfo.task.md +75 -0
  135. package/tasks/generate-template.task.md +129 -0
  136. package/tasks/generate-validator.task.md +122 -0
  137. package/tasks/show-db-table-summary.task.md +66 -0
  138. package/tasks/show-final-summary.task.md +108 -0
  139. package/tasks/show-init-summary.task.md +257 -0
  140. 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