Files
nilesh.pandClaude Opus 4.8 4d3eb18a6c Initial commit: TravelHub multi-tenant flight booking platform
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>
2026-09-13 11:38:01 +05:30

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 $$;