Migrate Auth/Brewery/Location repositories to pure Dapper; rename Timer to RowVersion (#237)

* Integrate Spectre.Console into seed application, use async data loading

* Replace stored-procedure repositories with Dapper-based repositories

Removes the USP_* stored procedures for Auth, Location, and Brewery
data access in favour of services built with Dapper.

Adds a new Features.Locations slice and optimistic concurrency support
for the Brewery slice.

* Add new documentation for Dapper migration/database schema

* Update FluentAssertions to most recent open source ver

* Update XML doc comment in backend projects

* Modify brewery location dto to include denormalized data

* - Updated column names from 'Timer' to 'RowVersion' in schema.sql for
- Introduced new BeerPost entity and updated BreweryPost and BreweryPostLocation entities to use 'RowVersion' for concurrency.
- Modified UserAccount, UserCredential, UserVerification, City, StateProvince, and Country entities to use 'RowVersion' instead of 'Timer'.
- Updated tests and command handlers to reflect changes in concurrency token from 'Timer' to 'RowVersion'.
- Adjusted BreweryDto and BreweryDtoMapper to include 'RowVersion' and updated mapping logic accordingly.
- Ensured all repository methods and interfaces are aligned with the new concurrency handling approach.

* updates

* Add BeerStyle entity and related properties; refactor BreweryDto to remove BeerPosts

* Fix Api.Specs test failures: stale Timer column refs and missing test.user fixture

* Remove repository tests, usages of registration dto used in repo

* archive old stored procs/functions

* update stale docs

* Add instructions for generating cryptographically secure base64 secrets on macOS/Linux and Windows
This commit is contained in:
2026-08-21 13:09:13 -04:00
committed by Aaron Po
parent b27b5bdb40
commit de1e9ac02b
151 changed files with 2790 additions and 1988 deletions

View File

@@ -11,6 +11,7 @@ using Shared.Contracts;
namespace Features.Breweries.Controllers;
/// <summary>Exposes CRUD operations for brewery posts.</summary>
/// <remarks>
/// Requires JWT authentication by default; <see cref="GetById" /> and <see cref="GetAll" /> opt out via
/// <c>[AllowAnonymous]</c>.
@@ -20,6 +21,7 @@ namespace Features.Breweries.Controllers;
[Authorize(AuthenticationSchemes = "JWT")]
public class BreweryController(IMediator mediator) : ControllerBase
{
/// <summary>Retrieves a single brewery post by ID.</summary>
/// <remarks>Allows anonymous access.</remarks>
/// <returns><c>200 OK</c> with the brewery if found; otherwise <c>404 Not Found</c>.</returns>
[AllowAnonymous]
@@ -39,6 +41,9 @@ public class BreweryController(IMediator mediator) : ControllerBase
);
}
/// <summary>Retrieves brewery posts, newest first.</summary>
/// <param name="limit">Maximum number of breweries to return. Unbounded if <see langword="null"/>.</param>
/// <param name="offset">Number of breweries to skip. Treated as zero if <see langword="null"/>.</param>
/// <remarks>Allows anonymous access.</remarks>
[AllowAnonymous]
[HttpGet]
@@ -59,6 +64,7 @@ public class BreweryController(IMediator mediator) : ControllerBase
);
}
/// <summary>Creates a new brewery post.</summary>
/// <returns><c>201 Created</c> with the newly created brewery.</returns>
[HttpPost]
public async Task<ActionResult<ResponseBody<BreweryDto>>> Create(
@@ -76,8 +82,13 @@ public class BreweryController(IMediator mediator) : ControllerBase
);
}
/// <summary>Updates an existing brewery post.</summary>
/// <param name="id">Must match <paramref name="command" />'s <c>BreweryPostId</c>.</param>
/// <returns><c>200 OK</c> with the updated brewery; or <c>400 Bad Request</c> if the route ID does not match the payload ID.</returns>
/// <returns>
/// <c>200 OK</c> with the updated brewery; <c>400 Bad Request</c> if the route ID does not match the
/// payload ID; <c>404 Not Found</c> if the brewery or its <c>CityId</c> does not exist; or
/// <c>409 Conflict</c> if the brewery was modified since <c>command.RowVersion</c> was read.
/// </returns>
[HttpPut("{id:guid}")]
public async Task<ActionResult<ResponseBody<BreweryDto>>> Update(
Guid id,
@@ -97,7 +108,8 @@ public class BreweryController(IMediator mediator) : ControllerBase
);
}
/// <returns><c>200 OK</c> confirming the deletion.</returns>
/// <summary>Deletes a brewery post.</summary>
/// <returns><c>200 OK</c> confirming the deletion; <c>404 Not Found</c> if no brewery exists with <paramref name="id" />.</returns>
[HttpDelete("{id:guid}")]
public async Task<ActionResult<ResponseBody>> Delete(Guid id)
{