feat: Enhanced CSS animations, improved UI components, and project reorganization
- Enhanced globals.css with comprehensive animation system - Added advanced map marker animations (GPS, parking) - Improved button and filter animations with hover effects - Added new UI components: BookingModal, ParkingDetails, WheelPicker - Reorganized project structure with better documentation - Added optimization scripts and improved development workflow - Updated deployment guides and technical documentation - Enhanced mobile responsiveness and accessibility support
This commit is contained in:
839
Documents/DATABASE_DESIGN.md
Normal file
839
Documents/DATABASE_DESIGN.md
Normal file
@@ -0,0 +1,839 @@
|
||||
# 🗄️ Database Design & Data Structure Documentation
|
||||
|
||||
## 📋 Table of Contents
|
||||
1. [Database Overview](#database-overview)
|
||||
2. [Entity Relationship Diagram](#entity-relationship-diagram)
|
||||
3. [Table Specifications](#table-specifications)
|
||||
4. [Indexes & Performance](#indexes--performance)
|
||||
5. [Data Migration Strategy](#data-migration-strategy)
|
||||
6. [Backup & Recovery](#backup--recovery)
|
||||
7. [Scaling Considerations](#scaling-considerations)
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Database Overview
|
||||
|
||||
### Technology Stack
|
||||
```json
|
||||
{
|
||||
"primary_database": "PostgreSQL 15.x",
|
||||
"cache_layer": "Redis 7.x",
|
||||
"orm": "TypeORM",
|
||||
"connection_pooling": "pg_pool",
|
||||
"backup_strategy": "Continuous WAL archiving",
|
||||
"monitoring": "PostgreSQL Stats + Custom metrics"
|
||||
}
|
||||
```
|
||||
|
||||
### Database Characteristics
|
||||
- **ACID Compliance**: Full ACID transaction support
|
||||
- **Concurrency**: Multi-version concurrency control (MVCC)
|
||||
- **Extensibility**: PostGIS for spatial operations
|
||||
- **Performance**: Optimized indexes for geospatial queries
|
||||
- **Scalability**: Read replicas and partitioning strategies
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Entity Relationship Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
Users ||--o{ Reservations : makes
|
||||
Users ||--o{ Reviews : writes
|
||||
Users ||--o{ Favorites : has
|
||||
Users ||--o{ Payments : processes
|
||||
|
||||
ParkingLots ||--o{ ParkingSpots : contains
|
||||
ParkingLots ||--o{ Reservations : receives
|
||||
ParkingLots ||--o{ Reviews : receives
|
||||
ParkingLots ||--o{ Favorites : featured_in
|
||||
ParkingLots ||--o{ PricingRules : has
|
||||
ParkingLots }|--|| Operators : managed_by
|
||||
|
||||
ParkingSpots ||--o{ Reservations : reserved_for
|
||||
ParkingSpots ||--o{ SpotHistory : tracks
|
||||
|
||||
Reservations ||--|| Payments : triggers
|
||||
Reservations ||--o{ ReservationHistory : logs
|
||||
|
||||
Operators ||--o{ ParkingLots : manages
|
||||
Operators ||--o{ OperatorUsers : employs
|
||||
|
||||
Users {
|
||||
uuid id PK
|
||||
string email UK
|
||||
string password_hash
|
||||
string full_name
|
||||
string phone
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
boolean is_active
|
||||
user_role role
|
||||
}
|
||||
|
||||
ParkingLots {
|
||||
uuid id PK
|
||||
string name
|
||||
text address
|
||||
decimal latitude
|
||||
decimal longitude
|
||||
integer total_spots
|
||||
integer available_spots
|
||||
decimal price_per_hour
|
||||
jsonb operating_hours
|
||||
text[] amenities
|
||||
uuid operator_id FK
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
boolean is_active
|
||||
}
|
||||
|
||||
ParkingSpots {
|
||||
uuid id PK
|
||||
uuid parking_lot_id FK
|
||||
string spot_number
|
||||
spot_type type
|
||||
boolean is_occupied
|
||||
boolean is_reserved
|
||||
timestamp last_updated
|
||||
}
|
||||
|
||||
Reservations {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
uuid parking_spot_id FK
|
||||
timestamp start_time
|
||||
timestamp end_time
|
||||
decimal total_cost
|
||||
reservation_status status
|
||||
timestamp created_at
|
||||
timestamp updated_at
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Table Specifications
|
||||
|
||||
### 1. Users Table
|
||||
|
||||
```sql
|
||||
CREATE TYPE user_role AS ENUM ('customer', 'operator', 'admin');
|
||||
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(255) NOT NULL,
|
||||
phone VARCHAR(20),
|
||||
avatar_url TEXT,
|
||||
|
||||
-- Preferences stored as JSONB for flexibility
|
||||
preferences JSONB DEFAULT '{}',
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
last_login_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Status fields
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
email_verified BOOLEAN DEFAULT FALSE,
|
||||
phone_verified BOOLEAN DEFAULT FALSE,
|
||||
role user_role DEFAULT 'customer',
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_email CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
|
||||
CONSTRAINT valid_phone CHECK (phone IS NULL OR phone ~* '^\+?[1-9]\d{1,14}$')
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_users_email ON users (email);
|
||||
CREATE INDEX idx_users_role_active ON users (role, is_active);
|
||||
CREATE INDEX idx_users_created_at ON users (created_at);
|
||||
|
||||
-- Update trigger
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER update_users_updated_at
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
```
|
||||
|
||||
### 2. Operators Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE operators (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
company_name VARCHAR(255) NOT NULL,
|
||||
contact_email VARCHAR(255) NOT NULL,
|
||||
contact_phone VARCHAR(20),
|
||||
address TEXT,
|
||||
|
||||
-- Business information
|
||||
business_license VARCHAR(100),
|
||||
tax_id VARCHAR(50),
|
||||
|
||||
-- Settings
|
||||
commission_rate DECIMAL(5,4) DEFAULT 0.1000, -- 10% default
|
||||
auto_approval BOOLEAN DEFAULT FALSE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
CONSTRAINT valid_commission_rate CHECK (commission_rate >= 0 AND commission_rate <= 1)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_operators_active ON operators (is_active);
|
||||
CREATE INDEX idx_operators_company_name ON operators (company_name);
|
||||
```
|
||||
|
||||
### 3. Parking Lots Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE parking_lots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
operator_id UUID REFERENCES operators(id) ON DELETE CASCADE,
|
||||
|
||||
-- Basic information
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
address TEXT NOT NULL,
|
||||
|
||||
-- Geolocation (using decimal for precision)
|
||||
latitude DECIMAL(10,8) NOT NULL,
|
||||
longitude DECIMAL(11,8) NOT NULL,
|
||||
|
||||
-- Capacity
|
||||
total_spots INTEGER NOT NULL DEFAULT 0,
|
||||
available_spots INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- Pricing
|
||||
price_per_hour DECIMAL(10,2) NOT NULL,
|
||||
currency CHAR(3) DEFAULT 'USD',
|
||||
|
||||
-- Operating hours stored as JSONB for flexibility
|
||||
operating_hours JSONB DEFAULT '{
|
||||
"monday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"tuesday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"wednesday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"thursday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"friday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"saturday": {"open": "00:00", "close": "23:59", "is24Hours": true},
|
||||
"sunday": {"open": "00:00", "close": "23:59", "is24Hours": true}
|
||||
}',
|
||||
|
||||
-- Amenities as array
|
||||
amenities TEXT[] DEFAULT '{}',
|
||||
|
||||
-- Images
|
||||
images JSONB DEFAULT '[]',
|
||||
|
||||
-- Ratings
|
||||
average_rating DECIMAL(3,2) DEFAULT 0,
|
||||
total_reviews INTEGER DEFAULT 0,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
last_availability_update TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_coordinates CHECK (
|
||||
latitude BETWEEN -90 AND 90 AND
|
||||
longitude BETWEEN -180 AND 180
|
||||
),
|
||||
CONSTRAINT valid_capacity CHECK (
|
||||
total_spots >= 0 AND
|
||||
available_spots >= 0 AND
|
||||
available_spots <= total_spots
|
||||
),
|
||||
CONSTRAINT valid_price CHECK (price_per_hour >= 0),
|
||||
CONSTRAINT valid_rating CHECK (average_rating BETWEEN 0 AND 5)
|
||||
);
|
||||
|
||||
-- Spatial index for location-based queries
|
||||
CREATE INDEX idx_parking_lots_location ON parking_lots
|
||||
USING GIST (ll_to_earth(latitude, longitude));
|
||||
|
||||
-- Regular indexes
|
||||
CREATE INDEX idx_parking_lots_operator ON parking_lots (operator_id);
|
||||
CREATE INDEX idx_parking_lots_active ON parking_lots (is_active);
|
||||
CREATE INDEX idx_parking_lots_price ON parking_lots (price_per_hour);
|
||||
CREATE INDEX idx_parking_lots_availability ON parking_lots (available_spots, is_active);
|
||||
CREATE INDEX idx_parking_lots_rating ON parking_lots (average_rating DESC);
|
||||
|
||||
-- Composite index for common queries
|
||||
CREATE INDEX idx_parking_lots_active_location ON parking_lots (is_active, latitude, longitude);
|
||||
```
|
||||
|
||||
### 4. Parking Spots Table
|
||||
|
||||
```sql
|
||||
CREATE TYPE spot_type AS ENUM ('regular', 'disabled', 'electric', 'compact', 'motorcycle');
|
||||
|
||||
CREATE TABLE parking_spots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
parking_lot_id UUID REFERENCES parking_lots(id) ON DELETE CASCADE,
|
||||
|
||||
-- Spot identification
|
||||
spot_number VARCHAR(20) NOT NULL,
|
||||
floor_level INTEGER DEFAULT 0,
|
||||
section VARCHAR(10),
|
||||
|
||||
-- Spot characteristics
|
||||
type spot_type DEFAULT 'regular',
|
||||
size_category VARCHAR(20) DEFAULT 'standard', -- compact, standard, large
|
||||
|
||||
-- Status
|
||||
is_occupied BOOLEAN DEFAULT FALSE,
|
||||
is_reserved BOOLEAN DEFAULT FALSE,
|
||||
is_maintenance BOOLEAN DEFAULT FALSE,
|
||||
|
||||
-- Timestamps
|
||||
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
occupied_since TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Constraints
|
||||
UNIQUE(parking_lot_id, spot_number),
|
||||
CONSTRAINT valid_floor CHECK (floor_level BETWEEN -10 AND 50)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_parking_spots_lot ON parking_spots (parking_lot_id);
|
||||
CREATE INDEX idx_parking_spots_status ON parking_spots (is_occupied, is_reserved, is_maintenance);
|
||||
CREATE INDEX idx_parking_spots_type ON parking_spots (type);
|
||||
CREATE INDEX idx_parking_spots_availability ON parking_spots (parking_lot_id, is_occupied, is_reserved, is_maintenance);
|
||||
```
|
||||
|
||||
### 5. Reservations Table
|
||||
|
||||
```sql
|
||||
CREATE TYPE reservation_status AS ENUM (
|
||||
'pending',
|
||||
'confirmed',
|
||||
'active',
|
||||
'completed',
|
||||
'cancelled',
|
||||
'no_show',
|
||||
'expired'
|
||||
);
|
||||
|
||||
CREATE TABLE reservations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
parking_spot_id UUID REFERENCES parking_spots(id) ON DELETE CASCADE,
|
||||
|
||||
-- Timing
|
||||
start_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
end_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
actual_start_time TIMESTAMP WITH TIME ZONE,
|
||||
actual_end_time TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Pricing
|
||||
base_cost DECIMAL(10,2) NOT NULL,
|
||||
discount_amount DECIMAL(10,2) DEFAULT 0,
|
||||
tax_amount DECIMAL(10,2) DEFAULT 0,
|
||||
total_cost DECIMAL(10,2) NOT NULL,
|
||||
currency CHAR(3) DEFAULT 'USD',
|
||||
|
||||
-- Vehicle information
|
||||
vehicle_info JSONB DEFAULT '{}',
|
||||
|
||||
-- Status and notes
|
||||
status reservation_status DEFAULT 'pending',
|
||||
special_requests TEXT,
|
||||
cancellation_reason TEXT,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
confirmation_code VARCHAR(10) UNIQUE,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_duration CHECK (end_time > start_time),
|
||||
CONSTRAINT valid_costs CHECK (
|
||||
base_cost >= 0 AND
|
||||
discount_amount >= 0 AND
|
||||
tax_amount >= 0 AND
|
||||
total_cost >= 0
|
||||
)
|
||||
);
|
||||
|
||||
-- Generate confirmation code
|
||||
CREATE OR REPLACE FUNCTION generate_confirmation_code()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.confirmation_code IS NULL THEN
|
||||
NEW.confirmation_code := upper(substring(md5(random()::text) from 1 for 8));
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER set_confirmation_code
|
||||
BEFORE INSERT ON reservations
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION generate_confirmation_code();
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_reservations_user ON reservations (user_id);
|
||||
CREATE INDEX idx_reservations_spot ON reservations (parking_spot_id);
|
||||
CREATE INDEX idx_reservations_status ON reservations (status);
|
||||
CREATE INDEX idx_reservations_time_range ON reservations (start_time, end_time);
|
||||
CREATE INDEX idx_reservations_confirmation ON reservations (confirmation_code);
|
||||
|
||||
-- Composite indexes for common queries
|
||||
CREATE INDEX idx_reservations_user_status ON reservations (user_id, status);
|
||||
CREATE INDEX idx_reservations_spot_time ON reservations (parking_spot_id, start_time, end_time);
|
||||
```
|
||||
|
||||
### 6. Payments Table
|
||||
|
||||
```sql
|
||||
CREATE TYPE payment_method AS ENUM ('credit_card', 'debit_card', 'digital_wallet', 'bank_transfer', 'cash');
|
||||
CREATE TYPE payment_status AS ENUM ('pending', 'processing', 'completed', 'failed', 'refunded', 'disputed');
|
||||
|
||||
CREATE TABLE payments (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
reservation_id UUID REFERENCES reservations(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
||||
-- Payment details
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
currency CHAR(3) DEFAULT 'USD',
|
||||
method payment_method NOT NULL,
|
||||
status payment_status DEFAULT 'pending',
|
||||
|
||||
-- External payment provider information
|
||||
provider_name VARCHAR(50), -- stripe, paypal, etc.
|
||||
provider_transaction_id VARCHAR(255),
|
||||
provider_fee DECIMAL(10,2) DEFAULT 0,
|
||||
|
||||
-- Payment metadata
|
||||
payment_date TIMESTAMP WITH TIME ZONE,
|
||||
processed_at TIMESTAMP WITH TIME ZONE,
|
||||
failure_reason TEXT,
|
||||
refund_amount DECIMAL(10,2) DEFAULT 0,
|
||||
refund_date TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Metadata
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT valid_amount CHECK (amount > 0),
|
||||
CONSTRAINT valid_refund CHECK (refund_amount >= 0 AND refund_amount <= amount)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_payments_reservation ON payments (reservation_id);
|
||||
CREATE INDEX idx_payments_user ON payments (user_id);
|
||||
CREATE INDEX idx_payments_status ON payments (status);
|
||||
CREATE INDEX idx_payments_method ON payments (method);
|
||||
CREATE INDEX idx_payments_provider ON payments (provider_name, provider_transaction_id);
|
||||
CREATE INDEX idx_payments_date ON payments (payment_date);
|
||||
```
|
||||
|
||||
### 7. Reviews Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE reviews (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
parking_lot_id UUID REFERENCES parking_lots(id) ON DELETE CASCADE,
|
||||
reservation_id UUID REFERENCES reservations(id) ON DELETE SET NULL,
|
||||
|
||||
-- Review content
|
||||
rating INTEGER NOT NULL,
|
||||
title VARCHAR(255),
|
||||
comment TEXT,
|
||||
|
||||
-- Review metadata
|
||||
is_verified BOOLEAN DEFAULT FALSE, -- verified if linked to actual reservation
|
||||
is_flagged BOOLEAN DEFAULT FALSE,
|
||||
admin_response TEXT,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Constraints
|
||||
CONSTRAINT valid_rating CHECK (rating BETWEEN 1 AND 5),
|
||||
UNIQUE(user_id, parking_lot_id) -- One review per user per parking lot
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX idx_reviews_parking_lot ON reviews (parking_lot_id);
|
||||
CREATE INDEX idx_reviews_user ON reviews (user_id);
|
||||
CREATE INDEX idx_reviews_rating ON reviews (rating);
|
||||
CREATE INDEX idx_reviews_verified ON reviews (is_verified);
|
||||
CREATE INDEX idx_reviews_created_at ON reviews (created_at);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Indexes & Performance
|
||||
|
||||
### 1. Spatial Indexes for Location Queries
|
||||
|
||||
```sql
|
||||
-- Enable PostGIS extension for advanced spatial operations
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
-- Convert existing lat/lng to geometry for better performance
|
||||
ALTER TABLE parking_lots ADD COLUMN geom GEOMETRY(POINT, 4326);
|
||||
|
||||
-- Populate geometry column
|
||||
UPDATE parking_lots SET geom = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326);
|
||||
|
||||
-- Create spatial index
|
||||
CREATE INDEX idx_parking_lots_geom ON parking_lots USING GIST (geom);
|
||||
|
||||
-- Function for distance-based queries
|
||||
CREATE OR REPLACE FUNCTION find_nearby_parking(
|
||||
user_lat DECIMAL,
|
||||
user_lng DECIMAL,
|
||||
radius_meters INTEGER DEFAULT 5000
|
||||
) RETURNS TABLE(
|
||||
id UUID,
|
||||
name VARCHAR,
|
||||
distance_meters DECIMAL
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
pl.id,
|
||||
pl.name,
|
||||
ST_Distance(
|
||||
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::geography,
|
||||
pl.geom::geography
|
||||
) as distance_meters
|
||||
FROM parking_lots pl
|
||||
WHERE pl.is_active = TRUE
|
||||
AND ST_DWithin(
|
||||
ST_SetSRID(ST_MakePoint(user_lng, user_lat), 4326)::geography,
|
||||
pl.geom::geography,
|
||||
radius_meters
|
||||
)
|
||||
ORDER BY distance_meters;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
```
|
||||
|
||||
### 2. Partitioning Strategy
|
||||
|
||||
```sql
|
||||
-- Partition reservations by month for better performance
|
||||
CREATE TABLE reservations_partitioned (
|
||||
LIKE reservations INCLUDING ALL
|
||||
) PARTITION BY RANGE (start_time);
|
||||
|
||||
-- Create partitions for current and future months
|
||||
CREATE TABLE reservations_2024_08 PARTITION OF reservations_partitioned
|
||||
FOR VALUES FROM ('2024-08-01') TO ('2024-09-01');
|
||||
|
||||
CREATE TABLE reservations_2024_09 PARTITION OF reservations_partitioned
|
||||
FOR VALUES FROM ('2024-09-01') TO ('2024-10-01');
|
||||
|
||||
-- Automated partition creation function
|
||||
CREATE OR REPLACE FUNCTION create_monthly_partition()
|
||||
RETURNS void AS $$
|
||||
DECLARE
|
||||
start_date DATE;
|
||||
end_date DATE;
|
||||
table_name TEXT;
|
||||
BEGIN
|
||||
start_date := DATE_TRUNC('month', CURRENT_DATE + INTERVAL '1 month');
|
||||
end_date := start_date + INTERVAL '1 month';
|
||||
table_name := 'reservations_' || TO_CHAR(start_date, 'YYYY_MM');
|
||||
|
||||
EXECUTE FORMAT(
|
||||
'CREATE TABLE %I PARTITION OF reservations_partitioned
|
||||
FOR VALUES FROM (%L) TO (%L)',
|
||||
table_name, start_date, end_date
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Schedule monthly partition creation
|
||||
SELECT cron.schedule('create-partition', '0 0 1 * *', 'SELECT create_monthly_partition();');
|
||||
```
|
||||
|
||||
### 3. Performance Monitoring Views
|
||||
|
||||
```sql
|
||||
-- View for monitoring table sizes
|
||||
CREATE VIEW table_sizes AS
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
attname,
|
||||
n_distinct,
|
||||
correlation,
|
||||
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size
|
||||
FROM pg_stats
|
||||
WHERE schemaname = 'public'
|
||||
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
|
||||
|
||||
-- View for monitoring index usage
|
||||
CREATE VIEW index_usage AS
|
||||
SELECT
|
||||
schemaname,
|
||||
tablename,
|
||||
attname,
|
||||
n_distinct,
|
||||
correlation
|
||||
FROM pg_stats
|
||||
WHERE schemaname = 'public';
|
||||
|
||||
-- View for monitoring slow queries
|
||||
CREATE VIEW slow_queries AS
|
||||
SELECT
|
||||
query,
|
||||
calls,
|
||||
total_time,
|
||||
mean_time,
|
||||
rows
|
||||
FROM pg_stat_statements
|
||||
WHERE mean_time > 100
|
||||
ORDER BY mean_time DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Data Migration Strategy
|
||||
|
||||
### 1. Migration Scripts Structure
|
||||
|
||||
```sql
|
||||
-- Migration: 001_initial_schema.sql
|
||||
-- Description: Create initial database schema
|
||||
-- Date: 2024-08-03
|
||||
-- Author: System
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Create enums
|
||||
CREATE TYPE user_role AS ENUM ('customer', 'operator', 'admin');
|
||||
CREATE TYPE spot_type AS ENUM ('regular', 'disabled', 'electric', 'compact', 'motorcycle');
|
||||
-- ... rest of schema creation
|
||||
|
||||
-- Create migration tracking table
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version VARCHAR(255) PRIMARY KEY,
|
||||
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('001_initial_schema');
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 2. Data Seeding
|
||||
|
||||
```sql
|
||||
-- Seed: 001_default_data.sql
|
||||
-- Description: Insert default operational data
|
||||
-- Date: 2024-08-03
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Insert default operator
|
||||
INSERT INTO operators (id, company_name, contact_email, contact_phone, is_active)
|
||||
VALUES (
|
||||
'b123b123-b123-b123-b123-b123b123b123',
|
||||
'City Parking Management',
|
||||
'admin@cityparking.com',
|
||||
'+1234567890',
|
||||
TRUE
|
||||
);
|
||||
|
||||
-- Insert sample parking lots
|
||||
INSERT INTO parking_lots (
|
||||
id, operator_id, name, address, latitude, longitude,
|
||||
total_spots, available_spots, price_per_hour, amenities
|
||||
) VALUES
|
||||
(
|
||||
'a123a123-a123-a123-a123-a123a123a123',
|
||||
'b123b123-b123-b123-b123-b123b123b123',
|
||||
'Downtown Parking Garage',
|
||||
'123 Main St, Downtown',
|
||||
40.7128,
|
||||
-74.0060,
|
||||
200,
|
||||
150,
|
||||
5.00,
|
||||
ARRAY['covered', 'security', 'electric_charging']
|
||||
);
|
||||
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
### 3. Backup Strategy
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup_database.sh
|
||||
# Automated database backup script
|
||||
|
||||
DB_NAME="smart_parking"
|
||||
BACKUP_DIR="/backups/postgresql"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
# Create full backup
|
||||
pg_dump -h localhost -U postgres -d $DB_NAME \
|
||||
--verbose --format=custom \
|
||||
--file="$BACKUP_DIR/full_backup_$DATE.backup"
|
||||
|
||||
# Create schema-only backup
|
||||
pg_dump -h localhost -U postgres -d $DB_NAME \
|
||||
--schema-only --verbose \
|
||||
--file="$BACKUP_DIR/schema_backup_$DATE.sql"
|
||||
|
||||
# Cleanup old backups (keep last 30 days)
|
||||
find $BACKUP_DIR -name "*.backup" -mtime +30 -delete
|
||||
find $BACKUP_DIR -name "*.sql" -mtime +30 -delete
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Scaling Considerations
|
||||
|
||||
### 1. Read Replicas Configuration
|
||||
|
||||
```sql
|
||||
-- Master database configuration
|
||||
-- postgresql.conf
|
||||
wal_level = replica
|
||||
max_wal_senders = 3
|
||||
wal_keep_segments = 64
|
||||
hot_standby = on
|
||||
|
||||
-- Replica database queries
|
||||
-- Route read-only queries to replicas
|
||||
CREATE OR REPLACE FUNCTION is_read_only_query(query_text TEXT)
|
||||
RETURNS BOOLEAN AS $$
|
||||
BEGIN
|
||||
RETURN query_text ~* '^(SELECT|WITH)' AND
|
||||
query_text !~* '(FOR UPDATE|FOR SHARE)';
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
```
|
||||
|
||||
### 2. Connection Pooling
|
||||
|
||||
```javascript
|
||||
// Database connection pool configuration
|
||||
const poolConfig = {
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
database: process.env.DB_NAME,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
|
||||
// Pool configuration
|
||||
min: 5, // Minimum connections
|
||||
max: 50, // Maximum connections
|
||||
idle: 10000, // Idle timeout (10 seconds)
|
||||
acquire: 60000, // Acquire timeout (60 seconds)
|
||||
evict: 1000, // Eviction run interval (1 second)
|
||||
|
||||
// Connection validation
|
||||
validate: true,
|
||||
validateTimeout: 3000,
|
||||
|
||||
// Logging
|
||||
logging: (sql, timing) => {
|
||||
if (timing > 1000) {
|
||||
console.warn(`Slow query detected: ${timing}ms - ${sql}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Caching Strategy
|
||||
|
||||
```sql
|
||||
-- Materialized views for expensive aggregations
|
||||
CREATE MATERIALIZED VIEW parking_lot_stats AS
|
||||
SELECT
|
||||
pl.id,
|
||||
pl.name,
|
||||
pl.total_spots,
|
||||
pl.available_spots,
|
||||
ROUND(AVG(r.rating), 2) as avg_rating,
|
||||
COUNT(r.id) as total_reviews,
|
||||
COUNT(res.id) as total_reservations
|
||||
FROM parking_lots pl
|
||||
LEFT JOIN reviews r ON pl.id = r.parking_lot_id
|
||||
LEFT JOIN reservations res ON pl.id = res.parking_spot_id
|
||||
WHERE pl.is_active = TRUE
|
||||
GROUP BY pl.id, pl.name, pl.total_spots, pl.available_spots;
|
||||
|
||||
-- Refresh materialized view regularly
|
||||
CREATE OR REPLACE FUNCTION refresh_parking_stats()
|
||||
RETURNS void AS $$
|
||||
BEGIN
|
||||
REFRESH MATERIALIZED VIEW CONCURRENTLY parking_lot_stats;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Schedule refresh every hour
|
||||
SELECT cron.schedule('refresh-stats', '0 * * * *', 'SELECT refresh_parking_stats();');
|
||||
```
|
||||
|
||||
### 4. Database Monitoring
|
||||
|
||||
```sql
|
||||
-- Create monitoring functions
|
||||
CREATE OR REPLACE FUNCTION database_health_check()
|
||||
RETURNS TABLE(
|
||||
metric VARCHAR,
|
||||
value DECIMAL,
|
||||
status VARCHAR
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH metrics AS (
|
||||
SELECT 'active_connections' as metric,
|
||||
COUNT(*)::DECIMAL as value,
|
||||
CASE WHEN COUNT(*) > 80 THEN 'WARNING' ELSE 'OK' END as status
|
||||
FROM pg_stat_activity
|
||||
WHERE state = 'active'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT 'cache_hit_ratio' as metric,
|
||||
ROUND(
|
||||
100.0 * sum(blks_hit) / (sum(blks_hit) + sum(blks_read)), 2
|
||||
) as value,
|
||||
CASE WHEN 100.0 * sum(blks_hit) / (sum(blks_hit) + sum(blks_read)) < 95
|
||||
THEN 'WARNING' ELSE 'OK' END as status
|
||||
FROM pg_stat_database
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT 'table_bloat' as metric,
|
||||
pg_size_pretty(pg_database_size(current_database()))::DECIMAL as value,
|
||||
'INFO' as status
|
||||
)
|
||||
SELECT * FROM metrics;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: August 3, 2025*
|
||||
*Version: 1.0.0*
|
||||
Reference in New Issue
Block a user