


- Role
- Co-built with a colleague
- Status
- Live
- Live site
- veseljko.hr (opens in a new tab)
- Repo
- Private; happy to walk through it
- Capabilities
- Back-end & data
- Front-end
- Infra and delivery
- Stack
- C#
- .NET 10
- Clean Architecture
- CQRS
- ASP.NET Core MVC
- Razor
- EF Core 10
- PostgreSQL
- Cloudflare R2
- QR generation
- headless Chromium PDF
- hr-HR i18n
Signal path · 5 stages
How it works
- Event owner: picks an event and a theme
- Theme engine: 30+ themes, CSS only
- Invitations & RSVP: QR codes for guests
- Guest uploads: photos and video to R2
- Print: headless Chromium PDF
What it does
Veseljko lets event owners run the whole lifecycle of a wedding, baptism, birthday, communion or bachelor(ette) party in one place: digital invitations with RSVP, guest photo and video uploads, printable invitations and QR cards, table seating, package and add-on commerce, and a back-office admin app.
It’s the kind of product you could hand to a couple six months before their wedding and never have to explain again. They set up an event, pick an invitation theme, fill in their schedule and contacts, send the invitation link and watch the RSVPs come in. During the celebration they get a live gallery of every photo and video their guests upload, which a single share link can put on the venue’s screen. Afterwards everything stays in one place: gallery, guest list, seating, expenses and paperwork.
Behind that one product are eight event types: wedding, bachelor party, bachelorette party, baptism, birthday, confirmation, first communion and a generic catch-all. Each has its own set of invitation themes, its own fields in the editor and its own copy on the public pages. A second, back-office app gives staff a shopping cart for packages and add-ons, partner management, reviews, enquiries and a blog. The two front ends share auth and persistence but stay separate processes with separate concerns.
How it’s built
Clean architecture in four layers
The solution splits into Domain, Application, Infrastructure and SharedKernel projects, with two front ends on top: Web for owners and guests, and Office for staff. Every layer’s dependencies point inward (Infrastructure depends on Application, Application on Domain, never the reverse), so the business rules live in code that has no idea how requests arrive or where data lands.
CQRS through custom abstractions
Reads and writes are physically separate handler types, IQueryHandler for reads and ICommandHandler for writes, registered automatically by convention, so there’s no service-locator wiring per feature. Every handler returns a Result<T> instead of throwing, which keeps failure paths explicit at every call site rather than bubbling up through middleware. We rolled the whole pattern by hand rather than pulling it from a library: small enough to fit in the SharedKernel, opinionated enough to keep the team honest.
“Roll your own CQRS” is a phrase that scares people for good reason. The trick was keeping it small: two handler interfaces, one auto-registration helper, one Result type, no mediator.
Persistence and migrations
EF Core 10 runs on PostgreSQL with snake_case naming conventions applied globally. Code-first migrations are applied across three environments (Development, RemoteDevelopment and RemoteProduction) by dedicated migration scripts that pick up the right connection string for each. Both front ends use antiforgery protection and cookie auth, backed by a shared identity.
Themed templating engine
More than 30 invitation themes are seeded in the database, organised as the cross-product of event type (Wedding, Bachelor, Bachelorette, Baptism, Birthday, Confirmation, First Communion, Other) and visual style (Classic, Deco, Editorial, Modern). Theming is CSS-only: a CustomCss field on the theme is scoped at render time, so two events with the same theme can diverge without forking the template. A placeholder substitution pipeline injects per-event data into slots such as names, schedule HTML, RSVP QR codes and contacts, so the same theme file serves every event of its type without code changes.
Owner-editable text and lock islands
Owners edit invitation copy inline, directly on the rendered page, through contenteditable and a tiny JavaScript driver. The trick is that some regions must stay in sync with the database because they change as the event evolves: schedule blocks, RSVP QR codes, organiser contact lists and RSVP-by dates. Those regions are wrapped in non-editable lock islands that the editor refuses to touch, and everything else is the owner’s to edit. Edits round-trip to the database without ever overwriting the live, database-derived content.
Print pipeline
Invitations print as multi-page, browser-native documents (blank, front, blank, back, two-up on 10×7-inch sheets) using a print stylesheet tuned per theme. A5 QR cards run on a separate layout for the bachelor and bachelorette party scenarios. The whole pipeline is verified end to end with headless Chromium running --print-to-pdf against the same templates, so the WYSIWYG preview really is what comes out of the printer.
Pluggable storage providers
Media upload sits behind a provider interface with two implementations: the local filesystem for development and Cloudflare R2 for production. Per-package upload limits control how much guests can submit, and thumbnails are generated at upload time so the gallery loads cheaply.
Password-protected guest galleries
Guest galleries are password-protected. Photos and videos share the same gallery view, with thumbnails for both.
Live-presentation slug
Every event gets a public slug that streams new guest uploads onto a projection screen. Open the link on the venue’s laptop, go full screen, and the gallery refreshes itself for the rest of the night. The projection slug has no auth, because guests don’t have accounts, but it is randomised per event.
Cart with per-event feature overrides
The Office app runs a shopping cart for packages and add-ons. The interesting twist is that some features, such as digital invitations and table management, can be toggled per event through cart admin actions, so a staffer can grant a one-off feature to a customer’s event without changing their package tier in the database. The overrides are first-class citizens, not flags hidden in a JSON blob.
Office modules
Beyond the cart, the back office handles partners (venues, photographers, caterers), customer reviews, inbound enquiries and a blog. It uses the same Clean Architecture skeleton and the same CQRS handler pattern, just with different read and write surfaces.
Localisation
The UI is fully Croatian, with culture-aware hr-HR date formatting throughout. Resources live in centralised .resx bundles under Web/Localization, so a future second language is a translation pass rather than an engineering project. Most of the engineering tax for that future internationalisation has already been paid.
Status
Veseljko is a personal project. The full feature surface above is implemented end to end (invitations, RSVPs, print, galleries, live presentation, cart and back office) and verified with the headless Chromium PDF pipeline.
In numbers
- event types
- 8
- invitation themes
- 30+
- front ends: Web and Office
- 2