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>
144 lines
5.1 KiB
Go
144 lines
5.1 KiB
Go
// Package models holds the API-facing representations of TravelHub data.
|
|
// JSON field names are camelCase so the Angular client can consume them
|
|
// without a translation layer.
|
|
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Tenant is one travel agency using the platform.
|
|
type Tenant struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Slug string `json:"slug"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// User belongs to exactly one tenant. Email is unique per tenant, not globally,
|
|
// so the same person can hold accounts at several agencies.
|
|
type User struct {
|
|
ID uuid.UUID `json:"id"`
|
|
TenantID uuid.UUID `json:"tenantId"`
|
|
Email string `json:"email"`
|
|
FullName string `json:"fullName"`
|
|
Role string `json:"role"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
// PasswordHash never leaves the server.
|
|
PasswordHash string `json:"-"`
|
|
}
|
|
|
|
// Airport is global reference data, shared by every tenant.
|
|
type Airport struct {
|
|
ID uuid.UUID `json:"id"`
|
|
IATACode string `json:"iataCode"`
|
|
Name string `json:"name"`
|
|
City string `json:"city"`
|
|
Country string `json:"country"`
|
|
Timezone string `json:"timezone"`
|
|
}
|
|
|
|
// Airline is global reference data, shared by every tenant.
|
|
type Airline struct {
|
|
ID uuid.UUID `json:"id"`
|
|
IATACode string `json:"iataCode"`
|
|
Name string `json:"name"`
|
|
LogoURL string `json:"logoUrl,omitempty"`
|
|
}
|
|
|
|
// Flight is tenant-scoped inventory: each agency loads its own fares, so the
|
|
// same physical route carries a different price per tenant.
|
|
type Flight struct {
|
|
ID uuid.UUID `json:"id"`
|
|
FlightNumber string `json:"flightNumber"`
|
|
Airline Airline `json:"airline"`
|
|
Origin Airport `json:"origin"`
|
|
Destination Airport `json:"destination"`
|
|
DepartureTime time.Time `json:"departureTime"`
|
|
ArrivalTime time.Time `json:"arrivalTime"`
|
|
DurationMinutes int `json:"durationMinutes"`
|
|
Aircraft string `json:"aircraft"`
|
|
CabinClass string `json:"cabinClass"`
|
|
BasePrice float64 `json:"basePrice"`
|
|
Currency string `json:"currency"`
|
|
SeatsTotal int `json:"seatsTotal"`
|
|
SeatsAvailable int `json:"seatsAvailable"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
// Passenger is one traveller on a booking.
|
|
type Passenger struct {
|
|
ID uuid.UUID `json:"id"`
|
|
FirstName string `json:"firstName"`
|
|
LastName string `json:"lastName"`
|
|
DateOfBirth *time.Time `json:"dateOfBirth,omitempty"`
|
|
PassportNumber string `json:"passportNumber,omitempty"`
|
|
SeatNumber string `json:"seatNumber,omitempty"`
|
|
}
|
|
|
|
// Booking is a confirmed sale against a flight, owned by one tenant.
|
|
type Booking struct {
|
|
ID uuid.UUID `json:"id"`
|
|
BookingReference string `json:"bookingReference"`
|
|
Flight Flight `json:"flight"`
|
|
Passengers []Passenger `json:"passengers"`
|
|
PassengerCount int `json:"passengerCount"`
|
|
TotalAmount float64 `json:"totalAmount"`
|
|
Currency string `json:"currency"`
|
|
Status string `json:"status"`
|
|
ContactEmail string `json:"contactEmail"`
|
|
ContactPhone string `json:"contactPhone,omitempty"`
|
|
BookedBy string `json:"bookedBy"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// FlightSearch carries the filters accepted by GET /api/flights.
|
|
type FlightSearch struct {
|
|
Origin string // IATA code, optional
|
|
Destination string // IATA code, optional
|
|
DepartDate *time.Time // matches the whole calendar day, optional
|
|
CabinClass string // optional
|
|
Passengers int // minimum seats that must still be available
|
|
SortBy string // price | duration | departure
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// PassengerInput is one traveller as supplied by the client. Dates arrive as
|
|
// YYYY-MM-DD strings and are parsed when the booking is written.
|
|
type PassengerInput struct {
|
|
FirstName string `json:"firstName"`
|
|
LastName string `json:"lastName"`
|
|
DateOfBirth string `json:"dateOfBirth"`
|
|
PassportNumber string `json:"passportNumber"`
|
|
}
|
|
|
|
// CreateBookingRequest is the POST /api/bookings payload.
|
|
type CreateBookingRequest struct {
|
|
FlightID uuid.UUID `json:"flightId"`
|
|
ContactEmail string `json:"contactEmail"`
|
|
ContactPhone string `json:"contactPhone"`
|
|
Passengers []PassengerInput `json:"passengers"`
|
|
}
|
|
|
|
// DashboardStats powers the agency overview screen.
|
|
type DashboardStats struct {
|
|
TotalBookings int `json:"totalBookings"`
|
|
ConfirmedBookings int `json:"confirmedBookings"`
|
|
CancelledBookings int `json:"cancelledBookings"`
|
|
TotalRevenue float64 `json:"totalRevenue"`
|
|
Currency string `json:"currency"`
|
|
FlightsInStock int `json:"flightsInStock"`
|
|
SeatsSold int `json:"seatsSold"`
|
|
RecentBookings []Booking `json:"recentBookings"`
|
|
TopRoutes []struct {
|
|
Route string `json:"route"`
|
|
Bookings int `json:"bookings"`
|
|
Revenue float64 `json:"revenue"`
|
|
} `json:"topRoutes"`
|
|
}
|