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
146 changes: 146 additions & 0 deletions scripts/test-google-sheets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Test Google Sheets Configuration
* Checks if Google Sheets API is working and shows available sheets
*/

import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.join(process.cwd(), '.env.local') });

import { google } from 'googleapis';

async function testGoogleSheets() {
console.log('🧪 Testing Google Sheets Configuration\n');
console.log('='.repeat(70));

// Check environment variables
const requiredVars = [
'GOOGLE_SERVICE_ACCOUNT_EMAIL',
'GOOGLE_PRIVATE_KEY',
'GOOGLE_SHEET_ID',
'GOOGLE_SHEET_NAME'
];

let allVarsPresent = true;
for (const varName of requiredVars) {
const value = process.env[varName];
if (!value) {
console.log(`❌ ${varName}: NOT SET`);
allVarsPresent = false;
} else {
console.log(`✅ ${varName}: ${value.substring(0, 30)}...`);
}
}

if (!allVarsPresent) {
console.log('\n❌ Missing required environment variables');
return;
}

console.log('\n' + '='.repeat(70));
console.log('Testing Google Sheets API connection...\n');

try {
// Initialize Google Sheets API
const auth = new google.auth.GoogleAuth({
credentials: {
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
private_key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
},
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});

const sheets = google.sheets({ version: 'v4', auth });
const spreadsheetId = process.env.GOOGLE_SHEET_ID;

// Get spreadsheet info
console.log('📊 Fetching spreadsheet information...');
const spreadsheet = await sheets.spreadsheets.get({
spreadsheetId: spreadsheetId!,
});

console.log(`✅ Spreadsheet found: ${spreadsheet.data.properties?.title}`);
console.log('');

// List all sheets
console.log('📋 Available sheet tabs:');
const availableSheets = spreadsheet.data.sheets || [];

if (availableSheets.length === 0) {
console.log(' ⚠️ No sheets found in spreadsheet');
} else {
availableSheets.forEach((sheet, index) => {
const title = sheet.properties?.title || 'Untitled';
const sheetId = sheet.properties?.sheetId;
const isTargetSheet = title === process.env.GOOGLE_SHEET_NAME;

console.log(` ${index + 1}. ${isTargetSheet ? '✅' : ' '} "${title}" (ID: ${sheetId})`);
});
}

// Check if target sheet exists
const targetSheetName = process.env.GOOGLE_SHEET_NAME;
const targetSheet = availableSheets.find(
(s) => s.properties?.title === targetSheetName
);

console.log('');
console.log('='.repeat(70));
console.log(`\n🎯 Target sheet: "${targetSheetName}"`);

if (targetSheet) {
console.log(`✅ Target sheet exists!`);
console.log(` Sheet ID: ${targetSheet.properties?.sheetId}`);

// Try to read data
const sheetRange = targetSheetName?.includes(' ') || targetSheetName?.includes('-')
? `'${targetSheetName}'!A1:I10`
: `${targetSheetName}!A1:I10`;

console.log(`\n📖 Reading sample data from ${sheetRange}...`);

try {
const data = await sheets.spreadsheets.values.get({
spreadsheetId: spreadsheetId!,
range: sheetRange,
});

const rows = data.data.values || [];
console.log(`✅ Successfully read ${rows.length} rows`);

if (rows.length > 0) {
console.log('\n📄 First row (headers):');
console.log(' ', rows[0].join(' | '));
}
} catch (error) {
const err = error as { message?: string };
console.log(`❌ Error reading data: ${err.message}`);
}
} else {
console.log(`❌ Target sheet does NOT exist!`);
console.log(`\n💡 Solution:`);
console.log(` Option 1: Rename one of the sheets to "${targetSheetName}"`);
console.log(` Option 2: Update GOOGLE_SHEET_NAME in .env.local to match an existing sheet`);
}

console.log('\n' + '='.repeat(70));
console.log('✅ Google Sheets test complete!');

} catch (error) {
const err = error as { message?: string; code?: number };
console.log('\n❌ Error testing Google Sheets:');
console.log(` ${err.message}`);

if (err.code === 403) {
console.log('\n💡 Permission Error:');
console.log(' The service account does not have access to the spreadsheet.');
console.log(' Share the spreadsheet with: ' + process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL);
} else if (err.code === 404) {
console.log('\n💡 Spreadsheet Not Found:');
console.log(' Check if GOOGLE_SHEET_ID is correct in .env.local');
}
}
}

testGoogleSheets();
11 changes: 10 additions & 1 deletion src/app/admin/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,17 @@ export default function AdminDashboard() {
});

if (res.ok) {
const data = await res.json();
setRegistrations(registrations.filter(r => r._id !== id));
toast.success('Registration deleted successfully!', { id: loadingToast });

if (data.sheetsDeleted) {
toast.success('Registration deleted from database and Google Sheets!', { id: loadingToast, duration: 4000 });
} else {
toast.error(
`Deleted from database, but Google Sheets failed: ${data.sheetsError || 'Unknown error'}`,
{ id: loadingToast, duration: 8000 }
);
}
} else {
toast.error('Failed to delete registration', { id: loadingToast });
}
Expand Down
25 changes: 19 additions & 6 deletions src/app/api/admin/registrations/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,32 @@ export async function DELETE(req: NextRequest) {
await Registration.findByIdAndDelete(id);

// Delete from Google Sheets
let sheetsDeleted = false;
let sheetsError = null;

try {
if (registration.teamName) {
await deleteFromGoogleSheets(registration.teamName);
console.log('✅ Deleted from both database and Google Sheets');
const result = await deleteFromGoogleSheets(registration.teamName);
if (result.success) {
sheetsDeleted = true;
console.log('✅ Deleted from both database and Google Sheets');
} else {
sheetsError = result.error;
console.error('⚠️ Failed to delete from Google Sheets:', result.error);
}
}
} catch (sheetsError) {
console.error('⚠️ Failed to delete from Google Sheets:', sheetsError);
// Continue even if sheets deletion fails
} catch (error) {
sheetsError = error instanceof Error ? error.message : 'Unknown error';
console.error('⚠️ Failed to delete from Google Sheets:', error);
}

return NextResponse.json({
success: true,
message: 'Registration deleted successfully from database and sheets',
message: sheetsDeleted
? 'Registration deleted successfully from database and Google Sheets'
: 'Registration deleted from database only. Google Sheets error: ' + (sheetsError || 'Unknown'),
sheetsDeleted,
sheetsError: sheetsDeleted ? null : sheetsError,
});
} catch (error) {
console.error('Error deleting registration:', error);
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
if (!globalRateLimiter.check(sessionId)) {
console.warn("⚠️ Rate limit exceeded for session:", sessionId);
return NextResponse.json({
reply: "⚠️ Too many requests. Please wait a moment before trying again."
reply: "Please slow down! You're sending messages too quickly. Wait a moment and try again. ⏱️"
}, { status: 429 });
}

Expand Down Expand Up @@ -889,7 +889,7 @@
// Check if this is a question instead of batch selection
const lowerMsg = trimmedMessage.toLowerCase();
const questionWords = ['what', 'when', 'where', 'how', 'why', 'can', 'is', 'are', 'do', 'does', 'will', 'should', 'which', 'who', 'tell', 'give', 'show', 'explain'];
const isLikelyQuestion = questionWords.some(word => lowerMsg.includes(word)) || lowerMsg.includes('?');

Check warning on line 892 in src/app/api/chat/route.ts

View workflow job for this annotation

GitHub Actions / Build-Test

'isLikelyQuestion' is assigned a value but never used

Check warning on line 892 in src/app/api/chat/route.ts

View workflow job for this annotation

GitHub Actions / Build-Test

'isLikelyQuestion' is assigned a value but never used

// If it looks like a question, let it go through the question handler above
// The question handler should have already caught it, but if we're here,
Expand Down
20 changes: 15 additions & 5 deletions src/components/ChatBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,11 +406,6 @@ export default function ChatBot() {
body: JSON.stringify({ sessionId, message }),
});

// Check if the response is ok
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}

// Check if response has content before parsing JSON
const text = await res.text();
if (!text) {
Expand All @@ -426,6 +421,21 @@ export default function ChatBot() {
throw new Error("Invalid JSON response from server");
}

// Handle rate limit error (429) with a nice toast
if (res.status === 429) {
showToast('Please slow down! Wait a moment before sending another message.', 'warning');
setIsTyping(false);
return;
}

// Check if the response is ok (after parsing to get error message)
if (!res.ok) {
const errorMessage = data.reply || `HTTP error! status: ${res.status}`;
showToast(errorMessage, 'error');
setIsTyping(false);
return;
}

if (data.reply) {
const botMessage: Message = {
role: "bot",
Expand Down
5 changes: 3 additions & 2 deletions src/lib/concurrencyHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,6 @@ export class RateLimiter {
}
}

// Global rate limiter instance (10 requests per minute per session)
export const globalRateLimiter = new RateLimiter(10, 60000);
// Global rate limiter instance (30 requests per minute per session)
// Registration flow needs ~15-20 requests (4 members × 3 fields + questions + confirmations)
export const globalRateLimiter = new RateLimiter(30, 60000);
31 changes: 28 additions & 3 deletions src/lib/googleSheets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ const auth = new google.auth.GoogleAuth({
const sheets = google.sheets({ version: 'v4', auth });

const SPREADSHEET_ID = process.env.GOOGLE_SHEET_ID;
const SHEET_NAME = process.env.GOOGLE_SHEET_NAME || 'Registration'; // Sheet name (can have spaces)
const SHEET_NAME = (process.env.GOOGLE_SHEET_NAME || 'Registration').trim(); // Sheet name (can have spaces)

// Debug log on module load
console.log('📋 Google Sheets Config:');
console.log(` SHEET_NAME: "${SHEET_NAME}" (length: ${SHEET_NAME.length})`);
console.log(` SPREADSHEET_ID: ${SPREADSHEET_ID}`);

export interface RegistrationData {
teamName: string;
Expand Down Expand Up @@ -253,18 +258,38 @@ export async function deleteFromGoogleSheets(teamName: string) {
}

// Get sheet ID for batch delete
console.log(`🔍 Getting spreadsheet metadata for deletion...`);
console.log(` Spreadsheet ID: ${SPREADSHEET_ID}`);
console.log(` Looking for sheet: "${SHEET_NAME}"`);

const spreadsheet = await sheets.spreadsheets.get({
spreadsheetId: SPREADSHEET_ID,
});

console.log(` Found ${spreadsheet.data.sheets?.length || 0} sheets in spreadsheet`);

// Debug: Show what we're comparing
spreadsheet.data.sheets?.forEach((s) => {
const title = s.properties?.title;
console.log(` - Sheet: "${title}" (length: ${title?.length}, match: ${title === SHEET_NAME})`);
});
console.log(` Target: "${SHEET_NAME}" (length: ${SHEET_NAME?.length})`);

const sheet = spreadsheet.data.sheets?.find(
(s) => s.properties?.title === SHEET_NAME
);

if (!sheet || !sheet.properties?.sheetId) {
return { success: false, error: 'Sheet not found' };
if (!sheet || sheet.properties?.sheetId === undefined || sheet.properties?.sheetId === null) {
const availableSheets = spreadsheet.data.sheets?.map(s => s.properties?.title).join(', ') || 'none';
console.log(`❌ Sheet "${SHEET_NAME}" not found in: ${availableSheets}`);
return {
success: false,
error: `Sheet "${SHEET_NAME}" not found. Available sheets: ${availableSheets}`
};
}

console.log(`✅ Found sheet "${SHEET_NAME}" with ID: ${sheet.properties.sheetId}`);

// Delete 3 rows (team takes 3 rows)
await sheets.spreadsheets.batchUpdate({
spreadsheetId: SPREADSHEET_ID,
Expand Down
Loading