@@ -140,6 +140,25 @@ async createLinkToken(telegramId) {
140140- **Rate limiting** (60 requests/minute per IP)
141141- **Input validation** on all endpoints
142142
143+ ### Encryption Implementation
144+ The ` encryptionService` provides transparent encryption/decryption:
145+ ` ` ` javascript
146+ const encryptionService = new EncryptionService ();
147+
148+ // Encrypt before storing
149+ const encrypted = encryptionService .encrypt (accessToken);
150+ await db .query (
151+ ' INSERT INTO plaid_connections (user_id, access_token) VALUES ($1, $2)' ,
152+ [userId, encrypted]
153+ );
154+
155+ // Decrypt when retrieving
156+ const connection = await db .query (' SELECT * FROM plaid_connections WHERE user_id = $1' , [userId]);
157+ const decrypted = encryptionService .decrypt (connection .rows [0 ].access_token );
158+ ` ` `
159+
160+ **Important:** Plaid access tokens MUST always be encrypted before storing.
161+
143162### When Adding New Features
144163- If handling tokens or secrets, use ` encryptionService`
145164- If adding API endpoints, apply rate limiting middleware
@@ -155,22 +174,64 @@ The database uses PostgreSQL with three main tables:
155174- **transactions_cache:** Cached transaction data
156175
157176### Model Methods
158- Models use static methods for database operations:
177+ Models use static methods for database operations. All queries use parameterized arguments ($1, $2, etc.) to prevent SQL injection :
159178` ` ` javascript
179+ // Example: Finding a user by telegram ID
160180static async findByTelegramId (telegramId ) {
161181 const result = await db .query (
162182 ' SELECT * FROM users WHERE telegram_id = $1' ,
163- [telegramId]
183+ [telegramId] // Parameter passed separately
164184 );
165185 return result .rows [0 ] || null ;
166186}
187+
188+ // Example: Creating/updating a user with ON CONFLICT
189+ static async create (userData ) {
190+ const { telegram_id , username , first_name , last_name } = userData;
191+ const result = await db .query (
192+ ` INSERT INTO users (telegram_id, username, first_name, last_name)
193+ VALUES ($1, $2, $3, $4)
194+ ON CONFLICT (telegram_id) DO UPDATE
195+ SET username = $2, first_name = $3, last_name = $4, updated_at = CURRENT_TIMESTAMP
196+ RETURNING *` ,
197+ [telegram_id, username, first_name, last_name]
198+ );
199+ return result .rows [0 ];
200+ }
167201` ` `
168202
169203### Best Practices
170204- Use CASCADE deletes to maintain referential integrity
171205- Always use connection pooling (pre-configured in ` database/ connection .js ` )
172206- Use indexes on frequently queried columns
173207- Handle database errors gracefully with proper logging
208+ - Never construct query strings with concatenation or template literals
209+
210+ ## Configuration Management
211+
212+ ### Config Pattern
213+ All environment-based configuration is centralized in ` src/ config/ index .js ` :
214+ ` ` ` javascript
215+ module .exports = {
216+ telegram: { botToken: process .env .TELEGRAM_BOT_TOKEN },
217+ plaid: {
218+ clientId: process .env .PLAID_CLIENT_ID ,
219+ secret: process .env .PLAID_SECRET ,
220+ env: process .env .PLAID_ENV || ' sandbox' ,
221+ },
222+ database: { url: process .env .DATABASE_URL },
223+ server: {
224+ port: process .env .PORT || 3000 ,
225+ apiBaseUrl: process .env .API_BASE_URL || ' http://localhost:3000' ,
226+ },
227+ security: { encryptionKey: process .env .ENCRYPTION_KEY },
228+ logging: { level: process .env .LOG_LEVEL || ' info' },
229+ };
230+ ` ` `
231+
232+ Import and use in all modules: ` const config = require (' ../config' );`
233+
234+ This centralizes configuration, making defaults clear and dependencies explicit.
174235
175236## Telegram Bot Patterns
176237
@@ -197,40 +258,46 @@ Commands are registered in `/bot/index.js`:
197258this .bot .command (' commandname' , commandHandler);
198259` ` `
199260
261+ ### Cross-Component Data Flow
262+ Commands call Express API endpoints via ` axios` :
263+ ` ` ` javascript
264+ // In /bot/commands/link.js
265+ const response = await axios .post (
266+ ` ${ config .server .apiBaseUrl } /api/plaid/create-link-token` ,
267+ { telegram_id: ctx .from .id }
268+ );
269+ ` ` `
270+
271+ This pattern keeps bot logic thin and delegates business logic to services accessed via REST API.
272+
200273## API Patterns
201274
202- ### Controller Structure
203- Controllers handle HTTP requests and call services :
275+ ### Controller Structure & Error Handling
276+ Controllers use ` asyncHandler ` wrapper for automatic error handling :
204277` ` ` javascript
205- async createLinkToken (req , res ) {
206- try {
207- const { telegram_id } = req .body ;
208-
209- // Validation
210- if (! telegram_id) {
211- return res .status (400 ).json ({
212- success: false ,
213- error: ' telegram_id is required'
214- });
215- }
278+ const { asyncHandler } = require (' ../../utils/errorHandler' );
216279
217- // Business logic in service
218- const result = await plaidService .createLinkToken (telegram_id);
219-
220- return res .json ({
221- success: true ,
222- ... result
223- });
224- } catch (error) {
225- logger .error (' Controller error:' , error);
226- return res .status (500 ).json ({
280+ const createLinkToken = asyncHandler (async (req , res ) => {
281+ const { telegram_id } = req .body ;
282+
283+ if (! telegram_id) {
284+ return res .status (400 ).json ({
227285 success: false ,
228- error : error . message
286+ message : ' telegram_id is required ' ,
229287 });
230288 }
231- }
289+
290+ const result = await plaidService .createLinkToken (telegram_id);
291+
292+ res .json ({
293+ success: true ,
294+ ... result,
295+ });
296+ });
232297` ` `
233298
299+ This pattern eliminates redundant try-catch blocks in every controller.
300+
234301### Response Format
235302All API responses follow this structure:
236303` ` ` javascript
0 commit comments