update draw
This commit is contained in:
parent
e404721818
commit
c4439e76a8
|
|
@ -633,7 +633,7 @@ const deleteMonthlyDraw = async (req, res) => {
|
||||||
const updateMonthlyDraw = async (req, res) => {
|
const updateMonthlyDraw = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { draw_id } = req.params;
|
const { draw_id } = req.params;
|
||||||
const { winner_id, prize_amount, notes } = req.body;
|
const { winner_id, prize_amount, notes, month, year, draw_date } = req.body;
|
||||||
const managerId = req.user.id;
|
const managerId = req.user.id;
|
||||||
|
|
||||||
// Find the draw
|
// Find the draw
|
||||||
|
|
@ -664,41 +664,120 @@ const updateMonthlyDraw = async (req, res) => {
|
||||||
|
|
||||||
const updates = {};
|
const updates = {};
|
||||||
|
|
||||||
// Update winner if provided
|
let nextMonth = monthlyDraw.month;
|
||||||
if (winner_id) {
|
let nextYear = monthlyDraw.year;
|
||||||
// Verify the winner is a member of the group
|
const monthProvided = month !== undefined && month !== null && month !== '';
|
||||||
const member = await GroupMember.findOne({
|
const yearProvided = year !== undefined && year !== null && year !== '';
|
||||||
where: {
|
if (monthProvided) {
|
||||||
group_id: monthlyDraw.ChitGroup.id,
|
nextMonth = parseInt(month, 10);
|
||||||
user_id: winner_id,
|
}
|
||||||
status: 'active'
|
if (yearProvided) {
|
||||||
},
|
nextYear = parseInt(year, 10);
|
||||||
include: [{ model: User, attributes: ['id', 'full_name'] }]
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (!member) {
|
if (monthProvided || yearProvided) {
|
||||||
|
if (Number.isNaN(nextMonth) || nextMonth < 1 || nextMonth > 12) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Winner must be an active member of this group'
|
message: 'Month must be between 1 and 12'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (Number.isNaN(nextYear) || nextYear < 2020) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Year must be 2020 or later'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const conflict = await MonthlyDraw.findOne({
|
||||||
|
where: {
|
||||||
|
group_id: monthlyDraw.group_id,
|
||||||
|
month: nextMonth,
|
||||||
|
year: nextYear,
|
||||||
|
id: { [Op.ne]: draw_id }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (conflict) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `Another draw already exists for ${nextMonth}/${nextYear}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updates.month = nextMonth;
|
||||||
|
updates.year = nextYear;
|
||||||
|
}
|
||||||
|
|
||||||
updates.winner_id = winner_id;
|
if (draw_date !== undefined && draw_date !== null && draw_date !== '') {
|
||||||
if (!notes) {
|
const d = new Date(draw_date);
|
||||||
updates.notes = `Winner updated to: ${member.User.full_name}`;
|
if (Number.isNaN(d.getTime())) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid draw_date'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updates.draw_date = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update winner if provided and different from current
|
||||||
|
if (winner_id) {
|
||||||
|
const winnerStr = String(winner_id);
|
||||||
|
const currentWinnerStr = monthlyDraw.winner_id != null ? String(monthlyDraw.winner_id) : '';
|
||||||
|
if (winnerStr !== currentWinnerStr) {
|
||||||
|
const member = await GroupMember.findOne({
|
||||||
|
where: {
|
||||||
|
group_id: monthlyDraw.ChitGroup.id,
|
||||||
|
user_id: winner_id,
|
||||||
|
status: 'active'
|
||||||
|
},
|
||||||
|
include: [{ model: User, attributes: ['id', 'full_name'] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!member) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Winner must be an active member of this group'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateWinner = await MonthlyDraw.findOne({
|
||||||
|
where: {
|
||||||
|
group_id: monthlyDraw.group_id,
|
||||||
|
winner_id,
|
||||||
|
id: { [Op.ne]: draw_id }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (duplicateWinner) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: `${member.User.full_name} has already won in another draw. Each member can only win once.`,
|
||||||
|
alreadyWon: true,
|
||||||
|
winnerName: member.User.full_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.winner_id = winner_id;
|
||||||
|
if (notes === undefined || notes === null) {
|
||||||
|
updates.notes = `Winner updated to: ${member.User.full_name}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update prize amount if provided
|
// Update prize amount if provided (including 0)
|
||||||
if (prize_amount) {
|
if (prize_amount != null && prize_amount !== '') {
|
||||||
updates.prize_amount = prize_amount;
|
updates.prize_amount = prize_amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update notes if provided
|
// Update notes if provided
|
||||||
if (notes) {
|
if (notes !== undefined && notes !== null) {
|
||||||
updates.notes = notes;
|
updates.notes = notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updates).length === 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'No valid fields to update'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Perform update
|
// Perform update
|
||||||
await monthlyDraw.update(updates);
|
await monthlyDraw.update(updates);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -584,6 +584,11 @@ class ApiService {
|
||||||
|
|
||||||
if (responseData is Map<String, dynamic>) {
|
if (responseData is Map<String, dynamic>) {
|
||||||
message = responseData['message']?.toString() ?? 'Request failed';
|
message = responseData['message']?.toString() ?? 'Request failed';
|
||||||
|
final merged = Map<String, dynamic>.from(responseData);
|
||||||
|
merged['success'] = false;
|
||||||
|
merged['message'] = message;
|
||||||
|
merged['statusCode'] = e.response?.statusCode;
|
||||||
|
return merged;
|
||||||
} else if (responseData is String) {
|
} else if (responseData is String) {
|
||||||
message = responseData;
|
message = responseData;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import '../../core/services/chit_group_service.dart';
|
import '../../core/services/chit_group_service.dart';
|
||||||
import '../../core/services/api_service.dart';
|
import '../../core/services/api_service.dart';
|
||||||
import '../../core/models/monthly_draw.dart';
|
import '../../core/models/monthly_draw.dart';
|
||||||
|
|
@ -26,6 +27,33 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
|
|
||||||
String? _selectedMemberId;
|
String? _selectedMemberId;
|
||||||
bool _isLoading = false;
|
bool _isLoading = false;
|
||||||
|
late int _editMonth;
|
||||||
|
late int _editYear;
|
||||||
|
late DateTime _drawDate;
|
||||||
|
|
||||||
|
static const _monthNames = <String>[
|
||||||
|
'', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
|
||||||
|
];
|
||||||
|
|
||||||
|
DateTime _baselineDrawDate() {
|
||||||
|
final d = widget.draw.drawDate;
|
||||||
|
if (d.millisecondsSinceEpoch == 0) {
|
||||||
|
return DateTime(widget.draw.year, widget.draw.month, 1);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _sameCalendarDay(DateTime a, DateTime b) {
|
||||||
|
return a.year == b.year && a.month == b.month && a.day == b.day;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _clampEditYear(int y) {
|
||||||
|
final minY = 2020;
|
||||||
|
final maxY = DateTime.now().year + 1;
|
||||||
|
if (y < minY) return minY;
|
||||||
|
if (y > maxY) return maxY;
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -33,6 +61,9 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
_selectedMemberId = widget.draw.winnerId;
|
_selectedMemberId = widget.draw.winnerId;
|
||||||
_prizeAmountController.text = widget.draw.prizeAmount.toStringAsFixed(0);
|
_prizeAmountController.text = widget.draw.prizeAmount.toStringAsFixed(0);
|
||||||
_notesController.text = widget.draw.notes ?? '';
|
_notesController.text = widget.draw.notes ?? '';
|
||||||
|
_editMonth = widget.draw.month.clamp(1, 12);
|
||||||
|
_editYear = _clampEditYear(widget.draw.year);
|
||||||
|
_drawDate = _baselineDrawDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -42,6 +73,18 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _pickDrawDate() async {
|
||||||
|
final picked = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _drawDate,
|
||||||
|
firstDate: DateTime(2020),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365 * 3)),
|
||||||
|
);
|
||||||
|
if (picked != null) {
|
||||||
|
setState(() => _drawDate = picked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleSubmit() async {
|
Future<void> _handleSubmit() async {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
if (_selectedMemberId == null) {
|
if (_selectedMemberId == null) {
|
||||||
|
|
@ -54,7 +97,16 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
try {
|
try {
|
||||||
final updates = <String, dynamic>{};
|
final updates = <String, dynamic>{};
|
||||||
|
|
||||||
// Only include changed fields
|
if (_editMonth != widget.draw.month) {
|
||||||
|
updates['month'] = _editMonth;
|
||||||
|
}
|
||||||
|
if (_editYear != widget.draw.year) {
|
||||||
|
updates['year'] = _editYear;
|
||||||
|
}
|
||||||
|
if (!_sameCalendarDay(_drawDate, _baselineDrawDate())) {
|
||||||
|
updates['draw_date'] = _drawDate.toUtc().toIso8601String();
|
||||||
|
}
|
||||||
|
|
||||||
if (_selectedMemberId != widget.draw.winnerId) {
|
if (_selectedMemberId != widget.draw.winnerId) {
|
||||||
updates['winner_id'] = _selectedMemberId;
|
updates['winner_id'] = _selectedMemberId;
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +116,7 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
updates['prize_amount'] = newPrizeAmount;
|
updates['prize_amount'] = newPrizeAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_notesController.text.isNotEmpty && _notesController.text != widget.draw.notes) {
|
if (_notesController.text != (widget.draw.notes ?? '')) {
|
||||||
updates['notes'] = _notesController.text;
|
updates['notes'] = _notesController.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,10 +135,20 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
);
|
);
|
||||||
Get.back(result: true);
|
Get.back(result: true);
|
||||||
} else {
|
} else {
|
||||||
SnackbarUtil.showError(
|
final isAlreadyWon = response['alreadyWon'] ?? false;
|
||||||
response['message'] ?? 'Failed to update draw',
|
final winnerName = response['winnerName'] ?? '';
|
||||||
title: 'Error',
|
if (isAlreadyWon && winnerName.isNotEmpty) {
|
||||||
);
|
SnackbarUtil.showError(
|
||||||
|
'$winnerName has already won in another draw.\nEach member can only win once.',
|
||||||
|
title: 'Duplicate winner',
|
||||||
|
duration: const Duration(seconds: 4),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
SnackbarUtil.showError(
|
||||||
|
response['message'] ?? 'Failed to update draw',
|
||||||
|
title: 'Error',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
SnackbarUtil.showError('Error: ${e.toString()}');
|
SnackbarUtil.showError('Error: ${e.toString()}');
|
||||||
|
|
@ -137,7 +199,7 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'Month ${widget.draw.month}/${widget.draw.year}',
|
'Originally ${widget.draw.month}/${widget.draw.year}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14.sp,
|
fontSize: 14.sp,
|
||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
|
|
@ -191,6 +253,108 @@ class _EditDrawDialogState extends State<EditDrawDialog> {
|
||||||
),
|
),
|
||||||
SizedBox(height: 24.h),
|
SizedBox(height: 24.h),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
'Draw month & year',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButtonFormField<int>(
|
||||||
|
value: _editMonth,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Month',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade50,
|
||||||
|
),
|
||||||
|
items: List.generate(12, (i) {
|
||||||
|
final m = i + 1;
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: m,
|
||||||
|
child: Text('${_monthNames[m]} ($m)'),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v != null) setState(() => _editMonth = v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12.w),
|
||||||
|
Expanded(
|
||||||
|
child: DropdownButtonFormField<int>(
|
||||||
|
value: _editYear,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'Year',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade50,
|
||||||
|
),
|
||||||
|
items: List.generate(
|
||||||
|
DateTime.now().year - 2019 + 2,
|
||||||
|
(i) {
|
||||||
|
final y = 2020 + i;
|
||||||
|
return DropdownMenuItem(
|
||||||
|
value: y,
|
||||||
|
child: Text('$y'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v != null) setState(() => _editYear = v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'This is the calendar month this draw belongs to. It must not clash with another draw in this group.',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
|
Text(
|
||||||
|
'Draw date',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
InkWell(
|
||||||
|
onTap: _pickDrawDate,
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: const Icon(Icons.calendar_today_outlined),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12.r),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade50,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
DateFormat('d MMM yyyy').format(_drawDate),
|
||||||
|
style: TextStyle(fontSize: 16.sp),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Text(
|
||||||
|
'When the draw actually happened (shown in history).',
|
||||||
|
style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
|
||||||
|
),
|
||||||
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
// Select Winner
|
// Select Winner
|
||||||
Text(
|
Text(
|
||||||
'Winner',
|
'Winner',
|
||||||
|
|
|
||||||
|
|
@ -1,80 +1,147 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# LuckyChit deployment — backend (Node/PM2) + frontend (Flutter web + PM2).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./scripts/deploy.sh # backend + frontend
|
||||||
|
# ./scripts/deploy.sh backend # API only
|
||||||
|
# ./scripts/deploy.sh frontend # Flutter web only
|
||||||
|
# ./scripts/deploy.sh backend --force # e.g. wipe node_modules / flutter clean
|
||||||
|
#
|
||||||
|
# Env (optional):
|
||||||
|
# LUCKYCHIT_ROOT Repo root (default: /home/luckychit/apps/chitfund)
|
||||||
|
# DEPLOY_BRANCH Git branch to fast-forward to (default: prod_i8n)
|
||||||
|
|
||||||
# LuckyChit Unified Deployment Script
|
set -eo pipefail
|
||||||
# Usage: ./deploy.sh [backend|frontend|--force]
|
|
||||||
|
|
||||||
set -e
|
PROJECT_DIR="${LUCKYCHIT_ROOT:-/home/luckychit/apps/chitfund}"
|
||||||
|
BRANCH="${DEPLOY_BRANCH:-prod_i8n}"
|
||||||
PROJECT_DIR="/home/luckychit/apps/chitfund"
|
|
||||||
FORCE_REBUILD=false
|
FORCE_REBUILD=false
|
||||||
|
TARGET=""
|
||||||
|
|
||||||
# Check for force flag
|
usage() {
|
||||||
if [ "$1" == "--force" ] || [ "$2" == "--force" ]; then
|
echo "Usage: $0 [backend|frontend] [--force]"
|
||||||
FORCE_REBUILD=true
|
echo " (no args) deploy backend and frontend"
|
||||||
|
echo " backend deploy Node API only (pm2: luckychit-api)"
|
||||||
|
echo " frontend build Flutter web + restart static server (pm2: luckychit-web)"
|
||||||
|
echo " --force aggressive clean (backend: rm node_modules; frontend: flutter clean)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--force)
|
||||||
|
FORCE_REBUILD=true
|
||||||
|
;;
|
||||||
|
backend|frontend)
|
||||||
|
if [ -n "$TARGET" ] && [ "$TARGET" != "$arg" ]; then
|
||||||
|
echo "Error: specify only one of backend or frontend."
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
TARGET="$arg"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown argument: $arg"
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
run_backend() {
|
||||||
|
[ "$TARGET" = "" ] || [ "$TARGET" = "backend" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
run_frontend() {
|
||||||
|
[ "$TARGET" = "" ] || [ "$TARGET" = "frontend" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
|
||||||
|
echo "LuckyChit deployment"
|
||||||
|
echo "===================="
|
||||||
|
echo "Root: $PROJECT_DIR"
|
||||||
|
echo "Branch: $BRANCH"
|
||||||
|
echo "Target: ${TARGET:-backend+frontend} force=$FORCE_REBUILD"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
echo "Warning: running as root. Prefer a normal user so Flutter/npm caches stay consistent."
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd $PROJECT_DIR
|
echo "Pulling latest code..."
|
||||||
|
git fetch origin "$BRANCH"
|
||||||
echo "🚀 LuckyChit Deployment"
|
if ! git rev-parse --verify "refs/remotes/origin/$BRANCH" >/dev/null 2>&1; then
|
||||||
echo "======================="
|
echo "Error: origin/$BRANCH not found. Fetch failed or branch missing on remote."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
git checkout "$BRANCH"
|
||||||
|
git merge --ff-only "origin/$BRANCH"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Git pull
|
if run_backend; then
|
||||||
echo "📥 Pulling latest code..."
|
echo "Deploying backend..."
|
||||||
git stash 2>/dev/null || true
|
cd "$PROJECT_DIR/backend"
|
||||||
git pull origin prod_i8n
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Deploy backend
|
if [ "$FORCE_REBUILD" = true ]; then
|
||||||
if [ "$1" == "" ] || [ "$1" == "backend" ] || [ "$1" == "--force" ]; then
|
echo "Force rebuild: removing node_modules and lockfile..."
|
||||||
echo "🔧 Deploying Backend..."
|
rm -rf node_modules package-lock.json
|
||||||
cd $PROJECT_DIR/backend
|
fi
|
||||||
|
|
||||||
if [ "$FORCE_REBUILD" = true ]; then
|
|
||||||
echo "🗑️ Force rebuild: Removing node_modules..."
|
|
||||||
rm -rf node_modules package-lock.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
if [ -f package-lock.json ]; then
|
||||||
|
npm ci
|
||||||
|
else
|
||||||
npm install
|
npm install
|
||||||
pm2 restart luckychit-api
|
fi
|
||||||
echo "✅ Backend deployed"
|
|
||||||
echo ""
|
if pm2 describe luckychit-api >/dev/null 2>&1; then
|
||||||
|
pm2 restart luckychit-api --update-env
|
||||||
|
else
|
||||||
|
pm2 start ecosystem.config.js --env production
|
||||||
|
fi
|
||||||
|
echo "Backend done."
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Deploy frontend
|
if run_frontend; then
|
||||||
if [ "$1" == "" ] || [ "$1" == "frontend" ] || [ "$1" == "--force" ]; then
|
echo "Deploying frontend (Flutter web)..."
|
||||||
echo "🎨 Deploying Frontend..."
|
cd "$PROJECT_DIR/luckychit"
|
||||||
cd $PROJECT_DIR/luckychit
|
|
||||||
|
|
||||||
if [ "$FORCE_REBUILD" = true ]; then
|
if [ "$FORCE_REBUILD" = true ]; then
|
||||||
echo "🗑️ Force rebuild: Cleaning Flutter cache..."
|
echo "Force rebuild: flutter clean..."
|
||||||
flutter clean
|
flutter clean
|
||||||
rm -rf .dart_tool build
|
rm -rf .dart_tool build
|
||||||
fi
|
fi
|
||||||
|
|
||||||
flutter pub get
|
flutter pub get
|
||||||
|
|
||||||
BUILD_NUMBER=$(date +%s)
|
if [ -f tool/sync_l10n.mjs ]; then
|
||||||
flutter build web --release --pwa-strategy=none --build-number=$BUILD_NUMBER
|
node tool/sync_l10n.mjs
|
||||||
echo "📦 Build version: $BUILD_NUMBER"
|
fi
|
||||||
|
|
||||||
pm2 restart luckychit-frontend
|
BUILD_NUMBER=$(date +%s)
|
||||||
echo "✅ Frontend deployed"
|
flutter build web --release --pwa-strategy=none --build-number="$BUILD_NUMBER"
|
||||||
echo ""
|
echo "Build number: $BUILD_NUMBER"
|
||||||
|
|
||||||
|
# Name must match luckychit/ecosystem.config.js (app name: luckychit-web)
|
||||||
|
if pm2 describe luckychit-web >/dev/null 2>&1; then
|
||||||
|
pm2 restart luckychit-web --update-env
|
||||||
|
else
|
||||||
|
pm2 start ecosystem.config.js --env production
|
||||||
|
fi
|
||||||
|
echo "Frontend done."
|
||||||
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Status
|
echo "PM2 status:"
|
||||||
echo "📊 PM2 Status:"
|
|
||||||
pm2 status
|
pm2 status
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Deployment complete!"
|
echo "Deployment complete."
|
||||||
echo ""
|
echo "Smoke tests:"
|
||||||
echo "🧪 Test:"
|
echo " curl -sf http://localhost:3000/health || curl -sf http://localhost:3000/api/health"
|
||||||
echo " Backend: curl http://localhost:3000/health"
|
echo " curl -sfI http://localhost:8080/"
|
||||||
echo " Frontend: curl http://localhost:8080"
|
echo "Logs: pm2 logs --lines 30"
|
||||||
echo " Domain: https://chitfund.deepteklabs.com"
|
|
||||||
echo ""
|
|
||||||
echo "📝 Check logs: pm2 logs --lines 20"
|
|
||||||
echo "💡 Clear browser cache: Ctrl + Shift + R"
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue