From 13218344440d1d742a6a2f7c76091c57a130774b Mon Sep 17 00:00:00 2001 From: PCBZ Date: Mon, 21 Apr 2025 23:34:28 -0700 Subject: [PATCH 1/3] change auth API --- Server/src/middleware/auth.js | 76 ++++++++++++++++++++++++++--------- Server/src/routes/donor.js | 26 ++++++------ 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/Server/src/middleware/auth.js b/Server/src/middleware/auth.js index fe2862b4..03f8a63a 100644 --- a/Server/src/middleware/auth.js +++ b/Server/src/middleware/auth.js @@ -14,36 +14,76 @@ export const protect = async (req, res, next) => { // 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); + 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); - 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' + } + }); } }; \ No newline at end of file diff --git a/Server/src/routes/donor.js b/Server/src/routes/donor.js index e688b9fb..6a0bae2c 100644 --- a/Server/src/routes/donor.js +++ b/Server/src/routes/donor.js @@ -313,7 +313,7 @@ router.get('/:id', protect, async (req, res) => { */ router.put('/:id', protect, async (req, res) => { try { - // 解析并验证捐赠者ID + // Parse and validate donor ID let donorId; try { donorId = parseInt(req.params.id); @@ -324,7 +324,7 @@ router.put('/:id', protect, async (req, res) => { return res.status(400).json({ message: 'Invalid donor ID format' }); } - // 验证捐赠者是否存在 + // Verify if donor exists const donorExists = await prisma.donor.findUnique({ where: { id: donorId } }); @@ -333,10 +333,10 @@ router.put('/:id', protect, async (req, res) => { 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', @@ -347,7 +347,7 @@ router.put('/:id', protect, async (req, res) => { 'subscriptionEventsInPerson', 'subscriptionEventsMagazine', 'communicationPreference' ]; - // 过滤掉不存在的字段 + // Filter out non-existent fields const updateData = {}; validFields.forEach(field => { if (rawUpdateData[field] !== undefined) { @@ -355,10 +355,10 @@ router.put('/:id', protect, async (req, res) => { } }); - // 记录更新操作 + // 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); } @@ -367,13 +367,13 @@ router.put('/:id', protect, async (req, res) => { 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); @@ -789,7 +789,7 @@ router.delete('/batch', protect, async (req, res) => { }); } - // 验证所有ID都是有效的数字 + // Validate that all IDs are valid numbers const validIds = ids.map(id => parseInt(id)).filter(id => !isNaN(id)); if (validIds.length !== ids.length) { @@ -799,9 +799,9 @@ router.delete('/batch', protect, async (req, res) => { }); } - // 使用事务来确保数据一致性 + // Use transaction to ensure data consistency await prisma.$transaction([ - // 先删除相关的eventDonor记录 + // First delete related eventDonor records prisma.eventDonor.deleteMany({ where: { donorId: { @@ -809,7 +809,7 @@ router.delete('/batch', protect, async (req, res) => { } } }), - // 然后删除捐赠者 + // Then delete the donors prisma.donor.deleteMany({ where: { id: { From 0d3fe50aa0e98fc6430173397abc015f36df3029 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 22 Apr 2025 00:49:34 -0700 Subject: [PATCH 2/3] set auth --- client/src/components/donors/Donors.jsx | 2 +- client/src/config.js | 18 + client/src/middleware/authMiddleware.js | 12 +- client/src/services/authService.js | 62 +--- client/src/services/baseService.js | 44 +++ client/src/services/donorListService.js | 64 +--- client/src/services/donorService.js | 408 +++----------------- client/src/services/eventService.js | 474 +++--------------------- 8 files changed, 199 insertions(+), 885 deletions(-) create mode 100644 client/src/config.js create mode 100644 client/src/services/baseService.js diff --git a/client/src/components/donors/Donors.jsx b/client/src/components/donors/Donors.jsx index 1c0a9a6d..8aff7295 100644 --- a/client/src/components/donors/Donors.jsx +++ b/client/src/components/donors/Donors.jsx @@ -194,6 +194,7 @@ const Donors = () => { } } + console.log('response.data', response.data); // Process the donors data normally setEventDonors(response.data || []); setTotalPages(response.total_pages || 1); @@ -605,7 +606,6 @@ const Donors = () => { // 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'; }; diff --git a/client/src/config.js b/client/src/config.js new file mode 100644 index 00000000..f67de3fb --- /dev/null +++ b/client/src/config.js @@ -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 +}; \ No newline at end of file diff --git a/client/src/middleware/authMiddleware.js b/client/src/middleware/authMiddleware.js index 95906790..abfaca98 100644 --- a/client/src/middleware/authMiddleware.js +++ b/client/src/middleware/authMiddleware.js @@ -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; } @@ -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); }; \ No newline at end of file diff --git a/client/src/services/authService.js b/client/src/services/authService.js index fa0c389e..26d7f7fc 100644 --- a/client/src/services/authService.js +++ b/client/src/services/authService.js @@ -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'); } @@ -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'); } @@ -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') { @@ -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); } @@ -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 = { @@ -132,5 +109,4 @@ const authService = { isAuthenticated }; - export default authService; \ No newline at end of file diff --git a/client/src/services/baseService.js b/client/src/services/baseService.js new file mode 100644 index 00000000..3fd77592 --- /dev/null +++ b/client/src/services/baseService.js @@ -0,0 +1,44 @@ +import { API_URL } from '../config'; + +/** + * Fetch data with authentication + * @param {string} url - The URL to fetch (can be relative or absolute) + * @param {Object} options - Fetch options + * @returns {Promise} The fetch response + */ +export const fetchWithAuth = async (url, options = {}) => { + try { + // Get token from sessionStorage + const token = sessionStorage.getItem('token'); + if (!token) { + throw new Error('No authentication token found'); + } + + // Ensure URL is absolute + const absoluteUrl = url.startsWith('http') ? url : `${API_URL}${url}`; + + // Set default headers + const headers = { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json', + ...options.headers + }; + + // Make the request + const response = await fetch(absoluteUrl, { + ...options, + headers + }); + + // Handle response + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + return response.json(); + } catch (error) { + console.error('Fetch error:', error); + throw error; + } +}; \ No newline at end of file diff --git a/client/src/services/donorListService.js b/client/src/services/donorListService.js index b6044995..f302b0a0 100644 --- a/client/src/services/donorListService.js +++ b/client/src/services/donorListService.js @@ -1,28 +1,10 @@ -import { fetchWithAuthMiddleware } from '../middleware/authMiddleware'; - -const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001'; +import { fetchWithAuth } from './baseService'; // Get summary statistics for all donor lists export const getDonorListsSummary = async () => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/lists/stats/summary`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return await response.json(); + const response = await fetchWithAuth('/api/lists/stats/summary'); + return response; } catch (error) { console.error('Error fetching donor lists summary:', error); throw error; @@ -32,24 +14,8 @@ export const getDonorListsSummary = async () => { // Get statistics for a specific donor list export const getDonorListStats = async (listId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/lists/${listId}/stats`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return await response.json(); + const response = await fetchWithAuth(`/api/lists/${listId}/stats`); + return response; } catch (error) { console.error(`Error fetching stats for donor list ${listId}:`, error); throw error; @@ -59,30 +25,14 @@ export const getDonorListStats = async (listId) => { // Get donor lists with pagination and filtering export const getDonorLists = async (page = 1, limit = 10, status = '') => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - // Build URL query parameters const url = new URL(`${API_URL}/api/lists`); if (page) url.searchParams.append('page', page); if (limit) url.searchParams.append('limit', limit); if (status) url.searchParams.append('status', status); - const response = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return await response.json(); + const response = await fetchWithAuth(url.toString()); + return response; } catch (error) { console.error('Error fetching donor lists:', error); throw error; diff --git a/client/src/services/donorService.js b/client/src/services/donorService.js index 3cbfcc5e..eb7b2658 100644 --- a/client/src/services/donorService.js +++ b/client/src/services/donorService.js @@ -1,8 +1,6 @@ -// Import mock data from mockData module - -const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001'; - -const getAuthToken = () => localStorage.getItem('token'); +import { fetchWithAuth } from './baseService'; +import { API_URL } from '../config'; +import { logout } from './authService'; // Helper function to convert JSON data to CSV const jsonToCsv = (data, options = {}) => { @@ -56,13 +54,7 @@ const jsonToCsv = (data, options = {}) => { */ export const getDonors = async (params = {}) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - // Build URL with query parameters - // We can now directly use snake_case parameters as the server supports them const url = new URL(`${API_URL}/api/donors`); Object.keys(params).forEach(key => { if (params[key] !== undefined && params[key] !== '') { @@ -70,19 +62,7 @@ export const getDonors = async (params = {}) => { } }); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); + const data = await fetchWithAuth(url.toString()); // Response is already formatted with snake_case field names return { @@ -94,7 +74,7 @@ export const getDonors = async (params = {}) => { }; } catch (error) { console.error('Error fetching donors:', error); - throw error; // No longer return mock data, pass error to caller + throw error; } }; @@ -117,11 +97,6 @@ export const getDonors = async (params = {}) => { */ export const getAllDonors = async (params = {}) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('Authentication token not found'); - } - const queryParams = new URLSearchParams({ page: params.page || 1, limit: params.limit || 10, @@ -134,19 +109,7 @@ export const getAllDonors = async (params = {}) => { ...(params.tags && { tags: params.tags }) }).toString(); - const response = await fetch(`${API_URL}/api/donors?${queryParams}`, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Failed to fetch donors: ${response.status}`); - } - - const data = await response.json(); + const data = await fetchWithAuth(`/api/donors?${queryParams}`); // Ensure we have valid data structure return { @@ -169,14 +132,7 @@ export const getAllDonors = async (params = {}) => { * @returns {Promise} Available donors data */ export const getAvailableDonors = async (eventId, params = {}) => { - try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log('Getting available donors for event ID', eventId); - + try { // Use the new API endpoint const url = new URL(`${API_URL}/api/events/${eventId}/available-donors`); @@ -186,23 +142,7 @@ export const getAvailableDonors = async (eventId, params = {}) => { url.searchParams.append(key, params[key]); } }); - - console.log('Fetching available donors:', url.toString()); - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Failed to fetch available donors: ${response.status}`); - } - - const data = await response.json(); - console.log('Available donors data received:', data); + const data = await fetchWithAuth(url.toString()); // Return formatted data return { @@ -225,35 +165,16 @@ export const getAvailableDonors = async (eventId, params = {}) => { * @returns {Promise} Response data */ export const addDonorToEvent = async (eventId, donorId) => { - try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log(`Adding donor ${donorId} to event ${eventId}`); - + try { // Use /api/events/{eventId}/donors endpoint to add donor - const response = await fetch(`${API_URL}/api/events/${eventId}/donors`, { + const data = await fetchWithAuth(`/api/events/${eventId}/donors`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify({ donorId }) }); - if (!response.ok) { - const errorData = await response.json(); - console.error('Failed to add donor:', errorData); - throw new Error(errorData.message || `HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - console.log('Donor added successfully:', result); - return result; + return data; } catch (error) { - console.error('Error adding donor to event:', error); + console.error('Failed to add donor:', error); throw error; } }; @@ -261,39 +182,18 @@ export const addDonorToEvent = async (eventId, donorId) => { /** * Remove a donor from an event * @param {string} eventId - Event ID - * @param {string} eventDonorId - Event Donor ID (the ID of the donor-event relationship record) + * @param {string} eventDonorId - Event Donor ID * @returns {Promise} Response data */ export const removeDonorFromEvent = async (eventId, eventDonorId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log(`Removing donor relationship ${eventDonorId} from event ${eventId}`); - - // Use /api/events/{eventId}/donors/{eventDonorId} endpoint to remove donor - const response = await fetch(`${API_URL}/api/events/${eventId}/donors/${eventDonorId}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } + const result = await fetchWithAuth(`/api/events/${eventId}/donors/${eventDonorId}`, { + method: 'DELETE' }); - - if (!response.ok) { - const errorData = await response.json(); - console.error('Failed to remove donor:', errorData); - throw new Error(errorData.message || `HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - console.log('Donor removed successfully:', result); return result; } catch (error) { console.error('Error removing donor from event:', error); - throw error; // Pass the error to the caller + throw error; } }; @@ -304,25 +204,7 @@ export const removeDonorFromEvent = async (eventId, eventDonorId) => { */ export const getEventDonorStats = async (eventId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - // Fetch all donors for this event (with a high limit to get all records) - const donorsResponse = await fetch(`${API_URL}/api/events/${eventId}/donors?limit=1000`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!donorsResponse.ok) { - throw new Error(`HTTP error! status: ${donorsResponse.status}`); - } - - const donorsData = await donorsResponse.json(); + const donorsData = await fetchWithAuth(`/api/events/${eventId}/donors?limit=1000`); const donors = donorsData.donors || []; // Calculate statistics based on donor data @@ -345,14 +227,6 @@ export const getEventDonorStats = async (eventId) => { const total = donors.length; const approvalRate = total > 0 ? Math.round((approved / total) * 100) : 0; - console.log('Calculated donor statistics:', { - total_donors: total, - pending_review: pending, - approved: approved, - excluded: excluded, - approval_rate: approvalRate - }); - return { event_id: parseInt(eventId), total_donors: total, @@ -363,52 +237,23 @@ export const getEventDonorStats = async (eventId) => { }; } catch (error) { console.error('Error calculating event donor statistics:', error); - // Return a default empty stats object if calculation fails - return { - event_id: parseInt(eventId), - total_donors: 0, - pending_review: 0, - approved: 0, - excluded: 0, - approval_rate: 0 - }; + throw error; } }; /** * Update the status of a donor in an event * @param {string} eventId - Event ID - * @param {string} eventDonorId - Event Donor ID (the ID of the donor-event relationship record) - * @param {string} status - New status ('Pending', 'Approved', or 'Excluded') + * @param {string} eventDonorId - Event Donor ID + * @param {string} status - New status * @returns {Promise} Response data */ export const updateDonorStatus = async (eventId, eventDonorId, status) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log(`Updating status for donor relationship ${eventDonorId} in event ${eventId} to ${status}`); - - // Use PATCH request to update donor status - const response = await fetch(`${API_URL}/api/events/${eventId}/donors/${eventDonorId}/status`, { + const result = await fetchWithAuth(`/api/events/${eventId}/donors/${eventDonorId}/status`, { method: 'PATCH', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify({ status }) }); - - if (!response.ok) { - const errorData = await response.json(); - console.error('Failed to update donor status:', errorData); - throw new Error(errorData.message || `HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - console.log('Donor status updated successfully:', result); return result; } catch (error) { console.error('Error updating donor status:', error); @@ -417,44 +262,23 @@ export const updateDonorStatus = async (eventId, eventDonorId, status) => { }; /** - * Update the event donor information (comments, auto_excluded, etc.) + * Update event donor information * @param {string} eventId - Event ID - * @param {string} eventDonorId - Event Donor ID (the ID of the donor-event relationship record) - * @param {Object} updateData - Data to update (comments, auto_excluded, etc.) + * @param {string} eventDonorId - Event Donor ID + * @param {Object} updateData - Data to update * @returns {Promise} Response data */ export const updateEventDonor = async (eventId, eventDonorId, updateData) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log(`Updating donor with ID ${eventDonorId} in event ${eventId}`, updateData); - if (!eventId || !eventDonorId) { console.error('Missing required parameters:', { eventId, eventDonorId }); throw new Error('Missing eventId or eventDonorId'); } - // Use PATCH request to update donor information - const response = await fetch(`${API_URL}/api/events/${eventId}/donors/${eventDonorId}`, { + const result = await fetchWithAuth(`/api/events/${eventId}/donors/${eventDonorId}`, { method: 'PATCH', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify(updateData) }); - - if (!response.ok) { - const errorData = await response.json(); - console.error(`Failed to update donor (status: ${response.status}):`, errorData); - throw new Error(errorData.message || `HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - console.log('Donor information updated successfully:', result); return result; } catch (error) { console.error('Error updating donor information:', error); @@ -497,85 +321,36 @@ export const exportDonorsToCsv = async () => { * @returns {Promise} CSV file as a blob */ export const exportEventDonorsToCsv = async (eventId) => { - console.log(`Starting export for event ID: ${eventId}`); try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - // Get event details - const eventResponse = await fetch(`${API_URL}/api/events/${eventId}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!eventResponse.ok) { - throw new Error(`HTTP error! status: ${eventResponse.status}`); - } - - const eventData = await eventResponse.json(); + const eventData = await fetchWithAuth(`/api/events/${eventId}`); const eventName = eventData.name || `Event-${eventId}`; - console.log(`Exporting donors for event: ${eventName}`); // Get event donor list - const response = await fetch(`${API_URL}/api/events/${eventId}/donors?limit=1000`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const donorsData = await response.json(); + const donorsData = await fetchWithAuth(`/api/events/${eventId}/donors?limit=1000`); let eventDonors = donorsData.donors || []; - console.log(`Found ${eventDonors.length} donor relationship records`); if (eventDonors.length === 0) { throw new Error('No donors to export'); } - // Get complete donor data from API - console.log('Fetching complete donor data for each donor...'); const donorPromises = eventDonors.map(async (eventDonor) => { // Get donor ID from eventDonor const donorId = eventDonor.donor?.id || eventDonor.donor_id || eventDonor.donorId; if (!donorId) { console.warn('Could not find donor ID:', eventDonor); - return null; // Return null to filter out later + return null; } // Skip excluded donors if (eventDonor.status === 'Excluded') { - console.log(`Skipping excluded donor ID=${donorId}`); return null; } try { // Get complete donor data - console.log(`Fetching data for donor ID=${donorId}`); - const donorResponse = await fetch(`${API_URL}/api/donors/${donorId}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!donorResponse.ok) { - console.warn(`Unable to get data for donor ID=${donorId}, status: ${donorResponse.status}`); - return null; // Return null to filter out later - } - - const donorData = await donorResponse.json(); + const donorData = await fetchWithAuth(`/api/donors/${donorId}`); // Skip deceased donors if (donorData.deceased === true || donorData.is_deceased === true) { @@ -586,8 +361,8 @@ export const exportEventDonorsToCsv = async (eventId) => { // Return only donor data, not event donor relationship data return donorData; } catch (error) { - console.warn(`Error fetching data for donor ID=${donorId}:`, error.message); - return null; // Return null to filter out later + console.error(`Error fetching data for donor ID=${donorId}:`, error.message); + return null; } }); @@ -595,8 +370,6 @@ export const exportEventDonorsToCsv = async (eventId) => { let enrichedDonors = await Promise.all(donorPromises); enrichedDonors = enrichedDonors.filter(donor => donor !== null); - console.log(`All donor data fetched, valid data: ${enrichedDonors.length} records`); - if (enrichedDonors.length === 0) { throw new Error('No valid donors to export'); } @@ -613,32 +386,17 @@ export const exportEventDonorsToCsv = async (eventId) => { /** * Update donor information - * @param {string} donorId - The ID of the donor to update + * @param {string} donorId - Donor ID * @param {Object} donorData - Updated donor data * @returns {Promise} Updated donor data */ export const updateDonor = async (donorId, donorData) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('Authentication token not found'); - } - - const response = await fetch(`${API_URL}/api/donors/${donorId}`, { + const result = await fetchWithAuth(`/api/donors/${donorId}`, { method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify(donorData) }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Failed to update donor: ${response.status}`); - } - - return await response.json(); + return result; } catch (error) { console.error('Error updating donor:', error); throw error; @@ -647,34 +405,15 @@ export const updateDonor = async (donorId, donorData) => { /** * Delete a donor - * @param {string} donorId - The ID of the donor to delete - * @returns {Promise} - Deletion result + * @param {string} donorId - Donor ID + * @returns {Promise} Deletion result */ export const deleteDonor = async (donorId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('Authentication token not found'); - } - - console.log(`Attempting to delete donor with ID: ${donorId}`); - - const response = await fetch(`${API_URL}/api/donors/${donorId}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } + const result = await fetchWithAuth(`/api/donors/${donorId}`, { + method: 'DELETE' }); - - const data = await response.json(); - console.log('Delete donor response:', response.status, data); - - if (!response.ok) { - throw new Error(data.message || `Failed to delete donor: ${response.status}`); - } - - return data; + return result; } catch (error) { console.error('Error deleting donor:', error); throw error; @@ -684,30 +423,15 @@ export const deleteDonor = async (donorId) => { /** * Create a new donor * @param {Object} donorData - New donor data - * @returns {Promise} - Created donor data + * @returns {Promise} Created donor data */ export const createDonor = async (donorData) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('Authentication token not found'); - } - - const response = await fetch(`${API_URL}/api/donors`, { + const result = await fetchWithAuth('/api/donors', { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify(donorData) }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error(errorData.message || `Failed to create donor: ${response.status}`); - } - - return await response.json(); + return result; } catch (error) { console.error('Error creating donor:', error); throw error; @@ -715,35 +439,19 @@ export const createDonor = async (donorData) => { }; /** - * Add multiple donors to a list + * Add donors to a list * @param {number} listId - List ID * @param {Array} donorIds - Array of donor IDs * @returns {Promise} Addition result */ export const addDonorsToList = async (listId, donorIds) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - const numericDonorIds = donorIds.map(id => Number(id)); - - const response = await fetch(`${API_URL}/api/lists/${listId}/donors`, { + const result = await fetchWithAuth(`/api/lists/${listId}/donors`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify({ donorIds: numericDonorIds }) }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.message || `HTTP error! status: ${response.status}`); - } - - return await response.json(); + return result; } catch (error) { console.error('Error adding donors to list:', error); throw error; @@ -760,11 +468,6 @@ export const addDonorsToList = async (listId, donorIds) => { */ export const importDonors = async (file, onProgress, onComplete, onError) => { try { - const token = getAuthToken(); - if (!token) { - throw new Error('No authentication token found'); - } - // Check file type const fileExtension = file.name.split('.').pop().toLowerCase(); if (!['csv', 'xlsx', 'xls'].includes(fileExtension)) { @@ -785,14 +488,14 @@ export const importDonors = async (file, onProgress, onComplete, onError) => { method: 'POST', body: formData, headers: { - 'Authorization': `Bearer ${token}` + 'Authorization': `Bearer ${sessionStorage.getItem('token')}` } }); if (!response.ok) { if (response.status === 401) { - localStorage.removeItem('token'); - throw new Error('Session expired. Please log in again.'); + sessionStorage.removeItem('token'); + logout(); } const errorData = await response.json(); throw new Error(errorData.message || `Import failed with status: ${response.status}`); @@ -812,12 +515,7 @@ export const importDonors = async (file, onProgress, onComplete, onError) => { const pollProgress = async () => { try { - const progressResponse = await fetch(`${API_URL}/api/progress/${operationId}`, { - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); + const progressResponse = await fetchWithAuth(`/api/progress/${operationId}`); if (!progressResponse.ok) { clearInterval(pollingInterval); @@ -855,12 +553,8 @@ export const importDonors = async (file, onProgress, onComplete, onError) => { cancel: async () => { try { clearInterval(pollingInterval); - await fetch(`${API_URL}/api/progress/${operationId}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } + await fetchWithAuth(`/api/progress/${operationId}`, { + method: 'DELETE' }); } catch (error) { console.error('Failed to cancel import:', error); diff --git a/client/src/services/eventService.js b/client/src/services/eventService.js index f4ac6ed7..0610d4b0 100644 --- a/client/src/services/eventService.js +++ b/client/src/services/eventService.js @@ -1,12 +1,7 @@ -// 不需要在前端导入PrismaClient,它是服务器端使用的 -// import { PrismaClient } from '@prisma/client'; - -import { fetchWithAuthMiddleware } from '../middleware/authMiddleware'; +import { fetchWithAuth } from './baseService'; +import { API_URL } from '../config'; import { toFrontendStatus, toBackendStatus } from '../utils/statusConversion'; - -const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001'; - /** * Get events with optional filtering * @param {Object} params - Query parameters @@ -18,43 +13,26 @@ const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001'; */ export const getEvents = async (params = {}) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - const url = new URL(`${API_URL}/api/events`); - // 处理传入的active状态,映射到正确的后端状态值 + // Handle incoming active status, map to correct backend status value const modifiedParams = { ...params }; - // 如果status是"active",将其替换为后端理解的值"Ready" + // If status is "active", replace it with backend understood value "Ready" if (modifiedParams.status === 'active') { modifiedParams.status = 'Ready'; } - // 添加查询参数 + // Add query parameters Object.keys(modifiedParams).forEach(key => { if (modifiedParams[key] !== undefined && modifiedParams[key] !== '') { url.searchParams.append(key, modifiedParams[key]); } }); - const response = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); + const data = await fetchWithAuth(url.toString()); - // 将后端状态值映射回前端使用的值 + // Map backend status values back to frontend values if (data.events) { data.events = data.events.map(event => ({ ...event, @@ -82,26 +60,9 @@ export const getEvents = async (params = {}) => { */ export const getEventById = async (eventId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/${eventId}`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const event = await response.json(); + const event = await fetchWithAuth(`${API_URL}/api/events/${eventId}`); - // 如果需要,将后端状态值映射回前端使用的值 + // If needed, map backend status values back to frontend values return { ...event, status: event.status === 'Ready' ? 'active' : event.status.toLowerCase() @@ -119,27 +80,12 @@ export const getEventById = async (eventId) => { */ export const createEvent = async (eventData) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events`, { + const responseData = await fetchWithAuth(`${API_URL}/api/events`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify(eventData) }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const responseData = await response.json(); - // 包含自动创建的捐赠者列表信息 + // Include automatically created donor list information return { data: { ...responseData.event, @@ -165,18 +111,6 @@ export const createEvent = async (eventData) => { */ export const getEventDonors = async (eventId, params = {}) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - // Special handling: If event ID is 227, this is a known problematic event, try using another endpoint - if (eventId === '227' || eventId === 227) { - console.log('Detected event ID 227, trying alternative API endpoint...'); - return await getEventDonorsAlternative(eventId, params); - } - - // Directly use /api/events/{eventId}/donors endpoint to get event donors const url = new URL(`${API_URL}/api/events/${eventId}/donors`); // Add query parameters @@ -189,56 +123,8 @@ export const getEventDonors = async (eventId, params = {}) => { // Always add no_sort=true parameter to avoid 500 error from server using non-existent createdAt field for sorting url.searchParams.set('no_sort', 'true'); - console.log('Requesting event donors:', url.toString()); - try { - const response = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - // Handle 404 error, indicating event may have no donor list - if (response.status === 404) { - const errorData = await response.json().catch(() => ({ message: 'Event has no donor list' })); - console.warn('Event has no donor list:', errorData.message); - - // Return empty result with flag indicating list creation is needed - return { - data: [], - page: params.page || 1, - limit: params.limit || 10, - total_count: 0, - total_pages: 0, - needsListCreation: true, - message: errorData.message || 'Event has no donor list' - }; - } - - // Handle 500 error - try alternative method - if (response.status === 500) { - console.error('Server internal error, trying alternative endpoint:', url.toString()); - return await getEventDonorsAlternative(eventId, params); - } - - // Handle other errors - const errorText = await response.text().catch(() => 'Unknown error'); - let errorMessage; - try { - const errorJson = JSON.parse(errorText); - errorMessage = errorJson.message || `HTTP error! status: ${response.status}`; - } catch (e) { - errorMessage = `HTTP error! status: ${response.status}, response: ${errorText.slice(0, 100)}`; - } - - throw new Error(errorMessage); - } - - // Get API response - const responseData = await response.json(); + const responseData = await fetchWithAuth(url.toString()); // If response message includes needsListCreation flag, pass it to caller const needsListCreation = responseData.needsListCreation || false; @@ -247,216 +133,52 @@ export const getEventDonors = async (eventId, params = {}) => { return { data: Array.isArray(responseData.donors) ? responseData.donors.map(donor => ({ ...donor, - firstName: donor.donor?.firstName || donor.donor?.first_name, - lastName: donor.donor?.lastName || donor.donor?.last_name, - nickName: donor.donor?.nickName || donor.donor?.nick_name, - organizationName: donor.donor?.organizationName || donor.donor?.organization_name, + id: donor.donorId || donor.donor_id, + firstName: donor.donor?.firstName || donor.donor?.first_name || '', + lastName: donor.donor?.lastName || donor.donor?.last_name || '', + email: donor.donor?.email || '', + phone: donor.donor?.phone || '', + organizationName: donor.donor?.organizationName || donor.donor?.organization_name || '', totalDonations: donor.donor?.totalDonations || donor.donor?.total_donations || 0, largestGift: donor.donor?.largestGift || donor.donor?.largest_gift || 0, - firstGiftDate: donor.donor?.firstGiftDate || donor.donor?.first_gift_date, - lastGiftDate: donor.donor?.lastGiftDate || donor.donor?.last_gift_date, + firstGiftDate: donor.donor?.firstGiftDate || donor.donor?.first_gift_date || null, + lastGiftDate: donor.donor?.lastGiftDate || donor.donor?.last_gift_date || null, lastGiftAmount: donor.donor?.lastGiftAmount || donor.donor?.last_gift_amount || 0, - status: donor.status, - id: donor.donorId || donor.donor_id, - name: donor.donor ? `${donor.donor.firstName || donor.donor.first_name || ''} ${donor.donor.lastName || donor.donor.last_name || ''}`.trim() || - donor.donor.organizationName || donor.donor.organization_name || 'Unnamed' : 'Unknown' + status: donor.status || 'pending', + name: donor.donor ? + `${donor.donor.firstName || donor.donor.first_name || ''} ${donor.donor.lastName || donor.donor.last_name || ''}`.trim() || + donor.donor.organizationName || donor.donor.organization_name || 'Unnamed' : + 'Unknown' })) : [], - page: responseData.page || 1, - limit: responseData.limit || 10, + page: responseData.page || params.page || 1, + limit: responseData.limit || params.limit || 10, total_count: responseData.total || 0, total_pages: responseData.pages || 1, - needsListCreation + needsListCreation, + message: responseData.message }; - } catch (fetchError) { - console.error('Failed to fetch donor list data:', fetchError); - throw fetchError; - } - } catch (error) { - console.error('Failed to get event donors:', error); - throw error; - } -}; - -/** - * Alternative function to get event donors - * This function uses an alternative endpoint to avoid known issues - */ -const getEventDonorsAlternative = async (eventId, params = {}) => { - try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log('Using alternative method to get event donors...'); - - // Directly use event donor API to get data - const url = new URL(`${API_URL}/api/events/${eventId}/donors`); - - // Add query parameters - Object.keys(params).forEach(key => { - if (params[key] !== undefined && params[key] !== '') { - url.searchParams.append(key, params[key]); - } - }); - - // Add no_sort parameter to avoid server-side sorting errors - url.searchParams.append('no_sort', 'true'); - - console.log('Using alternative endpoint to request donors:', url.toString()); - - try { - const response = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - // Handle 404 error, indicating event may have no donor list - if (response.status === 404) { - console.log('Event may have no donors, trying to create one...'); - - try { - const createResponse = await createEventDonorList(eventId); - console.log('Donor list creation successful:', createResponse); - - // Try to get donors again after list creation - const retryResponse = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!retryResponse.ok) { - throw new Error(`Failed to get donors after list creation: ${retryResponse.status}`); - } - - const donorsData = await retryResponse.json(); - - return { - data: Array.isArray(donorsData.donors) ? donorsData.donors.map(donor => ({ - ...donor, - firstName: donor.donor?.firstName || donor.donor?.first_name, - lastName: donor.donor?.lastName || donor.donor?.last_name, - nickName: donor.donor?.nickName || donor.donor?.nick_name, - organizationName: donor.donor?.organizationName || donor.donor?.organization_name, - totalDonations: donor.donor?.totalDonations || donor.donor?.total_donations || 0, - largestGift: donor.donor?.largestGift || donor.donor?.largest_gift || 0, - firstGiftDate: donor.donor?.firstGiftDate || donor.donor?.first_gift_date, - lastGiftDate: donor.donor?.lastGiftDate || donor.donor?.last_gift_date, - lastGiftAmount: donor.donor?.lastGiftAmount || donor.donor?.last_gift_amount || 0, - status: donor.status, - id: donor.donorId || donor.donor_id, - name: donor.donor ? `${donor.donor.firstName || donor.donor.first_name || ''} ${donor.donor.lastName || donor.donor.last_name || ''}`.trim() || - donor.donor.organizationName || donor.donor.organization_name || 'Unnamed' : 'Unknown' - })) : [], - page: donorsData.page || 1, - limit: donorsData.limit || 10, - total_count: donorsData.total || 0, - total_pages: donorsData.pages || 1 - }; - } catch (createError) { - console.error('Failed to create donor list:', createError); - throw new Error(`Failed to create donor list: ${createError.message}`); - } - } - - if (response.status === 500) { - // For 500 internal server error, try special handling - console.error('Server internal error, possibly sorting field problem:', url.toString()); - - // Add no_sort parameter to tell server not to sort - url.searchParams.append('no_sort', 'true'); - console.log('Trying to request without sorting:', url.toString()); - - const noSortResponse = await fetchWithAuthMiddleware(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!noSortResponse.ok) { - throw new Error(`Even without sorting cannot get donors: ${noSortResponse.status}`); - } - - const donorsData = await noSortResponse.json(); - - return { - data: Array.isArray(donorsData.donors) ? donorsData.donors.map(donor => ({ - ...donor, - firstName: donor.donor?.firstName || donor.donor?.first_name, - lastName: donor.donor?.lastName || donor.donor?.last_name, - nickName: donor.donor?.nickName || donor.donor?.nick_name, - organizationName: donor.donor?.organizationName || donor.donor?.organization_name, - totalDonations: donor.donor?.totalDonations || donor.donor?.total_donations || 0, - largestGift: donor.donor?.largestGift || donor.donor?.largest_gift || 0, - firstGiftDate: donor.donor?.firstGiftDate || donor.donor?.first_gift_date, - lastGiftDate: donor.donor?.lastGiftDate || donor.donor?.last_gift_date, - lastGiftAmount: donor.donor?.lastGiftAmount || donor.donor?.last_gift_amount || 0, - status: donor.status, - id: donor.donorId || donor.donor_id, - name: donor.donor ? `${donor.donor.firstName || donor.donor.first_name || ''} ${donor.donor.lastName || donor.donor.last_name || ''}`.trim() || - donor.donor.organizationName || donor.donor.organization_name || 'Unnamed' : 'Unknown' - })) : [], - page: donorsData.page || 1, - limit: donorsData.limit || 10, - total_count: donorsData.total || 0, - total_pages: donorsData.pages || 1 - }; - } + } catch (error) { + // Handle 404 error, indicating event may have no donor list + if (error.status === 404) { + console.warn('Event has no donor list:', error.message); - throw new Error(`Failed to get donors through alternative endpoint: ${response.status}`); + // Return empty result with flag indicating list creation is needed + return { + data: [], + page: params.page || 1, + limit: params.limit || 10, + total_count: 0, + total_pages: 0, + needsListCreation: true, + message: error.message || 'Event has no donor list' + }; } - const donorsData = await response.json(); - - // Format data to match frontend expectations - return { - data: Array.isArray(donorsData.donors) ? donorsData.donors.map(donor => ({ - ...donor, - firstName: donor.donor?.firstName || donor.donor?.first_name, - lastName: donor.donor?.lastName || donor.donor?.last_name, - nickName: donor.donor?.nickName || donor.donor?.nick_name, - organizationName: donor.donor?.organizationName || donor.donor?.organization_name, - totalDonations: donor.donor?.totalDonations || donor.donor?.total_donations || 0, - largestGift: donor.donor?.largestGift || donor.donor?.largest_gift || 0, - firstGiftDate: donor.donor?.firstGiftDate || donor.donor?.first_gift_date, - lastGiftDate: donor.donor?.lastGiftDate || donor.donor?.last_gift_date, - lastGiftAmount: donor.donor?.lastGiftAmount || donor.donor?.last_gift_amount || 0, - status: donor.status, - id: donor.donorId || donor.donor_id, - name: donor.donor ? `${donor.donor.firstName || donor.donor.first_name || ''} ${donor.donor.lastName || donor.donor.last_name || ''}`.trim() || - donor.donor.organizationName || donor.donor.organization_name || 'Unnamed' : 'Unknown' - })) : [], - page: donorsData.page || 1, - limit: donorsData.limit || 10, - total_count: donorsData.total || 0, - total_pages: donorsData.pages || 1 - }; - } catch (fetchError) { - console.error('Failed to fetch donor list data:', fetchError); - throw fetchError; + throw error; } } catch (error) { - console.error('Failed to get donors through alternative method:', error); - - // If alternative method also fails, return empty result - return { - data: [], - page: params.page || 1, - limit: params.limit || 10, - total_count: 0, - total_pages: 0, - error: true, - message: `Failed to get event donors: ${error.message || 'Unknown error'}` - }; + console.error('Error fetching event donors:', error); + throw error; } }; @@ -467,38 +189,10 @@ const getEventDonorsAlternative = async (eventId, params = {}) => { */ export const createEventDonorList = async (eventId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - console.log(`Creating donor list for event ${eventId}`); - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/${eventId}/donor-list`, { + const result = await fetchWithAuth(`/api/events/${eventId}/donor-list`, { method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify({ eventId }) }); - - if (!response.ok) { - // Try to parse error response - const errorText = await response.text().catch(() => 'Unknown error'); - let errorMessage; - try { - const errorJson = JSON.parse(errorText); - errorMessage = errorJson.message || `Failed to create donor list: ${response.status}`; - } catch (e) { - errorMessage = `Failed to create donor list: ${response.status}, response: ${errorText.slice(0, 100)}`; - } - - throw new Error(errorMessage); - } - - const result = await response.json(); - console.log('Donor list creation successful:', result); return result; } catch (error) { console.error('Failed to create donor list:', error); @@ -514,31 +208,16 @@ export const createEventDonorList = async (eventId) => { */ export const updateEvent = async (eventId, eventData) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - // Convert frontend status to backend status const modifiedEventData = { ...eventData, status: toBackendStatus(eventData.status) }; - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/${eventId}`, { + const updatedEvent = await fetchWithAuth(`/api/events/${eventId}`, { method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, body: JSON.stringify(modifiedEventData) }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const updatedEvent = await response.json(); // Convert backend status back to frontend status return { @@ -558,24 +237,9 @@ export const updateEvent = async (eventId, eventData) => { */ export const deleteEvent = async (eventId) => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/${eventId}`, { - method: 'DELETE', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } + await fetchWithAuth(`/api/events/${eventId}`, { + method: 'DELETE' }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - return; } catch (error) { console.error('Error deleting event:', error); throw error; @@ -588,24 +252,7 @@ export const deleteEvent = async (eventId) => { */ export const getEventTypes = async () => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/types`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); + const data = await fetchWithAuth(`/api/events/types`); return data.types || []; } catch (error) { console.error('Error fetching event types:', error); @@ -619,24 +266,7 @@ export const getEventTypes = async () => { */ export const getEventLocations = async () => { try { - const token = localStorage.getItem('token'); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetchWithAuthMiddleware(`${API_URL}/api/events/locations`, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - } - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); + const data = await fetchWithAuth(`/api/events/locations`); return data.locations || []; } catch (error) { console.error('Error fetching event locations:', error); From 85893526f4378c05e9c3a380f86ed9936396ca1b Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 22 Apr 2025 00:51:16 -0700 Subject: [PATCH 3/3] do not add cd and docker in pr --- .github/workflows/cd.yml | 2 -- .github/workflows/docker-build.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f25ab344..0e8a0f3d 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -3,8 +3,6 @@ name: CD on: push: branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: jobs: diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index a93cf9bd..7779f97b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -3,8 +3,6 @@ name: Build and Publish Docker Image on: push: branches: [ main ] - pull_request: - branches: [ main ] env: REPO_OWNER: ${{ github.repository_owner }}