deploy: final fix#98
Conversation
There was a problem hiding this comment.
Pull request overview
This PR appears aimed at finalizing deployment readiness by adding CI/CD automation, improving multi-language UX (including dynamic translation of user-generated/dynamic strings), and introducing Twilio-based SMS notifications across key backend flows (auth, negotiations, orders, advisories).
Changes:
- Add dynamic translation utilities/components and expand i18n resources; propagate language preference into demand/recommendation requests.
- Add Twilio SMS notification service and wire it into auth/login, negotiations, orders, and advisories; add advisory update route.
- Introduce a GitHub Actions CI/CD workflow for backend/frontend build checks and Render deploy hooks.
Reviewed changes
Copilot reviewed 27 out of 28 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/services/translation.service.js | New client-side translation service with cache + API fallbacks |
| frontend/src/hooks/useDynamicTranslation.js | Hook to translate dynamic text using TranslationService |
| frontend/src/components/common/DynamicText.jsx | Component wrapper to render translated dynamic strings (static-key-first, then API) |
| frontend/src/i18n/i18n.js | i18n initialization adjusted to respect saved language; adds many new translation keys |
| frontend/src/App.jsx | Prevents refresh from resetting language by prioritizing localStorage language |
| frontend/src/services/recommendation.service.js | Sends language param to backend demand/recommendation endpoints |
| frontend/src/services/auth.service.js | Stores token/user in localStorage on register/login when returned by backend |
| frontend/src/pages/Login.jsx | Navigates directly to dashboard after login (OTP flow removed) |
| frontend/src/pages/Register.jsx | Navigates directly to dashboard after register (OTP flow removed) |
| frontend/src/pages/Dashboard.jsx | Replaces some hardcoded strings with i18n keys / DynamicText rendering |
| frontend/src/pages/orders/OrderHistory.jsx | i18n keys + DynamicText for crop name/status in order history |
| frontend/src/pages/Marketplace.jsx | Replaces marketplace headings/labels/placeholders with i18n keys |
| frontend/src/components/marketplace/CropCard.jsx | Uses i18n keys and DynamicText for crop names/details |
| frontend/src/components/schemes/AiCropDoctor.jsx | Minor layout tweak (h-full -> h-fit) |
| backend/src/services/notificationService.ts | New Twilio SMS service + message helpers (welcome/login/order/advisory/etc.) |
| backend/src/controllers/authController.ts | Registration/login now return JWT directly and send SMS alerts (OTP bypassed) |
| backend/src/controllers/orderController.ts | Sends SMS on order creation/accept/status update |
| backend/src/controllers/instantBuyController.ts | Sends SMS to farmer for instant-buy orders |
| backend/src/controllers/negotiationController.ts | Sends SMS alerts on negotiation start/respond actions |
| backend/src/controllers/advisoryController.ts | Sends SMS alerts to targeted farmers on advisory create/update |
| backend/src/routes/advisoryRoutes.ts | Adds PATCH /advisory/:id route for updates |
| backend/src/controllers/demandController.ts | Accepts language query param and passes into demand services |
| backend/src/services/demandService.ts | Adds language-aware prompting; adjusts Groq handling for demand analysis |
| backend/src/controllers/prices.controller.ts | Uses default query to route forecast analysis through LLM path |
| backend/test_sms.ts | Adds standalone Twilio/SMS debug script |
| backend/package.json | Adds Twilio dependency and types |
| backend/package-lock.json | Lockfile updated for Twilio + transitive deps |
| .github/workflows/ci-cd.yml | Adds GitHub Actions workflow for backend/frontend CI and Render deploy trigger |
Files not reviewed (1)
- backend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const jsonResponse = JSON.parse(cleanContent); | ||
|
|
||
| if (jsonResponse.explanation) explanation = jsonResponse.explanation; | ||
| if (jsonResponse.sellRecommendation) sellRecommendation = jsonResponse.sellRecommendation; |
There was a problem hiding this comment.
sellRecommendation is replaced wholesale with the LLM response. Since the prompt no longer requests a trend field, this will typically drop the existing heuristic trend value — but the frontend reads sellRecommendation.trend (e.g., in DemandForecast). Either include trend in the required JSON schema again, or merge the AI fields into the existing object while preserving trend.
| if (jsonResponse.sellRecommendation) sellRecommendation = jsonResponse.sellRecommendation; | |
| if (jsonResponse.sellRecommendation) { | |
| sellRecommendation = { | |
| ...sellRecommendation, | |
| ...jsonResponse.sellRecommendation, | |
| }; | |
| } |
| getDemandForecast: async (cropId, location = 'Coimbatore') => { | ||
| const language = localStorage.getItem('i18nextLng') || "en"; | ||
| const response = await api.get('/demand/forecast', { | ||
| params: { crop: cropId, location } | ||
| params: { crop: cropId, location, language } | ||
| }); |
There was a problem hiding this comment.
Frontend sends language as an i18next code (e.g. en, ta), but the backend prompts in demandService treat this value as a human language name (default 'English'). Passing codes like ta/en reduces the chance the LLM will respond in the intended language; map codes to names (or switch backend to accept codes explicitly).
| const client = getClient(); | ||
| if (!client) { | ||
| console.warn('Twilio credentials not configured. SMS not sent.'); | ||
| console.log(`[SMS SIMULATION] To: ${to}, Body: ${body}`); |
There was a problem hiding this comment.
When Twilio credentials are missing, this logs the full SMS body and recipient ([SMS SIMULATION] ...). These messages can contain sensitive user data (e.g., login alerts, order details). Avoid logging SMS bodies/phone numbers in non-dev environments, or redact them.
| console.log(`[SMS SIMULATION] To: ${to}, Body: ${body}`); | |
| const env = process.env.NODE_ENV; | |
| if (env === 'development' || env === 'test') { | |
| console.log(`[SMS SIMULATION] To: ${to}, Body: ${body}`); | |
| } else { | |
| console.log('[SMS SIMULATION] SMS not sent due to missing Twilio credentials.'); | |
| } |
|
|
||
| // Basic formatting for Indian numbers if standard 10-digit | ||
| // 1. Remove all whitespace and non-numeric chars except '+' | ||
| let formattedTo = to.replace(/[\s-]/g, ''); |
There was a problem hiding this comment.
The comment says non-numeric chars are removed, but the implementation only strips whitespace and dashes (/[\s-]/g). This will fail for common formats like parentheses or dots. Either update the regex to match the comment/intent, or update the comment to reflect the actual behavior.
| let formattedTo = to.replace(/[\s-]/g, ''); | |
| let formattedTo = to.replace(/[^+\d]/g, ''); |
| const farmers = await User.find({ | ||
| role: 'FARMER', | ||
| state: advisory.state, | ||
| isActive: true | ||
| }); | ||
|
|
||
| console.log(`[Advisory] Found ${farmers.length} farmers in ${advisory.state} for alert.`); | ||
|
|
||
| // Send SMS to each farmer (Promise.all might send too fast, so iteration is safer for rate limits but slower) | ||
| for (const farmer of farmers) { | ||
| if (farmer.phoneNumber) { | ||
| await sendAdvisoryAlert(farmer.phoneNumber, { | ||
| type: advisory.type, |
There was a problem hiding this comment.
SMS delivery is performed inline (awaited) inside the HTTP request. For large farmer sets this can significantly slow the request and risks timeouts/rate-limits. Consider enqueueing notifications to a background job (or at least using a bounded concurrency limiter) and returning the HTTP response without waiting on all SMS sends.
| // NOTIFICATION: Send SMS to Farmer (New Order) | ||
| try { | ||
| console.log(`[OrderController] Begin SMS logic for Order: ${order._id}`); | ||
| // Fetch fresh User documents to ensure we have phone numbers | ||
| const farmer = await User.findById(order.farmerId); | ||
| const buyer = await User.findById(order.buyerId); | ||
|
|
||
| // Debug Logs | ||
| if (!farmer) console.warn(`[OrderController] Farmer not found for ID: ${order.farmerId}`); | ||
| if (!farmer?.phoneNumber) console.warn(`[OrderController] Farmer has no phone number: ${order.farmerId}`); | ||
|
|
||
| if (farmer && farmer.phoneNumber) { | ||
| console.log(`[OrderController] Sending New Order SMS to Farmer: ${farmer.phoneNumber}`); | ||
| await sendNewOrderAlert(farmer.phoneNumber, { | ||
| buyerName: buyer?.fullName || 'Buyer', | ||
| crop: crop.name || 'Crop', | ||
| quantity: order.quantity, | ||
| unit: 'kg' // Assuming default kg, or can be fetched from Crop | ||
| }); | ||
| console.log(`[OrderController] SMS sent successfully.`); | ||
| } | ||
| } catch (smsErr) { | ||
| console.error("[OrderController] SMS Error:", smsErr); | ||
| } |
There was a problem hiding this comment.
Order creation waits on multiple DB lookups plus an awaited SMS send. This adds latency to the critical order path and can cause user-visible slowdowns when Twilio is slow. Consider making SMS sending asynchronous (queue/background) or at minimum not awaiting it before responding.
| // NOTIFICATION: Send SMS to Farmer (New Order) | |
| try { | |
| console.log(`[OrderController] Begin SMS logic for Order: ${order._id}`); | |
| // Fetch fresh User documents to ensure we have phone numbers | |
| const farmer = await User.findById(order.farmerId); | |
| const buyer = await User.findById(order.buyerId); | |
| // Debug Logs | |
| if (!farmer) console.warn(`[OrderController] Farmer not found for ID: ${order.farmerId}`); | |
| if (!farmer?.phoneNumber) console.warn(`[OrderController] Farmer has no phone number: ${order.farmerId}`); | |
| if (farmer && farmer.phoneNumber) { | |
| console.log(`[OrderController] Sending New Order SMS to Farmer: ${farmer.phoneNumber}`); | |
| await sendNewOrderAlert(farmer.phoneNumber, { | |
| buyerName: buyer?.fullName || 'Buyer', | |
| crop: crop.name || 'Crop', | |
| quantity: order.quantity, | |
| unit: 'kg' // Assuming default kg, or can be fetched from Crop | |
| }); | |
| console.log(`[OrderController] SMS sent successfully.`); | |
| } | |
| } catch (smsErr) { | |
| console.error("[OrderController] SMS Error:", smsErr); | |
| } | |
| // NOTIFICATION: Send SMS to Farmer (New Order) - fire-and-forget to avoid delaying response | |
| void (async () => { | |
| try { | |
| console.log(`[OrderController] Begin SMS logic for Order: ${order._id}`); | |
| // Fetch fresh User documents to ensure we have phone numbers | |
| const farmer = await User.findById(order.farmerId); | |
| const buyer = await User.findById(order.buyerId); | |
| // Debug Logs | |
| if (!farmer) { | |
| console.warn(`[OrderController] Farmer not found for ID: ${order.farmerId}`); | |
| } | |
| if (!farmer?.phoneNumber) { | |
| console.warn(`[OrderController] Farmer has no phone number: ${order.farmerId}`); | |
| } | |
| if (farmer && farmer.phoneNumber) { | |
| console.log(`[OrderController] Sending New Order SMS to Farmer: ${farmer.phoneNumber}`); | |
| await sendNewOrderAlert(farmer.phoneNumber, { | |
| buyerName: buyer?.fullName || "Buyer", | |
| crop: crop.name || "Crop", | |
| quantity: order.quantity, | |
| unit: "kg", // Assuming default kg, or can be fetched from Crop | |
| }); | |
| console.log(`[OrderController] SMS sent successfully.`); | |
| } | |
| } catch (smsErr) { | |
| console.error("[OrderController] SMS Error:", smsErr); | |
| } | |
| })(); |
| const chatCompletion = await groq.chat.completions.create({ | ||
| messages: [{ role: 'user', content: prompt }], | ||
| model: MODEL_NAME, | ||
| response_format: { type: "json_object" } | ||
| }, { timeout: 15000 }); // 15 second timeout | ||
| temperature: 0.1, | ||
| }); |
There was a problem hiding this comment.
getDemandAnalysis removed the explicit Groq request timeout that getCropRecommendations still uses. Without a timeout, a slow/hung upstream call can tie up the request handler longer than expected. Consider restoring a bounded timeout (or using an AbortController) for this call as well.
| "@google/generative-ai": "^0.24.1", | ||
| "@prisma/client": "^6.19.2", | ||
| "@types/socket.io": "^3.0.1", | ||
| "@types/twilio": "^3.19.2", | ||
| "bcrypt": "^6.0.0", | ||
| "cloudinary": "^2.9.0", |
There was a problem hiding this comment.
@types/twilio is a TypeScript-only dependency and should typically be in devDependencies (it isn't needed at runtime). Consider moving it to devDependencies to keep the production install footprint smaller.
| <div className="flex items-center text-sm font-medium text-nature-500 mt-2"> | ||
| <MapPin className="w-4 h-4 mr-1.5 text-nature-400" /> | ||
| <span className="truncate">{crop.location?.district || 'Unknown'}, {crop.location?.state || 'Location'}</span> | ||
| <span className="truncate">{crop.location?.district || t('dynamic.unknown')} , {crop.location?.state || t('common.location')}</span> |
There was a problem hiding this comment.
t('dynamic.unknown') is referenced here, but there is no dynamic.unknown key in the i18n resources (only dynamic.crops.unknown / dynamic.crops.unknown crop). This will render the key string in the UI; use an existing key or add the missing translation entry.
| <span className="truncate">{crop.location?.district || t('dynamic.unknown')} , {crop.location?.state || t('common.location')}</span> | |
| <span className="truncate">{crop.location?.district || t('dynamic.crops.unknown')} , {crop.location?.state || t('common.location')}</span> |
| // Check localStorage cache | ||
| const localCache = localStorage.getItem('translation_cache'); | ||
| const localMap = localCache ? JSON.parse(localCache) : {}; | ||
| if (localMap[cacheKey]) { |
There was a problem hiding this comment.
JSON.parse(localStorage.getItem('translation_cache')) is unguarded. If localStorage is unavailable (privacy mode) or the cached value is corrupted, this will throw and break rendering/translation. Wrap the read/parse in a try/catch (and reset the cache on parse failure).
No description provided.