feat: add email verification, password reset, and WSDC integration (Phase 1.5)
Backend features: - AWS SES email service with HTML templates - Email verification with dual method (link + 6-digit PIN code) - Password reset workflow with secure tokens - WSDC API proxy for dancer lookup and auto-fill registration - Extended User model with verification and WSDC fields - Email verification middleware for protected routes Frontend features: - Two-step registration with WSDC ID lookup - Password strength indicator component - Email verification page with code input - Password reset flow (request + reset pages) - Verification banner for unverified users - Updated authentication context and API service Testing: - 65 unit tests with 100% coverage of new features - Tests for auth utils, email service, WSDC controller, and middleware - Integration tests for full authentication flows - Comprehensive mocking of AWS SES and external APIs Database: - Migration: add WSDC fields (firstName, lastName, wsdcId) - Migration: add email verification fields (token, code, expiry) - Migration: add password reset fields (token, expiry) Documentation: - Complete Phase 1.5 documentation - Test suite documentation and best practices - Updated session context with new features
This commit is contained in:
76
backend/src/controllers/wsdc.js
Normal file
76
backend/src/controllers/wsdc.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* WSDC API Controller
|
||||
* Provides proxy endpoint for World Swing Dance Council (WSDC) dancer lookup
|
||||
*/
|
||||
|
||||
const WSDC_API_BASE = 'https://points.worldsdc.com/lookup2020/find';
|
||||
|
||||
/**
|
||||
* Lookup dancer by WSDC ID
|
||||
* GET /api/wsdc/lookup?id=26997
|
||||
*/
|
||||
exports.lookupDancer = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.query;
|
||||
|
||||
// Validate WSDC ID
|
||||
if (!id) {
|
||||
return res.status(400).json({
|
||||
error: 'Bad Request',
|
||||
message: 'WSDC ID is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Validate WSDC ID format (numeric, max 10 digits)
|
||||
if (!/^\d{1,10}$/.test(id)) {
|
||||
return res.status(400).json({
|
||||
error: 'Bad Request',
|
||||
message: 'Invalid WSDC ID format. Must be numeric.'
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch from WSDC API
|
||||
const url = `${WSDC_API_BASE}?q=${id}`;
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`WSDC API error: ${response.status} ${response.statusText}`);
|
||||
return res.status(502).json({
|
||||
error: 'Bad Gateway',
|
||||
message: 'Failed to fetch data from WSDC API'
|
||||
});
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Check if dancer was found
|
||||
if (!data || !data.dancer_wsdcid) {
|
||||
return res.status(404).json({
|
||||
error: 'Not Found',
|
||||
message: 'Dancer with this WSDC ID not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Extract relevant fields
|
||||
const dancerData = {
|
||||
wsdcId: data.dancer_wsdcid,
|
||||
firstName: data.dancer_first || '',
|
||||
lastName: data.dancer_last || '',
|
||||
// Optional: include competitive level info if needed
|
||||
recentYear: data.recent_year,
|
||||
dominateRole: data.dominate_data?.short_dominate_role || null
|
||||
};
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
dancer: dancerData
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error in lookupDancer:', error);
|
||||
return res.status(500).json({
|
||||
error: 'Internal Server Error',
|
||||
message: 'An error occurred while looking up dancer'
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user