Skip to content

vladyslavm-dev/auction

Repository files navigation

Auction – Real-Time Art Auction Platform

Backend Frontend Database Cache Real-Time UI TypeScript C# Docker K8s Docker CI/CD K8s CI/CD codecov Live Demo YouTube

A full-stack real-time auction platform for fine art, built as an open-source reference application: atomic bidding with Redis Lua scripts, four-eyes moderation, workspace-isolated multi-tenancy, wallet accounting, automated bidding agents, and hybrid REST + WebSocket communication.

The demo environment is self-seeding: role-based login buttons create isolated workspaces with auctions, wallets, notifications, randomized timers, and bot activity so the full product can be explored immediately.

Landing page
Landing Page
User dashboard
User Dashboard
Auction detail
Auction Detail
Support panel
Support Panel
Admin panel
Admin Panel
Inspection viewer
Inspection Viewer

Features

Atomic Bidding with Redis Lua

Every bid is checked and written inside Redis with a Lua script: newBid > currentPrice and price update happen in one operation. The API holds wallet funds before the Redis call to fail fast, refunds the hold if the bid loses the race, and persists to MongoDB and broadcasts via SignalR only after Redis accepts the bid. Integration tests fire parallel bids against real Redis and assert that only the highest accepted bid wins.

Four-Eyes Moderation (4-Augen-Prinzip)

Suspicious listings move through a two-person moderation path: users report, support freezes, and admins ban or restore. One report per user per auction is enforced server-side, and no single role can both flag and permanently remove content.

Workspace Isolation

Every entity carries an indexed WorkspaceId, and the JWT includes a workspace_id claim. MongoDB queries filter by workspace before business logic runs, so demo sessions and user data remain isolated at the query layer.

Hybrid REST + SignalR

Writes go through authenticated REST endpoints with validation, wallet logic, and RFC 7807 error responses. Live reads such as bid updates and auction endings are pushed through a public read-only SignalR hub so auction pages update without polling.

Self-Seeding Demo Environment

Each demo login creates an isolated workspace from template auctions, users, wallets, notifications, and automated bidding agents. The seeder assigns randomized auction windows and starts five bot bidders so live bidding, outbid alerts, auction endings, wallet holds/refunds, and SignalR updates can be tested immediately.

Bot behavior is intentionally varied:

Profile Purpose
Nibbler Deterministic bidding schedule for user-created auctions
Ambient Low-frequency activity for seeded inventory
Pacer Delayed counter-bids after real user activity

Role-Based Access Control

Role Dashboard Capabilities
USER Card grid with tabs Browse, bid, create auctions, manage wallet
SUPPORT Moderation queue Review reports and freeze suspicious listings
ADMIN Compliance audit panel Ban frozen items, restore items, oversee reports

Wallet System

Wallets support top-up, withdraw, bid holds, outbid refunds, and sale revenue. Withdrawals and deposits use atomic MongoDB updates with cumulative caps, so balance checks and compliance limits cannot race under concurrent requests.

Live UI Feedback

Auction cards use priority-based glow states: gold for outbid, green for activity on your listing, blue for unrelated bid activity, and pink for expiring auctions. Countdown progress uses bounded transform updates instead of layout-heavy width changes.

Notification Bell

Notifications persist in the database, update the toolbar badge, and cover auction won/lost/expired/sold, outbid alerts, seller bid alerts, and wallet operations. Unread count also updates the browser tab title.

Cookie-Based JWT Authentication

JWTs are stored in HttpOnly, Secure, SameSite=Lax cookies, never in browser storage. The frontend never handles raw tokens. A cross-tab guard detects when another tab logs in as a different user and redirects stale sessions.

Input Validation and Security

  • FluentValidation on all request DTOs through a global validation filter.
  • Production-only auth rate limits behind Caddy-forwarded client IP.
  • HTML tag stripping before persistence.
  • Password pre-hash with pepper + bcrypt.
  • Typed MongoDB builders and LINQ only; no raw string queries.

Artwork Inspection Viewer

Preset artwork is generated into thumb and detail WebP variants so repeated surfaces do not ship the raw original asset. The inspection viewer supports bounded zoom, pan, pinch, and loupe behavior with GSAP limited to open/close, snap-back, and tracking transitions.

Cleanup and Sort Persistence

Demo workspaces are hard-deleted after 24 hours by a six-hour Janitor cycle. Dashboard and moderation sort preferences are persisted in URL query parameters, with independent sort state per user dashboard tab.


Architecture at a Glance

