Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:

jobs:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name: Build and Publish Docker Image
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

env:
REPO_OWNER: ${{ github.repository_owner }}
Expand Down
76 changes: 58 additions & 18 deletions Server/src/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,76 @@

// check if token exists
if (!token) {
return res.status(401).json({ message: 'Not authorized, no token' });
return res.status(401).json({
success: false,
error: {
code: 'AUTH_001',
message: 'Authentication token is missing',
details: 'Please provide a valid Bearer token in the Authorization header'
}
});
}

try {
// verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret_key');

// get user info
const user = await prisma.user.findUnique({
where: { id: parseInt(decoded.userId) }
});
try {
// get user info
const user = await prisma.user.findUnique({
where: { id: parseInt(decoded.userId) }
});

if (!user) {
return res.status(401).json({ message: 'Not authorized, token failed' });
}
if (!user) {
return res.status(401).json({
success: false,
error: {
code: 'AUTH_002',
message: 'User not found',
details: 'The user associated with this token no longer exists'
}
});
}

// add user info to request object
req.user = {
id: user.id,
name: user.name,
email: user.email,
role: user.role
};
// add user info to request object
req.user = {
id: user.id,
name: user.name,
email: user.email,
role: user.role
};

next();
next();
} catch (dbError) {
console.error('Database error:', dbError);

Check warning on line 58 in Server/src/middleware/auth.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
return res.status(500).json({
success: false,
error: {
code: 'DB_001',
message: 'Database connection error',
details: 'Failed to connect to the database while verifying user'
}
});
}
} catch (error) {
return res.status(401).json({ message: 'Not authorized, token failed' });
return res.status(401).json({
success: false,
error: {
code: 'AUTH_003',
message: 'Invalid token',
details: 'The provided token is invalid or has expired'
}
});
}
} catch (error) {
console.error('Auth middleware error:', error);

Check warning on line 79 in Server/src/middleware/auth.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
res.status(500).json({ message: 'Internal server error' });
res.status(500).json({
success: false,
error: {
code: 'AUTH_004',
message: 'Internal server error',
details: 'An unexpected error occurred during authentication'
}
});
}
};
26 changes: 13 additions & 13 deletions Server/src/routes/donor.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
excluded,
deceased,
tags,
min_donation,

Check warning on line 117 in Server/src/routes/donor.js

View workflow job for this annotation

GitHub Actions / lint

'min_donation' is assigned a value but never used
search
} = req.query;

Expand Down Expand Up @@ -313,7 +313,7 @@
*/
router.put('/:id', protect, async (req, res) => {
try {
// 解析并验证捐赠者ID
// Parse and validate donor ID
let donorId;
try {
donorId = parseInt(req.params.id);
Expand All @@ -324,7 +324,7 @@
return res.status(400).json({ message: 'Invalid donor ID format' });
}

// 验证捐赠者是否存在
// Verify if donor exists
const donorExists = await prisma.donor.findUnique({
where: { id: donorId }
});
Expand All @@ -333,10 +333,10 @@
return res.status(404).json({ message: 'Donor not found' });
}

// 从请求体中获取更新数据
// Get update data from request body
const rawUpdateData = req.body;

// 只保留有效的捐赠者字段
// Keep only valid donor fields
const validFields = [
'pmm', 'smm', 'vmm', 'excluded', 'deceased',
'firstName', 'nickName', 'lastName', 'organizationName',
Expand All @@ -347,18 +347,18 @@
'subscriptionEventsInPerson', 'subscriptionEventsMagazine', 'communicationPreference'
];

// 过滤掉不存在的字段
// Filter out non-existent fields
const updateData = {};
validFields.forEach(field => {
if (rawUpdateData[field] !== undefined) {
updateData[field] = rawUpdateData[field];
}
});

// 记录更新操作
// Log update operation
console.log(`Updating donor with ID ${donorId}:`, updateData);

// 处理日期字段,确保它们是有效的格式
// Process date fields to ensure they are in valid format
if (updateData.firstGiftDate) {
updateData.firstGiftDate = new Date(updateData.firstGiftDate);
}
Expand All @@ -367,13 +367,13 @@
updateData.lastGiftDate = new Date(updateData.lastGiftDate);
}

// 更新捐赠者信息
// Update donor information
const updatedDonor = await prisma.donor.update({
where: { id: donorId },
data: updateData
});

// 返回更新后的捐赠者信息
// Return updated donor information
res.json(formatDonor(updatedDonor));
} catch (error) {
console.error('Error updating donor:', error);
Expand Down Expand Up @@ -789,7 +789,7 @@
});
}

// 验证所有ID都是有效的数字
// Validate that all IDs are valid numbers
const validIds = ids.map(id => parseInt(id)).filter(id => !isNaN(id));

if (validIds.length !== ids.length) {
Expand All @@ -799,17 +799,17 @@
});
}

