mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
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:
@@ -10,7 +10,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup" Version="5.0.41" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="scripts/**/*.sql" />
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
# Copy everything from the context (src/Core/Database)
|
||||
# Copy everything from the context (src/Core), matching Database.Seed/Dockerfile's context so
|
||||
# ProjectReference paths outside Database/ (e.g. Infrastructure.Sql) resolve.
|
||||
COPY . .
|
||||
RUN dotnet restore "./Database.Migrations/Database.Migrations.csproj"
|
||||
RUN dotnet build "./Database.Migrations/Database.Migrations.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
RUN dotnet restore "./Database/Database.Migrations/Database.Migrations.csproj"
|
||||
RUN dotnet build "./Database/Database.Migrations/Database.Migrations.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./Database.Migrations/Database.Migrations.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
RUN dotnet publish "./Database/Database.Migrations/Database.Migrations.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final
|
||||
WORKDIR /app
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Reflection;
|
||||
using DbUp;
|
||||
using DbUp.Engine;
|
||||
using Infrastructure.Sql;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Database.Migrations;
|
||||
@@ -96,12 +97,6 @@ public static class Program
|
||||
Console.WriteLine($"Database '{settings.DatabaseName}' is present.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a connection, executes a non-query batch parameterised by database name, and
|
||||
/// disposes both the command and the connection.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The connection string to open.</param>
|
||||
/// <param name="sql">The Transact-SQL batch to execute.</param>
|
||||
/// <param name="databaseName">The value bound to the <c>@databaseName</c> parameter.</param>
|
||||
private static async Task ExecuteNonQueryAsync(
|
||||
string connectionString,
|
||||
@@ -171,16 +166,11 @@ public static class Program
|
||||
/// </exception>
|
||||
public static DatabaseSettings FromEnvironment()
|
||||
{
|
||||
string server = Required("DB_SERVER");
|
||||
string databaseName = Required("DB_NAME");
|
||||
string user = Required("DB_USER");
|
||||
string password = Required("DB_PASSWORD");
|
||||
|
||||
bool trustServerCertificate =
|
||||
!bool.TryParse(
|
||||
Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE"),
|
||||
out bool parsedTrust
|
||||
) || parsedTrust;
|
||||
string databaseName =
|
||||
Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException(
|
||||
"The DB_NAME environment variable is not set."
|
||||
);
|
||||
|
||||
bool clearDatabase = string.Equals(
|
||||
Environment.GetEnvironmentVariable("CLEAR_DATABASE"),
|
||||
@@ -189,28 +179,11 @@ public static class Program
|
||||
);
|
||||
|
||||
return new DatabaseSettings(
|
||||
Build(databaseName),
|
||||
Build("master"),
|
||||
SqlConnectionStringHelper.BuildConnectionString(databaseName),
|
||||
SqlConnectionStringHelper.BuildMasterConnectionString(),
|
||||
databaseName,
|
||||
clearDatabase
|
||||
);
|
||||
|
||||
string Build(string initialCatalog) =>
|
||||
new SqlConnectionStringBuilder
|
||||
{
|
||||
DataSource = server,
|
||||
InitialCatalog = initialCatalog,
|
||||
UserID = user,
|
||||
Password = password,
|
||||
Encrypt = true,
|
||||
TrustServerCertificate = trustServerCertificate,
|
||||
}.ConnectionString;
|
||||
}
|
||||
|
||||
private static string Required(string variableName) =>
|
||||
Environment.GetEnvironmentVariable(variableName)
|
||||
?? throw new InvalidOperationException(
|
||||
$"The {variableName} environment variable is not set."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,22 +24,22 @@ CREATE TABLE dbo.UserAccount
|
||||
UserAccountID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_UserAccountID DEFAULT NEWID(),
|
||||
|
||||
Username VARCHAR(64) NOT NULL,
|
||||
Username VARCHAR(64) NOT NULL,
|
||||
|
||||
FirstName NVARCHAR(128) NOT NULL,
|
||||
FirstName NVARCHAR(128) NOT NULL,
|
||||
|
||||
LastName NVARCHAR(128) NOT NULL,
|
||||
LastName NVARCHAR(128) NOT NULL,
|
||||
|
||||
Email VARCHAR(128) NOT NULL,
|
||||
Email VARCHAR(128) NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_UserAccount_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
UpdatedAt DATETIME,
|
||||
UpdatedAt DATETIME,
|
||||
|
||||
DateOfBirth DATE NOT NULL,
|
||||
DateOfBirth DATE NOT NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserAccount
|
||||
PRIMARY KEY (UserAccountID),
|
||||
@@ -56,18 +56,18 @@ CREATE TABLE dbo.UserAccount
|
||||
|
||||
CREATE TABLE Photo -- All photos must be linked to a user account, you cannot delete a user account if they have uploaded photos
|
||||
(
|
||||
PhotoID UNIQUEIDENTIFIER
|
||||
PhotoID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_PhotoID DEFAULT NEWID(),
|
||||
|
||||
Hyperlink NVARCHAR(256),
|
||||
Hyperlink NVARCHAR(256),
|
||||
-- storage is handled via filesystem or cloud service
|
||||
|
||||
UploadedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
UploadedAt DATETIME NOT NULL
|
||||
UploadedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_Photo_UploadedAt DEFAULT GETDATE(),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_Photo
|
||||
PRIMARY KEY (PhotoID),
|
||||
@@ -87,14 +87,14 @@ NONCLUSTERED INDEX IX_Photo_UploadedByID
|
||||
|
||||
CREATE TABLE UserAvatar -- delete avatar photo when user account is deleted
|
||||
(
|
||||
UserAvatarID UNIQUEIDENTIFIER
|
||||
UserAvatarID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_UserAvatarID DEFAULT NEWID(),
|
||||
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserAvatar PRIMARY KEY (UserAvatarID),
|
||||
|
||||
@@ -120,15 +120,15 @@ NONCLUSTERED INDEX IX_UserAvatar_UserAccount
|
||||
|
||||
CREATE TABLE UserVerification -- delete verification data when user account is deleted
|
||||
(
|
||||
UserVerificationID UNIQUEIDENTIFIER
|
||||
UserVerificationID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_UserVerificationID DEFAULT NEWID(),
|
||||
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
VerificationDateTime DATETIME NOT NULL
|
||||
VerificationDateTime DATETIME NOT NULL
|
||||
CONSTRAINT DF_VerificationDateTime DEFAULT GETDATE(),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserVerification
|
||||
PRIMARY KEY (UserVerificationID),
|
||||
@@ -154,23 +154,23 @@ CREATE TABLE UserCredential -- delete credentials when user account is deleted
|
||||
UserCredentialID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_UserCredentialID DEFAULT NEWID(),
|
||||
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_UserCredential_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
Expiry DATETIME NOT NULL
|
||||
Expiry DATETIME NOT NULL
|
||||
CONSTRAINT DF_UserCredential_Expiry DEFAULT DATEADD(DAY, 90, GETDATE()),
|
||||
|
||||
Hash NVARCHAR(256) NOT NULL,
|
||||
Hash NVARCHAR(256) NOT NULL,
|
||||
-- uses argon2
|
||||
|
||||
IsRevoked BIT NOT NULL
|
||||
IsRevoked BIT NOT NULL
|
||||
CONSTRAINT DF_UserCredential_IsRevoked DEFAULT 0,
|
||||
|
||||
RevokedAt DATETIME NULL,
|
||||
RevokedAt DATETIME NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserCredential
|
||||
PRIMARY KEY (UserCredentialID),
|
||||
@@ -195,17 +195,17 @@ NONCLUSTERED INDEX IX_UserCredential_Account_Active
|
||||
|
||||
CREATE TABLE UserFollow
|
||||
(
|
||||
UserFollowID UNIQUEIDENTIFIER
|
||||
UserFollowID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_UserFollowID DEFAULT NEWID(),
|
||||
|
||||
UserAccountID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
FollowingID UNIQUEIDENTIFIER NOT NULL,
|
||||
FollowingID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_UserFollow_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserFollow
|
||||
PRIMARY KEY (UserFollowID),
|
||||
@@ -223,7 +223,7 @@ CREATE TABLE UserFollow
|
||||
CONSTRAINT CK_CannotFollowOwnAccount
|
||||
CHECK (UserAccountID != FollowingID
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
|
||||
@@ -238,14 +238,14 @@ NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
|
||||
|
||||
CREATE TABLE Country
|
||||
(
|
||||
CountryID UNIQUEIDENTIFIER
|
||||
CountryID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_CountryID DEFAULT NEWID(),
|
||||
|
||||
CountryName NVARCHAR(100) NOT NULL,
|
||||
|
||||
ISO3166_1 CHAR(2) NOT NULL,
|
||||
ISO3166_1 CHAR(2) NOT NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_Country
|
||||
PRIMARY KEY (CountryID),
|
||||
@@ -259,17 +259,17 @@ CREATE TABLE Country
|
||||
|
||||
CREATE TABLE StateProvince
|
||||
(
|
||||
StateProvinceID UNIQUEIDENTIFIER
|
||||
StateProvinceID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_StateProvinceID DEFAULT NEWID(),
|
||||
|
||||
StateProvinceName NVARCHAR(100) NOT NULL,
|
||||
|
||||
ISO3166_2 CHAR(6) NOT NULL,
|
||||
ISO3166_2 CHAR(6) NOT NULL,
|
||||
-- eg 'US-CA' for California, 'CA-ON' for Ontario
|
||||
|
||||
CountryID UNIQUEIDENTIFIER NOT NULL,
|
||||
CountryID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_StateProvince
|
||||
PRIMARY KEY (StateProvinceID),
|
||||
@@ -291,14 +291,14 @@ NONCLUSTERED INDEX IX_StateProvince_Country
|
||||
|
||||
CREATE TABLE City
|
||||
(
|
||||
CityID UNIQUEIDENTIFIER
|
||||
CityID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_CityID DEFAULT NEWID(),
|
||||
|
||||
CityName NVARCHAR(100) NOT NULL,
|
||||
CityName NVARCHAR(100) NOT NULL,
|
||||
|
||||
StateProvinceID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_City
|
||||
PRIMARY KEY (CityID),
|
||||
@@ -320,18 +320,18 @@ CREATE TABLE BreweryPost -- A user cannot be deleted if they have a post
|
||||
BreweryPostID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BreweryPostID DEFAULT NEWID(),
|
||||
|
||||
BreweryName NVARCHAR(256) NOT NULL,
|
||||
BreweryName NVARCHAR(256) NOT NULL,
|
||||
|
||||
PostedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
PostedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Description NVARCHAR(512) NOT NULL,
|
||||
Description NVARCHAR(MAX) NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BreweryPost_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
UpdatedAt DATETIME NULL,
|
||||
UpdatedAt DATETIME NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPost
|
||||
PRIMARY KEY (BreweryPostID),
|
||||
@@ -354,19 +354,19 @@ CREATE TABLE BreweryPostLocation
|
||||
BreweryPostLocationID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BreweryPostLocationID DEFAULT NEWID(),
|
||||
|
||||
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
AddressLine1 NVARCHAR(256) NOT NULL,
|
||||
AddressLine1 NVARCHAR(256) NOT NULL,
|
||||
|
||||
AddressLine2 NVARCHAR(256),
|
||||
AddressLine2 NVARCHAR(256),
|
||||
|
||||
PostalCode NVARCHAR(20) NOT NULL,
|
||||
PostalCode NVARCHAR(20) NOT NULL,
|
||||
|
||||
CityID UNIQUEIDENTIFIER NOT NULL,
|
||||
CityID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Coordinates GEOGRAPHY NULL,
|
||||
Coordinates GEOGRAPHY NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPostLocation
|
||||
PRIMARY KEY (BreweryPostLocationID),
|
||||
@@ -412,14 +412,14 @@ CREATE TABLE BreweryPostPhoto -- All photos linked to a post are deleted if the
|
||||
BreweryPostPhotoID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BreweryPostPhotoID DEFAULT NEWID(),
|
||||
|
||||
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
LinkedAt DATETIME NOT NULL
|
||||
LinkedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BreweryPostPhoto_LinkedAt DEFAULT GETDATE(),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPostPhoto
|
||||
PRIMARY KEY (BreweryPostPhotoID),
|
||||
@@ -451,11 +451,11 @@ CREATE TABLE BeerStyle
|
||||
BeerStyleID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BeerStyleID DEFAULT NEWID(),
|
||||
|
||||
StyleName NVARCHAR(100) NOT NULL,
|
||||
StyleName NVARCHAR(100) NOT NULL,
|
||||
|
||||
Description NVARCHAR(MAX),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerStyle
|
||||
PRIMARY KEY (BeerStyleID),
|
||||
@@ -469,31 +469,31 @@ CREATE TABLE BeerStyle
|
||||
|
||||
CREATE TABLE BeerPost
|
||||
(
|
||||
BeerPostID UNIQUEIDENTIFIER
|
||||
BeerPostID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BeerPostID DEFAULT NEWID(),
|
||||
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
|
||||
Description NVARCHAR(MAX) NOT NULL,
|
||||
|
||||
ABV DECIMAL(4, 2) NOT NULL,
|
||||
ABV DECIMAL(4, 2) NOT NULL,
|
||||
-- Alcohol By Volume (typically 0-67%)
|
||||
|
||||
IBU INT NOT NULL,
|
||||
IBU INT NOT NULL,
|
||||
-- International Bitterness Units (typically 0-120)
|
||||
|
||||
PostedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
PostedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
BeerStyleID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
BrewedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
BrewedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BeerPost_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
UpdatedAt DATETIME,
|
||||
UpdatedAt DATETIME,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPost
|
||||
PRIMARY KEY (BeerPostID),
|
||||
@@ -538,14 +538,14 @@ CREATE TABLE BeerPostPhoto -- All photos linked to a beer post are deleted if th
|
||||
BeerPostPhotoID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BeerPostPhotoID DEFAULT NEWID(),
|
||||
|
||||
BeerPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
BeerPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
PhotoID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
LinkedAt DATETIME NOT NULL
|
||||
LinkedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BeerPostPhoto_LinkedAt DEFAULT GETDATE(),
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPostPhoto
|
||||
PRIMARY KEY (BeerPostPhotoID),
|
||||
@@ -577,20 +577,20 @@ CREATE TABLE BeerPostComment
|
||||
BeerPostCommentID UNIQUEIDENTIFIER
|
||||
CONSTRAINT DF_BeerPostComment DEFAULT NEWID(),
|
||||
|
||||
Comment NVARCHAR(250) NOT NULL,
|
||||
Comment NVARCHAR(250) NOT NULL,
|
||||
|
||||
BeerPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
BeerPostID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
CommentedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
CommentedByID UNIQUEIDENTIFIER NOT NULL,
|
||||
|
||||
Rating INT NOT NULL,
|
||||
Rating INT NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BeerPostComment_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
UpdatedAt DATETIME NULL,
|
||||
UpdatedAt DATETIME NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
RowVersion ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPostComment
|
||||
PRIMARY KEY (BeerPostCommentID),
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
CREATE OR ALTER FUNCTION dbo.UDF_GetCountryIdByCode(
|
||||
@CountryCode NVARCHAR(2)
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE
|
||||
@CountryId UNIQUEIDENTIFIER;
|
||||
|
||||
SELECT @CountryId = CountryID
|
||||
FROM dbo.Country
|
||||
WHERE ISO3166_1 = @CountryCode;
|
||||
|
||||
RETURN @CountryId;
|
||||
END;
|
||||
@@ -1,13 +0,0 @@
|
||||
CREATE OR ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode(
|
||||
@StateProvinceCode NVARCHAR(6)
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE
|
||||
@StateProvinceId UNIQUEIDENTIFIER;
|
||||
SELECT @StateProvinceId = StateProvinceID
|
||||
FROM dbo.StateProvince
|
||||
WHERE ISO3166_2 = @StateProvinceCode;
|
||||
RETURN @StateProvinceId;
|
||||
END;
|
||||
@@ -1,31 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_CreateUserAccount(
|
||||
@UserAccountId UNIQUEIDENTIFIER OUTPUT,
|
||||
@Username VARCHAR(64),
|
||||
@FirstName NVARCHAR(128),
|
||||
@LastName NVARCHAR(128),
|
||||
@DateOfBirth DATETIME,
|
||||
@Email VARCHAR(128)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
DECLARE
|
||||
@Inserted TABLE
|
||||
(
|
||||
UserAccountID UNIQUEIDENTIFIER
|
||||
);
|
||||
|
||||
INSERT INTO UserAccount
|
||||
(Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
DateOfBirth,
|
||||
Email)
|
||||
OUTPUT INSERTED.UserAccountID INTO @Inserted
|
||||
VALUES (@Username, @FirstName, @LastName, @DateOfBirth, @Email);
|
||||
|
||||
SELECT @UserAccountId = UserAccountID
|
||||
FROM @Inserted;
|
||||
END;
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_DeleteUserAccount(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM UserAccount WHERE UserAccountId = @UserAccountId)
|
||||
BEGIN
|
||||
RAISERROR
|
||||
('UserAccount with the specified ID does not exist.', 16,
|
||||
1);
|
||||
ROLLBACK TRANSACTION
|
||||
RETURN
|
||||
END
|
||||
|
||||
DELETE
|
||||
FROM UserAccount
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
END;
|
||||
@@ -1,15 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetAllUserAccounts
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount;
|
||||
END;
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetUserAccountByEmail(
|
||||
@Email VARCHAR(128)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Email = @Email;
|
||||
END;
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE USP_GetUserAccountById(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @UserAccountId;
|
||||
END
|
||||
@@ -1,20 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetUserAccountByUsername(
|
||||
@Username VARCHAR(64)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Username = @Username;
|
||||
END;
|
||||
@@ -1,28 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE usp_UpdateUserAccount(
|
||||
@Username VARCHAR(64),
|
||||
@FirstName NVARCHAR(128),
|
||||
@LastName NVARCHAR(128),
|
||||
@DateOfBirth DATETIME,
|
||||
@Email VARCHAR(128),
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
UPDATE UserAccount
|
||||
SET Username = @Username,
|
||||
FirstName = @FirstName,
|
||||
LastName = @LastName,
|
||||
DateOfBirth = @DateOfBirth,
|
||||
Email = @Email
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
|
||||
IF
|
||||
@@ROWCOUNT = 0
|
||||
BEGIN
|
||||
THROW
|
||||
50001, 'UserAccount with the specified ID does not exist.', 1;
|
||||
END
|
||||
END;
|
||||
@@ -1,18 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetActiveUserCredentialByUserAccountId(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserCredentialId,
|
||||
UserAccountId,
|
||||
Hash,
|
||||
IsRevoked,
|
||||
CreatedAt,
|
||||
RevokedAt
|
||||
FROM dbo.UserCredential
|
||||
WHERE UserAccountId = @UserAccountId
|
||||
AND IsRevoked = 0;
|
||||
END;
|
||||
@@ -1,28 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_InvalidateUserCredential(
|
||||
@UserAccountId_ UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC dbo.USP_GetUserAccountByID @UserAccountId = @UserAccountId_;
|
||||
IF
|
||||
@@ROWCOUNT = 0
|
||||
THROW 50001, 'User account not found', 1;
|
||||
|
||||
-- invalidate all other credentials by setting them to revoked
|
||||
UPDATE dbo.UserCredential
|
||||
SET IsRevoked = 1,
|
||||
RevokedAt = GETDATE()
|
||||
WHERE UserAccountId = @UserAccountId_
|
||||
AND IsRevoked != 1;
|
||||
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
END;
|
||||
@@ -1,50 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_RegisterUser(
|
||||
@Username VARCHAR(64),
|
||||
@FirstName NVARCHAR(128),
|
||||
@LastName NVARCHAR(128),
|
||||
@DateOfBirth DATETIME,
|
||||
@Email VARCHAR(128),
|
||||
@Hash NVARCHAR(MAX)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
DECLARE
|
||||
@UserAccountId_ UNIQUEIDENTIFIER;
|
||||
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC usp_CreateUserAccount
|
||||
@UserAccountId = @UserAccountId_ OUTPUT,
|
||||
@Username = @Username,
|
||||
@FirstName = @FirstName,
|
||||
@LastName = @LastName,
|
||||
@DateOfBirth = @DateOfBirth,
|
||||
@Email = @Email;
|
||||
|
||||
IF
|
||||
@UserAccountId_ IS NULL
|
||||
BEGIN
|
||||
THROW
|
||||
50000, 'Failed to create user account.', 1;
|
||||
END
|
||||
|
||||
INSERT INTO dbo.UserCredential
|
||||
(UserAccountId, Hash)
|
||||
VALUES (@UserAccountId_, @Hash);
|
||||
|
||||
IF
|
||||
@@ROWCOUNT = 0
|
||||
BEGIN
|
||||
THROW
|
||||
50002, 'Failed to create user credential.', 1;
|
||||
END
|
||||
COMMIT TRANSACTION;
|
||||
|
||||
SELECT @UserAccountId_ AS UserAccountId;
|
||||
END
|
||||
@@ -1,31 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_RotateUserCredential(
|
||||
@UserAccountId_ UNIQUEIDENTIFIER,
|
||||
@Hash NVARCHAR(MAX)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountId_
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
THROW 50001, 'User account not found', 1;
|
||||
|
||||
|
||||
-- invalidate all other credentials -- set them to revoked
|
||||
UPDATE dbo.UserCredential
|
||||
SET IsRevoked = 1,
|
||||
RevokedAt = GETDATE()
|
||||
WHERE UserAccountId = @UserAccountId_;
|
||||
|
||||
INSERT INTO dbo.UserCredential
|
||||
(UserAccountId, Hash)
|
||||
VALUES (@UserAccountId_, @Hash);
|
||||
|
||||
|
||||
END;
|
||||
@@ -1,27 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateUserVerification @UserAccountID_ UNIQUEIDENTIFIER,
|
||||
@VerificationDateTime DATETIME = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF
|
||||
@VerificationDateTime IS NULL
|
||||
SET @VerificationDateTime = GETDATE();
|
||||
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountID_;
|
||||
IF
|
||||
@@ROWCOUNT = 0
|
||||
THROW 50001, 'Could not find a user with that id', 1;
|
||||
|
||||
INSERT INTO dbo.UserVerification
|
||||
(UserAccountId, VerificationDateTime)
|
||||
VALUES (@UserAccountID_, @VerificationDateTime);
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
END
|
||||
@@ -1,38 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateCity(
|
||||
@CityName NVARCHAR(100),
|
||||
@StateProvinceCode NVARCHAR(6)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
BEGIN
|
||||
TRANSACTION
|
||||
DECLARE
|
||||
@StateProvinceId UNIQUEIDENTIFIER = dbo.UDF_GetStateProvinceIdByCode(@StateProvinceCode);
|
||||
IF
|
||||
@StateProvinceId IS NULL
|
||||
BEGIN
|
||||
THROW
|
||||
50001, 'State/province does not exist', 1;
|
||||
END
|
||||
|
||||
IF
|
||||
EXISTS (SELECT 1
|
||||
FROM dbo.City
|
||||
WHERE CityName = @CityName
|
||||
AND StateProvinceID = @StateProvinceId)
|
||||
BEGIN
|
||||
|
||||
THROW
|
||||
50002, 'City already exists.', 1;
|
||||
END
|
||||
|
||||
INSERT INTO dbo.City
|
||||
(StateProvinceID, CityName)
|
||||
VALUES (@StateProvinceId, @CityName);
|
||||
COMMIT TRANSACTION
|
||||
END;
|
||||
@@ -1,24 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateCountry(
|
||||
@CountryName NVARCHAR(100),
|
||||
@ISO3166_1 NVARCHAR(2)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
IF
|
||||
EXISTS (SELECT 1
|
||||
FROM dbo.Country
|
||||
WHERE ISO3166_1 = @ISO3166_1)
|
||||
THROW 50001, 'Country already exists', 1;
|
||||
|
||||
INSERT INTO dbo.Country
|
||||
(CountryName, ISO3166_1)
|
||||
VALUES (@CountryName, @ISO3166_1);
|
||||
COMMIT TRANSACTION;
|
||||
END;
|
||||
@@ -1,32 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateStateProvince(
|
||||
@StateProvinceName NVARCHAR(100),
|
||||
@ISO3166_2 NVARCHAR(6),
|
||||
@CountryCode NVARCHAR(2)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF
|
||||
EXISTS (SELECT 1
|
||||
FROM dbo.StateProvince
|
||||
WHERE ISO3166_2 = @ISO3166_2)
|
||||
RETURN;
|
||||
|
||||
DECLARE
|
||||
@CountryId UNIQUEIDENTIFIER = dbo.UDF_GetCountryIdByCode(@CountryCode);
|
||||
IF
|
||||
@CountryId IS NULL
|
||||
BEGIN
|
||||
THROW
|
||||
50001, 'Country does not exist', 1;
|
||||
|
||||
END
|
||||
|
||||
INSERT INTO dbo.StateProvince
|
||||
(StateProvinceName, ISO3166_2, CountryID)
|
||||
VALUES (@StateProvinceName, @ISO3166_2, @CountryId);
|
||||
END;
|
||||
@@ -1,59 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateBrewery(
|
||||
@BreweryName NVARCHAR(256),
|
||||
@Description NVARCHAR(512),
|
||||
@PostedByID UNIQUEIDENTIFIER,
|
||||
@CityID UNIQUEIDENTIFIER,
|
||||
@AddressLine1 NVARCHAR(256),
|
||||
@AddressLine2 NVARCHAR(256) = NULL,
|
||||
@PostalCode NVARCHAR(20),
|
||||
@Coordinates GEOGRAPHY = NULL
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF
|
||||
@BreweryName IS NULL
|
||||
THROW 50001, 'Brewery name cannot be null.', 1;
|
||||
|
||||
IF
|
||||
@Description IS NULL
|
||||
THROW 50002, 'Brewery description cannot be null.', 1;
|
||||
|
||||
IF
|
||||
NOT EXISTS (SELECT 1
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @PostedByID)
|
||||
THROW 50404, 'User not found.', 1;
|
||||
|
||||
IF
|
||||
NOT EXISTS (SELECT 1
|
||||
FROM dbo.City
|
||||
WHERE CityID = @CityID)
|
||||
THROW 50404, 'City not found.', 1;
|
||||
|
||||
DECLARE
|
||||
@NewBreweryID UNIQUEIDENTIFIER = NEWID();
|
||||
DECLARE
|
||||
@NewBrewerLocationID UNIQUEIDENTIFIER = NEWID();
|
||||
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
INSERT INTO dbo.BreweryPost
|
||||
(BreweryPostID, BreweryName, Description, PostedByID)
|
||||
VALUES (@NewBreweryID, @BreweryName, @Description, @PostedByID);
|
||||
|
||||
INSERT INTO dbo.BreweryPostLocation
|
||||
(BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
|
||||
VALUES (@NewBrewerLocationID, @NewBreweryID, @CityID, @AddressLine1, @AddressLine2, @PostalCode, @Coordinates);
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
|
||||
SELECT @NewBreweryID AS BreweryPostID,
|
||||
@NewBrewerLocationID AS BreweryPostLocationID;
|
||||
|
||||
END
|
||||
@@ -1,17 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_DeleteBrewery @BreweryPostID UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
IF
|
||||
NOT EXISTS (SELECT 1
|
||||
FROM dbo.BreweryPost
|
||||
WHERE BreweryPostID = @BreweryPostID)
|
||||
THROW 50404, 'Brewery not found.', 1;
|
||||
|
||||
-- BreweryPostLocation and BreweryPostPhoto cascade-delete with their parent BreweryPost.
|
||||
DELETE
|
||||
FROM dbo.BreweryPost
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
@@ -1,28 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetAllBreweries(
|
||||
@Limit INT = NULL,
|
||||
@Offset INT = NULL
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT bp.BreweryPostID,
|
||||
bp.PostedByID,
|
||||
bp.BreweryName,
|
||||
bp.Description,
|
||||
bp.CreatedAt,
|
||||
bp.UpdatedAt,
|
||||
bp.Timer,
|
||||
bpl.BreweryPostLocationID,
|
||||
bpl.CityID,
|
||||
bpl.AddressLine1,
|
||||
bpl.AddressLine2,
|
||||
bpl.PostalCode,
|
||||
bpl.Coordinates
|
||||
FROM dbo.BreweryPost bp
|
||||
LEFT JOIN dbo.BreweryPostLocation bpl
|
||||
ON bp.BreweryPostID = bpl.BreweryPostID
|
||||
ORDER BY bp.CreatedAt DESC
|
||||
OFFSET ISNULL(@Offset, 0) ROWS FETCH NEXT ISNULL(@Limit, 2147483647) ROWS ONLY;
|
||||
END
|
||||
@@ -1,9 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetBreweryById @BreweryPostID UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
SELECT *
|
||||
FROM BreweryPost bp
|
||||
INNER JOIN BreweryPostLocation bpl
|
||||
ON bp.BreweryPostID = bpl.BreweryPostID
|
||||
WHERE bp.BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
@@ -1,77 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_UpdateBrewery(
|
||||
@BreweryPostID UNIQUEIDENTIFIER,
|
||||
@BreweryName NVARCHAR(256),
|
||||
@Description NVARCHAR(512),
|
||||
@BreweryPostLocationID UNIQUEIDENTIFIER = NULL,
|
||||
@CityID UNIQUEIDENTIFIER = NULL,
|
||||
@AddressLine1 NVARCHAR(256) = NULL,
|
||||
@AddressLine2 NVARCHAR(256) = NULL,
|
||||
@PostalCode NVARCHAR(20) = NULL,
|
||||
@Coordinates GEOGRAPHY = NULL
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF
|
||||
@BreweryName IS NULL
|
||||
THROW 50001, 'Brewery name cannot be null.', 1;
|
||||
|
||||
IF
|
||||
@Description IS NULL
|
||||
THROW 50002, 'Brewery description cannot be null.', 1;
|
||||
|
||||
IF
|
||||
NOT EXISTS (SELECT 1
|
||||
FROM dbo.BreweryPost
|
||||
WHERE BreweryPostID = @BreweryPostID)
|
||||
THROW 50404, 'Brewery not found.', 1;
|
||||
|
||||
IF
|
||||
@CityID IS NOT NULL AND NOT EXISTS (SELECT 1
|
||||
FROM dbo.City
|
||||
WHERE CityID = @CityID)
|
||||
THROW 50404, 'City not found.', 1;
|
||||
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
UPDATE dbo.BreweryPost
|
||||
SET BreweryName = @BreweryName,
|
||||
Description = @Description,
|
||||
UpdatedAt = GETDATE()
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
|
||||
IF
|
||||
@CityID IS NULL
|
||||
BEGIN
|
||||
-- No location supplied: clear any existing location for this brewery.
|
||||
DELETE
|
||||
FROM dbo.BreweryPostLocation
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
ELSE
|
||||
IF EXISTS (SELECT 1
|
||||
FROM dbo.BreweryPostLocation
|
||||
WHERE BreweryPostID = @BreweryPostID)
|
||||
BEGIN
|
||||
UPDATE dbo.BreweryPostLocation
|
||||
SET CityID = @CityID,
|
||||
AddressLine1 = @AddressLine1,
|
||||
AddressLine2 = @AddressLine2,
|
||||
PostalCode = @PostalCode,
|
||||
Coordinates = @Coordinates
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
ELSE
|
||||
BEGIN
|
||||
INSERT INTO dbo.BreweryPostLocation
|
||||
(BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
|
||||
VALUES (ISNULL(@BreweryPostLocationID, NEWID()), @BreweryPostID, @CityID, @AddressLine1, @AddressLine2,
|
||||
@PostalCode, @Coordinates);
|
||||
END
|
||||
COMMIT TRANSACTION;
|
||||
END
|
||||
@@ -10,13 +10,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="idunno.Password.Generator" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.2" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
|
||||
|
||||
<!--
|
||||
remove this override once microsoft ships official fix to vulnerability
|
||||
https:
|
||||
-->
|
||||
<PackageReference Include="SQLitePCLRaw.lib.e_sqlite3" Version="3.50.3" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.11" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.57.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -24,14 +19,9 @@
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Auth\Features.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Locations\Features.Locations.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Pipeline-generated seed data, copied to the output directory so it can be opened via
|
||||
ConnectionStrings.SqliteConnectionString at runtime. -->
|
||||
<None
|
||||
CopyToOutputDirectory="PreserveNewest"
|
||||
Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite"
|
||||
/>
|
||||
<None CopyToOutputDirectory="PreserveNewest" Include="SeedData\biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region
|
||||
#region
|
||||
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Infrastructure.Sql;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -8,41 +8,7 @@ namespace Database.Seed.DatabaseHelpers;
|
||||
|
||||
public class ConnectionStrings
|
||||
{
|
||||
public static string SqlServerConnectionString => GetSqlServerConnectionString();
|
||||
public static string SqlServerConnectionString => SqlConnectionStringHelper.BuildConnectionString();
|
||||
public static string SqliteConnectionString =>
|
||||
"Data Source=SeedData/biergarten_seed_2026-07-20T05-46-02.993511Z.sqlite";
|
||||
|
||||
private static string GetSqlServerConnectionString()
|
||||
{
|
||||
string server =
|
||||
Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
|
||||
string dbName =
|
||||
Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
|
||||
string user =
|
||||
Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
|
||||
string password =
|
||||
Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
|
||||
string trustServerCertificate =
|
||||
Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE") ?? "True";
|
||||
|
||||
SqlConnectionStringBuilder builder = new()
|
||||
{
|
||||
DataSource = server,
|
||||
InitialCatalog = dbName,
|
||||
UserID = user,
|
||||
Password = password,
|
||||
TrustServerCertificate = bool.Parse(trustServerCertificate),
|
||||
Encrypt = true,
|
||||
};
|
||||
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,112 +7,257 @@ using Features.Auth.Dtos;
|
||||
using Features.Auth.Repository;
|
||||
using Features.Breweries.DependencyInjection;
|
||||
using Features.Breweries.Repository;
|
||||
using Features.Locations.DependencyInjection;
|
||||
using Features.Locations.Dtos;
|
||||
using Features.Locations.Repository;
|
||||
using idunno.Password;
|
||||
using Infrastructure.PasswordHashing;
|
||||
using Infrastructure.Sql;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Infrastructure.Sql.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Spectre.Console;
|
||||
|
||||
static async Task<int> main()
|
||||
return await RunAsync();
|
||||
|
||||
static async Task<int> RunAsync()
|
||||
{
|
||||
IReadOnlyList<BreweryRecord> breweries;
|
||||
IReadOnlyList<UserRecord> users;
|
||||
try
|
||||
{
|
||||
PipelineSeedDataReader reader = new(ConnectionStrings.SqliteConnectionString);
|
||||
breweries = reader.ReadBreweryRecords();
|
||||
users = reader.ReadUserRecords();
|
||||
ServiceCollection services = [];
|
||||
services.AddSingleton<IConfiguration>(
|
||||
new ConfigurationBuilder().Build());
|
||||
services.AddInfrastructureSql();
|
||||
services.AddFeaturesBreweries();
|
||||
services.AddFeaturesLocations();
|
||||
services.AddFeaturesAuth();
|
||||
|
||||
await using ServiceProvider provider = services.BuildServiceProvider();
|
||||
|
||||
IBreweryRepository breweryRepository =
|
||||
provider.GetRequiredService<IBreweryRepository>();
|
||||
ILocationRepository locationRepository =
|
||||
provider.GetRequiredService<ILocationRepository>();
|
||||
IAuthRepository authRepository =
|
||||
provider.GetRequiredService<IAuthRepository>();
|
||||
IPasswordInfrastructure passwordInfrastructure =
|
||||
provider.GetRequiredService<IPasswordInfrastructure>();
|
||||
|
||||
AnsiConsole.Write(new Rule("[bold green]Database Seeder[/]")
|
||||
.LeftJustified());
|
||||
AnsiConsole.MarkupLine(
|
||||
"[grey]Connecting to SQLite source and loading seed data...[/]");
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
PipelineSeedDataReader reader =
|
||||
new(ConnectionStrings.SqliteConnectionString);
|
||||
|
||||
SeedData seedData = null!;
|
||||
IReadOnlyList<Guid> postedByIds = [];
|
||||
|
||||
await AnsiConsole
|
||||
.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.SpinnerStyle(Style.Parse("green"))
|
||||
.StartAsync(
|
||||
"Loading seed data...",
|
||||
async ctx =>
|
||||
{
|
||||
seedData = await reader.ReadSeedDataAsync();
|
||||
ctx.Status(
|
||||
$"Loaded {seedData.Breweries.Count} breweries and {seedData.Users.Count} users."
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
AnsiConsole.MarkupLine(
|
||||
$"[green]✓[/] Loaded [bold]{seedData.Breweries.Count}[/] breweries."
|
||||
);
|
||||
AnsiConsole.Write(BuildBreweryTable(seedData.Breweries));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
AnsiConsole.MarkupLine(
|
||||
$"[green]✓[/] Loaded [bold]{seedData.Users.Count}[/] users.");
|
||||
AnsiConsole.Write(BuildUserTable(seedData.Users));
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
AnsiConsole.WriteLine("Seed data loaded successfully.");
|
||||
|
||||
AnsiConsole.Write(
|
||||
new Rule("[bold green]Loading seed data into target database.[/]")
|
||||
.LeftJustified()
|
||||
);
|
||||
|
||||
await AnsiConsole
|
||||
.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.SpinnerStyle(Style.Parse("green"))
|
||||
.StartAsync(
|
||||
"Loading user data into target database...",
|
||||
async ctx =>
|
||||
{
|
||||
postedByIds = await LoadUsersIntoDatabaseAsync(
|
||||
authRepository,
|
||||
passwordInfrastructure,
|
||||
seedData.Users
|
||||
);
|
||||
ctx.Status("User data loaded into target database.");
|
||||
}
|
||||
);
|
||||
|
||||
await AnsiConsole
|
||||
.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.SpinnerStyle(Style.Parse("green"))
|
||||
.StartAsync(
|
||||
"Loading brewery data into target database...",
|
||||
async ctx =>
|
||||
{
|
||||
await LoadBreweriesIntoDatabaseAsync(
|
||||
breweryRepository,
|
||||
locationRepository,
|
||||
seedData.Breweries,
|
||||
postedByIds
|
||||
);
|
||||
ctx.Status("Brewery data loaded into target database.");
|
||||
}
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"Error opening database connection: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
AnsiConsole.MarkupLine("[red]Seeding failed.[/]");
|
||||
AnsiConsole.WriteException(ex);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
ServiceCollection services = [];
|
||||
services.AddSingleton<IConfiguration>(new ConfigurationBuilder().Build());
|
||||
services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
|
||||
services.AddFeaturesBreweries();
|
||||
services.AddFeaturesAuth();
|
||||
static async Task<IReadOnlyList<Guid>> LoadUsersIntoDatabaseAsync(
|
||||
IAuthRepository authRepository,
|
||||
IPasswordInfrastructure passwordInfrastructure,
|
||||
IReadOnlyList<UserRecord> users
|
||||
)
|
||||
{
|
||||
List<Guid> userAccountIds = [];
|
||||
|
||||
using ServiceProvider provider = services.BuildServiceProvider();
|
||||
|
||||
IBreweryRepository breweryRepository;
|
||||
IAuthRepository authRepository;
|
||||
IPasswordInfrastructure passwordInfrastructure;
|
||||
try
|
||||
foreach (UserRecord userRecord in users)
|
||||
{
|
||||
breweryRepository = provider.GetRequiredService<IBreweryRepository>();
|
||||
authRepository = provider.GetRequiredService<IAuthRepository>();
|
||||
passwordInfrastructure = provider.GetRequiredService<IPasswordInfrastructure>();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"Error configuring database connection: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
return 1;
|
||||
UserAccount userAccount = await authRepository.RegisterUserAsync(
|
||||
new UserAccount()
|
||||
{
|
||||
FirstName = userRecord.User.FirstName,
|
||||
LastName = userRecord.User.LastName,
|
||||
DateOfBirth = DateTime.Parse(userRecord.DateOfBirth),
|
||||
Username = userRecord.User.Username,
|
||||
Email = userRecord.Email,
|
||||
UserCredential = new UserCredential
|
||||
{
|
||||
Hash = passwordInfrastructure.Hash(
|
||||
password: PasswordGenerator.Generate(64, 12, 12))
|
||||
}
|
||||
}
|
||||
);
|
||||
userAccountIds.Add(userAccount.UserAccountId);
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"Loaded {breweries.Count} breweries.");
|
||||
Console.ResetColor();
|
||||
return userAccountIds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists each <paramref name="breweries" /> record, resolving (and creating, if needed) its
|
||||
/// Country/StateProvince/City chain via <paramref name="locationRepository" />. The pipeline
|
||||
/// data has no street-level address or a specific user tied to each brewery, so a placeholder
|
||||
/// address is used and <c>PostedById</c> is assigned round-robin from <paramref name="posterUserIds" />
|
||||
/// to keep seeding deterministic across runs.
|
||||
/// </summary>
|
||||
static async Task LoadBreweriesIntoDatabaseAsync(
|
||||
IBreweryRepository breweryRepository,
|
||||
ILocationRepository locationRepository,
|
||||
IReadOnlyList<BreweryRecord> breweries,
|
||||
IReadOnlyList<Guid> posterUserIds
|
||||
)
|
||||
{
|
||||
if (posterUserIds.Count == 0)
|
||||
throw new InvalidOperationException(
|
||||
"Cannot load breweries without any registered users.");
|
||||
|
||||
for (int i = 0; i < breweries.Count; i++)
|
||||
{
|
||||
BreweryRecord brewery = breweries[i];
|
||||
Console.WriteLine(
|
||||
$"{i + 1}:\t{brewery.Brewery.NameEn}\t({brewery.Address.City.CityName}, {brewery.Address.City.Country})"
|
||||
BreweryRecord breweryRecord = breweries[i];
|
||||
|
||||
Guid cityId = await locationRepository.GetOrCreateCityIdAsync(
|
||||
new CityLocation(
|
||||
breweryRecord.Address.City.CityName,
|
||||
breweryRecord.Address.City.StateProvince,
|
||||
breweryRecord.Address.City.Iso31662,
|
||||
breweryRecord.Address.City.Country,
|
||||
breweryRecord.Address.City.Iso31661
|
||||
)
|
||||
);
|
||||
|
||||
await breweryRepository.CreateAsync(
|
||||
new BreweryPost
|
||||
{
|
||||
BreweryPostId = Guid.NewGuid(),
|
||||
BreweryName = breweryRecord.Brewery.NameEn,
|
||||
Description = breweryRecord.Brewery.DescriptionEn,
|
||||
PostedById = posterUserIds[i % posterUserIds.Count],
|
||||
Location = new BreweryPostLocation
|
||||
{
|
||||
BreweryPostLocationId = Guid.NewGuid(),
|
||||
CityId = cityId,
|
||||
AddressLine1 = "Address unavailable",
|
||||
PostalCode = breweryRecord.Address.PostalCode,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static Table BuildBreweryTable(IReadOnlyList<BreweryRecord> breweries)
|
||||
{
|
||||
Table table = new Table()
|
||||
.AddColumn("Brewery Name (EN)")
|
||||
.AddColumn("Brewery Name (Local)")
|
||||
.AddColumn("City")
|
||||
.AddColumn("State/Province")
|
||||
.AddColumn("Country")
|
||||
.AddColumn("Postal Code");
|
||||
|
||||
foreach (BreweryRecord brewery in breweries)
|
||||
{
|
||||
table.AddRow(
|
||||
brewery.Brewery.NameEn,
|
||||
brewery.Brewery.NameLocal,
|
||||
brewery.Address.City.CityName,
|
||||
brewery.Address.City.StateProvince,
|
||||
brewery.Address.City.Country,
|
||||
brewery.Address.PostalCode
|
||||
);
|
||||
}
|
||||
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine($"Loaded {users.Count} users.");
|
||||
Console.ResetColor();
|
||||
return table;
|
||||
}
|
||||
|
||||
static Table BuildUserTable(IReadOnlyList<UserRecord> users)
|
||||
{
|
||||
Table table = new Table()
|
||||
.AddColumn("Username")
|
||||
.AddColumn("First Name")
|
||||
.AddColumn("Last Name")
|
||||
.AddColumn("Email")
|
||||
.AddColumn("Date of Birth");
|
||||
|
||||
foreach (UserRecord user in users)
|
||||
{
|
||||
string username = user.User.Username;
|
||||
string firstName = user.User.FirstName;
|
||||
string lastName = user.User.LastName;
|
||||
string email = user.Email;
|
||||
DateTime dateOfBirth = DateTime.Parse(user.DateOfBirth);
|
||||
|
||||
string generatedPassword = PasswordGenerator.Generate(
|
||||
length: 64,
|
||||
numberOfDigits: 10,
|
||||
numberOfSymbols: 10
|
||||
table.AddRow(
|
||||
user.User.Username,
|
||||
user.User.FirstName,
|
||||
user.User.LastName,
|
||||
user.Email,
|
||||
user.DateOfBirth.ToString()
|
||||
);
|
||||
string hashedPassword = passwordInfrastructure.Hash(generatedPassword);
|
||||
|
||||
UserRegistrationDto registrationDto = new(
|
||||
username,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
dateOfBirth,
|
||||
hashedPassword
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
UserAccount createdUser = await authRepository.RegisterUserAsync(registrationDto);
|
||||
Console.WriteLine(
|
||||
$"Created user: {createdUser.Username} ({createdUser.UserAccountId})"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine($"Error creating user {username}: {ex.Message}");
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await main();
|
||||
return table;
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
#region
|
||||
|
||||
using System.Text.Json;
|
||||
using Database.Seed.PipelineData;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace Database.Seed.Sqlite;
|
||||
|
||||
public sealed record SeedData(
|
||||
IReadOnlyList<BreweryRecord> Breweries,
|
||||
IReadOnlyList<UserRecord> Users
|
||||
);
|
||||
|
||||
public sealed class PipelineSeedDataReader
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
@@ -17,13 +18,40 @@ public sealed class PipelineSeedDataReader
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
public IReadOnlyList<BreweryRecord> ReadBreweryRecords()
|
||||
/// <summary>
|
||||
/// Reads breweries and users in a single connection, loading the cities
|
||||
/// lookup only once and sharing it between both reads.
|
||||
/// </summary>
|
||||
public async Task<SeedData> ReadSeedDataAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using SqliteConnection connection = new(_connectionString);
|
||||
connection.Open();
|
||||
await using SqliteConnection connection = new(_connectionString);
|
||||
await connection.OpenAsync(cancellationToken);
|
||||
|
||||
IReadOnlyDictionary<long, City> cities = ReadCities(connection);
|
||||
IReadOnlyDictionary<long, City> cities = await ReadCitiesAsync(
|
||||
connection,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
IReadOnlyList<BreweryRecord> breweries = await ReadBreweryRecordsAsync(
|
||||
connection,
|
||||
cities,
|
||||
cancellationToken
|
||||
);
|
||||
IReadOnlyList<UserRecord> users = await ReadUserRecordsAsync(
|
||||
connection,
|
||||
cities,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
return new SeedData(breweries, users);
|
||||
}
|
||||
|
||||
private static async Task<IReadOnlyList<BreweryRecord>> ReadBreweryRecordsAsync(
|
||||
SqliteConnection connection,
|
||||
IReadOnlyDictionary<long, City> cities,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT b.name_en, b.description_en, b.name_local, b.description_local,
|
||||
ba.city_id, ba.postal_code
|
||||
@@ -31,25 +59,32 @@ public sealed class PipelineSeedDataReader
|
||||
JOIN brewery_addresses ba ON ba.brewery_id = b.id;
|
||||
""";
|
||||
|
||||
using SqliteCommand command = new(sql, connection);
|
||||
using SqliteDataReader reader = command.ExecuteReader();
|
||||
await using SqliteCommand command = new(sql, connection);
|
||||
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
|
||||
const int nameEnIndex = 0;
|
||||
const int descriptionEnIndex = 1;
|
||||
const int nameLocalIndex = 2;
|
||||
const int descriptionLocalIndex = 3;
|
||||
const int cityIdIndex = 4;
|
||||
const int postalCodeIndex = 5;
|
||||
|
||||
List<BreweryRecord> records = [];
|
||||
while (reader.Read())
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
records.Add(
|
||||
new BreweryRecord
|
||||
{
|
||||
Brewery = new BreweryResult
|
||||
{
|
||||
NameEn = reader.GetString(0),
|
||||
DescriptionEn = reader.GetString(1),
|
||||
NameLocal = reader.GetString(2),
|
||||
DescriptionLocal = reader.GetString(3),
|
||||
NameEn = reader.GetString(nameEnIndex),
|
||||
DescriptionEn = reader.GetString(descriptionEnIndex),
|
||||
NameLocal = reader.GetString(nameLocalIndex),
|
||||
DescriptionLocal = reader.GetString(descriptionLocalIndex),
|
||||
},
|
||||
Address = new BreweryAddress
|
||||
{
|
||||
City = cities[reader.GetInt64(4)],
|
||||
PostalCode = reader.GetString(5),
|
||||
City = cities[reader.GetInt64(cityIdIndex)],
|
||||
PostalCode = reader.GetString(postalCodeIndex),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -57,13 +92,12 @@ public sealed class PipelineSeedDataReader
|
||||
return records;
|
||||
}
|
||||
|
||||
public IReadOnlyList<UserRecord> ReadUserRecords()
|
||||
private static async Task<IReadOnlyList<UserRecord>> ReadUserRecordsAsync(
|
||||
SqliteConnection connection,
|
||||
IReadOnlyDictionary<long, City> cities,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
using SqliteConnection connection = new(_connectionString);
|
||||
connection.Open();
|
||||
|
||||
IReadOnlyDictionary<long, City> cities = ReadCities(connection);
|
||||
|
||||
const string sql = """
|
||||
SELECT u.first_name, u.last_name, u.gender, u.username, u.bio,
|
||||
u.activity_weight, u.email, u.date_of_birth,
|
||||
@@ -72,29 +106,40 @@ public sealed class PipelineSeedDataReader
|
||||
JOIN user_addresses ua ON ua.user_id = u.id;
|
||||
""";
|
||||
|
||||
using SqliteCommand command = new(sql, connection);
|
||||
using SqliteDataReader reader = command.ExecuteReader();
|
||||
await using SqliteCommand command = new(sql, connection);
|
||||
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
|
||||
const int firstNameIndex = 0;
|
||||
const int lastNameIndex = 1;
|
||||
const int genderIndex = 2;
|
||||
const int usernameIndex = 3;
|
||||
const int bioIndex = 4;
|
||||
const int activityWeightIndex = 5;
|
||||
const int emailIndex = 6;
|
||||
const int dateOfBirthIndex = 7;
|
||||
const int cityIdIndex = 8;
|
||||
const int postalCodeIndex = 9;
|
||||
|
||||
List<UserRecord> records = [];
|
||||
while (reader.Read())
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
records.Add(
|
||||
new UserRecord
|
||||
{
|
||||
User = new UserResult
|
||||
{
|
||||
FirstName = reader.GetString(0),
|
||||
LastName = reader.GetString(1),
|
||||
Gender = reader.GetString(2),
|
||||
Username = reader.GetString(3),
|
||||
Bio = reader.GetString(4),
|
||||
ActivityWeight = (float)reader.GetDouble(5),
|
||||
FirstName = reader.GetString(firstNameIndex),
|
||||
LastName = reader.GetString(lastNameIndex),
|
||||
Gender = reader.GetString(genderIndex),
|
||||
Username = reader.GetString(usernameIndex),
|
||||
Bio = reader.GetString(bioIndex),
|
||||
ActivityWeight = (float)reader.GetDouble(activityWeightIndex),
|
||||
},
|
||||
Email = reader.GetString(6),
|
||||
DateOfBirth = reader.GetString(7),
|
||||
Email = reader.GetString(emailIndex),
|
||||
DateOfBirth = reader.GetString(dateOfBirthIndex),
|
||||
Address = new UserAddress
|
||||
{
|
||||
City = cities[reader.GetInt64(8)],
|
||||
PostalCode = reader.GetString(9),
|
||||
City = cities[reader.GetInt64(cityIdIndex)],
|
||||
PostalCode = reader.GetString(postalCodeIndex),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -102,7 +147,10 @@ public sealed class PipelineSeedDataReader
|
||||
return records;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<long, City> ReadCities(SqliteConnection connection)
|
||||
private static async Task<IReadOnlyDictionary<long, City>> ReadCitiesAsync(
|
||||
SqliteConnection connection,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT
|
||||
@@ -118,33 +166,34 @@ public sealed class PipelineSeedDataReader
|
||||
FROM cities;
|
||||
""";
|
||||
|
||||
using SqliteCommand command = new(sql, connection);
|
||||
using SqliteDataReader reader = command.ExecuteReader();
|
||||
await using SqliteCommand command = new(sql, connection);
|
||||
await using SqliteDataReader reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||
|
||||
Dictionary<long, City> cities = [];
|
||||
|
||||
const int cityNameBindIndex = 1;
|
||||
const int stateProvinceBindIndex = 2;
|
||||
const int iso31662BindIndex = 3;
|
||||
const int countryBindIndex = 4;
|
||||
const int iso31661BindIndex = 5;
|
||||
const int localLanguagesBindIndex = 6;
|
||||
const int countryFormatRegexBindIndex = 7;
|
||||
const int cityRegexesBindIndex = 8;
|
||||
const int idIndex = 0;
|
||||
const int cityNameIndex = 1;
|
||||
const int stateProvinceIndex = 2;
|
||||
const int iso31662Index = 3;
|
||||
const int countryIndex = 4;
|
||||
const int iso31661Index = 5;
|
||||
const int localLanguagesIndex = 6;
|
||||
const int countryFormatRegexIndex = 7;
|
||||
const int cityRegexesIndex = 8;
|
||||
|
||||
while (reader.Read())
|
||||
cities[reader.GetInt64(0)] = new City
|
||||
while (await reader.ReadAsync(cancellationToken))
|
||||
cities[reader.GetInt64(idIndex)] = new City
|
||||
{
|
||||
CityName = reader.GetString(cityNameBindIndex),
|
||||
StateProvince = reader.GetString(stateProvinceBindIndex),
|
||||
Iso31662 = reader.GetString(iso31662BindIndex),
|
||||
Country = reader.GetString(countryBindIndex),
|
||||
Iso31661 = reader.GetString(iso31661BindIndex),
|
||||
LocalLanguages = DeserializeStringArray(reader.GetString(localLanguagesBindIndex)),
|
||||
CityName = reader.GetString(cityNameIndex),
|
||||
StateProvince = reader.GetString(stateProvinceIndex),
|
||||
Iso31662 = reader.GetString(iso31662Index),
|
||||
Country = reader.GetString(countryIndex),
|
||||
Iso31661 = reader.GetString(iso31661Index),
|
||||
LocalLanguages = DeserializeStringArray(reader.GetString(localLanguagesIndex)),
|
||||
PostalCode = new PostalCodeSpec
|
||||
{
|
||||
CountryFormatRegex = reader.GetString(countryFormatRegexBindIndex),
|
||||
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesBindIndex)),
|
||||
CountryFormatRegex = reader.GetString(countryFormatRegexIndex),
|
||||
CityRegexes = DeserializeStringArray(reader.GetString(cityRegexesIndex)),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace Database.Seed;
|
||||
|
||||
public interface IExportDatabase
|
||||
{
|
||||
void SeedLocations(IEnumerable<Location> locations);
|
||||
void SeedUsers(IEnumerable<User> users);
|
||||
|
||||
void SeedBreweries(IEnumerable<Brewery> breweries);
|
||||
}
|
||||
|
||||
public interface IImportDatabase
|
||||
{
|
||||
IEnumerable<Location> GetLocations();
|
||||
IEnumerable<User> GetUsers();
|
||||
|
||||
IEnumerable<Brewery> GetBreweries();
|
||||
}
|
||||
Reference in New Issue
Block a user