┌──────────────────────────────────────────────────────────┐
│                        Caddy                             │
│              TLS termination + reverse proxy             │
│         /backend/* → API:5000    /* → UI:3000            │
└────────────────┬─────────────────────┬───────────────────┘
                 │                     │
    ┌────────────▼──────────┐  ┌───────▼──────────┐
    │   .NET 10 WebAPI      │  │  Next.js 16 SSR  │
    │   Controllers + REST  │  │  App Router      │
    │   SignalR Hub         │  │  React 19        │
    │   Background Services │  │  Material UI v7  │
    │   FluentValidation    │  │  Zustand + RQ5   │
    └───┬──────────┬────────┘  └──────────────────┘
        │          │
   ┌────▼───┐  ┌──▼────┐
   │MongoDB │  │ Redis │
   │  8.0   │  │Alpine │
   └────────┘  └───────┘

Bid flow:

  1. Frontend sends POST /Auctions/{id}/bid.
  2. API validates JWT cookie and workspace scope.
  3. Service places a wallet hold for the bid amount.
  4. Redis Lua performs atomic check-and-set. Lost races refund the hold.
  5. Service persists the bid and refunds the previous highest bidder.
  6. SignalR broadcasts the accepted bid to connected clients.

Core Domain Model

Entity Responsibility
Auction Listing metadata, status, timing, image surface, owner, workspace
Bid Accepted bid history after Redis confirms the winning price
Wallet Balance plus cumulative top-up/withdrawal counters
WalletTransaction Ledger entries for top-up, withdraw, hold, refund, sale revenue
Report User-generated moderation signal with reporter uniqueness guard
Notification Persisted product events for bid, wallet, sale, and moderation flows

The runtime keeps fast-changing price state in Redis and durable business records in MongoDB. MongoDB remains the source of truth for history, moderation, wallet transactions, and workspace-scoped reads.

Why MongoDB

Auction items are polymorphic: paintings, sculptures, and digital pieces carry different attributes. A document model stores each listing as one complete object, maps cleanly to the TypeScript API shape, and avoids EAV tables or joins for auction detail rendering.

Performance Choices

  • Compound indexes on WorkspaceId across all collections.
  • Redis Lua resolves bid contention in one round trip without distributed locks.
  • Targeted reads fetch latest bid with SortByDescending().Limit(1).
  • SignalR pushes live bid and auction ending updates.
  • Shared frontend clock avoids per-card timers.
  • Artwork variants ship optimized thumb and detail surfaces.
  • Inspection viewer keeps zoom/pan in bounded transform math.

Runtime Guardrails

Concern Guardrail
Bid races Redis Lua accepts only one winning price update
Wallet races Atomic MongoDB updates enforce balance and cumulative caps
Demo growth Janitor hard-deletes stale workspaces after the retention window
Cross-tenant reads WorkspaceId is part of every scoped query
Auth brute force Production auth rate limits use forwarded client IP
UI churn Countdown/progress animations use shared clocks and transforms

Framework & Language Features

.NET 10

  • Controller-based WebAPI with attribute routing.
  • IHostedService workers for bots and cleanup.
  • SignalR hub for bid broadcasts and auction endings.
  • RFC 7807 ProblemDetails via global exception handling.
  • Redis Lua scripts for race-free bidding.
  • MongoDB typed filters and compound indexes.

Next.js 16 + React 19

  • App Router with SSR for legal/SEO pages and CSR for interactive dashboards.
  • Zustand for session/theme state and TanStack Query v5 for server state.
  • @microsoft/signalr hooks for live auction updates.
  • Material UI v7 theming with sx and styled().
  • Web Audio API synthesized sounds with no external files.

API Surface

Representative endpoints:

Area Endpoint Pattern
Auth POST /Auth/login, POST /Auth/register, POST /Auth/demo/{role}
Auctions GET /Auctions, POST /Auctions, GET /Auctions/{id}
Bidding POST /Auctions/{id}/bid
Moderation POST /Auctions/{id}/report, freeze, ban, restore
Wallet balance, top-up, withdraw, transaction history
Notifications unread count, list, mark-read, clear-all

SignalR publishes accepted bids and auction ending events; mutation endpoints stay REST-only so validation, authorization, and wallet logic remain centralized.


Project Structure

auction/
├── backend/
│   ├── BackgroundServices/       # bot bidding strategies + Janitor cleanup
│   ├── Controllers/              # auth, auctions, admin, wallet, notifications
│   ├── DTOs/                     # request/response contracts
│   ├── Hubs/                     # SignalR live auction updates
│   ├── Middleware/               # validation + RFC 7807 exception handling
│   ├── Models/                   # auctions, bids, reports, users, wallets
│   ├── Services/                 # bidding, wallet, seeding, JWT, sanitization
│   ├── Tests/                    # unit + integration + Testcontainers
│   └── Validators/               # FluentValidation request guards
│
├── frontend/
│   ├── e2e/                      # Playwright workflows
│   ├── public/images/presets/    # artwork originals + WebP variants
│   ├── scripts/                  # image variant generation
│   └── src/
│       ├── app/                  # Next.js routes and dashboards
│       ├── components/           # cards, panels, wallet, viewer, notifications
│       ├── hooks/                # SignalR + notification hooks
│       └── lib/                  # timing, sounds, query, session, image sources
│
├── k8s/                          # K3s manifest
├── docker-compose.yml            # production compose stack
├── docker-compose.local.yml      # local MongoDB + Redis
└── README.md

Tech Stack

Backend

Component Technology
Framework .NET 10 WebAPI
Language C#
Database MongoDB 8.0
Cache Redis Alpine + Lua
Real-time SignalR
Auth JWT in HttpOnly cookies
Validation FluentValidation
Testing xUnit + NSubstitute + FluentAssertions + Testcontainers

Frontend

Component Technology
Framework Next.js 16
Language TypeScript strict mode
UI Material UI v7
State Zustand + TanStack Query v5
Real-time @microsoft/signalr
Testing Vitest + React Testing Library + Playwright
Audio Web Audio API

Infrastructure

Component Technology
Containers Docker + Docker Compose
Orchestration Kubernetes / K3s
Reverse Proxy Caddy
CI/CD GitHub Actions
Coverage Coverlet OpenCover XML + Codecov

Getting Started

Prerequisites

  • Docker and Docker Compose.
  • .NET 10 SDK.
  • Node.js 24+ and npm.
  • WSL2 recommended on Windows.

Local Development

git clone https://github.com/vladyslavm-dev/auction.git
cd auction
docker compose -f docker-compose.local.yml up -d

Run the backend:

cd backend
dotnet watch run --urls "http://localhost:5000"

Run the frontend:

cd frontend
npm install
npm run dev

Open http://localhost:3000 and use the User, Support, or Admin demo buttons.

Production

Docker Compose and K3s deployment paths are both represented in CI/CD. Deployment guides will be finalized after production hardening.

Deployment Shape

Mode Purpose
docker-compose.local.yml Local MongoDB + Redis for development
docker-compose.yml Production service stack behind Caddy
k8s/auction-manifest.yaml K3s deployment path for the same service boundaries

The application is built as separate API and UI images. Caddy terminates TLS and routes /backend/* to the API while serving the Next.js frontend for all other paths.


API Documentation

All endpoints return RFC 7807 ProblemDetails on error. Auth endpoints are public; mutation endpoints require a valid auction_token cookie.


Testing

Three layers of automated tests cover business logic, data integrity, and full user journeys.

Full Local Test Matrix

(cd backend/Tests && dotnet test)
(cd frontend && npm run test)
(cd frontend && npm run build)
(cd frontend && npm run test:e2e)

Backend -- 93 Tests

Representative coverage:

  • Atomic Redis bidding, persistence, wallet holds/refunds, and contention handling.
  • Seeder and cleanup integration flows with real MongoDB + Redis containers.
  • Auth, demo access, auction creation, reporting, and moderation entry points.
  • Wallet operations, bidding rules, validators, JWT, password hashing, and sanitization.

Frontend -- 56 Tests

Representative coverage:

  • Countdown and expiry behavior.
  • Bid controls and error recovery.
  • Inspection viewer zoom, pan, and responsive behavior.
  • Wallet drawer and live balance refresh.
  • Moderation filters and dashboard default state.

E2E -- 7 Playwright Tests

auction-flow.spec.ts validates auth, bidding, moderation, wallet operations, notifications, image inspection, and responsive behavior.


CI/CD

Two GitHub Actions workflows follow the same pattern: Test → Build → Push → Deploy.

Workflow Trigger Target
deploy-docker.yml Push to main EC2 via Docker Compose
deploy-k8s.yml Manual dispatch EC2 via K3s + kubectl apply

Both workflows run backend and frontend tests, build multi-stage Docker images, push to Docker Hub, and deploy through SSH.


Artwork

Auction item images are provided by the National Gallery of Art under their Open Access (CC0) policy.


License

MIT License. Copyright (c) 2026 Vladyslav Marchenko

See LICENSE for details.


Author

Vladyslav Marchenko