// 使用事务来确保数据一致性
// Use transaction to ensure data consistency
await prisma.$transaction([
// 先删除相关的eventDonor记录
// First delete related eventDonor records
prisma.eventDonor.deleteMany({
where: {
donorId: {
in: validIds
}
}
}),
// 然后删除捐赠者
// Then delete the donors
prisma.donor.deleteMany({
where: {
id: {
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/donors/Donors.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
// Fetch events on component mount
useEffect(() => {
fetchEvents();
}, []);

Check warning on line 60 in client/src/components/donors/Donors.jsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has a missing dependency: 'fetchEvents'. Either include it or remove the dependency array

// Handle event selection from location state
useEffect(() => {
Expand Down Expand Up @@ -90,7 +90,7 @@
fetchEventStats();
}
}
}, [selectedEvent, searchQuery, currentPage]);

Check warning on line 93 in client/src/components/donors/Donors.jsx

View workflow job for this annotation

GitHub Actions / lint

React Hook useEffect has missing dependencies: 'fetchEventDonors' and 'fetchEventStats'. Either include them or remove the dependency array

// Fetch events
const fetchEvents = async () => {
Expand Down Expand Up @@ -194,6 +194,7 @@
}
}

console.log('response.data', response.data);
Copy link

Copilot AI Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider removing the debug log in production code to avoid unintended console output.

Suggested change
console.log('response.data', response.data);
// Debug log removed: console.log('response.data', response.data);

Copilot uses AI. Check for mistakes.
// Process the donors data normally
setEventDonors(response.data || []);
setTotalPages(response.total_pages || 1);
Expand Down Expand Up @@ -605,7 +606,6 @@
// Check if the selected event is in Ready status
const isEventReady = () => {
if (!selectedEvent) return false;
console.log('Checking event status:', selectedEvent.status);
return selectedEvent.status === 'active';
};

Expand Down
18 changes: 18 additions & 0 deletions client/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// API Configuration
export const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001';

// Other configurations can be added here
export const APP_CONFIG = {
// Pagination defaults
DEFAULT_PAGE_SIZE: 10,
MAX_PAGE_SIZE: 100,

// Timeouts
REQUEST_TIMEOUT: 30000, // 30 seconds

// File upload limits
MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB

// Cache settings
CACHE_DURATION: 5 * 60 * 1000, // 5 minutes
};
12 changes: 7 additions & 5 deletions client/src/middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import authService from '../services/authService';

// Create a generic fetch wrapper function to handle 401 errors
export const fetchWithAuthMiddleware = async (url, options = {}) => {
const response = await fetch(url, options);

export const withAuth = async (response) => {
// Check if it's a 401 error
if (response.status === 401) {
// Clear authentication information from local storage
authService.logout();

// Redirection handled automatically by logout function, no need for additional redirect here
return;
}

Expand All @@ -20,4 +16,10 @@ export const fetchWithAuthMiddleware = async (url, options = {}) => {
}

return response;
};

// Create a fetch wrapper, using middleware
export const fetchWithAuth = async (url, options = {}) => {
const response = await fetch(url, options);
return withAuth(response);
};
62 changes: 19 additions & 43 deletions client/src/services/authService.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,22 @@
import { fetchWithAuthMiddleware } from '../middleware/authMiddleware';

// API base URL
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001';

// Create a generic fetch function with authentication
const fetchWithAuth = async (endpoint, options = {}) => {
// Ensure headers exist
if (!options.headers) {
options.headers = {};
}

// Add content type header
options.headers['Content-Type'] = 'application/json';

// Add token to request header if it exists
const token = localStorage.getItem('token');
if (token) {
options.headers['Authorization'] = `Bearer ${token}`;
}

// Execute the request using middleware
const response = await fetchWithAuthMiddleware(`${API_URL}/api/user${endpoint}`, options);

// Throw error if response is not successful
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: 'Request failed' }));
throw errorData;
}

// Return response data
return response.json();
};
import { API_URL } from '../config';

// User registration
export const register = async (userData) => {
try {
const data = await fetchWithAuth('/register', {
const response = await fetch(`${API_URL}/api/user/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
return data;

if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: 'Registration failed' }));
throw errorData;
}

return response.json();
} catch (error) {
throw error.message ? error : new Error('Registration failed, please try again later');
}
Expand All @@ -64,14 +41,14 @@ export const login = async (email, password) => {

const data = await response.json();

// Store token and user data in localStorage
// Store token and user data in sessionStorage
if (data.token) {
localStorage.setItem('token', data.token);
sessionStorage.setItem('token', data.token);
console.log('Token stored successfully');

// Store user data if available
if (data.user) {
localStorage.setItem('user', JSON.stringify(data.user));
sessionStorage.setItem('user', JSON.stringify(data.user));
console.log('User data stored successfully');
}

Expand All @@ -97,8 +74,8 @@ export const login = async (email, password) => {

// User logout
export const logout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
sessionStorage.removeItem('token');
sessionStorage.removeItem('user');

// Use different routing format based on environment
if (process.env.NODE_ENV === 'production') {
Expand All @@ -112,7 +89,7 @@ export const logout = () => {

// Get current logged-in user
export const getCurrentUser = () => {
const userStr = localStorage.getItem('user');
const userStr = sessionStorage.getItem('user');
if (userStr) {
return JSON.parse(userStr);
}
Expand All @@ -121,7 +98,7 @@ export const getCurrentUser = () => {

// Check if user is authenticated
export const isAuthenticated = () => {
return localStorage.getItem('token') !== null;
return sessionStorage.getItem('token') !== null;
};

const authService = {
Expand All @@ -132,5 +109,4 @@ const authService = {
isAuthenticated
};


export default authService;
Loading
Loading