fixed multiple issyes
This commit is contained in:
parent
c95aa3eff7
commit
cde6f0495a
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue