Skip to content

Commit d03675f

Browse files
update the readme.md files
1 parent 163df42 commit d03675f

1 file changed

Lines changed: 94 additions & 27 deletions

File tree

.github/copilot-instructions.md

Lines changed: 94 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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
160180
static 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`:
197258
this.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
235302
All API responses follow this structure:
236303
```javascript

0 commit comments

Comments
 (0)