grabbit 46d8af6084 🎉 Epic 2 Milestone: Successfully completed the final story of Epic 2: Commercialization & Core User Experience with full-stack date filtering functionality.
📋 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
2025-08-03 10:30:29 +08:00

206 lines
6.4 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();