Eventiy is a full-stack event ticketing platform for browsing, creating, managing, reserving, and booking tickets for events such as matches, parties, conferences, concerts, theater shows, food events, education events, outdoor gatherings, and other live experiences.
The backend is built with .NET 9 using Clean Architecture, Domain-Driven Design, CQRS, MediatR, FluentValidation, EF Core 9, SQL Server, the Result pattern, JWT authentication with refresh tokens, Redis cache-aside, Redis Pub/Sub, WebSockets, Stripe Checkout, local file storage, and the Transactional Outbox pattern. The frontend is an Angular 19 standalone-component app that uses signals, lazy routes, layered frontend Clean Architecture, D3 seating-map rendering, HTTP/application services, guards, interceptors, and Tailwind CSS.
For the detailed architecture specification and workflow trace, see EVENTIY_ARCHITECTURE.md.
- Features
- Architecture at a glance
- Solution structure
- Backend design
- Frontend design
- API overview
- Tech stack
- Getting started
- Configuration
- Database and seed data
- Running the application
- Development commands
- Testing
- Project conventions
- Documentation
- Browse paginated events.
- Filter events by event type/category:
Music,Tech,Sports,Art,Food,Education,Theater, andOutdoors. - Find nearby events with latitude, longitude, and distance query parameters.
- View event details, ticket types, lowest ticket price, sold/available capacity, location, cover photo, and photo gallery.
- Create, update, publish, cancel, and manage events as an organizer or admin.
- Use admin override endpoints for event updates, ticket-type creation, and event publication.
- Upload, delete, reorder, update metadata for, and set cover photos for events.
- Store event photos locally under the API
wwwrootupload directory.
- Add ticket types with name, price, currency, and capacity.
- Track ticket inventory through reserved, sold, available, and unavailable counts.
- Reserve seats atomically when a booking is created.
- Confirm reservations and move seats from reserved to sold.
- Release reserved seats on pending-booking cancellation or expiration.
- Refund sold seats when confirmed bookings are cancelled/refunded.
- Support pending, pending-payment, confirmed, cancelled, expired, and refunded booking states.
- Support attendee self-cancellation for allowed bookings and admin/organizer booking management.
- View bookings by current user, by event, and through admin all-bookings endpoints.
- Show booking details at
/bookings/:idin the Angular app.
- Support
Instantpayments through Stripe Checkout. - Support
Deferredpayments with Fawry-style reference codes such asFAW-XXXXXXXX. - Store refreshable payment/booking hold windows: short holds for instant payments and longer holds for deferred payments.
- Persist the booking and reserved seats before calling Stripe, so the local transaction is always the first durable source of truth.
- Use deterministic payment idempotency keys in the
payment-initiate:{bookingId}shape when creating checkout sessions. - Stage durable compensation records when payment initiation fails or throws, then retry cancellation asynchronously instead of relying on inline catch-block cleanup.
- Confirm Stripe payments through the signed webhook endpoint as the sole confirmer for instant bookings.
- Confirm deferred payments through a backend command using the generated reference code.
- Use
MockPaymentGatewayfor local development whenStripe:UseMock=true.
- Use a D3-powered Angular seating chart for venue/section/seat selection.
- Lock and unlock seats over WebSockets at
/ws/venues/{eventId}. - Broadcast seat-state deltas across API instances with Redis Pub/Sub channels named
seats:event:{eventId}. - Handle collision responses when another user reserves a seat first.
- Use frontend signals for selected seats, pending locks, connection status, deltas, and reconnection state.
- Navigate from the seating chart to the created booking after checkout.
- Register and log in with JWT bearer authentication.
- Issue refresh tokens, store them as HttpOnly secure cookies, and rotate them through
/api/auth/refresh. - Revoke refresh tokens through
/api/auth/revoke. - Support
Admin,Organizer, andAttendeeroles. - Require organizer approval before organizer accounts can fully operate.
- Protect backend endpoints with role-based
[Authorize]rules. - Protect Angular routes with auth and role guards.
- Store frontend auth state in
sessionStorageinstead oflocalStorage.
- Return expected business and validation failures through
Resultand typedErrorvalues. - Run FluentValidation through a MediatR validation pipeline.
- Run request/use-case logging through a MediatR logging pipeline.
- Map unhandled exceptions to RFC 7807-style
ProblemDetailsresponses. - Add and propagate correlation IDs through
X-Correlation-Id. - Persist domain events using the Transactional Outbox pattern in the same database transaction as aggregate changes.
- Process outbox messages with a background worker, retries, exponential backoff, and dead-letter storage.
- Requeue dead-letter outbox messages through admin endpoints.
- Guard domain-event consumers, Stripe webhooks, and payment initiation with idempotency records.
- Use SQL Server
rowversionoptimistic concurrency plus retry loops for high-contention booking flows. - Expire stale pending and pending-payment bookings and release seats in a background job.
- Reconcile expired instant bookings by cancelling orphaned Stripe sessions in a background job.
- Process durable compensation logs with retry backoff and dead-letter fallback for external payment cleanup.
- Use Redis cache-aside for event list, event details, and event photos with graceful degradation if Redis is unavailable.
Browser / Angular 19 UI
|
v
Angular application services, signals, guards, interceptors, D3 seating chart
|
v
Eventy.WebApi controllers, middleware, HTTP API, WebSocket gateway
|
v
MediatR commands, queries, validators, pipeline behaviors
|
v
Domain aggregates, entities, value objects, domain events, Result objects
|
v
Infrastructure: EF Core, SQL Server, Redis, Stripe, JWT, file storage, outbox, jobs
Dependency direction follows Clean Architecture:
Eventy.WebApi -> Application -> Domain
Eventy.WebApi -> Infrastructure -> Application -> Domain
Infrastructure -> Application -> Domain
EventiyApp -> Eventy.WebApi HTTP/WebSocket API
The Domain project is the innermost layer. It has no ASP.NET Core, EF Core, MediatR, Redis, Stripe, or Angular dependency. Application code owns use-case orchestration; Infrastructure owns external I/O.
Eventiy.sln
├── Domain/ # Pure DDD model, Result, typed errors, domain events
├── Application/ # CQRS commands/queries, handlers, validators, abstractions
├── Infrastructure/ # EF Core, auth, caching, payments, real-time, outbox, jobs
├── Eventy.WebApi/ # ASP.NET Core API, controllers, middleware, OpenAPI/Scalar
├── EventiyApp/ # Angular 19 frontend
├── tests/ # Domain, application, integration, concurrency test projects
├── EVENTIY_ARCHITECTURE.md # Enterprise architecture specification and workflow trace
├── ABOUT.md # Short project overview
└── README.md # Project introduction and setup guide
| Project | Responsibility |
|---|---|
Domain |
Enterprise business rules: Event, TicketType, EventPhoto, Booking, User, RefreshToken, value objects, typed errors, domain events, and Result types. |
Application |
Use cases: MediatR commands/queries, handlers, FluentValidation validators, pipeline behaviors, repository/security/cache/payment/outbox abstractions. |
Infrastructure |
External details: EF Core ApplicationDbContext, migrations, repositories, JWT, BCrypt password hashing, Redis caching, Redis Pub/Sub, Stripe/mock payments, durable compensation logs, local file storage, outbox processing, background jobs, seed data. |
Eventy.WebApi |
Presentation layer: controllers, middleware, CORS, JSON options, OpenAPI, Scalar API reference, static file serving, WebSocket gateway. |
| Folder | Responsibility |
|---|---|
EventiyApp/src/app/core |
Frontend domain models, enums, mappers, and route guards. |
EventiyApp/src/app/application/http |
HTTP services that call backend APIs and return frontend Result<T> values. |
EventiyApp/src/app/application/services |
Use-case orchestration, mapping, caching, auth session state, booking/event workflows, seating services. |
EventiyApp/src/app/infrastructure |
Interceptors, directives, toast service, and session storage. |
EventiyApp/src/app/features |
Page-level features: home, auth, events, dashboards, admin outbox, booking detail, D3 seating chart. |
EventiyApp/src/app/shared |
Reusable UI components such as cards, navbar, footer, photo uploader, lightbox, skeleton loader, and pipes. |
EventiyApp/src/app/presentation |
Error pages such as unauthorized and not-found screens. |
The domain model centers on these aggregates and entities:
Event: event lifecycle, type/category, date, location, capacity, ticket types, photos, publication/cancellation/completion state, and row-version concurrency token.TicketType: ticket name, price, currency, capacity, reserved count, sold count, and availability rules. The application layer also contains venue-layout validation for section-aware ticketing flows.EventPhoto: uploaded image metadata, public URL, caption, display order, and cover-photo state.Booking: user reservation, ticket type, quantity, total amount, payment method, reference code, hold expiry, pending-payment transition, confirmation/cancellation/refund/expiry state, and row-version concurrency token.User: identity, role, password hash, organizer approval state, refresh tokens, and row-version concurrency token.RefreshToken: hashed refresh-token lifecycle, expiry, revocation, and replacement tracking.
Important value objects include:
EventId,TicketTypeId,BookingId,EventPhotoId,UserIdEventNameAddresswith optional latitude and longitudeMoneyEmailRole
Domain methods return Result or Result<T> for expected business failures. Domain events are raised directly from aggregates with new XxxEvent(...) calls. Domain methods receive DateTime utcNow from the Application layer instead of reading system time directly.
Application use cases are split into commands and queries:
- Commands implement
ICommandorICommand<TResponse>. - Queries implement
IQuery<TResponse>. - Handlers return
ResultorResult<TResponse>. LoggingPipelineBehaviorrecords execution details.ValidationPipelineBehaviorruns FluentValidation validators and creates typed Result failures without reflection.
Examples:
- Authentication:
RegisterUserCommand,LoginCommand,RefreshTokenCommand,ApproveOrganizerCommand - Events:
CreateEventCommand,UpdateEventCommand,PublishEventCommand,CancelEventCommand,AddTicketTypeCommand, photo commands - Bookings:
CreateBookingCommand,ConfirmBookingCommand,ConfirmBookingFromWebhookCommand,ConfirmDeferredPaymentCommand,CancelBookingCommand - Queries:
GetEventsQuery,GetEventDetailsQuery,GetEventPhotosQuery,GetAllBookingsQuery,GetBookingsByUserQuery,GetBookingDetailsQuery,GetBookingByEventQuery
Infrastructure uses EF Core 9 with SQL Server:
ApplicationDbContextfor writes and outbox persistence.ReadDbContextAdapter/IApplicationReadDbContextfor no-tracking read-side queries.- Repository implementations for events, bookings, users, event photos, outbox messages, and idempotency state.
- SQL Server
rowversioncolumns for optimistic concurrency. - Fluent API configurations in
Infrastructure/Persistence/Configuration. - Migrations in
Infrastructure/Migrations.
UnitOfWork.CommitAsync() extracts domain events from tracked aggregate roots, stores them as outbox messages, and commits aggregate changes plus outbox rows atomically. OutboxProcessor polls pending messages, locks batches, dispatches typed domain-event handlers, retries failures, and moves exhausted/non-retryable messages to dead letters.
ICacheService abstracts cache operations. RedisCacheService implements it with StackExchange.Redis and degrades gracefully if Redis is unavailable.
Current cache usage includes:
| Read model | TTL | Key shape |
|---|---|---|
| Event list | 30 seconds | events:list:{page}:{size}:{type}:{lat}:{lng}:{dist} |
| Event details | 60 seconds | event:details:{eventId} |
| Event photos | 120 seconds | event:photos:{eventId} |
Booking queries are intentionally uncached because they contain personal/financial data and mutate frequently. Cache invalidation runs after successful commits.
IPaymentService has two implementations:
StripePaymentGatewayfor production Stripe Checkout sessions and session cancellation.MockPaymentGatewayfor local development and tests.
Instant booking uses a pending-first flow:
Reserve seats -> create Pending booking -> commit booking + outbox atomically
-> initiate Stripe checkout with idempotency key payment-initiate:{bookingId}
-> webhook confirms paid checkout sessions
The booking and seat reservation are committed before the external Stripe call. If payment initiation fails after the local commit, the handler stages a durable compensation record and a background processor retries CancelPaymentAsync with backoff before moving exhausted records to dead letters.
The Stripe webhook is the source of truth for confirming instant bookings. BookingCreatedEventHandler marks the creation event as processed and does not confirm instant bookings itself, avoiding a race between outbox delivery and webhook delivery.
Deferred bookings generate a Fawry-style reference code and can be confirmed with ConfirmDeferredPaymentCommand.
The API exposes a WebSocket gateway at:
/ws/venues/{eventId}?token={jwt}
The WebSocket middleware:
- Accepts venue-scoped connections.
- Authenticates the user from a query-string JWT.
- Sends
CONNECTED,ACK,DELTA,COLLISION, and heartbeat messages. - Applies LOCK/UNLOCK seat messages against the
Eventaggregate. - Uses Redis Pub/Sub to broadcast seat-state changes across instances.
| Job | Interval | Purpose |
|---|---|---|
OutboxProcessor |
5 seconds | Polls and dispatches outbox messages. |
BookingExpirationJob |
1 minute | Expires pending/pending-payment bookings past hold time and releases reserved seats. |
PaymentReconciliationJob |
2 minutes | Cancels orphaned Stripe checkout sessions for expired instant bookings. |
CompensationProcessor |
10 seconds | Retries durable payment cleanup records and dead-letters exhausted entries. |
LocalFileStorageService stores uploaded event photos under:
Eventy.WebApi/wwwroot/uploads/events
Allowed image types are JPEG, PNG, and WebP. The maximum file size is 5 MB per image.
The Angular app uses standalone components, lazy routes, Angular signals, and layered frontend Clean Architecture.
core contains frontend domain types and pure mapping logic:
- Event, booking, auth, ticket, and result models.
- Event status, booking status, and user role enums.
- Backend DTO to frontend model mappers.
- Auth and role route guards.
The application layer separates HTTP access from orchestration:
- HTTP services include auth, events, event photos, bookings, admin events, and admin outbox.
- Application services map responses, manage cache/session state, expose use-case methods, and coordinate booking/event workflows.
EventApplicationServicestores user location, nearby filtering state, and short-lived event-list cache.BookingApplicationServicehandles booking creation, cancellation, details, and deferred-payment confirmation.
Cross-cutting frontend infrastructure includes:
auth.interceptorto attach JWT bearer tokens.error.interceptorto map HTTP errors to toasts and auth redirects.ToastServicewith Angular signals.SessionStorageServicefor session-scoped auth state.ImgFallbackDirectivefor image fallback behavior.
The seating chart feature uses:
VenueViewStorefor selected seats, venue data, filters, computed totals, and server deltas.VenueGraphRendererServicefor SVG rendering, zoom/pan, block highlighting, seat updates, and collision flashes.LiveStateSyncServicefor WebSocket connection, heartbeat, reconnects, deltas, collisions, and ACK streams.SeatLockOrchestratorServicefor optimistic seat locking and collision rollback.SeatingChartComponentto wire store, renderer, live sync, checkout, and booking creation.
The frontend includes lazy routes for:
/— home/login— login/register— registration/events— event listing/events/create— organizer/admin event creation/events/:id— event details/events/:id/edit— organizer/admin event editing/events/:id/seats— seating chart and seat selection/dashboard/organizer— organizer dashboard/dashboard/attendee— attendee bookings dashboard/bookings/:id— booking details/admin/outbox/dead-letters— admin dead-letter management/unauthorized— unauthorized page- wildcard not-found route
The Angular development server runs on port 57354 and proxies API requests through proxy.conf.json.
Controller base paths use ASP.NET Core [Route("api/[controller]")], so routes are shown lowercase for consistency. ASP.NET Core route matching is case-insensitive by default.
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/event/{id} |
Public | Get event details. |
GET |
/api/event?page=1&pageSize=20&type=Sports&userLatitude=...&userLongitude=...&distanceInKm=20 |
Public | Get paginated events with optional type and nearby filters. |
POST |
/api/event |
Organizer/Admin | Create an event. |
PUT |
/api/event/{id} |
Organizer/Admin | Update event details. |
POST |
/api/event/{eventId}/ticket-types |
Organizer/Admin | Add a ticket type. |
PUT |
/api/event/{id}/cancel |
Organizer/Admin | Cancel an event. |
GET |
/api/event/{id}/photos |
Public | Get event photos. |
POST |
/api/event/{id}/photos |
Organizer/Admin | Upload event photos with multipart/form-data. |
DELETE |
/api/event/{id}/photos/{photoId} |
Organizer/Admin | Delete an event photo. |
PUT |
/api/event/{id}/photos/{photoId}/cover |
Organizer/Admin | Set the cover photo. |
PUT |
/api/event/{id}/photos/{photoId}/metadata |
Organizer/Admin | Update photo caption/display order. |
PUT |
/api/event/{id}/photos/reorder |
Organizer/Admin | Reorder photos. |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/booking/{id} |
Any authenticated user | Get booking details. |
GET |
/api/booking/event/{eventId} |
Any authenticated user | Get bookings for an event. |
POST |
/api/booking |
Any authenticated user | Create a booking and optionally initiate payment. |
POST |
/api/booking/{bookingId}/confirm |
Any authenticated user | Confirm a booking. |
PUT |
/api/booking/{id}/cancel |
Any authenticated user | Cancel a booking. |
GET |
/api/booking/my |
Any authenticated user | Get bookings for the current user. |
POST |
/api/booking/confirm-deferred |
Any authenticated user | Confirm deferred payment by reference code. |
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/api/auth/register |
Public | Register a user and set refresh-token cookie when applicable. |
POST |
/api/auth/login |
Public | Log in, return auth response, and set refresh-token cookie. |
POST |
/api/auth/refresh |
Public | Rotate refresh token and issue a new access token. |
POST |
/api/auth/revoke |
Authenticated | Delete refresh-token cookie and revoke token flow. |
POST |
/api/auth/organizers/{userId}/approve |
Admin | Approve an organizer account. |
| Method | Route | Auth | Description |
|---|---|---|---|
PUT |
/api/admin/events/{id} |
Admin | Admin update event. |
POST |
/api/admin/events/{eventId}/ticket-types |
Admin | Admin add ticket type. |
POST |
/api/admin/events/{eventId}/publish |
Admin | Admin publish event. |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/admin/bookings?status=Pending |
Admin | List all bookings, optionally filtered by status. |
POST |
/api/admin/bookings/{bookingId}/confirm |
Admin | Admin confirm booking. |
PUT |
/api/admin/bookings/{bookingId}/cancel |
Admin | Admin cancel booking. |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/admin/outbox/dead-letters |
Admin | List failed outbox messages. |
POST |
/api/admin/outbox/dead-letters/{id}/requeue |
Admin | Requeue a dead-letter message. |
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/api/webhooks/stripe |
Stripe signature | Verify Stripe signature and confirm paid checkout sessions. |
| Endpoint | Auth | Description |
|---|---|---|
/ws/venues/{eventId}?token={jwt} |
Query-string JWT | Real-time seat lock/unlock and seat-state deltas for one event. |
- .NET 9
- ASP.NET Core Web API
- EF Core 9
- SQL Server 2022-compatible database
- MediatR 14.1.0
- FluentValidation 12.1.1
- JWT Bearer authentication
- BCrypt.Net-Next
- StackExchange.Redis 2.8.31
- Stripe.net 52.1.0
- Scalar API reference
- Angular 19
- Standalone components
- Angular signals
- D3 7.9
- Angular CDK
- RxJS
- Tailwind CSS
- Karma/Jasmine test setup
- xUnit
- FluentAssertions
- NSubstitute
- Microsoft.AspNetCore.Mvc.Testing
- Testcontainers.MsSql
- Respawn
- WireMock.Net reference
- .NET 9 SDK
- Node.js and npm
- SQL Server or a SQL Server-compatible database
- Optional: Redis on
localhost:6379for cache and real-time Pub/Sub support - Optional: EF Core CLI for migrations
- Optional: Stripe CLI for local webhook testing
Install EF Core CLI if needed:
dotnet tool install --global dotnet-efdotnet restore Eventiy.slncd EventiyApp
npm installdotnet build Eventy.WebApicd EventiyApp
npm run buildUpdate Eventy.WebApi/appsettings.json, user secrets, or environment variables for your local machine.
Required SQL Server connection string:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=EventiyDB;Trusted_Connection=True;TrustServerCertificate=True"
}
}Optional Redis connection string:
{
"ConnectionStrings": {
"Redis": "localhost:6379"
}
}If Redis is unavailable, cache operations log and degrade gracefully. Real-time Pub/Sub requires Redis for multi-instance broadcasting.
Infrastructure.DependencyInjection requires Jwt:Secret to exist and be at least 32 characters. The repository intentionally does not store this secret in appsettings.json.
For local development, use .NET user secrets:
dotnet user-secrets set "Jwt:Secret" "replace-with-a-local-development-secret-at-least-32-characters" --project Eventy.WebApi
dotnet user-secrets set "Jwt:Issuer" "Eventiy.Api" --project Eventy.WebApi
dotnet user-secrets set "Jwt:Audience" "Eventiy.Client" --project Eventy.WebApi
dotnet user-secrets set "Jwt:ExpiryMinutes" "1440" --project Eventy.WebApiDo not commit real secrets.
For local development without Stripe, enable the mock gateway:
dotnet user-secrets set "Stripe:UseMock" "true" --project Eventy.WebApiFor Stripe Checkout and webhooks, configure real settings through user secrets or environment variables:
dotnet user-secrets set "Stripe:UseMock" "false" --project Eventy.WebApi
dotnet user-secrets set "Stripe:SecretKey" "sk_test_..." --project Eventy.WebApi
dotnet user-secrets set "Stripe:WebhookSecret" "whsec_..." --project Eventy.WebApi
dotnet user-secrets set "Stripe:SuccessUrl" "http://127.0.0.1:57354/payment/success" --project Eventy.WebApi
dotnet user-secrets set "Stripe:CancelUrl" "http://127.0.0.1:57354/payment/cancel" --project Eventy.WebApiWhen Stripe:UseMock=false, the API fails startup if SecretKey, WebhookSecret, SuccessUrl, or CancelUrl are missing.
Development frontend API URL:
EventiyApp/src/environments/environment.ts
Default development value:
apiUrl: 'https://localhost:7001/api'Production frontend API URL:
EventiyApp/src/environments/environment.prod.ts
Default production value:
apiUrl: '/api'Apply migrations:
dotnet ef database update --project Infrastructure --startup-project Eventy.WebApiDatabaseSeederService runs from the API host, applies migrations, and seeds local/demo data when needed.
Seed data includes:
- Admin user
- Approved and pending organizers
- Multiple attendees
- 18 events across all event types with real coordinates
- Venue-mapped ticket types and realistic pricing
- Bookings using a mix of instant and deferred payment methods, including payment holds that can expire
The seed data is for local development and demo workflows.
dotnet run --project Eventy.WebApiDevelopment features:
- OpenAPI endpoint mapping
- Scalar API reference
- CORS for
localhostand127.0.0.1on Angular ports4200and57354 - Static file serving for uploaded event images
- WebSocket endpoint for venue seat updates
- Hosted background jobs for outbox processing, booking expiration, and payment reconciliation
The backend is expected at:
https://localhost:7001
cd EventiyApp
npm startThe Angular app runs at:
http://127.0.0.1:57354
From the repository root:
# Restore backend dependencies
dotnet restore Eventiy.sln
# Build backend API and referenced projects
dotnet build Eventy.WebApi
# Apply EF Core migrations
dotnet ef database update --project Infrastructure --startup-project Eventy.WebApi
# Run backend
dotnet run --project Eventy.WebApiFrom EventiyApp:
# Install frontend dependencies
npm install
# Run Angular dev server
npm start
# Build Angular app
npm run build
# Run Angular unit tests
npm testThe repository includes backend unit, integration, and concurrency tests:
tests/
├── Eventy.Domain.UnitTests/ # Pure domain/value-object tests
├── Eventy.Application.UnitTests/ # Handler tests with mocked dependencies
├── Eventy.IntegrationTests/ # Full HTTP pipeline with Testcontainers SQL Server
├── Eventy.ConcurrencyTests/ # Race-condition and high-contention booking tests
└── Eventy.Testing.Foundation/ # Shared factories, builders, fakes, and test auth
Useful commands:
# Run all .NET tests
dotnet test Eventiy.sln
# Run one test project
dotnet test tests/Eventy.Domain.UnitTests/Eventy.Domain.UnitTests.csproj
# Run Angular unit tests
cd EventiyApp
npm testIntegration and concurrency tests use SQL Server containers and may require Docker.
POST /api/auth/register
Content-Type: application/json
{
"firstName": "Sara",
"lastName": "Attendee",
"email": "sara@example.com",
"password": "StrongPassword123!",
"role": "Attendee"
}POST /api/auth/login
Content-Type: application/json
{
"email": "sara@example.com",
"password": "StrongPassword123!"
}POST /api/auth/refresh
Cookie: refreshToken={refresh-token-cookie}POST /api/event
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Championship Match",
"capacity": 50000,
"date": "2027-12-20T18:00:00Z",
"location": {
"country": "Egypt",
"city": "Cairo",
"street": "Stadium Road",
"latitude": 30.0444,
"longitude": 31.2357
},
"description": "Football championship final",
"type": "Sports"
}POST /api/event/{eventId}/ticket-types
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "VIP",
"amount": 250,
"currency": "USD",
"capacity": 100
}POST /api/booking
Authorization: Bearer {token}
Content-Type: application/json
{
"eventId": "00000000-0000-0000-0000-000000000000",
"ticketTypeId": "00000000-0000-0000-0000-000000000000",
"quantity": 2,
"paymentMethod": "Instant"
}POST /api/booking/confirm-deferred
Authorization: Bearer {token}
Content-Type: application/json
{
"referenceCode": "FAW-1A2B3C4D"
}POST /api/webhooks/stripe
Stripe-Signature: {stripe-signature}
Content-Type: application/json
{ ...checkout.session.completed payload... }- Domain remains framework-free: no EF Core, ASP.NET Core, MediatR, Redis, Stripe, or infrastructure dependencies.
- Domain must not contain authorization rules; roles are enforced in controllers and application handlers.
- Business validation uses
Resultand typedErrorvalues instead of exceptions. - Domain operations receive
DateTime utcNowfrom the Application layer. - Domain events are raised directly from aggregates and persisted through the outbox.
- Application handlers orchestrate use cases and call repositories through interfaces.
- Commands and queries are mediated through MediatR.
- FluentValidation validators live in the Application layer.
- Cache invalidation should happen after successful commits.
- Payment initiation must commit local booking state before external payment calls, use deterministic idempotency keys, and stage durable compensation for failed external cleanup.
- Angular HTTP services stay thin; application services map, cache, and orchestrate.
- Angular components should use standalone APIs, signals where appropriate, and route guards for protected pages.
Both the backend (.NET 9 Web API) and the frontend (Angular 19) are containerized via multi-stage Dockerfiles at the repository root. The build context for both is the repo root — this is required for the backend because Eventy.WebApi.csproj references its sibling Clean Architecture projects (../Application, ../Domain, ../Infrastructure).
| File | Purpose | Build context |
|---|---|---|
Dockerfile.backend |
Multi-stage .NET 9 build → aspnet:9.0 runtime on port 8080 (non-root) |
repo root |
Dockerfile.frontend |
Node 22 build → nginxinc/nginx-unprivileged:1.27-alpine serving Angular + reverse-proxying /api to the backend |
repo root |
EventiyApp/nginx.conf |
SPA fallback (try_files … /index.html), hashed-asset caching, /api/ reverse proxy with WebSocket upgrade |
consumed by Dockerfile.frontend |
.dockerignore |
Trims bin/, obj/, node_modules/, dist/, .git/, IDE folders from the build context |
repo root |
.gitlab-ci.yml |
Pipeline: unit tests → (integration tests w/ DinD) → build & push to the GitLab Container Registry | repo root |
# Backend — build & run on http://localhost:8080
docker build -f Dockerfile.backend -t eventy-backend .
docker run --rm -p 8080:8080 \
-e ConnectionStrings__DefaultConnection="..." \
-e ConnectionStrings__Redis="host.docker.internal:6379" \
eventy-backend
# Frontend — build & run on http://localhost:8080
docker build -f Dockerfile.frontend -t eventy-frontend .
docker run --rm -p 8080:8080 \
-e EVENTY_API_UPSTREAM=backend:8080 \
eventy-frontendTo run them together locally, give the backend container the network alias backend (e.g. docker run --name backend --network eventy eventy-backend), so the frontend's default EVENTY_API_UPSTREAM=backend:8080 resolves.
- TLS is terminated at the Azure edge (Front Door / App Gateway / App Service front-end). The backend listens on plain HTTP (8080) inside the container and uses
ForwardedHeadersmiddleware (Program.cs) to recover the original client scheme/IP.UseHttpsRedirectionis disabled outside Development to avoid redirect loops behind the proxy. - The frontend image is the public entry point. Nginx serves the SPA and proxies
/api/*,/scalar/,/openapi.jsonto the backend container over the internal network. The backend should not be exposed directly to the internet. - Override the backend upstream at deploy time via the
EVENTY_API_UPSTREAMenv var (no rebuild needed): e.g.EVENTY_API_UPSTREAM=10.0.0.5:8080.
Images are pushed to the GitLab Container Registry using GitLab's auto-injected variables ($CI_REGISTRY, $CI_REGISTRY_IMAGE, $CI_REGISTRY_USER, $CI_REGISTRY_PASSWORD). No manual CI/CD variables need to be set for registry auth — the namespace (including any numeric suffix GitLab adds, e.g. sameer-group6045629) is resolved automatically via $CI_REGISTRY_IMAGE.
Deploying to Azure? If the runtime target (App Service / AKS / Container Apps) is on Azure, you'll need to give it a deploy token or credentials to pull from the GitLab registry. The alternative — Azure Container Registry — needs no cross-tenant auth in that case; switch the
:imagejobs back to$AZURE_ACR_SERVERwith the threeAZURE_ACR_*variables if you prefer.
The pipeline runs: backend-unit-tests + frontend-build-check (every MR) → backend-integration-tests w/ Docker-in-Docker for Testcontainers (master/tags only) → backend:image + frontend:image buildx build & push with registry layer caching (--cache-from/--cache-to) → release:tag promotes :<sha> → :<tag> on version tags.
Image tags pushed: :<commit-sha> (immutable, deployable, enables safe rollback) and :latest (rolling).
EVENTIY_ARCHITECTURE.md: full enterprise architecture specification, pattern matrix, and end-to-end booking workflow trace.ABOUT.md: short product summary and project vision.EventiyApp/README.md: Angular CLI generated frontend notes.