341 lines
11 KiB
PL/PgSQL
341 lines
11 KiB
PL/PgSQL
-- Operations channel data model (no service-days table):
|
|
-- schedules -> trips -> trip_stops -> stop_updates
|
|
|
|
create table if not exists public.operations_schedules (
|
|
id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
|
|
channel_id text not null references public.channels(id) on delete cascade,
|
|
version integer not null check (version > 0),
|
|
source_file_name text not null,
|
|
source_mime text,
|
|
storage_path text,
|
|
file_sha256 text,
|
|
parser text not null default 'unknown',
|
|
parse_status text not null default 'pending',
|
|
parse_error text,
|
|
uploaded_by uuid not null references auth.users(id) on delete restrict,
|
|
uploaded_at timestamptz not null default now(),
|
|
parsed_at timestamptz,
|
|
is_active boolean not null default false,
|
|
unique (channel_id, version)
|
|
);
|
|
|
|
create unique index if not exists idx_operations_schedules_active_per_channel
|
|
on public.operations_schedules(channel_id)
|
|
where is_active = true;
|
|
|
|
create index if not exists idx_operations_schedules_channel_uploaded
|
|
on public.operations_schedules(channel_id, uploaded_at desc);
|
|
|
|
create table if not exists public.operations_trips (
|
|
id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
|
|
schedule_id text not null references public.operations_schedules(id) on delete cascade,
|
|
trip_number text not null,
|
|
duty_number text,
|
|
running_number text,
|
|
direction text,
|
|
service_code text,
|
|
sort_order integer not null default 0,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists idx_operations_trips_schedule_sort
|
|
on public.operations_trips(schedule_id, sort_order, trip_number);
|
|
|
|
create table if not exists public.operations_trip_stops (
|
|
id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
|
|
trip_id text not null references public.operations_trips(id) on delete cascade,
|
|
stop_sequence integer not null check (stop_sequence > 0),
|
|
stop_name text not null,
|
|
stop_code text,
|
|
scheduled_time text,
|
|
is_timing_point boolean not null default false,
|
|
raw_label text,
|
|
created_at timestamptz not null default now(),
|
|
unique (trip_id, stop_sequence)
|
|
);
|
|
|
|
create index if not exists idx_operations_trip_stops_trip_sequence
|
|
on public.operations_trip_stops(trip_id, stop_sequence);
|
|
|
|
create index if not exists idx_operations_trip_stops_trip_time
|
|
on public.operations_trip_stops(trip_id, scheduled_time);
|
|
|
|
create table if not exists public.operations_stop_updates (
|
|
id text primary key default public.gen_hash_id() check (id ~ '^[0-9a-f]{16}$'),
|
|
trip_stop_id text not null references public.operations_trip_stops(id) on delete cascade,
|
|
status text,
|
|
actual_time text,
|
|
vehicle_id text,
|
|
notes text,
|
|
updated_by uuid not null references auth.users(id) on delete restrict,
|
|
created_at timestamptz not null default now(),
|
|
updated_at timestamptz not null default now(),
|
|
unique (trip_stop_id)
|
|
);
|
|
|
|
create index if not exists idx_operations_stop_updates_trip_stop
|
|
on public.operations_stop_updates(trip_stop_id);
|
|
|
|
alter table public.operations_schedules enable row level security;
|
|
alter table public.operations_trips enable row level security;
|
|
alter table public.operations_trip_stops enable row level security;
|
|
alter table public.operations_stop_updates enable row level security;
|
|
|
|
drop policy if exists "operations_schedules_select_visible" on public.operations_schedules;
|
|
create policy "operations_schedules_select_visible"
|
|
on public.operations_schedules
|
|
for select
|
|
to authenticated
|
|
using (public.can_access_channel(channel_id));
|
|
|
|
drop policy if exists "operations_schedules_insert_admins" on public.operations_schedules;
|
|
create policy "operations_schedules_insert_admins"
|
|
on public.operations_schedules
|
|
for insert
|
|
to authenticated
|
|
with check (
|
|
uploaded_by = auth.uid()
|
|
and exists (
|
|
select 1
|
|
from public.channels c
|
|
where c.id = channel_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_schedules_update_admins" on public.operations_schedules;
|
|
create policy "operations_schedules_update_admins"
|
|
on public.operations_schedules
|
|
for update
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.channels c
|
|
where c.id = channel_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
)
|
|
with check (
|
|
exists (
|
|
select 1
|
|
from public.channels c
|
|
where c.id = channel_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_schedules_delete_admins" on public.operations_schedules;
|
|
create policy "operations_schedules_delete_admins"
|
|
on public.operations_schedules
|
|
for delete
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.channels c
|
|
where c.id = channel_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_trips_select_visible" on public.operations_trips;
|
|
create policy "operations_trips_select_visible"
|
|
on public.operations_trips
|
|
for select
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_schedules s
|
|
where s.id = schedule_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_trips_write_admins" on public.operations_trips;
|
|
create policy "operations_trips_write_admins"
|
|
on public.operations_trips
|
|
for all
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_schedules s
|
|
join public.channels c on c.id = s.channel_id
|
|
where s.id = schedule_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
)
|
|
with check (
|
|
exists (
|
|
select 1
|
|
from public.operations_schedules s
|
|
join public.channels c on c.id = s.channel_id
|
|
where s.id = schedule_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_trip_stops_select_visible" on public.operations_trip_stops;
|
|
create policy "operations_trip_stops_select_visible"
|
|
on public.operations_trip_stops
|
|
for select
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_trips t
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where t.id = trip_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_trip_stops_write_admins" on public.operations_trip_stops;
|
|
create policy "operations_trip_stops_write_admins"
|
|
on public.operations_trip_stops
|
|
for all
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_trips t
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
join public.channels c on c.id = s.channel_id
|
|
where t.id = trip_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
)
|
|
with check (
|
|
exists (
|
|
select 1
|
|
from public.operations_trips t
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
join public.channels c on c.id = s.channel_id
|
|
where t.id = trip_id
|
|
and c.type = 'operations'
|
|
and public.org_role(c.organization_id) in ('owner', 'admin')
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_stop_updates_select_visible" on public.operations_stop_updates;
|
|
create policy "operations_stop_updates_select_visible"
|
|
on public.operations_stop_updates
|
|
for select
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_trip_stops ts
|
|
join public.operations_trips t on t.id = ts.trip_id
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where ts.id = trip_stop_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_stop_updates_insert_members" on public.operations_stop_updates;
|
|
create policy "operations_stop_updates_insert_members"
|
|
on public.operations_stop_updates
|
|
for insert
|
|
to authenticated
|
|
with check (
|
|
updated_by = auth.uid()
|
|
and exists (
|
|
select 1
|
|
from public.operations_trip_stops ts
|
|
join public.operations_trips t on t.id = ts.trip_id
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where ts.id = trip_stop_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_stop_updates_update_members" on public.operations_stop_updates;
|
|
create policy "operations_stop_updates_update_members"
|
|
on public.operations_stop_updates
|
|
for update
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_trip_stops ts
|
|
join public.operations_trips t on t.id = ts.trip_id
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where ts.id = trip_stop_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
)
|
|
with check (
|
|
updated_by = auth.uid()
|
|
and exists (
|
|
select 1
|
|
from public.operations_trip_stops ts
|
|
join public.operations_trips t on t.id = ts.trip_id
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where ts.id = trip_stop_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
drop policy if exists "operations_stop_updates_delete_members" on public.operations_stop_updates;
|
|
create policy "operations_stop_updates_delete_members"
|
|
on public.operations_stop_updates
|
|
for delete
|
|
to authenticated
|
|
using (
|
|
exists (
|
|
select 1
|
|
from public.operations_trip_stops ts
|
|
join public.operations_trips t on t.id = ts.trip_id
|
|
join public.operations_schedules s on s.id = t.schedule_id
|
|
where ts.id = trip_stop_id
|
|
and public.can_access_channel(s.channel_id)
|
|
)
|
|
);
|
|
|
|
-- Keep updated_at fresh on edits.
|
|
create or replace function public.tg_set_updated_at()
|
|
returns trigger
|
|
language plpgsql
|
|
as $$
|
|
begin
|
|
new.updated_at := now();
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
drop trigger if exists trg_operations_stop_updates_updated_at on public.operations_stop_updates;
|
|
create trigger trg_operations_stop_updates_updated_at
|
|
before update on public.operations_stop_updates
|
|
for each row execute procedure public.tg_set_updated_at();
|
|
|
|
-- Realtime availability for operations collaboration features.
|
|
do $$
|
|
begin
|
|
begin
|
|
alter publication supabase_realtime add table public.operations_schedules;
|
|
exception when duplicate_object then null;
|
|
end;
|
|
begin
|
|
alter publication supabase_realtime add table public.operations_trips;
|
|
exception when duplicate_object then null;
|
|
end;
|
|
begin
|
|
alter publication supabase_realtime add table public.operations_trip_stops;
|
|
exception when duplicate_object then null;
|
|
end;
|
|
begin
|
|
alter publication supabase_realtime add table public.operations_stop_updates;
|
|
exception when duplicate_object then null;
|
|
end;
|
|
end $$;
|
|
|