mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 17:47:22 +00:00
code style cleanup
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Migrations</RootNamespace>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Migrations</RootNamespace>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup" Version="5.0.41" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="scripts/**/*.sql" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dbup" Version="5.0.41"/>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.2"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="scripts/**/*.sql"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,50 +1,57 @@
|
||||
using System.Data;
|
||||
using System.Reflection;
|
||||
using DbUp;
|
||||
using DbUp.Engine;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace Database.Migrations;
|
||||
|
||||
/// <summary>
|
||||
/// Entry point for the database migration runner. Reads connection details from
|
||||
/// environment variables, optionally clears and recreates the target database, and
|
||||
/// applies all SQL migration scripts embedded in this assembly using DbUp.
|
||||
/// Entry point for the database migration runner. Reads connection details from
|
||||
/// environment variables, optionally clears and recreates the target database, and
|
||||
/// applies all SQL migration scripts embedded in this assembly using DbUp.
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
/// <summary>The connection string for the target application database (<c>DB_NAME</c>).</summary>
|
||||
private static readonly string connectionString = BuildConnectionString();
|
||||
|
||||
/// <summary>The connection string for the <c>master</c> database, used for create/drop operations.</summary>
|
||||
private static readonly string masterConnectionString = BuildConnectionString("master");
|
||||
|
||||
/// <summary>
|
||||
/// Builds a SQL Server connection string from the <c>DB_SERVER</c>, <c>DB_NAME</c>,
|
||||
/// <c>DB_USER</c>, <c>DB_PASSWORD</c>, and <c>DB_TRUST_SERVER_CERTIFICATE</c>
|
||||
/// environment variables.
|
||||
/// Builds a SQL Server connection string from the <c>DB_SERVER</c>, <c>DB_NAME</c>,
|
||||
/// <c>DB_USER</c>, <c>DB_PASSWORD</c>, and <c>DB_TRUST_SERVER_CERTIFICATE</c>
|
||||
/// environment variables.
|
||||
/// </summary>
|
||||
/// <param name="databaseName">
|
||||
/// The database (initial catalog) to connect to. When <c>null</c>, falls back to the
|
||||
/// <c>DB_NAME</c> environment variable.
|
||||
/// The database (initial catalog) to connect to. When <c>null</c>, falls back to the
|
||||
/// <c>DB_NAME</c> environment variable.
|
||||
/// </param>
|
||||
/// <returns>A fully built SQL Server connection string.</returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when <c>DB_SERVER</c>, <c>DB_USER</c>, <c>DB_PASSWORD</c>, or (when
|
||||
/// <paramref name="databaseName"/> is <c>null</c>) <c>DB_NAME</c> is not set.
|
||||
/// Thrown when <c>DB_SERVER</c>, <c>DB_USER</c>, <c>DB_PASSWORD</c>, or (when
|
||||
/// <paramref name="databaseName" /> is <c>null</c>) <c>DB_NAME</c> is not set.
|
||||
/// </exception>
|
||||
private static string BuildConnectionString(string? databaseName = null)
|
||||
{
|
||||
var server = Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
string server = Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
|
||||
var dbName = databaseName
|
||||
?? Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
string dbName = databaseName
|
||||
?? Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
|
||||
var user = Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
string user = Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
|
||||
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
string password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
|
||||
var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
SqlConnectionStringBuilder builder = new()
|
||||
{
|
||||
DataSource = server,
|
||||
InitialCatalog = dbName,
|
||||
@@ -57,47 +64,41 @@ public static class Program
|
||||
return builder.ConnectionString;
|
||||
}
|
||||
|
||||
/// <summary>The connection string for the target application database (<c>DB_NAME</c>).</summary>
|
||||
private static readonly string connectionString = BuildConnectionString();
|
||||
|
||||
/// <summary>The connection string for the <c>master</c> database, used for create/drop operations.</summary>
|
||||
private static readonly string masterConnectionString = BuildConnectionString("master");
|
||||
|
||||
/// <summary>
|
||||
/// Applies all pending SQL migration scripts embedded in this assembly to the target
|
||||
/// database using DbUp, logging progress to the console.
|
||||
/// Applies all pending SQL migration scripts embedded in this assembly to the target
|
||||
/// database using DbUp, logging progress to the console.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the upgrade completed successfully; otherwise <c>false</c>.</returns>
|
||||
private static bool DeployMigrations()
|
||||
{
|
||||
var upgrader = DeployChanges
|
||||
UpgradeEngine? upgrader = DeployChanges
|
||||
.To.SqlDatabase(connectionString)
|
||||
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
|
||||
.LogToConsole()
|
||||
.Build();
|
||||
|
||||
var result = upgrader.PerformUpgrade();
|
||||
DatabaseUpgradeResult? result = upgrader.PerformUpgrade();
|
||||
return result.Successful;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the <c>Biergarten</c> database if it exists, first forcing it into single-user
|
||||
/// mode with rollback to terminate any existing connections.
|
||||
/// Drops the <c>Biergarten</c> database if it exists, first forcing it into single-user
|
||||
/// mode with rollback to terminate any existing connections.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the database was dropped (or did not exist) without error; <c>false</c>
|
||||
/// if an error occurred while connecting or dropping the database.
|
||||
/// <c>true</c> if the database was dropped (or did not exist) without error; <c>false</c>
|
||||
/// if an error occurred while connecting or dropping the database.
|
||||
/// </returns>
|
||||
private static bool ClearDatabase()
|
||||
{
|
||||
var myConn = new SqlConnection(masterConnectionString);
|
||||
SqlConnection myConn = new(masterConnectionString);
|
||||
|
||||
try
|
||||
{
|
||||
myConn.Open();
|
||||
|
||||
// First, set the database to single user mode to close all connections
|
||||
var setModeCommand = new SqlCommand(
|
||||
SqlCommand setModeCommand = new(
|
||||
"IF EXISTS (SELECT 1 FROM sys.databases WHERE name = 'Biergarten') " +
|
||||
"ALTER DATABASE [Biergarten] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;",
|
||||
myConn);
|
||||
@@ -106,79 +107,75 @@ public static class Program
|
||||
setModeCommand.ExecuteNonQuery();
|
||||
Console.WriteLine("Database set to single user mode.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Warning: Could not set single user mode: {ex.Message}");
|
||||
}
|
||||
|
||||
// Then drop the database
|
||||
var dropCommand = new SqlCommand("DROP DATABASE IF EXISTS [Biergarten];", myConn);
|
||||
SqlCommand dropCommand = new("DROP DATABASE IF EXISTS [Biergarten];", myConn);
|
||||
try
|
||||
{
|
||||
dropCommand.ExecuteNonQuery();
|
||||
Console.WriteLine("Database cleared successfully.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error dropping database: {ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error clearing database: {ex}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (myConn.State == ConnectionState.Open)
|
||||
{
|
||||
myConn.Close();
|
||||
}
|
||||
if (myConn.State == ConnectionState.Open) myConn.Close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the <c>Biergarten</c> database on the master connection if it does not
|
||||
/// already exist. Errors encountered while creating the database are logged but do
|
||||
/// not stop execution.
|
||||
/// Creates the <c>Biergarten</c> database on the master connection if it does not
|
||||
/// already exist. Errors encountered while creating the database are logged but do
|
||||
/// not stop execution.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> always; this method does not propagate database errors as a failure result.</returns>
|
||||
private static bool CreateDatabaseIfNotExists()
|
||||
{
|
||||
var myConn = new SqlConnection(masterConnectionString);
|
||||
SqlConnection myConn = new(masterConnectionString);
|
||||
|
||||
const string str = """
|
||||
IF NOT EXISTS (SELECT 1 FROM sys.databases WHERE name = 'Biergarten')
|
||||
CREATE DATABASE [Biergarten]
|
||||
""";
|
||||
|
||||
var myCommand = new SqlCommand(str, myConn);
|
||||
SqlCommand myCommand = new(str, myConn);
|
||||
try
|
||||
{
|
||||
myConn.Open();
|
||||
myCommand.ExecuteNonQuery();
|
||||
Console.WriteLine("Database creation command executed successfully.");
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error creating database: {ex}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (myConn.State == ConnectionState.Open)
|
||||
{
|
||||
myConn.Close();
|
||||
}
|
||||
if (myConn.State == ConnectionState.Open) myConn.Close();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Migration runner entry point. Optionally clears the existing database when the
|
||||
/// <c>CLEAR_DATABASE</c> environment variable is set to <c>"true"</c>, ensures the
|
||||
/// database exists, then deploys all pending migrations.
|
||||
/// Migration runner entry point. Optionally clears the existing database when the
|
||||
/// <c>CLEAR_DATABASE</c> environment variable is set to <c>"true"</c>, ensures the
|
||||
/// database exists, then deploys all pending migrations.
|
||||
/// </summary>
|
||||
/// <param name="args">Command-line arguments (unused).</param>
|
||||
/// <returns><c>0</c> if migrations completed successfully; <c>1</c> if they failed or an error occurred.</returns>
|
||||
@@ -188,7 +185,7 @@ public static class Program
|
||||
|
||||
try
|
||||
{
|
||||
var clearDatabase = Environment.GetEnvironmentVariable("CLEAR_DATABASE");
|
||||
string? clearDatabase = Environment.GetEnvironmentVariable("CLEAR_DATABASE");
|
||||
if (clearDatabase == "true")
|
||||
{
|
||||
Console.WriteLine("CLEAR_DATABASE is enabled. Clearing existing database...");
|
||||
@@ -196,18 +193,16 @@ public static class Program
|
||||
}
|
||||
|
||||
CreateDatabaseIfNotExists();
|
||||
var success = DeployMigrations();
|
||||
bool success = DeployMigrations();
|
||||
|
||||
if (success)
|
||||
{
|
||||
Console.WriteLine("Database migrations completed successfully.");
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Database migrations failed.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine("Database migrations failed.");
|
||||
return 1;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -216,4 +211,4 @@ public static class Program
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserAccount
|
||||
PRIMARY KEY (UserAccountID),
|
||||
@@ -56,29 +56,30 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_Photo
|
||||
PRIMARY KEY (PhotoID),
|
||||
|
||||
CONSTRAINT FK_Photo_UploadedBy
|
||||
FOREIGN KEY (UploadedByID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_Photo_UploadedByID
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_Photo_UploadedByID
|
||||
ON Photo(UploadedByID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -86,31 +87,32 @@ CREATE 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserAvatar PRIMARY KEY (UserAvatarID),
|
||||
|
||||
CONSTRAINT FK_UserAvatar_UserAccount
|
||||
FOREIGN KEY (UserAccountID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE CASCADE,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT FK_UserAvatar_PhotoID
|
||||
FOREIGN KEY (PhotoID)
|
||||
REFERENCES Photo(PhotoID),
|
||||
REFERENCES Photo (PhotoID),
|
||||
|
||||
CONSTRAINT AK_UserAvatar_UserAccountID
|
||||
UNIQUE (UserAccountID)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserAvatar_UserAccount
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserAvatar_UserAccount
|
||||
ON UserAvatar(UserAccountID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -118,29 +120,30 @@ CREATE 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserVerification
|
||||
PRIMARY KEY (UserVerificationID),
|
||||
|
||||
CONSTRAINT FK_UserVerification_UserAccount
|
||||
FOREIGN KEY (UserAccountID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE CASCADE,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT AK_UserVerification_UserAccountID
|
||||
UNIQUE (UserAccountID)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserVerification_UserAccount
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserVerification_UserAccount
|
||||
ON UserVerification(UserAccountID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -151,37 +154,39 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserCredential
|
||||
PRIMARY KEY (UserCredentialID),
|
||||
|
||||
CONSTRAINT FK_UserCredential_UserAccount
|
||||
FOREIGN KEY (UserAccountID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE CASCADE
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserCredential_UserAccount
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserCredential_UserAccount
|
||||
ON UserCredential(UserAccountID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserCredential_Account_Active
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserCredential_Account_Active
|
||||
ON UserCredential(UserAccountID, IsRevoked, Expiry)
|
||||
INCLUDE (Hash);
|
||||
|
||||
@@ -190,39 +195,42 @@ CREATE 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_UserFollow
|
||||
PRIMARY KEY (UserFollowID),
|
||||
|
||||
CONSTRAINT FK_UserFollow_UserAccount
|
||||
FOREIGN KEY (UserAccountID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
|
||||
CONSTRAINT FK_UserFollow_UserAccountFollowing
|
||||
FOREIGN KEY (FollowingID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
|
||||
CONSTRAINT CK_CannotFollowOwnAccount
|
||||
CHECK (UserAccountID != FollowingID)
|
||||
);
|
||||
CHECK (UserAccountID != FollowingID
|
||||
)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
|
||||
ON UserFollow(UserAccountID, FollowingID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
|
||||
ON UserFollow(FollowingID, UserAccountID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -230,14 +238,14 @@ CREATE 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_Country
|
||||
PRIMARY KEY (CountryID),
|
||||
@@ -251,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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_StateProvince
|
||||
PRIMARY KEY (StateProvinceID),
|
||||
@@ -271,10 +279,11 @@ CREATE TABLE StateProvince
|
||||
|
||||
CONSTRAINT FK_StateProvince_Country
|
||||
FOREIGN KEY (CountryID)
|
||||
REFERENCES Country(CountryID)
|
||||
REFERENCES Country (CountryID)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_StateProvince_Country
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_StateProvince_Country
|
||||
ON StateProvince(CountryID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -282,24 +291,25 @@ CREATE 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_City
|
||||
PRIMARY KEY (CityID),
|
||||
|
||||
CONSTRAINT FK_City_StateProvince
|
||||
FOREIGN KEY (StateProvinceID)
|
||||
REFERENCES StateProvince(StateProvinceID)
|
||||
REFERENCES StateProvince (StateProvinceID)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_City_StateProvince
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_City_StateProvince
|
||||
ON City(StateProvinceID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -310,29 +320,30 @@ 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(512) NOT NULL,
|
||||
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CreatedAt DATETIME NOT NULL
|
||||
CONSTRAINT DF_BreweryPost_CreatedAt DEFAULT GETDATE(),
|
||||
|
||||
UpdatedAt DATETIME NULL,
|
||||
UpdatedAt DATETIME NULL,
|
||||
|
||||
Timer ROWVERSION,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPost
|
||||
PRIMARY KEY (BreweryPostID),
|
||||
|
||||
CONSTRAINT FK_BreweryPost_UserAccount
|
||||
FOREIGN KEY (PostedByID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BreweryPost_PostedByID
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BreweryPost_PostedByID
|
||||
ON BreweryPost(PostedByID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -343,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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPostLocation
|
||||
PRIMARY KEY (BreweryPostLocationID),
|
||||
@@ -365,18 +376,20 @@ CREATE TABLE BreweryPostLocation
|
||||
|
||||
CONSTRAINT FK_BreweryPostLocation_BreweryPost
|
||||
FOREIGN KEY (BreweryPostID)
|
||||
REFERENCES BreweryPost(BreweryPostID)
|
||||
ON DELETE CASCADE,
|
||||
REFERENCES BreweryPost (BreweryPostID)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT FK_BreweryPostLocation_City
|
||||
FOREIGN KEY (CityID)
|
||||
REFERENCES City(CityID)
|
||||
REFERENCES City (CityID)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_BreweryPost
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BreweryPostLocation_BreweryPost
|
||||
ON BreweryPostLocation(BreweryPostID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_City
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BreweryPostLocation_City
|
||||
ON BreweryPostLocation(CityID);
|
||||
|
||||
-- To assess when the time comes:
|
||||
@@ -399,33 +412,35 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BreweryPostPhoto
|
||||
PRIMARY KEY (BreweryPostPhotoID),
|
||||
|
||||
CONSTRAINT FK_BreweryPostPhoto_BreweryPost
|
||||
FOREIGN KEY (BreweryPostID)
|
||||
REFERENCES BreweryPost(BreweryPostID)
|
||||
ON DELETE CASCADE,
|
||||
REFERENCES BreweryPost (BreweryPostID)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT FK_BreweryPostPhoto_Photo
|
||||
FOREIGN KEY (PhotoID)
|
||||
REFERENCES Photo(PhotoID)
|
||||
ON DELETE CASCADE
|
||||
REFERENCES Photo (PhotoID)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_Photo_BreweryPost
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BreweryPostPhoto_Photo_BreweryPost
|
||||
ON BreweryPostPhoto(PhotoID, BreweryPostID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_BreweryPost_Photo
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BreweryPostPhoto_BreweryPost_Photo
|
||||
ON BreweryPostPhoto(BreweryPostID, PhotoID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -436,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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerStyle
|
||||
PRIMARY KEY (BeerStyleID),
|
||||
@@ -454,47 +469,47 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPost
|
||||
PRIMARY KEY (BeerPostID),
|
||||
|
||||
CONSTRAINT FK_BeerPost_PostedBy
|
||||
FOREIGN KEY (PostedByID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
|
||||
CONSTRAINT FK_BeerPost_BeerStyle
|
||||
FOREIGN KEY (BeerStyleID)
|
||||
REFERENCES BeerStyle(BeerStyleID),
|
||||
REFERENCES BeerStyle (BeerStyleID),
|
||||
|
||||
CONSTRAINT FK_BeerPost_Brewery
|
||||
FOREIGN KEY (BrewedByID)
|
||||
REFERENCES BreweryPost(BreweryPostID),
|
||||
REFERENCES BreweryPost (BreweryPostID),
|
||||
|
||||
CONSTRAINT CHK_BeerPost_ABV
|
||||
CHECK (ABV >= 0 AND ABV <= 67),
|
||||
@@ -503,13 +518,16 @@ CREATE TABLE BeerPost
|
||||
CHECK (IBU >= 0 AND IBU <= 120)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPost_PostedBy
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPost_PostedBy
|
||||
ON BeerPost(PostedByID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPost_BeerStyle
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPost_BeerStyle
|
||||
ON BeerPost(BeerStyleID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPost_BrewedBy
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPost_BrewedBy
|
||||
ON BeerPost(BrewedByID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -520,33 +538,35 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPostPhoto
|
||||
PRIMARY KEY (BeerPostPhotoID),
|
||||
|
||||
CONSTRAINT FK_BeerPostPhoto_BeerPost
|
||||
FOREIGN KEY (BeerPostID)
|
||||
REFERENCES BeerPost(BeerPostID)
|
||||
ON DELETE CASCADE,
|
||||
REFERENCES BeerPost (BeerPostID)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT FK_BeerPostPhoto_Photo
|
||||
FOREIGN KEY (PhotoID)
|
||||
REFERENCES Photo(PhotoID)
|
||||
ON DELETE CASCADE
|
||||
REFERENCES Photo (PhotoID)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_Photo_BeerPost
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPostPhoto_Photo_BeerPost
|
||||
ON BeerPostPhoto(PhotoID, BeerPostID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_BeerPost_Photo
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPostPhoto_BeerPost_Photo
|
||||
ON BeerPostPhoto(BeerPostID, PhotoID);
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
@@ -557,39 +577,41 @@ 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,
|
||||
Timer ROWVERSION,
|
||||
|
||||
CONSTRAINT PK_BeerPostComment
|
||||
PRIMARY KEY (BeerPostCommentID),
|
||||
|
||||
CONSTRAINT FK_BeerPostComment_BeerPost
|
||||
FOREIGN KEY (BeerPostID)
|
||||
REFERENCES BeerPost(BeerPostID),
|
||||
REFERENCES BeerPost (BeerPostID),
|
||||
|
||||
CONSTRAINT FK_BeerPostComment_UserAccount
|
||||
FOREIGN KEY (CommentedByID)
|
||||
REFERENCES UserAccount(UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
REFERENCES UserAccount (UserAccountID)
|
||||
ON DELETE NO ACTION,
|
||||
|
||||
CONSTRAINT CHK_BeerPostComment_Rating
|
||||
CHECK (Rating BETWEEN 1 AND 5)
|
||||
);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPostComment_BeerPost
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPostComment_BeerPost
|
||||
ON BeerPostComment(BeerPostID);
|
||||
|
||||
CREATE NONCLUSTERED INDEX IX_BeerPostComment_CommentedBy
|
||||
CREATE
|
||||
NONCLUSTERED INDEX IX_BeerPostComment_CommentedBy
|
||||
ON BeerPostComment(CommentedByID);
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
CREATE OR ALTER FUNCTION dbo.UDF_GetCountryIdByCode
|
||||
(
|
||||
CREATE
|
||||
OR
|
||||
ALTER FUNCTION dbo.UDF_GetCountryIdByCode
|
||||
(
|
||||
@CountryCode NVARCHAR(2)
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @CountryId UNIQUEIDENTIFIER;
|
||||
DECLARE
|
||||
@CountryId UNIQUEIDENTIFIER;
|
||||
|
||||
SELECT @CountryId = CountryID
|
||||
FROM dbo.Country
|
||||
WHERE ISO3166_1 = @CountryCode;
|
||||
SELECT @CountryId = CountryID
|
||||
FROM dbo.Country
|
||||
WHERE ISO3166_1 = @CountryCode;
|
||||
|
||||
RETURN @CountryId;
|
||||
RETURN @CountryId;
|
||||
END;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
CREATE OR ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode
|
||||
(
|
||||
CREATE
|
||||
OR
|
||||
ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode
|
||||
(
|
||||
@StateProvinceCode NVARCHAR(6)
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
)
|
||||
RETURNS UNIQUEIDENTIFIER
|
||||
AS
|
||||
BEGIN
|
||||
DECLARE @StateProvinceId UNIQUEIDENTIFIER;
|
||||
SELECT @StateProvinceId = StateProvinceID
|
||||
FROM dbo.StateProvince
|
||||
WHERE ISO3166_2 = @StateProvinceCode;
|
||||
RETURN @StateProvinceId;
|
||||
DECLARE
|
||||
@StateProvinceId UNIQUEIDENTIFIER;
|
||||
SELECT @StateProvinceId = StateProvinceID
|
||||
FROM dbo.StateProvince
|
||||
WHERE ISO3166_2 = @StateProvinceCode;
|
||||
RETURN @StateProvinceId;
|
||||
END;
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
|
||||
CREATE OR ALTER PROCEDURE usp_CreateUserAccount
|
||||
(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_CreateUserAccount
|
||||
(
|
||||
@UserAccountId UNIQUEIDENTIFIER OUTPUT,
|
||||
@Username VARCHAR(64),
|
||||
@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
|
||||
@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
|
||||
VALUES
|
||||
(
|
||||
@Username,
|
||||
@FirstName,
|
||||
@LastName,
|
||||
@DateOfBirth,
|
||||
@Email
|
||||
@Username, @FirstName, @LastName, @DateOfBirth, @Email
|
||||
);
|
||||
|
||||
SELECT @UserAccountId = UserAccountID FROM @Inserted;
|
||||
SELECT @UserAccountId = UserAccountID
|
||||
FROM @Inserted;
|
||||
END;
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
|
||||
CREATE OR ALTER PROCEDURE usp_DeleteUserAccount
|
||||
(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_DeleteUserAccount
|
||||
(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
SET
|
||||
NOCOUNT ON
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM UserAccount WHERE UserAccountId = @UserAccountId)
|
||||
BEGIN
|
||||
RAISERROR('UserAccount with the specified ID does not exist.', 16,
|
||||
BEGIN
|
||||
RAISERROR
|
||||
('UserAccount with the specified ID does not exist.', 16,
|
||||
1);
|
||||
ROLLBACK TRANSACTION
|
||||
ROLLBACK TRANSACTION
|
||||
RETURN
|
||||
END
|
||||
END
|
||||
|
||||
DELETE FROM UserAccount
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
DELETE
|
||||
FROM UserAccount
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
END;
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetAllUserAccounts
|
||||
AS
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_GetAllUserAccounts
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount;
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount;
|
||||
END;
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetUserAccountByEmail(
|
||||
@Email VARCHAR(128)
|
||||
)
|
||||
AS
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_GetUserAccountByEmail(
|
||||
@Email VARCHAR (128)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Email = @Email;
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Email = @Email;
|
||||
END;
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
CREATE OR ALTER PROCEDURE USP_GetUserAccountById(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE USP_GetUserAccountById(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @UserAccountId;
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @UserAccountId;
|
||||
END
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
CREATE OR ALTER PROCEDURE usp_GetUserAccountByUsername(
|
||||
@Username VARCHAR(64)
|
||||
)
|
||||
AS
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_GetUserAccountByUsername(
|
||||
@Username VARCHAR (64)
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Username = @Username;
|
||||
SELECT UserAccountID,
|
||||
Username,
|
||||
FirstName,
|
||||
LastName,
|
||||
Email,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DateOfBirth,
|
||||
Timer
|
||||
FROM dbo.UserAccount
|
||||
WHERE Username = @Username;
|
||||
END;
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
CREATE OR ALTER PROCEDURE usp_UpdateUserAccount(
|
||||
@Username VARCHAR(64),
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE usp_UpdateUserAccount(
|
||||
@Username VARCHAR (64),
|
||||
@FirstName NVARCHAR(128),
|
||||
@LastName NVARCHAR(128),
|
||||
@DateOfBirth DATETIME,
|
||||
@Email VARCHAR(128),
|
||||
@Email VARCHAR (128),
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
NOCOUNT ON;
|
||||
|
||||
UPDATE UserAccount
|
||||
SET Username = @Username,
|
||||
FirstName = @FirstName,
|
||||
LastName = @LastName,
|
||||
DateOfBirth = @DateOfBirth,
|
||||
Email = @Email
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
UPDATE UserAccount
|
||||
SET Username = @Username,
|
||||
FirstName = @FirstName,
|
||||
LastName = @LastName,
|
||||
DateOfBirth = @DateOfBirth,
|
||||
Email = @Email
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
|
||||
IF @@ROWCOUNT = 0
|
||||
BEGIN
|
||||
IF
|
||||
@@ROWCOUNT = 0
|
||||
BEGIN
|
||||
THROW
|
||||
50001, 'UserAccount with the specified ID does not exist.', 1;
|
||||
END
|
||||
50001, 'UserAccount with the specified ID does not exist.', 1;
|
||||
END
|
||||
END;
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetActiveUserCredentialByUserAccountId(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_GetActiveUserCredentialByUserAccountId(
|
||||
@UserAccountId UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
SELECT
|
||||
UserCredentialId,
|
||||
UserAccountId,
|
||||
Hash,
|
||||
IsRevoked,
|
||||
CreatedAt,
|
||||
RevokedAt
|
||||
FROM dbo.UserCredential
|
||||
WHERE UserAccountId = @UserAccountId AND IsRevoked = 0;
|
||||
SELECT UserCredentialId,
|
||||
UserAccountId,
|
||||
Hash,
|
||||
IsRevoked,
|
||||
CreatedAt,
|
||||
RevokedAt
|
||||
FROM dbo.UserCredential
|
||||
WHERE UserAccountId = @UserAccountId
|
||||
AND IsRevoked = 0;
|
||||
END;
|
||||
@@ -1,24 +1,30 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_InvalidateUserCredential(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_InvalidateUserCredential(
|
||||
@UserAccountId_ UNIQUEIDENTIFIER
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC dbo.USP_GetUserAccountByID @UserAccountId = @UserAccountId_;
|
||||
IF @@ROWCOUNT = 0
|
||||
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;
|
||||
UPDATE dbo.UserCredential
|
||||
SET IsRevoked = 1,
|
||||
RevokedAt = GETDATE()
|
||||
WHERE UserAccountId = @UserAccountId_
|
||||
AND IsRevoked != 1;
|
||||
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
COMMIT TRANSACTION;
|
||||
END;
|
||||
@@ -1,21 +1,27 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_RegisterUser(
|
||||
@Username VARCHAR(64),
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_RegisterUser(
|
||||
@Username VARCHAR (64),
|
||||
@FirstName NVARCHAR(128),
|
||||
@LastName NVARCHAR(128),
|
||||
@DateOfBirth DATETIME,
|
||||
@Email VARCHAR(128),
|
||||
@Email VARCHAR (128),
|
||||
@Hash NVARCHAR(MAX)
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
DECLARE @UserAccountId_ UNIQUEIDENTIFIER;
|
||||
DECLARE
|
||||
@UserAccountId_ UNIQUEIDENTIFIER;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC usp_CreateUserAccount
|
||||
EXEC usp_CreateUserAccount
|
||||
@UserAccountId = @UserAccountId_ OUTPUT,
|
||||
@Username = @Username,
|
||||
@FirstName = @FirstName,
|
||||
@@ -23,20 +29,24 @@ BEGIN
|
||||
@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;
|
||||
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,28 +1,33 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_RotateUserCredential(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_RotateUserCredential(
|
||||
@UserAccountId_ UNIQUEIDENTIFIER,
|
||||
@Hash NVARCHAR(MAX)
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountId_
|
||||
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_;
|
||||
UPDATE dbo.UserCredential
|
||||
SET IsRevoked = 1,
|
||||
RevokedAt = GETDATE()
|
||||
WHERE UserAccountId = @UserAccountId_;
|
||||
|
||||
INSERT INTO dbo.UserCredential
|
||||
(UserAccountId, Hash)
|
||||
VALUES (@UserAccountId_, @Hash);
|
||||
INSERT INTO dbo.UserCredential
|
||||
(UserAccountId, Hash)
|
||||
VALUES (@UserAccountId_, @Hash);
|
||||
|
||||
|
||||
END;
|
||||
@@ -1,22 +1,29 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateUserVerification @UserAccountID_ UNIQUEIDENTIFIER,
|
||||
@VerificationDateTime DATETIME = NULL
|
||||
AS
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_CreateUserVerification @UserAccountID_ UNIQUEIDENTIFIER,
|
||||
@VerificationDateTime DATETIME = NULL
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF @VerificationDateTime IS NULL
|
||||
IF
|
||||
@VerificationDateTime IS NULL
|
||||
SET @VerificationDateTime = GETDATE();
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
EXEC USP_GetUserAccountByID @UserAccountId = @UserAccountID_;
|
||||
IF @@ROWCOUNT = 0
|
||||
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);
|
||||
INSERT INTO dbo.UserVerification
|
||||
(UserAccountId, VerificationDateTime)
|
||||
VALUES (@UserAccountID_, @VerificationDateTime);
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
COMMIT TRANSACTION;
|
||||
END
|
||||
|
||||
@@ -1,30 +1,40 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateCity(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_CreateCity(
|
||||
@CityName NVARCHAR(100),
|
||||
@StateProvinceCode NVARCHAR(6)
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
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
|
||||
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
|
||||
IF
|
||||
EXISTS (SELECT 1
|
||||
FROM dbo.City
|
||||
WHERE CityName = @CityName
|
||||
AND StateProvinceID = @StateProvinceId)
|
||||
BEGIN
|
||||
BEGIN
|
||||
|
||||
THROW 50002, 'City already exists.', 1;
|
||||
END
|
||||
THROW
|
||||
50002, 'City already exists.', 1;
|
||||
END
|
||||
|
||||
INSERT INTO dbo.City
|
||||
(StateProvinceID, CityName)
|
||||
VALUES (@StateProvinceId, @CityName);
|
||||
COMMIT TRANSACTION
|
||||
INSERT INTO dbo.City
|
||||
(StateProvinceID, CityName)
|
||||
VALUES (@StateProvinceId, @CityName);
|
||||
COMMIT TRANSACTION
|
||||
END;
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateCountry(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_CreateCountry(
|
||||
@CountryName NVARCHAR(100),
|
||||
@ISO3166_1 NVARCHAR(2)
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
BEGIN TRANSACTION;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
IF EXISTS (SELECT 1
|
||||
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;
|
||||
INSERT INTO dbo.Country
|
||||
(CountryName, ISO3166_1)
|
||||
VALUES (@CountryName, @ISO3166_1);
|
||||
COMMIT TRANSACTION;
|
||||
END;
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateStateProvince(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_CreateStateProvince(
|
||||
@StateProvinceName NVARCHAR(100),
|
||||
@ISO3166_2 NVARCHAR(6),
|
||||
@CountryCode NVARCHAR(2)
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF EXISTS (SELECT 1
|
||||
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;
|
||||
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);
|
||||
VALUES (@StateProvinceName, @ISO3166_2, @CountryId);
|
||||
END;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_CreateBrewery(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_CreateBrewery(
|
||||
@BreweryName NVARCHAR(256),
|
||||
@Description NVARCHAR(512),
|
||||
@PostedByID UNIQUEIDENTIFIER,
|
||||
@@ -7,44 +9,53 @@ CREATE OR ALTER PROCEDURE dbo.USP_CreateBrewery(
|
||||
@AddressLine2 NVARCHAR(256) = NULL,
|
||||
@PostalCode NVARCHAR(20),
|
||||
@Coordinates GEOGRAPHY = NULL
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF @BreweryName IS NULL
|
||||
IF
|
||||
@BreweryName IS NULL
|
||||
THROW 50001, 'Brewery name cannot be null.', 1;
|
||||
|
||||
IF @Description IS NULL
|
||||
IF
|
||||
@Description IS NULL
|
||||
THROW 50002, 'Brewery description cannot be null.', 1;
|
||||
|
||||
IF NOT EXISTS (SELECT 1
|
||||
IF
|
||||
NOT EXISTS (SELECT 1
|
||||
FROM dbo.UserAccount
|
||||
WHERE UserAccountID = @PostedByID)
|
||||
THROW 50404, 'User not found.', 1;
|
||||
|
||||
IF NOT EXISTS (SELECT 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();
|
||||
DECLARE
|
||||
@NewBreweryID UNIQUEIDENTIFIER = NEWID();
|
||||
DECLARE
|
||||
@NewBrewerLocationID UNIQUEIDENTIFIER = NEWID();
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
INSERT INTO dbo.BreweryPost
|
||||
(BreweryPostID, BreweryName, Description, PostedByID)
|
||||
VALUES (@NewBreweryID, @BreweryName, @Description, @PostedByID);
|
||||
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);
|
||||
INSERT INTO dbo.BreweryPostLocation
|
||||
(BreweryPostLocationID, BreweryPostID, CityID, AddressLine1, AddressLine2, PostalCode, Coordinates)
|
||||
VALUES (@NewBrewerLocationID, @NewBreweryID, @CityID, @AddressLine1, @AddressLine2, @PostalCode, @Coordinates);
|
||||
|
||||
COMMIT TRANSACTION;
|
||||
COMMIT TRANSACTION;
|
||||
|
||||
SELECT @NewBreweryID AS BreweryPostID,
|
||||
@NewBrewerLocationID AS BreweryPostLocationID;
|
||||
SELECT @NewBreweryID AS BreweryPostID,
|
||||
@NewBrewerLocationID AS BreweryPostLocationID;
|
||||
|
||||
END
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_DeleteBrewery
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_DeleteBrewery
|
||||
@BreweryPostID UNIQUEIDENTIFIER
|
||||
AS
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
|
||||
IF NOT EXISTS (SELECT 1
|
||||
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;
|
||||
DELETE
|
||||
FROM dbo.BreweryPost
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetAllBreweries(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_GetAllBreweries(
|
||||
@Limit INT = NULL,
|
||||
@Offset INT = NULL
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
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;
|
||||
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 +1,11 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_GetBreweryById @BreweryPostID UNIQUEIDENTIFIER
|
||||
AS
|
||||
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;
|
||||
SELECT *
|
||||
FROM BreweryPost bp
|
||||
INNER JOIN BreweryPostLocation bpl
|
||||
ON bp.BreweryPostID = bpl.BreweryPostID
|
||||
WHERE bp.BreweryPostID = @BreweryPostID;
|
||||
END
|
||||
@@ -1,4 +1,6 @@
|
||||
CREATE OR ALTER PROCEDURE dbo.USP_UpdateBrewery(
|
||||
CREATE
|
||||
OR
|
||||
ALTER PROCEDURE dbo.USP_UpdateBrewery(
|
||||
@BreweryPostID UNIQUEIDENTIFIER,
|
||||
@BreweryName NVARCHAR(256),
|
||||
@Description NVARCHAR(512),
|
||||
@@ -8,61 +10,70 @@ CREATE OR ALTER PROCEDURE dbo.USP_UpdateBrewery(
|
||||
@AddressLine2 NVARCHAR(256) = NULL,
|
||||
@PostalCode NVARCHAR(20) = NULL,
|
||||
@Coordinates GEOGRAPHY = NULL
|
||||
)
|
||||
AS
|
||||
)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON;
|
||||
SET XACT_ABORT ON;
|
||||
SET
|
||||
NOCOUNT ON;
|
||||
SET
|
||||
XACT_ABORT ON;
|
||||
|
||||
IF @BreweryName IS NULL
|
||||
IF
|
||||
@BreweryName IS NULL
|
||||
THROW 50001, 'Brewery name cannot be null.', 1;
|
||||
|
||||
IF @Description IS NULL
|
||||
IF
|
||||
@Description IS NULL
|
||||
THROW 50002, 'Brewery description cannot be null.', 1;
|
||||
|
||||
IF NOT EXISTS (SELECT 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
|
||||
IF
|
||||
@CityID IS NOT NULL AND NOT EXISTS (SELECT 1
|
||||
FROM dbo.City
|
||||
WHERE CityID = @CityID)
|
||||
THROW 50404, 'City not found.', 1;
|
||||
|
||||
BEGIN TRANSACTION;
|
||||
BEGIN
|
||||
TRANSACTION;
|
||||
|
||||
UPDATE dbo.BreweryPost
|
||||
SET BreweryName = @BreweryName,
|
||||
Description = @Description,
|
||||
UpdatedAt = GETDATE()
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
UPDATE dbo.BreweryPost
|
||||
SET BreweryName = @BreweryName,
|
||||
Description = @Description,
|
||||
UpdatedAt = GETDATE()
|
||||
WHERE BreweryPostID = @BreweryPostID;
|
||||
|
||||
IF @CityID IS NULL
|
||||
BEGIN
|
||||
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
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Seed</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Database.Seed</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="idunno.Password.Generator" Version="1.0.1" />
|
||||
<PackageReference
|
||||
Include="Konscious.Security.Cryptography.Argon2"
|
||||
Version="1.3.1"
|
||||
/>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageReference Include="dbup" Version="5.0.41" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="idunno.Password.Generator" Version="1.0.1"/>
|
||||
<PackageReference
|
||||
Include="Konscious.Security.Cryptography.Argon2"
|
||||
Version="1.3.1"
|
||||
/>
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4"/>
|
||||
<PackageReference Include="dbup" Version="5.0.41"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -3,15 +3,15 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a unit of seed data that can be applied to the database. Implementations
|
||||
/// should be safe to run against an already-seeded database (e.g. by checking for
|
||||
/// existing data before inserting) and may depend on data created by seeders that run
|
||||
/// before them.
|
||||
/// Defines a unit of seed data that can be applied to the database. Implementations
|
||||
/// should be safe to run against an already-seeded database (e.g. by checking for
|
||||
/// existing data before inserting) and may depend on data created by seeders that run
|
||||
/// before them.
|
||||
/// </summary>
|
||||
internal interface ISeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts this seeder's data into the database using the supplied open connection.
|
||||
/// Inserts this seeder's data into the database using the supplied open connection.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when seeding is finished.</returns>
|
||||
|
||||
@@ -4,13 +4,13 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
|
||||
/// associate other entities (e.g. breweries, users) with a geographic location.
|
||||
/// Countries must be seeded before states/provinces, which must be seeded before
|
||||
/// cities, since each level references its parent by code. The underlying stored
|
||||
/// procedures (<c>USP_CreateCountry</c>, <c>USP_CreateStateProvince</c>,
|
||||
/// <c>USP_CreateCity</c>) are expected to be idempotent, so re-running this seeder
|
||||
/// against an already-seeded database is safe.
|
||||
/// Seeds the location hierarchy (countries, states/provinces, and cities) used to
|
||||
/// associate other entities (e.g. breweries, users) with a geographic location.
|
||||
/// Countries must be seeded before states/provinces, which must be seeded before
|
||||
/// cities, since each level references its parent by code. The underlying stored
|
||||
/// procedures (<c>USP_CreateCountry</c>, <c>USP_CreateStateProvince</c>,
|
||||
/// <c>USP_CreateCity</c>) are expected to be idempotent, so re-running this seeder
|
||||
/// against an already-seeded database is safe.
|
||||
/// </summary>
|
||||
internal class LocationSeeder : ISeeder
|
||||
{
|
||||
@@ -22,12 +22,12 @@ internal class LocationSeeder : ISeeder
|
||||
[
|
||||
("Canada", "CA"),
|
||||
("Mexico", "MX"),
|
||||
("United States", "US"),
|
||||
("United States", "US")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The set of states/provinces to seed, each identified by name, ISO 3166-2 code,
|
||||
/// and the ISO 3166-1 code of its parent country.
|
||||
/// The set of states/provinces to seed, each identified by name, ISO 3166-2 code,
|
||||
/// and the ISO 3166-1 code of its parent country.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<(string StateProvinceName, string StateProvinceCode, string CountryCode)> States
|
||||
{
|
||||
@@ -134,12 +134,12 @@ internal class LocationSeeder : ISeeder
|
||||
("Veracruz de Ignacio de la Llave", "MX-VER", "MX"),
|
||||
("Yucatán", "MX-YUC", "MX"),
|
||||
("Zacatecas", "MX-ZAC", "MX"),
|
||||
("Ciudad de México", "MX-CMX", "MX"),
|
||||
("Ciudad de México", "MX-CMX", "MX")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
|
||||
/// parent state/province.
|
||||
/// The set of cities to seed, each identified by name and the ISO 3166-2 code of its
|
||||
/// parent state/province.
|
||||
/// </summary>
|
||||
private static IReadOnlyList<(string StateProvinceCode, string CityName)> Cities { get; } =
|
||||
[
|
||||
@@ -255,38 +255,32 @@ internal class LocationSeeder : ISeeder
|
||||
("MX-COA", "Saltillo"),
|
||||
("MX-BCS", "La Paz"),
|
||||
("MX-NAY", "Tepic"),
|
||||
("MX-ZAC", "Zacatecas"),
|
||||
("MX-ZAC", "Zacatecas")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Seeds all countries, then states/provinces, then cities, in that order, so that
|
||||
/// each level's parent reference already exists by the time it is created.
|
||||
/// Seeds all countries, then states/provinces, then cities, in that order, so that
|
||||
/// each level's parent reference already exists by the time it is created.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when all locations have been seeded.</returns>
|
||||
public async Task SeedAsync(SqlConnection connection)
|
||||
{
|
||||
foreach (var (countryName, countryCode) in Countries)
|
||||
{
|
||||
foreach ((string countryName, string countryCode) in Countries)
|
||||
await CreateCountryAsync(connection, countryName, countryCode);
|
||||
}
|
||||
|
||||
foreach (
|
||||
var (stateProvinceName, stateProvinceCode, countryCode) in States
|
||||
(string stateProvinceName, string stateProvinceCode, string countryCode) in States
|
||||
)
|
||||
{
|
||||
await CreateStateProvinceAsync(
|
||||
connection,
|
||||
stateProvinceName,
|
||||
stateProvinceCode,
|
||||
countryCode
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var (stateProvinceCode, cityName) in Cities)
|
||||
{
|
||||
foreach ((string stateProvinceCode, string cityName) in Cities)
|
||||
await CreateCityAsync(connection, cityName, stateProvinceCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Creates a single country by invoking the <c>dbo.USP_CreateCountry</c> stored procedure.</summary>
|
||||
@@ -300,7 +294,7 @@ internal class LocationSeeder : ISeeder
|
||||
string countryCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateCountry",
|
||||
connection
|
||||
);
|
||||
@@ -324,7 +318,7 @@ internal class LocationSeeder : ISeeder
|
||||
string countryCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateStateProvince",
|
||||
connection
|
||||
);
|
||||
@@ -350,7 +344,7 @@ internal class LocationSeeder : ISeeder
|
||||
string stateProvinceCode
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateCity",
|
||||
connection
|
||||
);
|
||||
@@ -363,4 +357,4 @@ internal class LocationSeeder : ISeeder
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
using DbUp;
|
||||
using System.Reflection;
|
||||
using Database.Seed;
|
||||
using Database.Seed;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
// Entry point for the database seeding utility. Connects to the target database
|
||||
// (retrying on transient failures), then runs each registered ISeeder in order to
|
||||
@@ -19,22 +17,22 @@ using Database.Seed;
|
||||
/// </exception>
|
||||
string BuildConnectionString()
|
||||
{
|
||||
var server = Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
string server = Environment.GetEnvironmentVariable("DB_SERVER")
|
||||
?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
|
||||
|
||||
var dbName = Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
string dbName = Environment.GetEnvironmentVariable("DB_NAME")
|
||||
?? throw new InvalidOperationException("DB_NAME environment variable is not set");
|
||||
|
||||
var user = Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
string user = Environment.GetEnvironmentVariable("DB_USER")
|
||||
?? throw new InvalidOperationException("DB_USER environment variable is not set");
|
||||
|
||||
var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
string password = Environment.GetEnvironmentVariable("DB_PASSWORD")
|
||||
?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
|
||||
|
||||
var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
string trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
|
||||
?? "True";
|
||||
|
||||
var builder = new SqlConnectionStringBuilder
|
||||
SqlConnectionStringBuilder builder = new()
|
||||
{
|
||||
DataSource = server,
|
||||
InitialCatalog = dbName,
|
||||
@@ -50,7 +48,7 @@ string BuildConnectionString()
|
||||
|
||||
try
|
||||
{
|
||||
var connectionString = BuildConnectionString();
|
||||
string connectionString = BuildConnectionString();
|
||||
|
||||
Console.WriteLine("Attempting to connect to database...");
|
||||
|
||||
@@ -60,7 +58,6 @@ try
|
||||
int retryDelayMs = 2000;
|
||||
|
||||
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
connection = new SqlConnection(connectionString);
|
||||
@@ -76,12 +73,8 @@ try
|
||||
connection?.Dispose();
|
||||
connection = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection == null)
|
||||
{
|
||||
throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
|
||||
}
|
||||
if (connection == null) throw new Exception($"Failed to connect to database after {maxRetries} attempts.");
|
||||
|
||||
Console.WriteLine("Starting seeding...");
|
||||
|
||||
@@ -90,10 +83,10 @@ try
|
||||
ISeeder[] seeders =
|
||||
[
|
||||
new LocationSeeder(),
|
||||
new UserSeeder(),
|
||||
new UserSeeder()
|
||||
];
|
||||
|
||||
foreach (var seeder in seeders)
|
||||
foreach (ISeeder seeder in seeders)
|
||||
{
|
||||
Console.WriteLine($"Seeding {seeder.GetType().Name}...");
|
||||
await seeder.SeedAsync(connection);
|
||||
|
||||
@@ -8,13 +8,13 @@ using Microsoft.Data.SqlClient;
|
||||
namespace Database.Seed;
|
||||
|
||||
/// <summary>
|
||||
/// Seeds user accounts, credentials, and verification records. Creates one fixed
|
||||
/// "Test User" account (<c>test.user@thebiergarten.app</c> / password <c>"password"</c>)
|
||||
/// for testing, followed by a randomly-generated account for each name in
|
||||
/// <see cref="SeedNames"/>, each with a random password and date of birth. Skips
|
||||
/// adding a verification record for a user that already has one, so this seeder is
|
||||
/// safe to re-run, though re-running will still attempt to register (and may fail on)
|
||||
/// users that already exist.
|
||||
/// Seeds user accounts, credentials, and verification records. Creates one fixed
|
||||
/// "Test User" account (<c>test.user@thebiergarten.app</c> / password <c>"password"</c>)
|
||||
/// for testing, followed by a randomly-generated account for each name in
|
||||
/// <see cref="SeedNames" />, each with a random password and date of birth. Skips
|
||||
/// adding a verification record for a user that already has one, so this seeder is
|
||||
/// safe to re-run, though re-running will still attempt to register (and may fail on)
|
||||
/// users that already exist.
|
||||
/// </summary>
|
||||
internal class UserSeeder : ISeeder
|
||||
{
|
||||
@@ -123,21 +123,21 @@ internal class UserSeeder : ISeeder
|
||||
("Zara", "Wilkinson"),
|
||||
("Zaria", "Gibson"),
|
||||
("Zion", "Watkins"),
|
||||
("Zoie", "Armstrong"),
|
||||
("Zoie", "Armstrong")
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Registers a fixed test user account followed by one randomly-generated account
|
||||
/// per entry in <see cref="SeedNames"/>, adding a user verification record for each
|
||||
/// newly created account that does not already have one. Progress counts are
|
||||
/// written to the console on completion.
|
||||
/// Registers a fixed test user account followed by one randomly-generated account
|
||||
/// per entry in <see cref="SeedNames" />, adding a user verification record for each
|
||||
/// newly created account that does not already have one. Progress counts are
|
||||
/// written to the console on completion.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <returns>A task that completes when all users have been seeded.</returns>
|
||||
public async Task SeedAsync(SqlConnection connection)
|
||||
{
|
||||
var generator = new PasswordGenerator();
|
||||
var rng = new Random();
|
||||
PasswordGenerator generator = new();
|
||||
Random rng = new();
|
||||
int createdUsers = 0;
|
||||
int createdCredentials = 0;
|
||||
int createdVerifications = 0;
|
||||
@@ -147,8 +147,8 @@ internal class UserSeeder : ISeeder
|
||||
const string firstName = "Test";
|
||||
const string lastName = "User";
|
||||
const string email = "test.user@thebiergarten.app";
|
||||
var dob = new DateTime(1985, 03, 01);
|
||||
var hash = GeneratePasswordHash("password");
|
||||
DateTime dob = new(1985, 03, 01);
|
||||
string hash = GeneratePasswordHash("password");
|
||||
|
||||
await RegisterUserAsync(
|
||||
connection,
|
||||
@@ -160,24 +160,24 @@ internal class UserSeeder : ISeeder
|
||||
hash
|
||||
);
|
||||
}
|
||||
foreach (var (firstName, lastName) in SeedNames)
|
||||
foreach ((string firstName, string lastName) in SeedNames)
|
||||
{
|
||||
// prepare user fields
|
||||
var username = $"{firstName[0]}.{lastName}";
|
||||
var email = $"{firstName}.{lastName}@thebiergarten.app";
|
||||
var dob = GenerateDateOfBirth(rng);
|
||||
string username = $"{firstName[0]}.{lastName}";
|
||||
string email = $"{firstName}.{lastName}@thebiergarten.app";
|
||||
DateTime dob = GenerateDateOfBirth(rng);
|
||||
|
||||
// generate a password and hash it
|
||||
string pwd = generator.Generate(
|
||||
length: 64,
|
||||
numberOfDigits: 10,
|
||||
numberOfSymbols: 10
|
||||
64,
|
||||
10,
|
||||
10
|
||||
);
|
||||
string hash = GeneratePasswordHash(pwd);
|
||||
|
||||
|
||||
// register the user (creates account + credential)
|
||||
var id = await RegisterUserAsync(
|
||||
Guid id = await RegisterUserAsync(
|
||||
connection,
|
||||
username,
|
||||
firstName,
|
||||
@@ -203,8 +203,8 @@ internal class UserSeeder : ISeeder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new user account and its credential by invoking the
|
||||
/// <c>dbo.USP_RegisterUser</c> stored procedure.
|
||||
/// Registers a new user account and its credential by invoking the
|
||||
/// <c>dbo.USP_RegisterUser</c> stored procedure.
|
||||
/// </summary>
|
||||
/// <param name="connection">An open connection to the target database.</param>
|
||||
/// <param name="username">The unique username for the account.</param>
|
||||
@@ -212,8 +212,8 @@ internal class UserSeeder : ISeeder
|
||||
/// <param name="lastName">The user's last name.</param>
|
||||
/// <param name="dateOfBirth">The user's date of birth.</param>
|
||||
/// <param name="email">The user's email address.</param>
|
||||
/// <param name="hash">The salted password hash, as produced by <see cref="GeneratePasswordHash"/>.</param>
|
||||
/// <returns>The newly created user account's <see cref="Guid"/> identifier.</returns>
|
||||
/// <param name="hash">The salted password hash, as produced by <see cref="GeneratePasswordHash" />.</param>
|
||||
/// <returns>The newly created user account's <see cref="Guid" /> identifier.</returns>
|
||||
private static async Task<Guid> RegisterUserAsync(
|
||||
SqlConnection connection,
|
||||
string username,
|
||||
@@ -224,7 +224,7 @@ internal class UserSeeder : ISeeder
|
||||
string hash
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand("dbo.USP_RegisterUser", connection);
|
||||
await using SqlCommand command = new("dbo.USP_RegisterUser", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
|
||||
@@ -235,16 +235,16 @@ internal class UserSeeder : ISeeder
|
||||
command.Parameters.Add("@Email", SqlDbType.VarChar, 128).Value = email;
|
||||
command.Parameters.Add("@Hash", SqlDbType.NVarChar, -1).Value = hash;
|
||||
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
object? result = await command.ExecuteScalarAsync();
|
||||
|
||||
|
||||
return (Guid)result!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
|
||||
/// salt, a degree of parallelism matching the available processor count, 64 MB of
|
||||
/// memory, and 4 iterations.
|
||||
/// Hashes a plaintext password using Argon2id with a randomly generated 16-byte
|
||||
/// salt, a degree of parallelism matching the available processor count, 64 MB of
|
||||
/// memory, and 4 iterations.
|
||||
/// </summary>
|
||||
/// <param name="pwd">The plaintext password to hash.</param>
|
||||
/// <returns>A string containing the base64-encoded salt and hash, separated by a colon.</returns>
|
||||
@@ -252,12 +252,12 @@ internal class UserSeeder : ISeeder
|
||||
{
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(16);
|
||||
|
||||
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(pwd))
|
||||
Argon2id argon2 = new(Encoding.UTF8.GetBytes(pwd))
|
||||
{
|
||||
Salt = salt,
|
||||
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
|
||||
MemorySize = 65536,
|
||||
Iterations = 4,
|
||||
Iterations = 4
|
||||
};
|
||||
|
||||
byte[] hash = argon2.GetBytes(32);
|
||||
@@ -278,9 +278,9 @@ internal class UserSeeder : ISeeder
|
||||
FROM dbo.UserVerification
|
||||
WHERE UserAccountId = @UserAccountId;
|
||||
""";
|
||||
await using var command = new SqlCommand(sql, connection);
|
||||
await using SqlCommand command = new(sql, connection);
|
||||
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
|
||||
var result = await command.ExecuteScalarAsync();
|
||||
object? result = await command.ExecuteScalarAsync();
|
||||
return result is not null;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ internal class UserSeeder : ISeeder
|
||||
Guid userAccountId
|
||||
)
|
||||
{
|
||||
await using var command = new SqlCommand(
|
||||
await using SqlCommand command = new(
|
||||
"dbo.USP_CreateUserVerification",
|
||||
connection
|
||||
);
|
||||
@@ -304,8 +304,8 @@ internal class UserSeeder : ISeeder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random date of birth corresponding to an age between 19 and 48
|
||||
/// years (inclusive), with a random day offset within that birth year.
|
||||
/// Generates a random date of birth corresponding to an age between 19 and 48
|
||||
/// years (inclusive), with a random day offset within that birth year.
|
||||
/// </summary>
|
||||
/// <param name="random">The random number source to use.</param>
|
||||
/// <returns>A randomly generated date of birth.</returns>
|
||||
@@ -316,4 +316,4 @@ internal class UserSeeder : ISeeder
|
||||
int offsetDays = random.Next(0, 365);
|
||||
return baseDate.AddDays(-offsetDays);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user