Files
sides/docs/06-DATABASE.md
root 6863f39a24 docs: rewrite all documentation to reflect current state
- Remove adminer references (service was removed)
- Remove mermaid diagrams (ASCII only)
- Remove hardcoded credentials (use env var references)
- Update all Docker references to 4-container setup (app, postgres, web, pgadmin)
- Document env-based admin credentials (ADMIN_EMAIL/ADMIN_PASSWORD)
- Document parameterized queries (SQL injection fixed)
- Document FCM topic routing by stationtype+level
- Document siren stationtype=3 fix in sidesdecode.py
- Document idempotent seeder (firstOrCreate)
- Document reverse proxy setup in deployment guide
- Remove Makefile references (Docker Compose only)
2026-05-21 02:59:32 +08:00

10 KiB

Database

SIDES uses PostgreSQL 16 running in a Docker container. Laravel connects via pgsql driver using the Docker service name as the host.

Connection Details

Setting Value Source
DB_CONNECTION pgsql src/.env
DB_HOST postgres (Docker service) src/.env
DB_PORT 5432 src/.env
DB_DATABASE ${POSTGRES_DB} src/.env
DB_USERNAME ${POSTGRES_USER} src/.env
DB_PASSWORD ${POSTGRES_PASSWORD} src/.env

Default database name: sides_db (set by POSTGRES_DB in the root .env).

The container (tckdev-db) uses a named Docker volume pgdata for persistent storage. Data survives container restarts and rebuilds.

Entity-Relationship Diagram

 station (PK: stationid)
 |--------------|
 | stationid    |---+----------------+----------------+
 | name         |   |                |                |
 | district     |   |                |                |
 | lng, lat     |   |                |                |
 | mainriverbasin|  |                |                |
 | subriverbasin|   |                |                |
 | rainfall     |   |                |                |
 | waterlevel   |   |                |                |
 | siren        |   |                |                |
 | cctv_link    |   |                |                |
 |--------------|   |                |                |
                    v                v                v
          rainfall             waterlevel          siren
          (PK: id)             (PK: id)            (PK: id)
          |--------------|     |--------------|     |--------------|
          | id           |     | id           |     | id           |
          | stationid    |     | stationid    |     | stationid    |
          | timestamp    |     | datetime     |     | stationtype  |
          | anncum       |     | waterlevel   |     | active_time  |
          | daily        |     | alert        |     | level        |
          | hourly       |     | warning      |     |--------------|
          | currentrf    |     | danger       |
          | battery      |     |--------------|
          |--------------|
                    |                |
                    v                v
                 notification (PK: id)
                 |--------------|
                 | id           |
                 | stationid    |
                 | timestamp    |
                 | stationtype  |
                 | level        |
                 | active_time  |
                 |--------------|

Note: There are no database-level foreign keys between station and the data tables. Relationships are enforced at the application level by matching stationid values.

Application Tables

station

Primary reference table for all monitoring stations.

Column Type Nullable Notes
stationid string (PK) no External station ID
name string no Station display name
district string no
lng float no Longitude
lat float no Latitude
mainriverbasin string no
subriverbasin string no
rainfall integer no Flag/counter
waterlevel integer no Flag/counter
siren integer yes Added by migration
cctv_link string yes Added by migration

Migration: 2025_11_06_071853_create_station_table.php Alter migrations: 2025_11_08_004548, 2025_11_25_113158

rainfall

Rainfall sensor readings per station.

Column Type Nullable
id bigint (PK) no
stationid string no
timestamp timestamp no
anncum double no
daily double no
hourly double no
currentrf double no
battery double no

Migration: 2025_11_06_072709_create_rainfall__table.php

waterlevel

Water level sensor readings per station.

Column Type Nullable
id bigint (PK) no
stationid string no
datetime timestamp no
waterlevel double no
alert double no
warning double no
danger double no

Migration: 2025_11_06_072738_create_waterlevel_table.php

siren

Siren activation records.

Column Type Nullable
id bigint (PK) no
stationid string no
stationtype string no
active_time timestamp no
level string no

Migration: 2025_11_07_031601_create_siren__table.php

The stationtype column is always 3 for siren inserts (see stationtype values below).

notification

Notification log for alerts across all station types.

Column Type Nullable
id bigint (PK) no
stationid string no
timestamp timestamp no
stationtype integer no
level string no
active_time timestamp yes

Migration: 2025_11_07_024940_create_notification_table.php

users

Application users with access control and blocking support.

Column Type Nullable Default Notes
id bigint (PK) no auto
name string no
email string no unique
email_verified_at timestamp yes
password string no bcrypt hashed
access_level integer no 2 1=admin, 2=user
is_blocked boolean no false
login_attempts integer no 0
remember_token string yes
created_at timestamp yes
updated_at timestamp yes

Base: 0001_01_01_000000_create_users_table.php Alter: 2025_11_07_063825, 2025_11_07_065418

stationtype Values

The stationtype field is used in the notification and siren tables to identify the source station type.

Value Type
1 Rainfall
2 Water level
3 Siren

Laravel Standard Tables

These tables are created by Laravel's built-in migrations and support framework features (sessions, caching, queue jobs).

Table Purpose
password_reset_tokens Password reset tokens
sessions Database-backed sessions
cache Application cache store
cache_locks Atomic cache locks
jobs Queue job queue
job_batches Batched job tracking
failed_jobs Failed job records
migrations Migration tracking

Default Admin User

The DatabaseSeeder creates a default admin user on first run using firstOrCreate, making it idempotent -- safe to re-run without duplicates.

User::firstOrCreate(
    ['email' => env('ADMIN_EMAIL', 'admin@example.com')],
    [
        'name' => 'Admin',
        'password' => Hash::make(env('ADMIN_PASSWORD', str()->random(32))),
        'access_level' => 1,
    ]
);
  • Email: set by ADMIN_EMAIL env var (default: admin@example.com)
  • Password: set by ADMIN_PASSWORD env var (default: random 32-char string)
  • access_level: 1 (admin)

The seeder is triggered automatically when RUN_SEEDER=true in the Docker environment (see docker/entrypoint.sh).

The migration 2025_12_11_124201_add_default_user_to_users_table.php exists but is a no-op (empty up() and down()) -- admin creation is handled exclusively by the seeder.

Seeding and Migrations

Migrations and seeding run automatically at container startup via docker/entrypoint.sh:

# Runs when RUN_MIGRATIONS=true
php artisan migrate --force

# Runs when RUN_SEEDER=true
php artisan db:seed --force

Both RUN_MIGRATIONS and RUN_SEEDER default to true in docker-compose.yml.

Design Notes

No foreign keys. The stationid column in rainfall, waterlevel, siren, and notification references station.stationid at the application level only. There are no database-level REFERENCES constraints. This means orphaned data rows can exist if a station is deleted without cleaning associated records.

No additional indexes. Beyond primary keys and Laravel's default indexes (on sessions.user_id, sessions.last_activity, jobs.queue), no custom indexes exist on the data tables. This is a potential performance concern as the rainfall, waterlevel, and notification tables grow -- queries filtering by stationid or ordering by timestamp will perform full table scans.