meteor_detection_system/meteor-web-backend/migrations/1753893935265_create-raw-events-table.js
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

107 lines
2.8 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 raw_events table for storing uploaded event data
pgm.createTable('raw_events', {
id: {
type: 'uuid',
primaryKey: true,
default: pgm.func('gen_random_uuid()'),
},
device_id: {
type: 'uuid',
notNull: true,
references: 'devices(id)',
onDelete: 'CASCADE',
comment: 'The device that uploaded this event',
},
user_profile_id: {
type: 'uuid',
notNull: true,
references: 'user_profiles(id)',
onDelete: 'CASCADE',
comment: 'Owner of the device',
},
file_path: {
type: 'text',
notNull: true,
comment: 'S3 file path/key for the uploaded file',
},
file_size: {
type: 'bigint',
comment: 'File size in bytes',
},
file_type: {
type: 'varchar(100)',
comment: 'MIME type of the uploaded file',
},
original_filename: {
type: 'varchar(255)',
comment: 'Original filename from the client',
},
event_type: {
type: 'varchar(50)',
notNull: true,
comment: 'Type of event (motion, alert, etc.)',
},
event_timestamp: {
type: 'timestamp with time zone',
notNull: true,
comment: 'When the event occurred on the device',
},
metadata: {
type: 'jsonb',
comment: 'Additional event metadata from the device',
},
processing_status: {
type: 'varchar(20)',
notNull: true,
default: 'pending',
comment: 'Processing status: pending, processing, completed, failed',
},
sqs_message_id: {
type: 'varchar(255)',
comment: 'SQS message ID for tracking',
},
processed_at: {
type: 'timestamp with time zone',
comment: 'When processing was completed',
},
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('raw_events', 'device_id');
pgm.createIndex('raw_events', 'user_profile_id');
pgm.createIndex('raw_events', 'event_type');
pgm.createIndex('raw_events', 'event_timestamp');
pgm.createIndex('raw_events', 'processing_status');
pgm.createIndex('raw_events', ['device_id', 'event_timestamp']);
pgm.createIndex('raw_events', ['user_profile_id', 'created_at']);
};
/**
* @param pgm {import('node-pg-migrate').MigrationBuilder}
* @param run {() => void | undefined}
* @returns {Promise<void> | void}
*/
exports.down = (pgm) => {
pgm.dropTable('raw_events');
};