Исправлено дублирование сообщений в task-чате: защита от двойного Enter/клика по кнопке отправки

This commit is contained in:
2026-08-24 16:50:05 +03:00
parent 422eafd41f
commit 8b713e66c0
12 changed files with 1674 additions and 1 deletions

View File

@@ -0,0 +1,106 @@
-- GPS-мониторинг: объекты (транспорт/люди), треки, геозоны, события, группы, подписки.
-- Интеграция с Traccar (forward позиций на /api/gps/ingest) и приём геолокации из PWA.
-- Объекты мониторинга (машины, люди с трекерами)
CREATE TABLE IF NOT EXISTS gps_assets (
id SERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'vehicle',
user_id INTEGER REFERENCES users(id),
external_id TEXT,
color TEXT DEFAULT '#3b82f6',
is_active BOOLEAN DEFAULT TRUE,
is_online BOOLEAN DEFAULT FALSE,
last_lat DOUBLE PRECISION,
last_lng DOUBLE PRECISION,
last_speed DOUBLE PRECISION,
last_course DOUBLE PRECISION,
last_seen_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS gps_assets_org_idx ON gps_assets(organization_id);
-- IMEI/идентификатор трекера уникален в рамках организации (только для заполненных)
CREATE UNIQUE INDEX IF NOT EXISTS gps_assets_org_external_idx
ON gps_assets(organization_id, external_id) WHERE external_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS gps_assets_user_idx ON gps_assets(user_id);
-- История позиций (треки)
CREATE TABLE IF NOT EXISTS gps_positions (
id BIGSERIAL PRIMARY KEY,
asset_id INTEGER NOT NULL REFERENCES gps_assets(id) ON DELETE CASCADE,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
lat DOUBLE PRECISION NOT NULL,
lng DOUBLE PRECISION NOT NULL,
speed DOUBLE PRECISION,
course DOUBLE PRECISION,
accuracy DOUBLE PRECISION,
recorded_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS gps_positions_asset_recorded_idx ON gps_positions(asset_id, recorded_at);
CREATE INDEX IF NOT EXISTS gps_positions_org_recorded_idx ON gps_positions(organization_id, recorded_at);
-- Геозоны (полигоны)
CREATE TABLE IF NOT EXISTS gps_geozones (
id SERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
polygon JSONB NOT NULL,
color TEXT DEFAULT '#f59e0b',
notify_enter BOOLEAN DEFAULT TRUE,
notify_exit BOOLEAN DEFAULT TRUE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS gps_geozones_org_idx ON gps_geozones(organization_id);
-- События входа/выхода из геозон
CREATE TABLE IF NOT EXISTS gps_geozone_events (
id BIGSERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
geozone_id INTEGER NOT NULL REFERENCES gps_geozones(id) ON DELETE CASCADE,
asset_id INTEGER NOT NULL REFERENCES gps_assets(id) ON DELETE CASCADE,
event TEXT NOT NULL,
lat DOUBLE PRECISION,
lng DOUBLE PRECISION,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS gps_geozone_events_asset_idx ON gps_geozone_events(asset_id, created_at);
CREATE INDEX IF NOT EXISTS gps_geozone_events_org_idx ON gps_geozone_events(organization_id, created_at);
-- Текущее состояние «объект внутри геозоны» (для детекта входа/выхода)
CREATE TABLE IF NOT EXISTS gps_geozone_states (
asset_id INTEGER NOT NULL REFERENCES gps_assets(id) ON DELETE CASCADE,
geozone_id INTEGER NOT NULL REFERENCES gps_geozones(id) ON DELETE CASCADE,
inside BOOLEAN NOT NULL,
updated_at TIMESTAMP DEFAULT NOW(),
PRIMARY KEY (asset_id, geozone_id)
);
-- Группы объектов (для фильтров на карте)
CREATE TABLE IF NOT EXISTS gps_groups (
id SERIAL PRIMARY KEY,
organization_id INTEGER NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS gps_groups_org_idx ON gps_groups(organization_id);
-- Связь групп и объектов (многие-ко-многим)
CREATE TABLE IF NOT EXISTS gps_group_assets (
group_id INTEGER NOT NULL REFERENCES gps_groups(id) ON DELETE CASCADE,
asset_id INTEGER NOT NULL REFERENCES gps_assets(id) ON DELETE CASCADE,
PRIMARY KEY (group_id, asset_id)
);
-- Подписчики на события объекта (уведомления о входе/выходе/офлайне)
CREATE TABLE IF NOT EXISTS gps_asset_subscribers (
id SERIAL PRIMARY KEY,
asset_id INTEGER NOT NULL REFERENCES gps_assets(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
on_enter BOOLEAN DEFAULT TRUE,
on_exit BOOLEAN DEFAULT TRUE,
on_offline BOOLEAN DEFAULT FALSE,
UNIQUE (asset_id, user_id)
);