Skip to content

Commit 8482939

Browse files
committed
yay security bro
1 parent 2515b4c commit 8482939

File tree

3 files changed

+310
-91
lines changed

3 files changed

+310
-91
lines changed

public/member/appscriptPOST.gs

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,90 @@ function appendDataToSheet(data, sheetName) {
130130
}
131131

132132
/**
133-
* Handle GET requests - can be used for testing
133+
* Handle GET requests - provides filtered PublishedPayments data as CSV when given an EID
134+
* @param {Object} e - The event object containing query parameters
134135
*/
135-
function doGet() {
136-
return ContentService.createTextOutput(
137-
JSON.stringify({
138-
'status': 'success',
139-
'message': 'The API is running. Please use POST to submit data.'
140-
})
141-
).setMimeType(ContentService.MimeType.JSON);
136+
function doGet(e) {
137+
try {
138+
// Check if EID query parameter is provided
139+
if (!e || !e.parameter || !e.parameter.eid) {
140+
return ContentService.createTextOutput(
141+
JSON.stringify({
142+
'status': 'error',
143+
'message': 'Missing required query parameter: eid'
144+
})
145+
).setMimeType(ContentService.MimeType.JSON);
146+
}
147+
148+
const eid = e.parameter.eid.toLowerCase().trim();
149+
150+
// Get the PublishedPayments sheet
151+
const ss = SpreadsheetApp.getActiveSpreadsheet();
152+
const sheet = ss.getSheetByName('PublishedPayments');
153+
154+
if (!sheet) {
155+
return ContentService.createTextOutput(
156+
JSON.stringify({
157+
'status': 'error',
158+
'message': 'PublishedPayments sheet not found'
159+
})
160+
).setMimeType(ContentService.MimeType.JSON);
161+
}
162+
163+
// Get all data from the sheet
164+
const data = sheet.getDataRange().getValues();
165+
166+
// Filter rows that contain the given EID in any cell
167+
const filteredRows = data.filter((row, index) => {
168+
if (index === 0) return true; // Keep headers
169+
170+
// Check if any cell in the row contains the EID
171+
return row.some(cell => {
172+
if (cell === null || cell === undefined) return false;
173+
return cell.toString().toLowerCase().trim().includes(eid);
174+
});
175+
});
176+
177+
// Convert filtered data to CSV
178+
if (filteredRows.length <= 1) { // Only headers or nothing
179+
return ContentService.createTextOutput(
180+
JSON.stringify({
181+
'status': 'success',
182+
'message': 'No records found for the provided EID',
183+
'eid': eid
184+
})
185+
).setMimeType(ContentService.MimeType.JSON);
186+
}
187+
188+
// Convert to CSV
189+
const csv = filteredRows.map(row =>
190+
row.map(cell => {
191+
// Handle special characters and ensure proper CSV formatting
192+
if (cell === null || cell === undefined) return '';
193+
194+
const cellStr = cell.toString();
195+
// If cell contains comma, quote, or newline, wrap in quotes and escape quotes
196+
if (cellStr.includes(',') || cellStr.includes('"') || cellStr.includes('\n')) {
197+
return '"' + cellStr.replace(/"/g, '""') + '"';
198+
}
199+
return cellStr;
200+
}).join(',')
201+
).join('\n');
202+
203+
// Return as CSV
204+
return ContentService.createTextOutput(csv)
205+
.setMimeType(ContentService.MimeType.CSV)
206+
.downloadAsFile(`payments_${eid}.csv`);
207+
208+
} catch (error) {
209+
// Return error response
210+
return ContentService.createTextOutput(
211+
JSON.stringify({
212+
'status': 'error',
213+
'message': `Error processing request: ${error.message}`
214+
})
215+
).setMimeType(ContentService.MimeType.JSON);
216+
}
142217
}
143218

144219
/**

public/member/eidStats.js

Lines changed: 205 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,37 @@
33
* Functions for handling EID lookups and membership data
44
*/
55

6-
import { MEMBERSHIP_CSV_URL } from './constants.js';
6+
import { MEMBERSHIP_CSV_URL, GOOGLE_APPS_SCRIPT_URL } from './constants.js';
77

88
// Store membership data globally
99
let membershipRows = null;
1010
// Store purchases by EID
1111
let purchasesByEid = new Map();
12+
// Store the current EID
13+
let currentEid = null;
14+
15+
// Initialize module - auto-load saved EID on script load
16+
initializeModule();
1217

1318
/**
14-
* Function to load membership data once
19+
* Function to load membership data from Apps Script
20+
* @param {string} eid - EID to fetch data for
1521
* @returns {Promise<boolean>} - Promise resolving to true if data was loaded successfully
1622
*/
17-
export async function loadMembershipData() {
23+
export async function loadMembershipData(eid) {
1824
try {
19-
const response = await fetch(MEMBERSHIP_CSV_URL);
20-
if (!response.ok) {
21-
throw new Error('Failed to fetch membership data');
22-
}
23-
24-
const csvText = await response.text();
25-
membershipRows = parseCSV(csvText);
26-
27-
// Parse purchase information from column 3 (index 2)
28-
parsePurchaseData(membershipRows);
25+
const result = await fetchDataFromAppsScript(GOOGLE_APPS_SCRIPT_URL, eid);
2926

30-
return true;
27+
if (result.success && result.format === 'csv') {
28+
membershipRows = result.data;
29+
30+
// Parse purchase information if it exists in the data
31+
if (membershipRows.length > 0) {
32+
parsePurchaseData(membershipRows);
33+
}
34+
return true;
35+
}
36+
return false;
3137
} catch (error) {
3238
console.error('Error loading membership data:', error);
3339
return false;
@@ -95,53 +101,41 @@ export async function fetchMemberInfo(eid) {
95101
return false;
96102
}
97103

98-
// Display loading status
104+
// Get status element
99105
const statusElement = document.getElementById('eidStatus');
100-
statusElement.textContent = 'Checking membership...';
101-
statusElement.className = 'status-message loading';
102-
103-
// Normalize the EID for comparison (to lowercase)
104-
const normalizedEid = eid.trim().toLowerCase();
106+
if (statusElement) {
107+
statusElement.textContent = 'Checking membership...';
108+
statusElement.className = 'status-message loading';
109+
}
105110

106111
try {
107-
// Make sure membership data is loaded
108-
if (!membershipRows) {
109-
statusElement.textContent = 'Loading membership data...';
110-
const dataLoaded = await loadMembershipData();
111-
if (!dataLoaded) {
112-
throw new Error('Failed to load membership data');
113-
}
112+
// Load membership data directly using shared fetchDataFromAppsScript function
113+
const dataLoaded = await loadMembershipData(eid);
114+
if (!dataLoaded) {
115+
throw new Error('Failed to load membership data');
114116
}
115117

116-
// Use the already loaded membership data
117-
const rows = membershipRows;
118-
119-
// Check if the EID exists in the first column
120-
// We assume the first row contains headers and the first column contains EIDs
121-
let memberExists = false;
118+
// Check if any data was returned
119+
const memberExists = membershipRows && membershipRows.length > 1; // More than just headers
122120

123-
// Skip the header row (index 0) and check remaining rows
124-
for (let i = 1; i < rows.length; i++) {
125-
if (rows[i].length > 0 && rows[i][0].toLowerCase() === normalizedEid) {
126-
memberExists = true;
127-
break;
121+
// Update the status message if element exists
122+
if (statusElement) {
123+
if (memberExists) {
124+
statusElement.textContent = 'Welcome back! Your membership is active.';
125+
statusElement.className = 'status-message success';
126+
} else {
127+
statusElement.textContent = 'No membership found. Please fill out the form below.';
128+
statusElement.className = 'status-message warning';
128129
}
129130
}
130131

131-
// Update the status message
132-
if (memberExists) {
133-
statusElement.textContent = 'Welcome back! Your membership is active.';
134-
statusElement.className = 'status-message success';
135-
} else {
136-
statusElement.textContent = 'No membership found. Please fill out the form below.';
137-
statusElement.className = 'status-message warning';
138-
}
139-
140132
return memberExists;
141133
} catch (error) {
142134
console.error('Error fetching member info:', error);
143-
statusElement.textContent = 'Error checking membership. Please try again.';
144-
statusElement.className = 'status-message error';
135+
if (statusElement) {
136+
statusElement.textContent = 'Error checking membership. Please try again.';
137+
statusElement.className = 'status-message error';
138+
}
145139
return false;
146140
}
147141
}
@@ -198,4 +192,166 @@ export function getPurchasesByEid(eid) {
198192
*/
199193
export function isMembershipDataLoaded() {
200194
return membershipRows !== null;
195+
}
196+
197+
/**
198+
* Get the current EID that was loaded
199+
* @returns {string|null} - Current EID or null if none
200+
*/
201+
export function getCurrentEid() {
202+
return currentEid;
203+
}
204+
205+
/**
206+
* Save EID to localStorage for future use
207+
* @param {string} eid - EID to save
208+
* @param {boolean} memberExists - Whether the member exists
209+
*/
210+
export function saveEidToStorage(eid, memberExists) {
211+
if (!eid) return;
212+
213+
try {
214+
localStorage.setItem('savedEid', eid.trim().toLowerCase());
215+
localStorage.setItem('savedEidMemberExists', memberExists ? '1' : '0');
216+
// Update current EID
217+
currentEid = eid.trim().toLowerCase();
218+
} catch (e) {
219+
console.warn('Unable to save EID to localStorage', e);
220+
}
221+
}
222+
223+
/**
224+
* Clear saved EID from localStorage
225+
*/
226+
export function clearSavedEid() {
227+
try {
228+
localStorage.removeItem('savedEid');
229+
localStorage.removeItem('savedEidMemberExists');
230+
currentEid = null;
231+
} catch (e) {
232+
console.warn('Unable to clear localStorage for EID', e);
233+
}
234+
}
235+
236+
/**
237+
* Initialize the module and auto-load saved EID if available
238+
*/
239+
function initializeModule() {
240+
// Run this once when the script is loaded
241+
try {
242+
const savedEid = localStorage.getItem('savedEid');
243+
if (savedEid && savedEid.trim() !== '') {
244+
currentEid = savedEid.trim().toLowerCase();
245+
console.log(`Found saved EID: ${currentEid}, will auto-load data`);
246+
247+
// Use setTimeout to ensure this runs after the DOM is ready
248+
setTimeout(() => {
249+
loadMembershipData(currentEid).then(success => {
250+
if (success) {
251+
console.log(`Auto-loaded membership data for ${currentEid}`);
252+
// Dispatch a custom event that other scripts can listen for
253+
document.dispatchEvent(new CustomEvent('eidDataLoaded', {
254+
detail: {
255+
eid: currentEid,
256+
success: true
257+
}
258+
}));
259+
}
260+
});
261+
}, 0);
262+
}
263+
} catch (e) {
264+
console.warn('Unable to auto-load EID from localStorage', e);
265+
}
266+
}
267+
268+
/**
269+
* Fetch data from arbitrary Apps Script URL with EID parameter
270+
* @param {string} url - The Apps Script URL to fetch from
271+
* @param {string} eid - The EID to fetch data for
272+
* @param {string} [statusElementId='dataStatus'] - ID of status element to update
273+
* @returns {Promise<Object>} - Promise resolving to the response data
274+
*/
275+
export async function fetchDataFromAppsScript(url, eid, statusElementId = 'dataStatus') {
276+
if (!url || !eid) {
277+
throw new Error('URL and EID are required');
278+
}
279+
280+
// Get status element if ID provided
281+
const statusElement = statusElementId ? document.getElementById(statusElementId) : null;
282+
283+
// Update status if element exists
284+
if (statusElement) {
285+
statusElement.textContent = 'Loading data...';
286+
statusElement.className = 'status-message loading';
287+
}
288+
289+
try {
290+
// Construct URL with EID parameter
291+
const fullUrl = new URL(url);
292+
fullUrl.searchParams.set('eid', eid.trim().toLowerCase());
293+
294+
console.log(`Fetching data from: ${fullUrl.toString()}`);
295+
296+
// Make the fetch request
297+
const response = await fetch(fullUrl.toString());
298+
if (!response.ok) {
299+
throw new Error('Failed to fetch data');
300+
}
301+
302+
// Check if response is CSV (based on Content-Type header)
303+
const contentType = response.headers.get('Content-Type') || '';
304+
305+
if (contentType.includes('text/csv')) {
306+
// Handle CSV response
307+
const csvText = await response.text();
308+
const parsedData = parseCSV(csvText);
309+
310+
// Update status if element exists
311+
if (statusElement) {
312+
statusElement.textContent = 'Data loaded successfully!';
313+
statusElement.className = 'status-message success';
314+
}
315+
316+
return {
317+
success: true,
318+
format: 'csv',
319+
data: parsedData
320+
};
321+
} else {
322+
// Handle JSON response
323+
const jsonData = await response.json();
324+
325+
// Update status based on response
326+
if (statusElement) {
327+
if (jsonData.status === 'success') {
328+
statusElement.textContent = jsonData.message || 'Data loaded successfully!';
329+
statusElement.className = 'status-message success';
330+
} else {
331+
statusElement.textContent = jsonData.message || 'No data found.';
332+
statusElement.className = 'status-message warning';
333+
}
334+
}
335+
336+
return {
337+
success: jsonData.status === 'success',
338+
format: 'json',
339+
data: jsonData
340+
};
341+
}
342+
} catch (error) {
343+
console.error('Error fetching data from Apps Script:', error);
344+
345+
// Update status element if it exists
346+
if (statusElement) {
347+
statusElement.textContent = 'Error loading data. Please try again.';
348+
statusElement.className = 'status-message error';
349+
}
350+
351+
return {
352+
success: false,
353+
format: 'error',
354+
error: error.message
355+
};
356+
}
201357
}

0 commit comments

Comments
 (0)