fixed multiple issyes
This commit is contained in:
parent
c95aa3eff7
commit
cde6f0495a
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
PORT=3000
|
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
|
# Database: sqlite (default) or postgres
|
||||||
DB_DIALECT=sqlite
|
DB_DIALECT=sqlite
|
||||||
|
|
@ -21,9 +23,8 @@ SQLITE_STORAGE=./data/luckychit.sqlite
|
||||||
|
|
||||||
JWT_SECRET=change-me-in-production
|
JWT_SECRET=change-me-in-production
|
||||||
|
|
||||||
# Public draw result links (no login). Use your API origin WITHOUT /api — e.g. https://api.example.com
|
# Public draw links (/share/draw/...): origin WITHOUT /api. Optional if Nginx sends Host + X-Forwarded-Proto.
|
||||||
# Dev fallback: if unset and NODE_ENV!=production, defaults to http://localhost:PORT
|
# PUBLIC_BASE_URL=https://your-domain.com
|
||||||
PUBLIC_BASE_URL=http://localhost:3000
|
|
||||||
|
|
||||||
# Signed tokens for /share/draw/:token — defaults to permanent links (no JWT expiry)
|
# Signed tokens for /share/draw/:token — defaults to permanent links (no JWT expiry)
|
||||||
# Set to false to use SHARE_TOKEN_TTL_DAYS instead
|
# Set to false to use SHARE_TOKEN_TTL_DAYS instead
|
||||||
|
|
|
||||||
|
|
@ -616,8 +616,11 @@ const generateFinancialData = (chitGroup) => {
|
||||||
const monthlyInstallment = chitGroup.monthly_installment;
|
const monthlyInstallment = chitGroup.monthly_installment;
|
||||||
const commission = chitGroup.foreman_commission_amount;
|
const commission = chitGroup.foreman_commission_amount;
|
||||||
|
|
||||||
// Use actual start_date if available, otherwise default to current date
|
// Use actual start_date if valid; invalid/legacy DB values → today (avoids NaN → "undefined-N" labels).
|
||||||
const startDate = chitGroup.start_date ? new Date(chitGroup.start_date) : new Date();
|
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 startMonth = startDate.getMonth(); // 0-11 (0 = January)
|
||||||
const startYear = startDate.getFullYear();
|
const startYear = startDate.getFullYear();
|
||||||
|
|
||||||
|
|
@ -676,6 +679,9 @@ const getMonthName = (monthIndex) => {
|
||||||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||||||
];
|
];
|
||||||
|
if (typeof monthIndex !== 'number' || Number.isNaN(monthIndex) || monthIndex < 0 || monthIndex > 11) {
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
return months[monthIndex];
|
return months[monthIndex];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const jwt = require('jsonwebtoken');
|
||||||
const { authenticateToken: auth, requireManager } = require('../middleware/auth');
|
const { authenticateToken: auth, requireManager } = require('../middleware/auth');
|
||||||
const { Payment, MonthlyDraw, ChitGroup, GroupMember, User, Notification } = require('../models');
|
const { Payment, MonthlyDraw, ChitGroup, GroupMember, User, Notification } = require('../models');
|
||||||
const WhatsAppHelper = require('../utils/whatsapp-helper');
|
const WhatsAppHelper = require('../utils/whatsapp-helper');
|
||||||
|
const { resolvePublicBaseUrl } = require('../utils/public-base-url');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route POST /api/share/payment-receipt
|
* @route POST /api/share/payment-receipt
|
||||||
|
|
@ -150,16 +151,12 @@ router.post('/draw-link', auth, requireManager, async (req, res) => {
|
||||||
signOpts
|
signOpts
|
||||||
);
|
);
|
||||||
|
|
||||||
let publicBaseUrl = (process.env.PUBLIC_BASE_URL || process.env.APP_BASE_URL || '').replace(/\/$/, '');
|
const publicBaseUrl = resolvePublicBaseUrl(req);
|
||||||
if (!publicBaseUrl && process.env.NODE_ENV !== 'production') {
|
|
||||||
const p = process.env.PORT || 3000;
|
|
||||||
publicBaseUrl = `http://localhost:${p}`;
|
|
||||||
}
|
|
||||||
if (!publicBaseUrl) {
|
if (!publicBaseUrl) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
success: false,
|
success: false,
|
||||||
message:
|
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}`;
|
const shareUrl = `${publicBaseUrl}/share/draw/${token}`;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@ const ReminderService = require('./services/reminder-service');
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
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
|
// Security middleware
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
|
||||||
|
|
@ -51,7 +56,7 @@ app.use(cors({
|
||||||
const limiter = rateLimit({
|
const limiter = rateLimit({
|
||||||
windowMs: Number.parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 15 * 60 * 1000, // 15 minutes
|
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,
|
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);
|
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';
|
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 {
|
class MonthlyDraw {
|
||||||
final String id;
|
final String id;
|
||||||
final String groupId;
|
final String groupId;
|
||||||
|
|
@ -42,25 +53,36 @@ class MonthlyDraw {
|
||||||
});
|
});
|
||||||
|
|
||||||
factory MonthlyDraw.fromJson(Map<String, dynamic> json) {
|
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(
|
return MonthlyDraw(
|
||||||
id: json['id'],
|
id: json['id']?.toString() ?? '',
|
||||||
groupId: json['group_id'],
|
groupId: (json['group_id'] ?? json['groupId'])?.toString() ?? '',
|
||||||
month: _parseInt(json['month']),
|
month: _parseInt(json['month']),
|
||||||
year: _parseInt(json['year']),
|
year: _parseInt(json['year']),
|
||||||
drawDate: DateTime.parse(json['draw_date']),
|
drawDate: drawDate,
|
||||||
eligibleMembers: _parseEligibleMembers(json['eligible_members']),
|
eligibleMembers: _parseEligibleMembers(json['eligible_members'] ?? json['eligibleMembers']),
|
||||||
winnerId: json['winner_id'],
|
winnerId: (json['winner_id'] ?? json['winnerId'])?.toString(),
|
||||||
prizeAmount: _parseDouble(json['prize_amount']),
|
prizeAmount: _parseDouble(json['prize_amount'] ?? json['prizeAmount']),
|
||||||
serverSeed: json['server_seed'] ?? '',
|
serverSeed: (json['server_seed'] ?? json['serverSeed'] ?? '').toString(),
|
||||||
serverSeedHash: json['server_seed_hash'] ?? '',
|
serverSeedHash: (json['server_seed_hash'] ?? json['serverSeedHash'] ?? '').toString(),
|
||||||
clientSeed: json['client_seed'] ?? '',
|
clientSeed: (json['client_seed'] ?? json['clientSeed'] ?? '').toString(),
|
||||||
nonce: _parseInt(json['nonce']),
|
nonce: _parseInt(json['nonce']),
|
||||||
resultHash: json['result_hash'] ?? '',
|
resultHash: (json['result_hash'] ?? json['resultHash'] ?? '').toString(),
|
||||||
status: json['status'],
|
status: json['status']?.toString() ?? 'pending',
|
||||||
notes: json['notes'],
|
notes: json['notes']?.toString(),
|
||||||
createdAt: DateTime.parse(json['created_at']),
|
createdAt: _parseDrawDate(json['created_at'] ?? json['createdAt']) ?? DateTime.now(),
|
||||||
updatedAt: DateTime.parse(json['updated_at']),
|
updatedAt: _parseDrawDate(json['updated_at'] ?? json['updatedAt']) ?? DateTime.now(),
|
||||||
winner: json['winner'] != null ? User.fromJson(json['winner']) : null,
|
winner: winnerMap != null ? User.fromJson(winnerMap) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -441,8 +441,14 @@ class ChitGroupService extends GetxController {
|
||||||
final response = await _apiService.getGroupMonthlyDraws(groupId, status: status);
|
final response = await _apiService.getGroupMonthlyDraws(groupId, status: status);
|
||||||
|
|
||||||
if (response['success']) {
|
if (response['success']) {
|
||||||
final drawsData = response['data']['monthlyDraws'] as List;
|
final data = response['data'] as Map<String, dynamic>?;
|
||||||
_monthlyDraws.value = drawsData.map((json) => MonthlyDraw.fromJson(json)).toList();
|
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 {
|
} else {
|
||||||
Get.snackbar('Error', response['message']);
|
Get.snackbar('Error', response['message']);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue