cowork-server

Development: Started - Updated

cowork-server is a C# API for a coworking space with shared meeting rooms. The interesting part is not the room CRUD. The booking flow has to deal with a shared resource, user permissions, time limits, conflicts, and pricing without putting all of that logic into the API controllers.

Problem

Booking a room is more than checking whether a time slot is empty. A member can reserve only an active room, during its opening hours and inside the booking window, without colliding with another reservation or exceeding the daily limit.

Architecture

The codebase is split into Web API, Application, Domain, Infrastructure, and Framework projects. Controllers send requests through MediatR. Application handlers coordinate repositories and apply booking rules; EF Core persistence stays in infrastructure.

Authentication & Authorization

ASP.NET Core Identity manages users, logins, refresh tokens, OTP codes, roles, and claims. Meeting-room endpoints require an authenticated, active user. Booking commands take the user ID from the current authenticated context, not from the request body.

Booking Domain

Coworking booking domain: users have roles, permissions, bookings, and monthly allowances; bookings include their details.

A booking records its room, owner, date and time range, status, the room’s hourly price at booking time, billable minutes, and the total amount. The model also stores a monthly allowance record per user, with used and remaining minutes.

Business rules

CQRS

Commands and queries are separate record types with their own handlers. Commands go through the write path and change state; queries use read repositories and return the data needed by the caller. This is an application-level split, not a separate physical read store.

Web API commands and queries use separate handlers and paths while sharing one database.

Why minimal CQRS?

Booking is a business operation with rules, not a generic update to a room schedule. BookMeetingRoom gives the application one place to validate the request, identify the current user, reject conflicts, calculate the amount, and persist the result. Queries can return room availability or a member’s booking history without rebuilding that workflow.

Commands and queries stay inside the same API and use the same database. I wanted the separation in the application code without introducing separate services, databases, or messaging just for the sake of CQRS.

Consistency & Transactions

The booking handler uses an in-process semaphore keyed by room and date before it checks for overlap and saves the booking. This works only within a single application process. If the API were running on multiple instances, the booking conflict would need to be enforced at the database level or handled with a distributed lock.

The codebase uses a shared EF Core unit of work for persistence. It also has a MediatR transaction behavior for commands that opt into the transactional-command interface. The current booking command persists its changes through the shared unit of work directly.

Notifications

The codebase includes a database-backed outbox. A hosted worker reads pending events and routes notification or record events to processors. Notification models support email, in-app, and SMS channels. The booking handler itself does not publish a notification event.

Infrastructure

Infrastructure contains the EF Core repositories, ASP.NET Core Identity stores, caching support, structured logging, and configuration through options. The Web API is the application boundary; controllers are thin request-to-handler adapters.

Testing

The test suite checks that every command and query is a record and has a matching handler. These tests prevent the command/query structure from drifting as new handlers are added.

Technical Decisions

View the source on GitHub