📋 What Was Accomplished Backend Changes: - ✅ Enhanced API Endpoint: Updated GET /api/v1/events to accept optional date query parameter - ✅ Input Validation: Added YYYY-MM-DD format validation to PaginationQueryDto - ✅ Database Filtering: Implemented timezone-aware date filtering in EventsService - ✅ Controller Integration: Updated EventsController to pass date parameter to service Frontend Changes: - ✅ Date Picker Component: Created reusable DatePicker component following project design system - ✅ Gallery UI Enhancement: Integrated date picker into gallery page with clear labeling - ✅ State Management: Implemented reactive date state with automatic re-fetching - ✅ Clear Filter Functionality: Added "Clear Filter" button for easy reset - ✅ Enhanced UX: Improved empty states for filtered vs unfiltered views 🔍 Technical Implementation API Design: GET /api/v1/events?date=2025-08-02&limit=20&cursor=xxx Key Files Modified: - meteor-web-backend/src/events/dto/pagination-query.dto.ts - meteor-web-backend/src/events/events.service.ts - meteor-web-backend/src/events/events.controller.ts - meteor-frontend/src/components/ui/date-picker.tsx (new) - meteor-frontend/src/app/gallery/page.tsx - meteor-frontend/src/hooks/use-events.ts - meteor-frontend/src/services/events.ts ✅ All Acceptance Criteria Met 1. ✅ Backend API Enhancement: Accepts optional date parameter 2. ✅ Date Filtering Logic: Returns events for specific calendar date 3. ✅ Date Picker UI: Clean, accessible interface component 4. ✅ Automatic Re-fetching: Immediate data updates on date selection 5. ✅ Filtered Display: Correctly shows only events for selected date 6. ✅ Clear Filter: One-click reset to view all events 🧪 Quality Assurance - ✅ Backend Build: Successful compilation with no errors - ✅ Frontend Build: Successful Next.js build with no warnings - ✅ Linting: All ESLint checks pass - ✅ Functionality: Feature working as specified 🎉 Epic 2 Complete! With Story 2.9 completion, Epic 2: Commercialization & Core User Experience is now DONE! Epic 2 Achievements: - 🔐 Full-stack device status monitoring - 💳 Robust payment and subscription system - 🛡️ Subscription-based access control - 📊 Enhanced data browsing with detail pages - 📅 Date-based event filtering
206 lines
6.4 KiB
JavaScript
206 lines
6.4 KiB
JavaScript
const FormData = require('form-data');
|
||
const fs = require('fs');
|
||
const fetch = require('node-fetch');
|
||
|
||
/**
|
||
* Test script for the Event Upload API
|
||
* This demonstrates how to upload events from edge devices
|
||
*/
|
||
|
||
const API_BASE_URL = 'http://localhost:3000';
|
||
|
||
async function testEventUpload() {
|
||
console.log('🧪 Testing Event Upload API');
|
||
console.log('============================');
|
||
|
||
try {
|
||
// Step 1: Register a test user
|
||
console.log('1️⃣ Registering test user...');
|
||
const registerResponse = await fetch(`${API_BASE_URL}/api/v1/auth/register-email`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
email: 'eventtest@example.com',
|
||
password: 'TestPassword123',
|
||
displayName: 'Event Test User',
|
||
}),
|
||
});
|
||
|
||
if (!registerResponse.ok) {
|
||
const error = await registerResponse.json();
|
||
if (error.message?.includes('already exists')) {
|
||
console.log(' User already exists, continuing...');
|
||
} else {
|
||
throw new Error(`Registration failed: ${error.message}`);
|
||
}
|
||
} else {
|
||
console.log(' ✅ User registered successfully');
|
||
}
|
||
|
||
// Step 2: Login to get JWT token
|
||
console.log('2️⃣ Logging in to get JWT token...');
|
||
const loginResponse = await fetch(`${API_BASE_URL}/api/v1/auth/login-email`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
email: 'eventtest@example.com',
|
||
password: 'TestPassword123',
|
||
}),
|
||
});
|
||
|
||
if (!loginResponse.ok) {
|
||
const error = await loginResponse.json();
|
||
throw new Error(`Login failed: ${error.message}`);
|
||
}
|
||
|
||
const loginData = await loginResponse.json();
|
||
const jwtToken = loginData.accessToken;
|
||
console.log(' ✅ Login successful, got JWT token');
|
||
|
||
// Step 3: Register a device (if not already registered)
|
||
console.log('3️⃣ Registering test device...');
|
||
|
||
// First add the device to inventory
|
||
const addToInventoryResponse = await fetch(`${API_BASE_URL}/api/v1/devices/inventory`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${jwtToken}`,
|
||
},
|
||
body: JSON.stringify({
|
||
hardwareId: 'EVENT_TEST_DEVICE_001',
|
||
deviceModel: 'Test Camera v1.0',
|
||
}),
|
||
});
|
||
|
||
// Register the device
|
||
const deviceResponse = await fetch(`${API_BASE_URL}/api/v1/devices/register`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${jwtToken}`,
|
||
},
|
||
body: JSON.stringify({
|
||
hardwareId: 'EVENT_TEST_DEVICE_001',
|
||
}),
|
||
});
|
||
|
||
let deviceId;
|
||
if (deviceResponse.ok) {
|
||
const deviceData = await deviceResponse.json();
|
||
deviceId = deviceData.device.id;
|
||
console.log(` ✅ Device registered: ${deviceId}`);
|
||
} else {
|
||
const error = await deviceResponse.json();
|
||
if (error.message?.includes('already registered')) {
|
||
console.log(' Device already registered, fetching device list...');
|
||
|
||
// Get user's devices
|
||
const devicesResponse = await fetch(`${API_BASE_URL}/api/v1/devices`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${jwtToken}`,
|
||
},
|
||
});
|
||
|
||
if (devicesResponse.ok) {
|
||
const devicesData = await devicesResponse.json();
|
||
if (devicesData.devices.length > 0) {
|
||
deviceId = devicesData.devices[0].id;
|
||
console.log(` ✅ Using existing device: ${deviceId}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!deviceId) {
|
||
throw new Error(`Device registration failed: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
// Step 4: Create a test file
|
||
console.log('4️⃣ Creating test image file...');
|
||
const testImageData = Buffer.from('fake-image-data-for-testing');
|
||
|
||
// Step 5: Upload an event
|
||
console.log('5️⃣ Uploading event...');
|
||
|
||
const form = new FormData();
|
||
form.append('file', testImageData, {
|
||
filename: 'motion_capture.jpg',
|
||
contentType: 'image/jpeg',
|
||
});
|
||
|
||
const eventData = {
|
||
eventType: 'motion',
|
||
eventTimestamp: new Date().toISOString(),
|
||
metadata: {
|
||
deviceId: deviceId,
|
||
location: 'front_door',
|
||
confidence: 0.95,
|
||
temperature: 22.5,
|
||
},
|
||
};
|
||
|
||
form.append('eventData', JSON.stringify(eventData));
|
||
|
||
const uploadResponse = await fetch(`${API_BASE_URL}/api/v1/events/upload`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Authorization': `Bearer ${jwtToken}`,
|
||
},
|
||
body: form,
|
||
});
|
||
|
||
if (!uploadResponse.ok) {
|
||
const error = await uploadResponse.json();
|
||
throw new Error(`Event upload failed: ${error.message}`);
|
||
}
|
||
|
||
const uploadResult = await uploadResponse.json();
|
||
console.log(' ✅ Event uploaded successfully!');
|
||
console.log(` Event ID: ${uploadResult.rawEventId}`);
|
||
console.log(` Message: ${uploadResult.message}`);
|
||
|
||
// Step 6: Verify the event was saved
|
||
console.log('6️⃣ Verifying event was saved...');
|
||
const eventsResponse = await fetch(`${API_BASE_URL}/api/v1/events/user`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${jwtToken}`,
|
||
},
|
||
});
|
||
|
||
if (eventsResponse.ok) {
|
||
const eventsData = await eventsResponse.json();
|
||
console.log(` ✅ Found ${eventsData.events.length} events for user`);
|
||
|
||
if (eventsData.events.length > 0) {
|
||
const latestEvent = eventsData.events[0];
|
||
console.log(` Latest event: ${latestEvent.eventType} at ${latestEvent.eventTimestamp}`);
|
||
}
|
||
}
|
||
|
||
console.log('\n🎉 Event Upload API Test Completed Successfully!');
|
||
console.log('================================================');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Test failed:', error.message);
|
||
console.log('\n💡 Make sure:');
|
||
console.log(' - Backend server is running (npm run start:dev)');
|
||
console.log(' - Database is accessible and migrated');
|
||
console.log(' - AWS credentials are configured (for actual S3/SQS operations)');
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Handle missing node-fetch gracefully
|
||
if (typeof fetch === 'undefined') {
|
||
console.log('📦 Installing required dependencies...');
|
||
console.log('Run: npm install node-fetch@2 form-data');
|
||
console.log('Then run this script again: node test-event-upload.js');
|
||
process.exit(1);
|
||
}
|
||
|
||
testEventUpload(); |