📋 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
110 lines
2.7 KiB
JavaScript
110 lines
2.7 KiB
JavaScript
/**
|
|
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
|
|
*/
|
|
exports.shorthands = undefined;
|
|
|
|
/**
|
|
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
* @param run {() => void | undefined}
|
|
* @returns {Promise<void> | void}
|
|
*/
|
|
exports.up = (pgm) => {
|
|
// Create inventory_devices table - for pre-registered legitimate devices
|
|
pgm.createTable('inventory_devices', {
|
|
id: {
|
|
type: 'uuid',
|
|
primaryKey: true,
|
|
default: pgm.func('gen_random_uuid()'),
|
|
},
|
|
hardware_id: {
|
|
type: 'varchar(255)',
|
|
notNull: true,
|
|
unique: true,
|
|
},
|
|
is_claimed: {
|
|
type: 'boolean',
|
|
notNull: true,
|
|
default: false,
|
|
},
|
|
device_model: {
|
|
type: 'varchar(255)',
|
|
comment: 'Optional device model information',
|
|
},
|
|
created_at: {
|
|
type: 'timestamp with time zone',
|
|
notNull: true,
|
|
default: pgm.func('now()'),
|
|
},
|
|
updated_at: {
|
|
type: 'timestamp with time zone',
|
|
notNull: true,
|
|
default: pgm.func('now()'),
|
|
},
|
|
});
|
|
|
|
// Create devices table - for user-claimed devices
|
|
pgm.createTable('devices', {
|
|
id: {
|
|
type: 'uuid',
|
|
primaryKey: true,
|
|
default: pgm.func('gen_random_uuid()'),
|
|
},
|
|
user_profile_id: {
|
|
type: 'uuid',
|
|
notNull: true,
|
|
references: 'user_profiles(id)',
|
|
onDelete: 'CASCADE',
|
|
},
|
|
hardware_id: {
|
|
type: 'varchar(255)',
|
|
notNull: true,
|
|
unique: true,
|
|
},
|
|
device_name: {
|
|
type: 'varchar(255)',
|
|
comment: 'User-assigned friendly name for the device',
|
|
},
|
|
status: {
|
|
type: 'varchar(50)',
|
|
notNull: true,
|
|
default: 'active',
|
|
comment: 'Device status: active, inactive, maintenance',
|
|
},
|
|
last_seen_at: {
|
|
type: 'timestamp with time zone',
|
|
comment: 'Last time device was seen online',
|
|
},
|
|
registered_at: {
|
|
type: 'timestamp with time zone',
|
|
notNull: true,
|
|
default: pgm.func('now()'),
|
|
},
|
|
created_at: {
|
|
type: 'timestamp with time zone',
|
|
notNull: true,
|
|
default: pgm.func('now()'),
|
|
},
|
|
updated_at: {
|
|
type: 'timestamp with time zone',
|
|
notNull: true,
|
|
default: pgm.func('now()'),
|
|
},
|
|
});
|
|
|
|
// Create indexes for better performance
|
|
pgm.createIndex('inventory_devices', 'hardware_id');
|
|
pgm.createIndex('inventory_devices', 'is_claimed');
|
|
pgm.createIndex('devices', 'user_profile_id');
|
|
pgm.createIndex('devices', 'hardware_id');
|
|
pgm.createIndex('devices', 'status');
|
|
};
|
|
|
|
/**
|
|
* @param pgm {import('node-pg-migrate').MigrationBuilder}
|
|
* @param run {() => void | undefined}
|
|
* @returns {Promise<void> | void}
|
|
*/
|
|
exports.down = (pgm) => {
|
|
pgm.dropTable('devices');
|
|
pgm.dropTable('inventory_devices');
|
|
}; |