fixed multiple issyes

This commit is contained in:
Deep Koluguri 2026-04-05 16:31:28 -04:00
parent c95aa3eff7
commit cde6f0495a
7 changed files with 101 additions and 29 deletions

View File

@ -2,6 +2,8 @@
NODE_ENV=development
PORT=3000
# Nginx reverse proxy hop count (express-rate-limit + req.ip). Do not set to true in code; use 1+.
# TRUST_PROXY_HOPS=1
# Database: sqlite (default) or postgres
DB_DIALECT=sqlite
@ -21,9 +23,8 @@ SQLITE_STORAGE=./data/luckychit.sqlite
JWT_SECRET=change-me-in-production
# Public draw result links (no login). Use your API origin WITHOUT /api — e.g. https://api.example.com
# Dev fallback: if unset and NODE_ENV!=production, defaults to http://localhost:PORT
PUBLIC_BASE_URL=http://localhost:3000
# Public draw links (/share/draw/...): origin WITHOUT /api. Optional if Nginx sends Host + X-Forwarded-Proto.
# PUBLIC_BASE_URL=https://your-domain.com
# Signed tokens for /share/draw/:token — defaults to permanent links (no JWT expiry)
# Set to false to use SHARE_TOKEN_TTL_DAYS instead

View File

@ -616,8 +616,11 @@ const generateFinancialData = (chitGroup) => {
const monthlyInstallment = chitGroup.monthly_installment;
const commission = chitGroup.foreman_commission_amount;
// Use actual start_date if available, otherwise default to current date
const startDate = chitGroup.start_date ? new Date(chitGroup.start_date) : new Date();
// Use actual start_date if valid; invalid/legacy DB values → today (avoids NaN → "undefined-N" labels).
let startDate = chitGroup.start_date ? new Date(chitGroup.start_date) : new Date();
if (Number.isNaN(startDate.getTime())) {
startDate = new Date();
}
const startMonth = startDate.getMonth(); // 0-11 (0 = January)
const startYear = startDate.getFullYear();
@ -676,6 +679,9 @@ const getMonthName = (monthIndex) => {
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
if (typeof monthIndex !== 'number' || Number.isNaN(monthIndex) || monthIndex < 0 || monthIndex > 11) {
return '?';
}
return months[monthIndex];
};

View File

@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
const { authenticateToken: auth, requireManager } = require('../middleware/auth');
const { Payment, MonthlyDraw, ChitGroup, GroupMember, User, Notification } = require('../models');
const WhatsAppHelper = require('../utils/whatsapp-helper');
const { resolvePublicBaseUrl } = require('../utils/public-base-url');
/**
* @route POST /api/share/payment-receipt
@ -150,16 +151,12 @@ router.post('/draw-link', auth, requireManager, async (req, res) => {
signOpts
);
let publicBaseUrl = (process.env.PUBLIC_BASE_URL || process.env.APP_BASE_URL || '').replace(/\/$/, '');
if (!publicBaseUrl && process.env.NODE_ENV !== 'production') {
const p = process.env.PORT || 3000;
publicBaseUrl = `http://localhost:${p}`;
}
const publicBaseUrl = resolvePublicBaseUrl(req);
if (!publicBaseUrl) {
return res.status(500).json({
success: false,
message:
'PUBLIC_BASE_URL not configured (set to your public API origin, e.g. https://api.example.com)'
'PUBLIC_BASE_URL not configured (set to your site origin without /api, e.g. https://chitfund.deepteklabs.com)'
});
}
const shareUrl = `${publicBaseUrl}/share/draw/${token}`;

View File

@ -24,6 +24,11 @@ const ReminderService = require('./services/reminder-service');
const app = express();
const PORT = process.env.PORT || 3000;
// Behind Nginx: required for correct req.ip, HTTPS, PUBLIC_BASE_URL inference, and express-rate-limit.
// Must be a number (hop count), not `true` — express-rate-limit rejects trust proxy === true.
const _trustHops = Number.parseInt(process.env.TRUST_PROXY_HOPS || '1', 10);
app.set('trust proxy', Number.isFinite(_trustHops) && _trustHops >= 1 ? _trustHops : 1);
// Security middleware
app.use(helmet());
@ -51,7 +56,7 @@ app.use(cors({
const limiter = rateLimit({
windowMs: Number.parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 15 * 60 * 1000, // 15 minutes
max: Number.parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) || 300,
message: 'Too many requests from this IP, please try again later.'
message: 'Too many requests from this IP, please try again later.',
});
app.use('/api', limiter);

View File

@ -0,0 +1,35 @@
/**
* Origin for public links served by this app (no trailing slash, no `/api`).
* Uses PUBLIC_BASE_URL / APP_BASE_URL when set; otherwise infers from proxy headers
* when Host is not loopback (so production works without env if Nginx sends standard headers).
*/
function resolvePublicBaseUrl(req) {
const envBase = (process.env.PUBLIC_BASE_URL || process.env.APP_BASE_URL || '')
.trim()
.replace(/\/$/, '');
if (envBase) return envBase;
const rawHost = (req.get('x-forwarded-host') || req.get('host') || '').split(',')[0].trim();
const isLoopback =
!rawHost ||
/^localhost(:\d+)?$/i.test(rawHost) ||
/^127\.\d+\.\d+\.\d+(:\d+)?$/i.test(rawHost) ||
/^\[::1\](:\d+)?$/i.test(rawHost);
if (isLoopback) {
if (process.env.NODE_ENV === 'production') {
return '';
}
const p = process.env.PORT || 3000;
return `http://localhost:${String(p)}`;
}
let proto = (req.get('x-forwarded-proto') || '').split(',')[0].trim();
if (!proto && req.secure) proto = 'https';
if (!proto) proto = req.protocol || 'https';
if (proto !== 'http' && proto !== 'https') proto = 'https';
return `${proto}://${rawHost}`.replace(/\/$/, '');
}
module.exports = { resolvePublicBaseUrl };

View File

@ -1,5 +1,16 @@
import 'user.dart';
DateTime? _parseDrawDate(dynamic value) {
if (value == null) return null;
final s = value.toString().trim();
if (s.isEmpty) return null;
try {
return DateTime.parse(s);
} catch (_) {
return null;
}
}
class MonthlyDraw {
final String id;
final String groupId;
@ -42,25 +53,36 @@ class MonthlyDraw {
});
factory MonthlyDraw.fromJson(Map<String, dynamic> json) {
final rawWinner = json['winner'];
Map<String, dynamic>? winnerMap;
if (rawWinner is Map<String, dynamic>) {
winnerMap = rawWinner;
} else if (rawWinner is Map) {
winnerMap = Map<String, dynamic>.from(rawWinner);
}
final drawDate =
_parseDrawDate(json['draw_date'] ?? json['drawDate']) ?? DateTime.fromMillisecondsSinceEpoch(0);
return MonthlyDraw(
id: json['id'],
groupId: json['group_id'],
id: json['id']?.toString() ?? '',
groupId: (json['group_id'] ?? json['groupId'])?.toString() ?? '',
month: _parseInt(json['month']),
year: _parseInt(json['year']),
drawDate: DateTime.parse(json['draw_date']),
eligibleMembers: _parseEligibleMembers(json['eligible_members']),
winnerId: json['winner_id'],
prizeAmount: _parseDouble(json['prize_amount']),
serverSeed: json['server_seed'] ?? '',
serverSeedHash: json['server_seed_hash'] ?? '',
clientSeed: json['client_seed'] ?? '',
drawDate: drawDate,
eligibleMembers: _parseEligibleMembers(json['eligible_members'] ?? json['eligibleMembers']),
winnerId: (json['winner_id'] ?? json['winnerId'])?.toString(),
prizeAmount: _parseDouble(json['prize_amount'] ?? json['prizeAmount']),
serverSeed: (json['server_seed'] ?? json['serverSeed'] ?? '').toString(),
serverSeedHash: (json['server_seed_hash'] ?? json['serverSeedHash'] ?? '').toString(),
clientSeed: (json['client_seed'] ?? json['clientSeed'] ?? '').toString(),
nonce: _parseInt(json['nonce']),
resultHash: json['result_hash'] ?? '',
status: json['status'],
notes: json['notes'],
createdAt: DateTime.parse(json['created_at']),
updatedAt: DateTime.parse(json['updated_at']),
winner: json['winner'] != null ? User.fromJson(json['winner']) : null,
resultHash: (json['result_hash'] ?? json['resultHash'] ?? '').toString(),
status: json['status']?.toString() ?? 'pending',
notes: json['notes']?.toString(),
createdAt: _parseDrawDate(json['created_at'] ?? json['createdAt']) ?? DateTime.now(),
updatedAt: _parseDrawDate(json['updated_at'] ?? json['updatedAt']) ?? DateTime.now(),
winner: winnerMap != null ? User.fromJson(winnerMap) : null,
);
}

View File

@ -441,8 +441,14 @@ class ChitGroupService extends GetxController {
final response = await _apiService.getGroupMonthlyDraws(groupId, status: status);
if (response['success']) {
final drawsData = response['data']['monthlyDraws'] as List;
_monthlyDraws.value = drawsData.map((json) => MonthlyDraw.fromJson(json)).toList();
final data = response['data'] as Map<String, dynamic>?;
final rawList = data?['monthlyDraws'] ?? data?['monthly_draws'];
final drawsData = rawList is List ? rawList : <dynamic>[];
_monthlyDraws.value = drawsData
.map((json) => MonthlyDraw.fromJson(
json is Map<String, dynamic> ? json : Map<String, dynamic>.from(json as Map),
))
.toList();
} else {
Get.snackbar('Error', response['message']);
}