Personal projectlive · veseljko.hr

Veseljko — a full-stack platform for family events.

An end-to-end Croatian platform that lets event owners run the whole lifecycle of a wedding, baptism, birthday, communion, or bachelor(ette) party. 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 — all under one roof. Co-built with a colleague; clean-architecture .NET 10 with custom CQRS abstractions.

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

01What it does

The product is the kind of thing you'd hand to a couple six months out from a 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, watch RSVPs come in, and during the celebration itself they get a live gallery of every photo and video their guests upload — projectable on the venue's screen via a single share link. Afterwards everything stays in one place: gallery, guest list, seating, expenses, paperwork.

Behind that one product there are eight event types — wedding, bachelor and bachelorette party, baptism, birthday, confirmation, first communion, and a generic catch-all — each with its own invitation theme set, its own field set on the editor, its own copy on the public-facing pages. A second back-office app gives staff a shopping cart for packages and add-ons, partner management, reviews, inquiries, and a blog. The two front-ends share auth and persistence but stay separate processes with separate concerns.

02Architecture & patterns

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, Office for staff). Each 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 via custom abstractions

Reads and writes are physically separate handler types — IQueryHandler for reads, ICommandHandler for writes — registered automatically by convention so there's no service-locator wiring per feature. Every handler returns a Result<T> rather than throwing, so failure paths are explicit at every call site instead of bubbling up through middleware. The whole pattern was rolled by hand rather than pulled 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 on PostgreSQL with snake_case naming conventions applied globally, code-first migrations applied across three environments — Development, RemoteDevelopment, RemoteProduction — via dedicated migration scripts that pick up the right connection string per environment. Antiforgery plus cookie auth across both front-ends, with shared identity backing them.

03Invitations & print

Themed templating engine

Thirty-plus 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 like 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 vs. lock islands

Owners edit invitation copy inline directly on the rendered page — contenteditable plus a tiny JS driver. The trick: certain regions must stay synced to the database (schedule blocks, RSVP QR codes, organiser contact lists, RSVP-by dates) because they change as the event evolves. Those regions are wrapped in non-editable lock islands that the editor refuses to touch; everything else is owner-editable. Edits round-trip to the database without ever stomping the live, database-derived content.

Print pipeline

Invitations print as multi-page browser-native documents — blank / front / blank / back, two-up on 10×7" sheets — using a print stylesheet tuned per theme. A5 QR cards run on a separate layout for the bachelor(ette)-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 is genuinely what comes out of the printer.

04Media & presentation

Pluggable storage providers

Media upload sits behind a provider interface with two implementations: local filesystem for development, Cloudflare R2 for production. Per-package upload limits control how much guests can submit; thumbnail generation runs at upload time so the gallery loads cheap.

Password-protected guest galleries

Guest galleries are password-protected; passwords are hashed for verification but a replayable plaintext copy is kept for the owner's display, so they can hand the password to guests without resetting it. Photos and videos are surfaced through the same gallery view with thumbnails for both.

Live-presentation slug

Every event gets a public-facing slug that streams new guest uploads onto a projection screen — drop the link onto the venue laptop, hit fullscreen, and the gallery refreshes itself for the rest of the night. No auth on the projection slug (guests don't have accounts), but the slug is randomised per event.

05Commerce & back-office

Cart with per-event feature overrides

The Office app runs a shopping cart for packages and add-ons. The interesting twist: certain features (digital invitations, table management) can be toggled on a per-event basis via 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 inquiries, and a blog. Same Clean Architecture skeleton, same CQRS handler pattern, just different read/write surfaces.

Localization

Full Croatian UI with hr-HR culture-aware 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.

06Status

Personal project, co-built with a colleague. Live at veseljko.hr. The full feature surface above is implemented end-to-end — invitations, RSVPs, print, galleries, live presentation, cart, back-office — and verified with the headless Chromium PDF pipeline. Repository is private; happy to walk through architecture or specific subsystems on request.

Event types

8

Invitation themes

30+seeded

Front-ends

2Web · Office

Resources

Repoprivate — happy to walk through it

Want a code walkthrough?

Happy to share screen on the CQRS abstractions, the templating engine, or the print pipeline.

[email protected]