Angular + Go + PostgreSQL sample with strict per-tenant isolation. - Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool) - Frontend: Angular 22 SPA (search, checkout, bookings, dashboard) - Database: schema, seed data and demo bookings migrations - Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e), plus a generated evidence report with PDF export - Security: whitebox VAPT report under security/ Secrets (.env, tokens, DB credentials) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
123 lines
5.3 KiB
SQL
123 lines
5.3 KiB
SQL
-- Demo reservations, so the dashboard shows a trading picture on first run
|
|
-- instead of an empty state. Both agencies have sold tickets, at their own
|
|
-- fares, on their own inventory.
|
|
--
|
|
-- The whole file is one guarded block: if the demo references are already
|
|
-- present it returns immediately, which keeps seat counts from being
|
|
-- decremented twice on a re-run.
|
|
|
|
DO $$
|
|
DECLARE
|
|
already_seeded boolean;
|
|
BEGIN
|
|
SELECT EXISTS (SELECT 1 FROM bookings WHERE booking_reference = 'QX7A2M')
|
|
INTO already_seeded;
|
|
|
|
IF already_seeded THEN
|
|
RAISE NOTICE 'demo bookings already present, skipping';
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Rank each tenant's future inventory so the same flights are chosen on
|
|
-- every machine, then attach a booking to a handful of them.
|
|
CREATE TEMP TABLE demo_picks ON COMMIT DROP AS
|
|
WITH ranked AS (
|
|
SELECT f.id AS flight_id,
|
|
f.tenant_id,
|
|
f.base_price,
|
|
f.currency,
|
|
t.slug,
|
|
row_number() OVER (PARTITION BY t.slug
|
|
ORDER BY f.departure_time, f.flight_number) AS rn
|
|
FROM flights f
|
|
JOIN tenants t ON t.id = f.tenant_id
|
|
WHERE f.departure_time > now()
|
|
),
|
|
spec(slug, rn, pnr, pax, email, phone, status) AS (
|
|
VALUES
|
|
('skyline', 2, 'QX7A2M', 2, 'harper.quinn@example.com', '+1-212-555-0143', 'confirmed'),
|
|
('skyline', 7, 'MT4RB9', 1, 'd.oyelaran@example.com', '+1-415-555-0199', 'confirmed'),
|
|
('skyline', 15, 'KP2VJ8', 4, 'the.sandovals@example.com','+1-646-555-0117', 'confirmed'),
|
|
('skyline', 23, 'RN9WD3', 2, 'a.lindqvist@example.com', '+44-20-7946-0821','confirmed'),
|
|
('skyline', 31, 'HB6ZC5', 3, 'marco.bellini@example.com','+39-06-555-0164', 'confirmed'),
|
|
('skyline', 44, 'VF3LK7', 1, 'j.nakamura@example.com', '+81-3-5555-0132', 'cancelled'),
|
|
('globetrotter', 4, 'TG8MH2', 2, 'priya.raman@example.com', '+91-22-5555-0178','confirmed'),
|
|
('globetrotter', 11, 'WS5QN6', 3, 'okoro.family@example.com', '+234-1-555-0155', 'confirmed'),
|
|
('globetrotter', 19, 'ZD7XP4', 1, 'l.fontaine@example.com', '+33-1-5555-0190', 'confirmed'),
|
|
('globetrotter', 27, 'CJ2YR8', 2, 'sam.whitaker@example.com', '+61-2-5555-0146', 'confirmed')
|
|
),
|
|
-- Attribute every demo sale to that tenant's agent account.
|
|
selling_agent AS (
|
|
SELECT DISTINCT ON (tenant_id) tenant_id, id
|
|
FROM users
|
|
WHERE role = 'agent'
|
|
ORDER BY tenant_id, email
|
|
)
|
|
SELECT r.flight_id,
|
|
r.tenant_id,
|
|
a.id AS user_id,
|
|
s.pnr,
|
|
s.pax,
|
|
s.email,
|
|
s.phone,
|
|
s.status,
|
|
ROUND(r.base_price * s.pax, 2) AS total_amount,
|
|
r.currency
|
|
FROM spec s
|
|
JOIN ranked r ON r.slug = s.slug AND r.rn = s.rn
|
|
JOIN selling_agent a ON a.tenant_id = r.tenant_id;
|
|
|
|
INSERT INTO bookings (tenant_id, user_id, flight_id, booking_reference,
|
|
passenger_count, total_amount, currency, status,
|
|
contact_email, contact_phone, created_at)
|
|
SELECT p.tenant_id, p.user_id, p.flight_id, p.pnr, p.pax, p.total_amount,
|
|
p.currency, p.status, p.email, p.phone,
|
|
-- Spread creation times over the past three weeks.
|
|
now() - ((row_number() OVER (ORDER BY p.pnr) * 47) || ' hours')::interval
|
|
FROM demo_picks p;
|
|
|
|
-- Give each booking its own travellers from a small pool of names.
|
|
INSERT INTO passengers (tenant_id, booking_id, first_name, last_name,
|
|
date_of_birth, passport_number)
|
|
SELECT b.tenant_id,
|
|
b.id,
|
|
n.first_name,
|
|
n.last_name,
|
|
DATE '1970-01-01' + ((n.idx * 1447) || ' days')::interval,
|
|
n.passport
|
|
FROM (
|
|
SELECT bk.id, bk.tenant_id, bk.passenger_count,
|
|
row_number() OVER (ORDER BY bk.booking_reference) AS bn
|
|
FROM bookings bk
|
|
JOIN demo_picks dp ON dp.pnr = bk.booking_reference
|
|
AND dp.tenant_id = bk.tenant_id
|
|
) b
|
|
CROSS JOIN LATERAL generate_series(1, b.passenger_count) AS g(i)
|
|
JOIN (VALUES
|
|
(0, 'Harper', 'Quinn', 'P1029384'),
|
|
(1, 'Milo', 'Vance', 'P5568211'),
|
|
(2, 'Ada', 'Okonjo', 'P7741903'),
|
|
(3, 'Ben', 'Sorensen', 'P3310927'),
|
|
(4, 'Ines', 'Marchetti', 'P9084417'),
|
|
(5, 'Theo', 'Nakamura', 'P2276650'),
|
|
(6, 'Rania', 'Haddad', 'P6693108'),
|
|
(7, 'Oscar', 'Lindqvist', 'P4417789'),
|
|
(8, 'Nadia', 'Petrov', 'P8820134'),
|
|
(9, 'Caleb', 'Mwangi', 'P3345562'),
|
|
(10, 'Sofia', 'Delgado', 'P7719026'),
|
|
(11, 'Arjun', 'Raman', 'P5502943')
|
|
) AS n(idx, first_name, last_name, passport)
|
|
ON n.idx = (b.bn * 5 + g.i) % 12;
|
|
|
|
-- Confirmed sales consume seats; cancelled ones do not.
|
|
UPDATE flights f
|
|
SET seats_available = GREATEST(0, f.seats_available - sold.pax)
|
|
FROM (SELECT flight_id, SUM(pax) AS pax
|
|
FROM demo_picks
|
|
WHERE status = 'confirmed'
|
|
GROUP BY flight_id) sold
|
|
WHERE f.id = sold.flight_id;
|
|
|
|
RAISE NOTICE 'demo bookings created';
|
|
END $$;
|