- 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
23 KiB
23 KiB
🗄️ Database Design & Data Structure Documentation
📋 Table of Contents
- Database Overview
- Entity Relationship Diagram
- Table Specifications
- Indexes & Performance
- Data Migration Strategy
- Backup & Recovery
- Scaling Considerations
🌐 Database Overview
Technology Stack
{
"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
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
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
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
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
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
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
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
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
-- 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
-- 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
-- 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
-- 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
-- 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
#!/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
-- 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
// 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
-- 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
-- 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