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

73 lines
2.1 KiB
Go

package repository
import (
"context"
"errors"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/travelswitch/travelhub/backend/internal/models"
)
// TenantBySlug resolves the tenant a caller is signing in to. Suspended
// tenants are treated as absent so their users cannot authenticate.
func (r *Repository) TenantBySlug(ctx context.Context, slug string) (models.Tenant, error) {
const q = `
SELECT id, slug, name, status, created_at
FROM tenants
WHERE slug = $1 AND status = 'active'`
var t models.Tenant
err := r.pool.QueryRow(ctx, q, slug).
Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return models.Tenant{}, ErrNotFound
}
return t, err
}
// ListTenants powers the tenant picker on the sign-in screen. It is the only
// unauthenticated read in the service and deliberately exposes nothing beyond
// the slug and display name.
func (r *Repository) ListTenants(ctx context.Context) ([]models.Tenant, error) {
const q = `
SELECT id, slug, name, status, created_at
FROM tenants
WHERE status = 'active'
ORDER BY name`
rows, err := r.pool.Query(ctx, q)
if err != nil {
return nil, err
}
defer rows.Close()
tenants := []models.Tenant{}
for rows.Next() {
var t models.Tenant
if err := rows.Scan(&t.ID, &t.Slug, &t.Name, &t.Status, &t.CreatedAt); err != nil {
return nil, err
}
tenants = append(tenants, t)
}
return tenants, rows.Err()
}
// UserByEmail looks up a login within one tenant. The tenant filter is what
// stops credentials from one agency authenticating against another.
func (r *Repository) UserByEmail(ctx context.Context, tenantID uuid.UUID, email string) (models.User, error) {
const q = `
SELECT id, tenant_id, email, password_hash, full_name, role, created_at
FROM users
WHERE tenant_id = $1 AND lower(email) = lower($2)`
var u models.User
err := r.pool.QueryRow(ctx, q, tenantID, email).Scan(
&u.ID, &u.TenantID, &u.Email, &u.PasswordHash, &u.FullName, &u.Role, &u.CreatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return models.User{}, ErrNotFound
}
return u, err
}