#PhotoBattle
Competitive photo battle platform: Photographers submit photos, voters choose their favorites in 1-on-1 duels, and an Elo rating system calculates objective rankings.
#Architecture
Hybrid approach: Event Sourcing for the core competitive domains, CRUD for the rest.
┌─────────────────────────────────────────────┐
│ FotoBattle Domains │
├──────────────────┬──────────────────────────┤
│ CRUD │ Event Sourced (ES) │
├──────────────────┼──────────────────────────┤
│ User Management │ Battle Domain │
│ Photo Management │ Rating Domain │
│ Social Features │ Infrastructure │
│ Read-Side │ (Event Store) │
└──────────────────┴──────────────────────────┘
#Roles
| Role |
Description |
| Visitor |
View gallery, leaderboard, and top photos |
| Voter |
Registration with email/password. Vote, comment, like, follow |
| Photographer |
Must be approved by admin. Upload photos and submit them to Battles |
| Admin |
Approve photographers, manage Battles, moderation, statistics |
#Features
#MVP (P0)
| ID |
Feature |
Description |
| PHOTO-1 |
User Registration & Login |
Email/password, bcrypt, rate limiting (5 attempts → 15-minute lockout) |
| PHOTO-2 |
Photographer Approval & Roles |
Application, admin approval, role change |
| PHOTO-3 |
Photo Upload & Management |
Upload to battles, status management (pending → active → withdrawn) |
| PHOTO-4 |
Event Store & Aggregate Infrastructure |
MySQL-based event sourcing with optimistic locking |
| PHOTO-5 |
1v1 Photo Battle & Voting |
Pairwise matchmaking (min. 10 photos), duplicate protection (24h) |
| PHOTO-6 |
Elo Rating Engine |
K-factor: 40 (new) / 20 (established), Floor 100, Start 1500 |
| PHOTO-7 |
Ranking & Leaderboard |
Photo and photographer rankings from rating_history |
#Social (P1)
| ID |
Feature |
Description |
| PHOTO-8 |
Comments |
Comments on photos |
| PHOTO-9 |
Likes |
Favorites toggle (unique per user+entry) |
| PHOTO-10 |
Follow System |
Follow photographers |
| PHOTO-11 |
User Profiles & Galleries |
Profile with avatar, bio, photo gallery |
#Advanced (P2)
| ID |
Feature |
Description |
| PHOTO-12 |
Categories & Tags |
Battle and entry categorization |
| PHOTO-13 |
Battle History & Statistics |
Voting volume, top voters, battle statistics |
| FOTO-14 |
Notifications |
In-app notifications |
#Elo Rating
Erwartete Punktzahl: E_A = 1 / (1 + 10^((R_B - R_A) / 400))
Neues Rating: R'_A = R_A + K × (S - E_A)
K-Faktor: 40 (< 30 Battles) / 20 (≥ 30 Battles)
Floor: 100 (Ratings fallen nie unter 100)
Startwert: 1500
#Event Store
Custom implementation (no external EventStore):
append(AggregateRoot) — Write event to event_store table
getEvents(aggregateId) — Load all events for an aggregate
- Optimistic locking via
aggregate_id + event_version (Unique)
ConcurrencyException for version conflicts
- Projections:
elo_rating to battle_entries (Cache), rating_history (Leaderboard)
#Routing
#Public
| Method |
Path |
Description |
GET |
/ |
Home page (votable battles) |
GET/POST |
/register |
Registration |
GET/POST |
/login |
Login |
POST |
/logout |
Logout |
GET |
/leaderboard |
Photo Rankings |
GET |
/leaderboard/photographers |
Photographer Rankings |
GET |
/vote/{id} |
Battle Arena (1v1) |
POST |
/api/vote |
Voting (AJAX) |
GET |
/entry/{id} |
Photo Details Page |
POST |
/api/favorite/{id} |
Like Toggle |
#Authenticated
| Method |
Path |
Description |
GET/POST |
/apply |
Photographer Application |
GET |
/my-entries |
My Photos |
GET |
/profile/{username} |
User Profile |
GET |
/following |
Following List |
POST |
/api/follow/{id} |
Follow Toggle |
#Admin
| Method |
Path |
Description |
GET/POST |
/admin/battles |
Create and manage battles (draft → open → voting → closed) |
GET/POST |
/admin/photographers |
Approve applications |
GET/POST |
/admin/users |
User management |
GET |
/admin/statistics |
Statistics and ratings |
#Gallery Integration (HMAC-protected)
| Method |
Path |
Description |
POST |
/api/gallery-submit |
Send photo from Gallery to Battle |
POST |
/api/gallery-withdraw |
Withdraw photo from Battle |
#Database (FotoBattle Tables)
#User & Auth
| Table |
Description |
users |
Username, Email, Password Hash, Role (voter/photographer/admin), Avatar, Bio |
photographer_applications |
Applications with status (pending/approved/rejected) |
login_attempts |
Rate limiting by IP hash |
#Battles & Voting
| Table |
Description |
battles |
Title, Status (draft/open/voting/closed), Timeframe |
battle_entries |
Photos in Battles, FK on images.id (Gallery), Elo Rating Cache |
battle_votes |
Votes (left/right/winner Entry, User, IP hash) |
#Rating & Events
| Table |
Description |
rating_history |
Elo history per entry (old → new, K-factor, opponent) |
event_store |
Event Sourcing (aggregate_id, type, data JSON, version) |
#Social
| Table |
Description |
comments |
Comments on entries |
favorites |
Likes (unique per user+entry) |
follows |
Follow relationships (unique per pair) |
notifications |
In-app notifications |
#Metadata
| Table |
Description |
categories + entry_categories |
Categories (M:N) |
tags + entry_tags |
Tags (M:N) |
#Tests
19 PHPUnit test files:
- Event Store & Aggregate (2 tests)
- Elo calculation & Rating Service (2 tests)
- Auth, Battle, Vote, Entry Services (4 tests)
- Social Services: Comment, Favorite, Follow, Profile, Notification (5 tests)
- Leaderboard, Category, Role, Statistics, Tag, RateLimiter (6 tests)