Merge remote-tracking branch 'dotnet/main' into integrate-dotnet-backend-repo

This commit is contained in:
Aaron Po
2026-01-26 18:58:40 -05:00
55 changed files with 169182 additions and 1 deletions

13
.config/dotnet-tools.json Normal file
View File

@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"csharpier": {
"version": "1.2.1",
"commands": [
"csharpier"
],
"rollForward": false
}
}
}

10
.csharpierrc.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/belav/csharpier/main/src/CSharpier.Cli/schema.json",
"printWidth": 80,
"useTabs": false,
"tabWidth": 4,
"endOfLine": "auto",
"indentStyle": "space",
"lineEndings": "auto",
"wrapLineLength": 80
}

View File

@@ -0,0 +1,43 @@
---
name: Feature Request (BDD)
about: Create a new feature using user story + BDD acceptance criteria
title: "[Feature] "
labels: ["feature", "BDD"]
assignees: []
---
## User Story
**As a** (who wants to accomplish something)
**I want to** (what they want to accomplish)
**So that** (why they want to accomplish that thing)
## Acceptance Criteria (BDD)
### Scenario 1
Given ...
When ...
Then ...
### Scenario 2
Given ...
When ...
Then ...
### Scenario 3
Given ...
When ...
Then ...
## Subtasks
- [ ] Task 1
- [ ] Task 2
- [ ] Task 3

434
.gitignore vendored
View File

@@ -46,3 +46,437 @@ next-env.d.ts
/cloudinary-images /cloudinary-images
.obsidian .obsidian
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.env
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Dd]ebug/x64/
[Dd]ebugPublic/x64/
[Rr]elease/x64/
[Rr]eleases/x64/
bin/x64/
obj/x64/
[Dd]ebug/x86/
[Dd]ebugPublic/x86/
[Rr]elease/x86/
[Rr]eleases/x86/
bin/x86/
obj/x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
**/.paket/paket.exe
paket-files/
# FAKE - F# Make
**/.fake/
# CodeRush personal settings
**/.cr/personal
# Python Tools for Visual Studio (PTVS)
**/__pycache__/
*.pyc
# Cake - Uncomment if you are using it
#tools/**
#!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
**/.mfractor/
# Local History for Visual Studio
**/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
**/.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
.DS_Store
*/data_source/other
.fake
.idea

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>WebAPI</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Infrastructure\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
<ProjectReference Include="..\..\Service\Service.Core\Service.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
{
[ApiController]
[ApiExplorerSettings(IgnoreApi = true)]
[Route("error")] // ← required
public class NotFoundController : ControllerBase
{
[HttpGet("404")] // ← required
public IActionResult Handle404()
{
return NotFound(new { message = "Route not found." });
}
}
}

View File

@@ -0,0 +1,26 @@
using BusinessLayer.Services;
using DataAccessLayer.Entities;
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UserController(IUserService userService) : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IEnumerable<UserAccount>>> GetAll([FromQuery] int? limit, [FromQuery] int? offset)
{
var users = await userService.GetAllAsync(limit, offset);
return Ok(users);
}
[HttpGet("{id:guid}")]
public async Task<ActionResult<UserAccount>> GetById(Guid id)
{
var user = await userService.GetByIdAsync(id);
if (user is null) return NotFound();
return Ok(user);
}
}
}

25
API/API.Core/Program.cs Normal file
View File

@@ -0,0 +1,25 @@
using BusinessLayer.Services;
using DataAccessLayer.Repositories;
using DataAccessLayer.Sql;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddOpenApi();
// Dependency Injection
builder.Services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
builder.Services.AddScoped<IUserAccountRepository, UserAccountRepository>();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.MapOpenApi();
app.UseHttpsRedirection();
app.MapControllers();
app.MapFallbackToController("Handle404", "NotFound");
app.Run();

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5069",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7002;http://localhost:5069",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

64
API/API.Core/WebAPI.http Normal file
View File

@@ -0,0 +1,64 @@
@WebAPI_HostAddress = http://localhost:5069
GET {{WebAPI_HostAddress}}/weatherforecast/
Accept: application/json
###
GET {{WebAPI_HostAddress}}/api/users
Accept: application/json
###
GET {{WebAPI_HostAddress}}/api/users/{{userId}}
Accept: application/json
###
GET {{WebAPI_HostAddress}}/api/users/by-username/{{username}}
Accept: application/json
###
GET {{WebAPI_HostAddress}}/api/users/by-email/{{email}}
Accept: application/json
###
POST {{WebAPI_HostAddress}}/api/users
Content-Type: application/json
Accept: application/json
{
"userAccountID": "00000000-0000-0000-0000-000000000000",
"username": "testuser",
"firstName": "Test",
"lastName": "User",
"email": "testuser@example.com",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": null,
"dateOfBirth": "1990-01-01T00:00:00Z",
"timer": null
}
###
PUT {{WebAPI_HostAddress}}/api/users/{{userId}}
Content-Type: application/json
Accept: application/json
{
"userAccountID": "{{userId}}",
"username": "testuser",
"firstName": "Updated",
"lastName": "User",
"email": "testuser@example.com",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-02-01T00:00:00Z",
"dateOfBirth": "1990-01-01T00:00:00Z",
"timer": null
}
###
DELETE {{WebAPI_HostAddress}}/api/users/{{userId}}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DataLayer</RootNamespace>
</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>
</Project>

View File

@@ -0,0 +1,32 @@
// Get connection string from environment variable
using System.Reflection;
using DbUp;
var connectionString = Environment.GetEnvironmentVariable(
"DB_CONNECTION_STRING"
);
var upgrader = DeployChanges
.To.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
var result = upgrader.PerformUpgrade();
if (!result.Successful)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.ResetColor();
#if DEBUG
Console.ReadLine();
#endif
return -1;
}
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
Console.ResetColor();
return 0;

View File

@@ -0,0 +1,552 @@
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
/*
USE master;
IF EXISTS (SELECT name
FROM sys.databases
WHERE name = N'Biergarten')
BEGIN
ALTER DATABASE Biergarten SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
END
DROP DATABASE IF EXISTS Biergarten;
CREATE DATABASE Biergarten;
USE Biergarten;
*/
-- ----------------------------------------------------------------------------
-- ----------------------------------------------------------------------------
CREATE TABLE dbo.UserAccount
(
UserAccountID UNIQUEIDENTIFIER
CONSTRAINT DF_UserAccountID DEFAULT NEWID(),
Username VARCHAR(64) NOT NULL,
FirstName NVARCHAR(128) NOT NULL,
LastName NVARCHAR(128) NOT NULL,
Email VARCHAR(128) NOT NULL,
CreatedAt DATETIME NOT NULL
CONSTRAINT DF_UserAccount_CreatedAt DEFAULT GETDATE(),
UpdatedAt DATETIME,
DateOfBirth DATETIME NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_UserAccount
PRIMARY KEY (UserAccountID),
CONSTRAINT AK_Username
UNIQUE (Username),
CONSTRAINT AK_Email
UNIQUE (Email)
);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
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
CONSTRAINT DF_PhotoID DEFAULT NEWID(),
Hyperlink NVARCHAR(256),
-- storage is handled via filesystem or cloud service
UploadedByID UNIQUEIDENTIFIER NOT NULL,
UploadedAt DATETIME NOT NULL
CONSTRAINT DF_Photo_UploadedAt DEFAULT GETDATE(),
Timer ROWVERSION,
CONSTRAINT PK_Photo
PRIMARY KEY (PhotoID),
CONSTRAINT FK_Photo_UploadedBy
FOREIGN KEY (UploadedByID)
REFERENCES UserAccount(UserAccountID)
ON DELETE NO ACTION
);
CREATE NONCLUSTERED INDEX IX_Photo_UploadedByID
ON Photo(UploadedByID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE UserAvatar -- delete avatar photo when user account is deleted
(
UserAvatarID UNIQUEIDENTIFIER
CONSTRAINT DF_UserAvatarID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
PhotoID UNIQUEIDENTIFIER NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_UserAvatar PRIMARY KEY (UserAvatarID),
CONSTRAINT FK_UserAvatar_UserAccount
FOREIGN KEY (UserAccountID)
REFERENCES UserAccount(UserAccountID)
ON DELETE CASCADE,
CONSTRAINT FK_UserAvatar_PhotoID
FOREIGN KEY (PhotoID)
REFERENCES Photo(PhotoID),
CONSTRAINT AK_UserAvatar_UserAccountID
UNIQUE (UserAccountID)
)
CREATE NONCLUSTERED INDEX IX_UserAvatar_UserAccount
ON UserAvatar(UserAccountID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE UserVerification -- delete verification data when user account is deleted
(
UserVerificationID UNIQUEIDENTIFIER
CONSTRAINT DF_UserVerificationID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
VerificationDateTime DATETIME NOT NULL
CONSTRAINT DF_VerificationDateTime
DEFAULT GETDATE(),
Timer ROWVERSION,
CONSTRAINT PK_UserVerification
PRIMARY KEY (UserVerificationID),
CONSTRAINT FK_UserVerification_UserAccount
FOREIGN KEY (UserAccountID)
REFERENCES UserAccount(UserAccountID)
ON DELETE CASCADE,
CONSTRAINT AK_UserVerification_UserAccountID
UNIQUE (UserAccountID)
);
CREATE NONCLUSTERED INDEX IX_UserVerification_UserAccount
ON UserVerification(UserAccountID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE UserCredential -- delete credentials when user account is deleted
(
UserCredentialID UNIQUEIDENTIFIER
CONSTRAINT DF_UserCredentialID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
CreatedAt DATETIME
CONSTRAINT DF_UserCredential_CreatedAt DEFAULT GETDATE() NOT NULL,
Expiry DATETIME
CONSTRAINT DF_UserCredential_Expiry DEFAULT DATEADD(DAY, 90, GETDATE()) NOT NULL,
Hash NVARCHAR(MAX) NOT NULL,
-- uses argon2
Timer ROWVERSION,
CONSTRAINT PK_UserCredential
PRIMARY KEY (UserCredentialID),
CONSTRAINT FK_UserCredential_UserAccount
FOREIGN KEY (UserAccountID)
REFERENCES UserAccount(UserAccountID)
ON DELETE CASCADE,
CONSTRAINT AK_UserCredential_UserAccountID
UNIQUE (UserAccountID)
);
CREATE NONCLUSTERED INDEX IX_UserCredential_UserAccount
ON UserCredential(UserAccountID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE UserFollow
(
UserFollowID UNIQUEIDENTIFIER
CONSTRAINT DF_UserFollowID DEFAULT NEWID(),
UserAccountID UNIQUEIDENTIFIER NOT NULL,
FollowingID UNIQUEIDENTIFIER NOT NULL,
CreatedAt DATETIME
CONSTRAINT DF_UserFollow_CreatedAt DEFAULT GETDATE() NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_UserFollow
PRIMARY KEY (UserFollowID),
CONSTRAINT FK_UserFollow_UserAccount
FOREIGN KEY (UserAccountID)
REFERENCES UserAccount(UserAccountID),
CONSTRAINT FK_UserFollow_UserAccountFollowing
FOREIGN KEY (FollowingID)
REFERENCES UserAccount(UserAccountID),
CONSTRAINT CK_CannotFollowOwnAccount
CHECK (UserAccountID != FollowingID)
);
CREATE NONCLUSTERED INDEX IX_UserFollow_UserAccount_FollowingID
ON UserFollow(UserAccountID, FollowingID);
CREATE NONCLUSTERED INDEX IX_UserFollow_FollowingID_UserAccount
ON UserFollow(FollowingID, UserAccountID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE Country
(
CountryID UNIQUEIDENTIFIER
CONSTRAINT DF_CountryID DEFAULT NEWID(),
CountryName NVARCHAR(100) NOT NULL,
ISO3616_1 CHAR(2) NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_Country
PRIMARY KEY (CountryID),
CONSTRAINT AK_Country_ISO3616_1
UNIQUE (ISO3616_1)
);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE StateProvince
(
StateProvinceID UNIQUEIDENTIFIER
CONSTRAINT DF_StateProvinceID DEFAULT NEWID(),
StateProvinceName NVARCHAR(100) NOT NULL,
ISO3616_2 CHAR(6) NOT NULL,
-- eg 'US-CA' for California, 'CA-ON' for Ontario
CountryID UNIQUEIDENTIFIER NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_StateProvince
PRIMARY KEY (StateProvinceID),
CONSTRAINT AK_StateProvince_ISO3616_2
UNIQUE (ISO3616_2),
CONSTRAINT FK_StateProvince_Country
FOREIGN KEY (CountryID)
REFERENCES Country(CountryID)
);
CREATE NONCLUSTERED INDEX IX_StateProvince_Country
ON StateProvince(CountryID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE City
(
CityID UNIQUEIDENTIFIER
CONSTRAINT DF_CityID DEFAULT NEWID(),
CityName NVARCHAR(100) NOT NULL,
StateProvinceID UNIQUEIDENTIFIER NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_City
PRIMARY KEY (CityID),
CONSTRAINT FK_City_StateProvince
FOREIGN KEY (StateProvinceID)
REFERENCES StateProvince(StateProvinceID)
);
CREATE NONCLUSTERED INDEX IX_City_StateProvince
ON City(StateProvinceID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BreweryPost -- A user cannot be deleted if they have a post
(
BreweryPostID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostID DEFAULT NEWID(),
PostedByID UNIQUEIDENTIFIER NOT NULL,
Description NVARCHAR(512) NOT NULL,
CreatedAt DATETIME NOT NULL
CONSTRAINT DF_BreweryPost_CreatedAt DEFAULT GETDATE(),
UpdatedAt DATETIME NULL,
Timer ROWVERSION,
CONSTRAINT PK_BreweryPost
PRIMARY KEY (BreweryPostID),
CONSTRAINT FK_BreweryPost_UserAccount
FOREIGN KEY (PostedByID)
REFERENCES UserAccount(UserAccountID)
ON DELETE NO ACTION,
)
CREATE NONCLUSTERED INDEX IX_BreweryPost_PostedByID
ON BreweryPost(PostedByID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BreweryPostLocation (
BreweryPostLocationID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostLocationID DEFAULT NEWID(),
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
AddressLine1 NVARCHAR(256) NOT NULL,
AddressLine2 NVARCHAR(256),
PostalCode NVARCHAR(20) NOT NULL,
CityID UNIQUEIDENTIFIER NOT NULL,
Coordinates GEOGRAPHY NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_BreweryPostLocation
PRIMARY KEY (BreweryPostLocationID),
CONSTRAINT AK_BreweryPostLocation_BreweryPostID
UNIQUE (BreweryPostID),
CONSTRAINT FK_BreweryPostLocation_BreweryPost
FOREIGN KEY (BreweryPostID)
REFERENCES BreweryPost(BreweryPostID)
ON DELETE CASCADE
);
CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_BreweryPost
ON BreweryPostLocation(BreweryPostID);
CREATE NONCLUSTERED INDEX IX_BreweryPostLocation_City
ON BreweryPostLocation(CityID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BreweryPostPhoto -- All photos linked to a post are deleted if the post is deleted
(
BreweryPostPhotoID UNIQUEIDENTIFIER
CONSTRAINT DF_BreweryPostPhotoID DEFAULT NEWID(),
BreweryPostID UNIQUEIDENTIFIER NOT NULL,
PhotoID UNIQUEIDENTIFIER NOT NULL,
LinkedAt DATETIME NOT NULL
CONSTRAINT DF_BreweryPostPhoto_LinkedAt DEFAULT GETDATE(),
Timer ROWVERSION,
CONSTRAINT PK_BreweryPostPhoto
PRIMARY KEY (BreweryPostPhotoID),
CONSTRAINT FK_BreweryPostPhoto_BreweryPost
FOREIGN KEY (BreweryPostID)
REFERENCES BreweryPost(BreweryPostID)
ON DELETE CASCADE,
CONSTRAINT FK_BreweryPostPhoto_Photo
FOREIGN KEY (PhotoID)
REFERENCES Photo(PhotoID)
ON DELETE CASCADE
);
CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_Photo_BreweryPost
ON BreweryPostPhoto(PhotoID, BreweryPostID);
CREATE NONCLUSTERED INDEX IX_BreweryPostPhoto_BreweryPost_Photo
ON BreweryPostPhoto(BreweryPostID, PhotoID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BeerStyle
(
BeerStyleID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerStyleID DEFAULT NEWID(),
StyleName NVARCHAR(100) NOT NULL,
Description NVARCHAR(MAX),
Timer ROWVERSION,
CONSTRAINT PK_BeerStyle
PRIMARY KEY (BeerStyleID),
CONSTRAINT AK_BeerStyle_StyleName
UNIQUE (StyleName)
);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BeerPost
(
BeerPostID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostID DEFAULT NEWID(),
Name NVARCHAR(100) NOT NULL,
Description NVARCHAR(MAX) NOT NULL,
ABV DECIMAL(4,2) NOT NULL,
-- Alcohol By Volume (typically 0-67%)
IBU INT NOT NULL,
-- International Bitterness Units (typically 0-100)
PostedByID UNIQUEIDENTIFIER NOT NULL,
BeerStyleID UNIQUEIDENTIFIER NOT NULL,
BrewedByID UNIQUEIDENTIFIER NOT NULL,
CreatedAt DATETIME NOT NULL
CONSTRAINT DF_BeerPost_CreatedAt DEFAULT GETDATE(),
UpdatedAt DATETIME,
Timer ROWVERSION,
CONSTRAINT PK_BeerPost
PRIMARY KEY (BeerPostID),
CONSTRAINT FK_BeerPost_PostedBy
FOREIGN KEY (PostedByID)
REFERENCES UserAccount(UserAccountID),
CONSTRAINT FK_BeerPost_BeerStyle
FOREIGN KEY (BeerStyleID)
REFERENCES BeerStyle(BeerStyleID),
CONSTRAINT FK_BeerPost_Brewery
FOREIGN KEY (BrewedByID)
REFERENCES BreweryPost(BreweryPostID),
CONSTRAINT CHK_BeerPost_ABV
CHECK (ABV >= 0 AND ABV <= 67),
CONSTRAINT CHK_BeerPost_IBU
CHECK (IBU >= 0 AND IBU <= 120)
);
CREATE NONCLUSTERED INDEX IX_BeerPost_PostedBy
ON BeerPost(PostedByID);
CREATE NONCLUSTERED INDEX IX_BeerPost_BeerStyle
ON BeerPost(BeerStyleID);
CREATE NONCLUSTERED INDEX IX_BeerPost_BrewedBy
ON BeerPost(BrewedByID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BeerPostPhoto -- All photos linked to a beer post are deleted if the post is deleted
(
BeerPostPhotoID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostPhotoID DEFAULT NEWID(),
BeerPostID UNIQUEIDENTIFIER NOT NULL,
PhotoID UNIQUEIDENTIFIER NOT NULL,
LinkedAt DATETIME NOT NULL
CONSTRAINT DF_BeerPostPhoto_LinkedAt DEFAULT GETDATE(),
Timer ROWVERSION,
CONSTRAINT PK_BeerPostPhoto
PRIMARY KEY (BeerPostPhotoID),
CONSTRAINT FK_BeerPostPhoto_BeerPost
FOREIGN KEY (BeerPostID)
REFERENCES BeerPost(BeerPostID)
ON DELETE CASCADE,
CONSTRAINT FK_BeerPostPhoto_Photo
FOREIGN KEY (PhotoID)
REFERENCES Photo(PhotoID)
ON DELETE CASCADE
);
CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_Photo_BeerPost
ON BeerPostPhoto(PhotoID, BeerPostID);
CREATE NONCLUSTERED INDEX IX_BeerPostPhoto_BeerPost_Photo
ON BeerPostPhoto(BeerPostID, PhotoID);
----------------------------------------------------------------------------
----------------------------------------------------------------------------
CREATE TABLE BeerPostComment
(
BeerPostCommentID UNIQUEIDENTIFIER
CONSTRAINT DF_BeerPostComment DEFAULT NEWID(),
Comment NVARCHAR(250) NOT NULL,
BeerPostID UNIQUEIDENTIFIER NOT NULL,
Rating INT NOT NULL,
Timer ROWVERSION,
CONSTRAINT PK_BeerPostComment
PRIMARY KEY (BeerPostCommentID),
CONSTRAINT FK_BeerPostComment_BeerPost
FOREIGN KEY (BeerPostID) REFERENCES BeerPost(BeerPostID)
)
CREATE NONCLUSTERED INDEX IX_BeerPostComment_BeerPost
ON BeerPostComment(BeerPostID)

View File

@@ -0,0 +1,15 @@
CREATE OR ALTER FUNCTION dbo.UDF_GetCountryIdByCode
(
@CountryCode NVARCHAR(2)
)
RETURNS UNIQUEIDENTIFIER
AS
BEGIN
DECLARE @CountryId UNIQUEIDENTIFIER;
SELECT @CountryId = CountryID
FROM dbo.Country
WHERE ISO3616_1 = @CountryCode;
RETURN @CountryId;
END;

View File

@@ -0,0 +1,13 @@
CREATE OR ALTER FUNCTION dbo.UDF_GetStateProvinceIdByCode
(
@StateProvinceCode NVARCHAR(6)
)
RETURNS UNIQUEIDENTIFIER
AS
BEGIN
DECLARE @StateProvinceId UNIQUEIDENTIFIER;
SELECT @StateProvinceId = StateProvinceID
FROM dbo.StateProvince
WHERE ISO3616_2 = @StateProvinceCode;
RETURN @StateProvinceId;
END;

View File

@@ -0,0 +1,36 @@
CREATE OR ALTER PROCEDURE usp_CreateUserAccount
(
@UserAccountId UNIQUEIDENTIFIER = NULL,
@Username VARCHAR(64),
@FirstName NVARCHAR(128),
@LastName NVARCHAR(128),
@DateOfBirth DATETIME,
@Email VARCHAR(128)
)
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRANSACTION
INSERT INTO UserAccount
(
UserAccountID,
Username,
FirstName,
LastName,
DateOfBirth,
Email
)
VALUES
(
COALESCE(@UserAccountId, NEWID()),
@Username,
@FirstName,
@LastName,
@DateOfBirth,
@Email
);
COMMIT TRANSACTION
END;

View File

@@ -0,0 +1,23 @@
CREATE OR ALTER PROCEDURE usp_DeleteUserAccount
(
@UserAccountId UNIQUEIDENTIFIER
)
AS
BEGIN
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRANSACTION
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;
COMMIT TRANSACTION
END;

View File

@@ -0,0 +1,17 @@
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;

View File

@@ -0,0 +1,21 @@
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;

View File

@@ -0,0 +1,21 @@
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;

View File

@@ -0,0 +1,21 @@
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;

View File

@@ -0,0 +1,35 @@
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
SET XACT_ABORT ON
BEGIN TRANSACTION
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
UPDATE UserAccount
SET
Username = @Username,
FirstName = @FirstName,
LastName = @LastName,
DateOfBirth = @DateOfBirth,
Email = @Email
WHERE UserAccountId = @UserAccountId;
COMMIT TRANSACTION
END;

View File

@@ -0,0 +1,33 @@
CREATE OR ALTER PROCEDURE dbo.USP_AddUserCredential(
@UserAccountId uniqueidentifier,
@Hash nvarchar(max)
)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRANSACTION;
IF NOT EXISTS (
SELECT 1
FROM dbo.UserAccount
WHERE UserAccountID = @UserAccountId
)
THROW 50001, 'UserAccountID does not exist.', 1;
IF EXISTS (
SELECT 1
FROM dbo.UserCredential
WHERE UserAccountID = @UserAccountId
)
THROW 50002, 'UserCredential for this UserAccountID already exists.', 1;
INSERT INTO dbo.UserCredential
(UserAccountId, Hash)
VALUES
(@UserAccountId, @Hash);
COMMIT TRANSACTION;
END;

View File

@@ -0,0 +1,20 @@
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;
INSERT INTO dbo.UserVerification
(UserAccountId, VerificationDateTime)
VALUES
(@UserAccountID, @VerificationDateTime);
COMMIT TRANSACTION;
END

View File

@@ -0,0 +1,30 @@
CREATE OR ALTER PROCEDURE dbo.USP_CreateCity
(
@CityName NVARCHAR(100),
@StateProvinceCode NVARCHAR(6)
)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @StateProvinceId UNIQUEIDENTIFIER = dbo.UDF_GetStateProvinceIdByCode(@StateProvinceCode);
IF @StateProvinceId IS NULL
BEGIN
RAISERROR('State/province not found for code.', 16, 1);
RETURN;
END
IF EXISTS (
SELECT 1
FROM dbo.City
WHERE CityName = @CityName
AND StateProvinceID = @StateProvinceId
)
RETURN;
INSERT INTO dbo.City
(StateProvinceID, CityName)
VALUES
(@StateProvinceId, @CityName);
END;

View File

@@ -0,0 +1,22 @@
CREATE OR ALTER PROCEDURE dbo.USP_CreateCountry
(
@CountryName NVARCHAR(100),
@ISO3616_1 NVARCHAR(2)
)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT 1
FROM dbo.Country
WHERE ISO3616_1 = @ISO3616_1
)
RETURN;
INSERT INTO dbo.Country
(CountryName, ISO3616_1)
VALUES
(@CountryName, @ISO3616_1);
END;

View File

@@ -0,0 +1,30 @@
CREATE OR ALTER PROCEDURE dbo.USP_CreateStateProvince
(
@StateProvinceName NVARCHAR(100),
@ISO3616_2 NVARCHAR(6),
@CountryCode NVARCHAR(2)
)
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
IF EXISTS (
SELECT 1
FROM dbo.StateProvince
WHERE ISO3616_2 = @ISO3616_2
)
RETURN;
DECLARE @CountryId UNIQUEIDENTIFIER = dbo.UDF_GetCountryIdByCode(@CountryCode);
IF @CountryId IS NULL
BEGIN
RAISERROR('Country not found for code.', 16, 1);
RETURN;
END
INSERT INTO dbo.StateProvince
(StateProvinceName, ISO3616_2, CountryID)
VALUES
(@StateProvinceName, @ISO3616_2, @CountryId);
END;

View File

@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DBSeed</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.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using Microsoft.Data.SqlClient;
namespace DBSeed
{
internal interface ISeeder
{
Task SeedAsync(SqlConnection connection);
}
}

View File

@@ -0,0 +1,328 @@
using System.Data;
using Microsoft.Data.SqlClient;
namespace DBSeed
{
internal class LocationSeeder : ISeeder
{
private static readonly IReadOnlyList<(
string CountryName,
string CountryCode
)> Countries =
[
("Canada", "CA"),
("Mexico", "MX"),
("United States", "US"),
];
private static IReadOnlyList<(string StateProvinceName, string StateProvinceCode, string CountryCode)> States
{
get;
} =
[
("Alabama", "US-AL", "US"),
("Alaska", "US-AK", "US"),
("Arizona", "US-AZ", "US"),
("Arkansas", "US-AR", "US"),
("California", "US-CA", "US"),
("Colorado", "US-CO", "US"),
("Connecticut", "US-CT", "US"),
("Delaware", "US-DE", "US"),
("Florida", "US-FL", "US"),
("Georgia", "US-GA", "US"),
("Hawaii", "US-HI", "US"),
("Idaho", "US-ID", "US"),
("Illinois", "US-IL", "US"),
("Indiana", "US-IN", "US"),
("Iowa", "US-IA", "US"),
("Kansas", "US-KS", "US"),
("Kentucky", "US-KY", "US"),
("Louisiana", "US-LA", "US"),
("Maine", "US-ME", "US"),
("Maryland", "US-MD", "US"),
("Massachusetts", "US-MA", "US"),
("Michigan", "US-MI", "US"),
("Minnesota", "US-MN", "US"),
("Mississippi", "US-MS", "US"),
("Missouri", "US-MO", "US"),
("Montana", "US-MT", "US"),
("Nebraska", "US-NE", "US"),
("Nevada", "US-NV", "US"),
("New Hampshire", "US-NH", "US"),
("New Jersey", "US-NJ", "US"),
("New Mexico", "US-NM", "US"),
("New York", "US-NY", "US"),
("North Carolina", "US-NC", "US"),
("North Dakota", "US-ND", "US"),
("Ohio", "US-OH", "US"),
("Oklahoma", "US-OK", "US"),
("Oregon", "US-OR", "US"),
("Pennsylvania", "US-PA", "US"),
("Rhode Island", "US-RI", "US"),
("South Carolina", "US-SC", "US"),
("South Dakota", "US-SD", "US"),
("Tennessee", "US-TN", "US"),
("Texas", "US-TX", "US"),
("Utah", "US-UT", "US"),
("Vermont", "US-VT", "US"),
("Virginia", "US-VA", "US"),
("Washington", "US-WA", "US"),
("West Virginia", "US-WV", "US"),
("Wisconsin", "US-WI", "US"),
("Wyoming", "US-WY", "US"),
("District of Columbia", "US-DC", "US"),
("Puerto Rico", "US-PR", "US"),
("U.S. Virgin Islands", "US-VI", "US"),
("Guam", "US-GU", "US"),
("Northern Mariana Islands", "US-MP", "US"),
("American Samoa", "US-AS", "US"),
("Ontario", "CA-ON", "CA"),
("Québec", "CA-QC", "CA"),
("Nova Scotia", "CA-NS", "CA"),
("New Brunswick", "CA-NB", "CA"),
("Manitoba", "CA-MB", "CA"),
("British Columbia", "CA-BC", "CA"),
("Prince Edward Island", "CA-PE", "CA"),
("Saskatchewan", "CA-SK", "CA"),
("Alberta", "CA-AB", "CA"),
("Newfoundland and Labrador", "CA-NL", "CA"),
("Northwest Territories", "CA-NT", "CA"),
("Yukon", "CA-YT", "CA"),
("Nunavut", "CA-NU", "CA"),
("Aguascalientes", "MX-AGU", "MX"),
("Baja California", "MX-BCN", "MX"),
("Baja California Sur", "MX-BCS", "MX"),
("Campeche", "MX-CAM", "MX"),
("Chiapas", "MX-CHP", "MX"),
("Chihuahua", "MX-CHH", "MX"),
("Coahuila de Zaragoza", "MX-COA", "MX"),
("Colima", "MX-COL", "MX"),
("Durango", "MX-DUR", "MX"),
("Guanajuato", "MX-GUA", "MX"),
("Guerrero", "MX-GRO", "MX"),
("Hidalgo", "MX-HID", "MX"),
("Jalisco", "MX-JAL", "MX"),
("México State", "MX-MEX", "MX"),
("Michoacán de Ocampo", "MX-MIC", "MX"),
("Morelos", "MX-MOR", "MX"),
("Nayarit", "MX-NAY", "MX"),
("Nuevo León", "MX-NLE", "MX"),
("Oaxaca", "MX-OAX", "MX"),
("Puebla", "MX-PUE", "MX"),
("Querétaro", "MX-QUE", "MX"),
("Quintana Roo", "MX-ROO", "MX"),
("San Luis Potosí", "MX-SLP", "MX"),
("Sinaloa", "MX-SIN", "MX"),
("Sonora", "MX-SON", "MX"),
("Tabasco", "MX-TAB", "MX"),
("Tamaulipas", "MX-TAM", "MX"),
("Tlaxcala", "MX-TLA", "MX"),
("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"),
];
private static IReadOnlyList<(string StateProvinceCode, string CityName)> Cities { get; } =
[
("US-CA", "Los Angeles"),
("US-CA", "San Diego"),
("US-CA", "San Francisco"),
("US-CA", "Sacramento"),
("US-TX", "Houston"),
("US-TX", "Dallas"),
("US-TX", "Austin"),
("US-TX", "San Antonio"),
("US-FL", "Miami"),
("US-FL", "Orlando"),
("US-FL", "Tampa"),
("US-NY", "New York"),
("US-NY", "Buffalo"),
("US-NY", "Rochester"),
("US-IL", "Chicago"),
("US-IL", "Springfield"),
("US-PA", "Philadelphia"),
("US-PA", "Pittsburgh"),
("US-AZ", "Phoenix"),
("US-AZ", "Tucson"),
("US-CO", "Denver"),
("US-CO", "Colorado Springs"),
("US-MA", "Boston"),
("US-MA", "Worcester"),
("US-WA", "Seattle"),
("US-WA", "Spokane"),
("US-GA", "Atlanta"),
("US-GA", "Savannah"),
("US-NV", "Las Vegas"),
("US-NV", "Reno"),
("US-MI", "Detroit"),
("US-MI", "Grand Rapids"),
("US-MN", "Minneapolis"),
("US-MN", "Saint Paul"),
("US-OH", "Columbus"),
("US-OH", "Cleveland"),
("US-OR", "Portland"),
("US-OR", "Salem"),
("US-TN", "Nashville"),
("US-TN", "Memphis"),
("US-VA", "Richmond"),
("US-VA", "Virginia Beach"),
("US-MD", "Baltimore"),
("US-MD", "Frederick"),
("US-DC", "Washington"),
("US-UT", "Salt Lake City"),
("US-UT", "Provo"),
("US-LA", "New Orleans"),
("US-LA", "Baton Rouge"),
("US-KY", "Louisville"),
("US-KY", "Lexington"),
("US-IA", "Des Moines"),
("US-IA", "Cedar Rapids"),
("US-OK", "Oklahoma City"),
("US-OK", "Tulsa"),
("US-NE", "Omaha"),
("US-NE", "Lincoln"),
("US-MO", "Kansas City"),
("US-MO", "St. Louis"),
("US-NC", "Charlotte"),
("US-NC", "Raleigh"),
("US-SC", "Columbia"),
("US-SC", "Charleston"),
("US-WI", "Milwaukee"),
("US-WI", "Madison"),
("US-MN", "Duluth"),
("US-AK", "Anchorage"),
("US-HI", "Honolulu"),
("CA-ON", "Toronto"),
("CA-ON", "Ottawa"),
("CA-QC", "Montréal"),
("CA-QC", "Québec City"),
("CA-BC", "Vancouver"),
("CA-BC", "Victoria"),
("CA-AB", "Calgary"),
("CA-AB", "Edmonton"),
("CA-MB", "Winnipeg"),
("CA-NS", "Halifax"),
("CA-SK", "Saskatoon"),
("CA-SK", "Regina"),
("CA-NB", "Moncton"),
("CA-NB", "Saint John"),
("CA-PE", "Charlottetown"),
("CA-NL", "St. John's"),
("CA-ON", "Hamilton"),
("CA-ON", "London"),
("CA-QC", "Gatineau"),
("CA-QC", "Laval"),
("CA-BC", "Kelowna"),
("CA-AB", "Red Deer"),
("CA-MB", "Brandon"),
("MX-CMX", "Ciudad de México"),
("MX-JAL", "Guadalajara"),
("MX-NLE", "Monterrey"),
("MX-PUE", "Puebla"),
("MX-ROO", "Cancún"),
("MX-GUA", "Guanajuato"),
("MX-MIC", "Morelia"),
("MX-BCN", "Tijuana"),
("MX-JAL", "Zapopan"),
("MX-NLE", "San Nicolás"),
("MX-CAM", "Campeche"),
("MX-TAB", "Villahermosa"),
("MX-VER", "Veracruz"),
("MX-OAX", "Oaxaca"),
("MX-SLP", "San Luis Potosí"),
("MX-CHH", "Chihuahua"),
("MX-AGU", "Aguascalientes"),
("MX-MEX", "Toluca"),
("MX-COA", "Saltillo"),
("MX-BCS", "La Paz"),
("MX-NAY", "Tepic"),
("MX-ZAC", "Zacatecas"),
];
public async Task SeedAsync(SqlConnection connection)
{
foreach (var (countryName, countryCode) in Countries)
{
await CreateCountryAsync(connection, countryName, countryCode);
}
foreach (
var (stateProvinceName, stateProvinceCode, countryCode) in States
)
{
await CreateStateProvinceAsync(
connection,
stateProvinceName,
stateProvinceCode,
countryCode
);
}
foreach (var (stateProvinceCode, cityName) in Cities)
{
await CreateCityAsync(connection, cityName, stateProvinceCode);
}
}
private static async Task CreateCountryAsync(
SqlConnection connection,
string countryName,
string countryCode
)
{
await using var command = new SqlCommand(
"dbo.USP_CreateCountry",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@CountryName", countryName);
command.Parameters.AddWithValue("@ISO3616_1", countryCode);
await command.ExecuteNonQueryAsync();
}
private static async Task CreateStateProvinceAsync(
SqlConnection connection,
string stateProvinceName,
string stateProvinceCode,
string countryCode
)
{
await using var command = new SqlCommand(
"dbo.USP_CreateStateProvince",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue(
"@StateProvinceName",
stateProvinceName
);
command.Parameters.AddWithValue("@ISO3616_2", stateProvinceCode);
command.Parameters.AddWithValue("@CountryCode", countryCode);
await command.ExecuteNonQueryAsync();
}
private static async Task CreateCityAsync(
SqlConnection connection,
string cityName,
string stateProvinceCode
)
{
await using var command = new SqlCommand(
"dbo.USP_CreateCity",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@CityName", cityName);
command.Parameters.AddWithValue(
"@StateProvinceCode",
stateProvinceCode
);
await command.ExecuteNonQueryAsync();
}
}
}

View File

@@ -0,0 +1,40 @@
using DBSeed;
using Microsoft.Data.SqlClient;
try
{
var connectionString = Environment.GetEnvironmentVariable(
"DB_CONNECTION_STRING"
);
if (string.IsNullOrWhiteSpace(connectionString))
throw new InvalidOperationException(
"Environment variable DB_CONNECTION_STRING is not set or is empty."
);
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
Console.WriteLine("Connected to database.");
ISeeder[] seeders =
[
new LocationSeeder(),
new UserSeeder(),
];
foreach (var seeder in seeders)
{
Console.WriteLine($"Seeding {seeder.GetType().Name}...");
await seeder.SeedAsync(connection);
Console.WriteLine($"{seeder.GetType().Name} seeded.");
}
Console.WriteLine("Seed completed successfully.");
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine("Seed failed:");
Console.Error.WriteLine(ex);
return 1;
}

View File

@@ -0,0 +1,274 @@
using System.Data;
using System.Security.Cryptography;
using System.Text;
using DataAccessLayer.Entities;
using DataAccessLayer.Repositories;
using idunno.Password;
using Konscious.Security.Cryptography;
using Microsoft.Data.SqlClient;
namespace DBSeed
{
internal class UserSeeder : ISeeder
{
private static readonly IReadOnlyList<(
string FirstName,
string LastName
)> SeedNames =
[
("Aarya", "Mathews"),
("Aiden", "Wells"),
("Aleena", "Gonzalez"),
("Alessandra", "Nelson"),
("Amari", "Tucker"),
("Ameer", "Huff"),
("Amirah", "Hicks"),
("Analia", "Dominguez"),
("Anne", "Jenkins"),
("Apollo", "Davis"),
("Arianna", "White"),
("Aubree", "Moore"),
("Aubrielle", "Raymond"),
("Aydin", "Odom"),
("Bowen", "Casey"),
("Brock", "Huber"),
("Caiden", "Strong"),
("Cecilia", "Rosales"),
("Celeste", "Barber"),
("Chance", "Small"),
("Clara", "Roberts"),
("Collins", "Brandt"),
("Damir", "Wallace"),
("Declan", "Crawford"),
("Dennis", "Decker"),
("Dylan", "Lang"),
("Eliza", "Kane"),
("Elle", "Poole"),
("Elliott", "Miles"),
("Emelia", "Lucas"),
("Emilia", "Simpson"),
("Emmett", "Lugo"),
("Ethan", "Stephens"),
("Etta", "Woods"),
("Gael", "Moran"),
("Grant", "Benson"),
("Gwen", "James"),
("Huxley", "Chen"),
("Isabella", "Fisher"),
("Ivan", "Mathis"),
("Jamir", "McMillan"),
("Jaxson", "Shields"),
("Jimmy", "Richmond"),
("Josiah", "Flores"),
("Kaden", "Enriquez"),
("Kai", "Lawson"),
("Karsyn", "Adkins"),
("Karsyn", "Proctor"),
("Kayden", "Henson"),
("Kaylie", "Spears"),
("Kinslee", "Jones"),
("Kora", "Guerra"),
("Lane", "Skinner"),
("Laylani", "Christian"),
("Ledger", "Carroll"),
("Leilany", "Small"),
("Leland", "McCall"),
("Leonard", "Calhoun"),
("Levi", "Ochoa"),
("Lillie", "Vang"),
("Lola", "Sheppard"),
("Luciana", "Poole"),
("Maddox", "Hughes"),
("Mara", "Blackwell"),
("Marcellus", "Bartlett"),
("Margo", "Koch"),
("Maurice", "Gibson"),
("Maxton", "Dodson"),
("Mia", "Parrish"),
("Millie", "Fuentes"),
("Nellie", "Villanueva"),
("Nicolas", "Mata"),
("Nicolas", "Miller"),
("Oakleigh", "Foster"),
("Octavia", "Pierce"),
("Paisley", "Allison"),
("Quincy", "Andersen"),
("Quincy", "Frazier"),
("Raiden", "Roberts"),
("Raquel", "Lara"),
("Rudy", "McIntosh"),
("Salvador", "Stein"),
("Samantha", "Dickson"),
("Solomon", "Richards"),
("Sylvia", "Hanna"),
("Talia", "Trujillo"),
("Thalia", "Farrell"),
("Trent", "Mayo"),
("Trinity", "Cummings"),
("Ty", "Perry"),
("Tyler", "Romero"),
("Valeria", "Pierce"),
("Vance", "Neal"),
("Whitney", "Bell"),
("Wilder", "Graves"),
("William", "Logan"),
("Zara", "Wilkinson"),
("Zaria", "Gibson"),
("Zion", "Watkins"),
("Zoie", "Armstrong"),
];
public async Task SeedAsync(SqlConnection connection)
{
var generator = new PasswordGenerator();
var rng = new Random();
int createdUsers = 0;
int createdCredentials = 0;
int createdVerifications = 0;
foreach (var (firstName, lastName) in SeedNames)
{
// create the user in the database
var userAccountId = Guid.NewGuid();
await AddUserAccountAsync(connection, new UserAccount
{
UserAccountId = userAccountId,
FirstName = firstName,
LastName = lastName,
Email = $"{firstName}.{lastName}@thebiergarten.app",
Username = $"{firstName[0]}.{lastName}",
DateOfBirth = GenerateDateOfBirth(rng)
});
createdUsers++;
// add user credentials
if (!await HasUserCredentialAsync(connection, userAccountId))
{
string pwd = generator.Generate(
length: 64,
numberOfDigits: 10,
numberOfSymbols: 10
);
string hash = GeneratePasswordHash(pwd);
await AddUserCredentialAsync(connection, userAccountId, hash);
createdCredentials++;
}
// add user verification
if (await HasUserVerificationAsync(connection, userAccountId)) continue;
await AddUserVerificationAsync(connection, userAccountId);
createdVerifications++;
}
Console.WriteLine($"Created {createdUsers} user accounts.");
Console.WriteLine($"Added {createdCredentials} user credentials.");
Console.WriteLine($"Added {createdVerifications} user verifications.");
}
private static async Task AddUserAccountAsync(SqlConnection connection, UserAccount ua)
{
await using var command = new SqlCommand("usp_CreateUserAccount", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = ua.UserAccountId;
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = ua.Username;
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = ua.FirstName;
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = ua.LastName;
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = ua.Email;
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = ua.DateOfBirth;
await command.ExecuteNonQueryAsync();
}
private static string GeneratePasswordHash(string pwd)
{
byte[] salt = RandomNumberGenerator.GetBytes(16);
var argon2 = new Argon2id(Encoding.UTF8.GetBytes(pwd))
{
Salt = salt,
DegreeOfParallelism = Math.Max(Environment.ProcessorCount, 1),
MemorySize = 65536,
Iterations = 4,
};
byte[] hash = argon2.GetBytes(32);
return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}";
}
private static async Task<bool> HasUserCredentialAsync(
SqlConnection connection,
Guid userAccountId
)
{
const string sql = $"""
SELECT 1
FROM dbo.UserCredential
WHERE UserAccountId = @UserAccountId;
""";
await using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
object? result = await command.ExecuteScalarAsync();
return result is not null;
}
private static async Task AddUserCredentialAsync(
SqlConnection connection,
Guid userAccountId,
string hash
)
{
await using var command = new SqlCommand(
"dbo.USP_AddUserCredential",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
command.Parameters.AddWithValue("@Hash", hash);
await command.ExecuteNonQueryAsync();
}
private static async Task<bool> HasUserVerificationAsync(
SqlConnection connection,
Guid userAccountId
)
{
const string sql = """
SELECT 1
FROM dbo.UserVerification
WHERE UserAccountId = @UserAccountId;
""";
await using var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@UserAccountId", userAccountId);
var result = await command.ExecuteScalarAsync();
return result is not null;
}
private static async Task AddUserVerificationAsync(
SqlConnection connection,
Guid userAccountId
)
{
await using var command = new SqlCommand(
"dbo.USP_CreateUserVerification",
connection
);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("@UserAccountID", userAccountId);
await command.ExecuteNonQueryAsync();
}
private static DateTime GenerateDateOfBirth(Random random)
{
int age = 19 + random.Next(0, 30);
DateTime baseDate = DateTime.UtcNow.Date.AddYears(-age);
int offsetDays = random.Next(0, 365);
return baseDate.AddDays(-offsetDays);
}
}
}

1
README.md Normal file
View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,14 @@
namespace DataAccessLayer.Entities;
public class UserAccount
{
public Guid UserAccountId { get; set; }
public string Username { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace DataAccessLayer.Entities;
public class UserCredential
{
public Guid UserCredentialId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime Expiry { get; set; }
public string Hash { get; set; } = string.Empty;
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace DataAccessLayer.Entities;
public class UserVerification
{
public Guid UserVerificationId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime VerificationDateTime { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,15 @@
using DataAccessLayer.Entities;
namespace DataAccessLayer.Repositories
{
public interface IUserAccountRepository
{
Task Add(UserAccount userAccount);
Task<UserAccount?> GetById(Guid id);
Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset);
Task Update(UserAccount userAccount);
Task Delete(Guid id);
Task<UserAccount?> GetByUsername(string username);
Task<UserAccount?> GetByEmail(string email);
}
}

View File

@@ -0,0 +1,24 @@
using DataAccessLayer.Sql;
using Microsoft.Data.SqlClient;
namespace DataAccessLayer.Repositories
{
public abstract class Repository<T>(ISqlConnectionFactory connectionFactory)
where T : class
{
protected async Task<SqlConnection> CreateConnection()
{
var connection = connectionFactory.CreateConnection();
await connection.OpenAsync();
return connection;
}
public abstract Task Add(T entity);
public abstract Task<IEnumerable<T>> GetAll(int? limit, int? offset);
public abstract Task<T?> GetById(Guid id);
public abstract Task Update(T entity);
public abstract Task Delete(Guid id);
protected abstract T MapToEntity(SqlDataReader reader);
}
}

View File

@@ -0,0 +1,134 @@
using DataAccessLayer.Entities;
using DataAccessLayer.Sql;
using Microsoft.Data.SqlClient;
using System.Data;
namespace DataAccessLayer.Repositories
{
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
: Repository<UserAccount>(connectionFactory), IUserAccountRepository
{
public override async Task Add(UserAccount userAccount)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_CreateUserAccount", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = userAccount.UserAccountId;
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = userAccount.Username;
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = userAccount.FirstName;
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = userAccount.LastName;
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = userAccount.Email;
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = userAccount.DateOfBirth;
await command.ExecuteNonQueryAsync();
}
public override async Task<UserAccount?> GetById(Guid id)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetUserAccountById", connection)
{
CommandType = CommandType.StoredProcedure
};
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = id;
await using var reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public override async Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetAllUserAccounts", connection);
command.CommandType = CommandType.StoredProcedure;
if (limit.HasValue)
command.Parameters.Add("@Limit", SqlDbType.Int).Value = limit.Value;
if (offset.HasValue)
command.Parameters.Add("@Offset", SqlDbType.Int).Value = offset.Value;
await using var reader = await command.ExecuteReaderAsync();
var users = new List<UserAccount>();
while (await reader.ReadAsync())
{
users.Add(MapToEntity(reader));
}
return users;
}
public override async Task Update(UserAccount userAccount)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_UpdateUserAccount", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = userAccount.UserAccountId;
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = userAccount.Username;
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = userAccount.FirstName;
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = userAccount.LastName;
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = userAccount.Email;
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = userAccount.DateOfBirth;
await command.ExecuteNonQueryAsync();
}
public override async Task Delete(Guid id)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_DeleteUserAccount", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = id;
await command.ExecuteNonQueryAsync();
}
public async Task<UserAccount?> GetByUsername(string username)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetUserAccountByUsername", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = username;
await using var reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public async Task<UserAccount?> GetByEmail(string email)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetUserAccountByEmail", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = email;
await using var reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
protected override UserAccount MapToEntity(SqlDataReader reader)
{
return new UserAccount
{
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
Username = reader.GetString(reader.GetOrdinal("Username")),
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
LastName = reader.GetString(reader.GetOrdinal("LastName")),
Email = reader.GetString(reader.GetOrdinal("Email")),
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
? null
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
? null
: (byte[])reader["Timer"]
};
}
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>DataAccessLayer</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
<PackageReference
Include="Microsoft.SqlServer.Types"
Version="160.1000.6"
/>
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
namespace DataAccessLayer.Sql
{
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
{
private readonly string _connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING")
?? configuration.GetConnectionString("Default")
?? throw new InvalidOperationException(
"Database connection string not configured. Set DB_CONNECTION_STRING env var or ConnectionStrings:Default."
);
public SqlConnection CreateConnection()
{
return new SqlConnection(_connectionString);
}
}
}

View File

@@ -0,0 +1,9 @@
using Microsoft.Data.SqlClient;
namespace DataAccessLayer.Sql
{
public interface ISqlConnectionFactory
{
SqlConnection CreateConnection();
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>DALTests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,280 @@
using DataAccessLayer;
using DataAccessLayer.Entities;
using DataAccessLayer.Repositories;
namespace DALTests
{
public class UserAccountRepositoryTests
{
private readonly IUserAccountRepository _repository = new InMemoryUserAccountRepository();
[Fact]
public async Task Add_ShouldInsertUserAccount()
{
// Arrange
var userAccount = new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = "testuser",
FirstName = "Test",
LastName = "User",
Email = "testuser@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1990, 1, 1),
};
// Act
await _repository.Add(userAccount);
var retrievedUser = await _repository.GetById(userAccount.UserAccountId);
// Assert
Assert.NotNull(retrievedUser);
Assert.Equal(userAccount.Username, retrievedUser.Username);
}
[Fact]
public async Task GetById_ShouldReturnUserAccount()
{
// Arrange
var userId = Guid.NewGuid();
var userAccount = new UserAccount
{
UserAccountId = userId,
Username = "existinguser",
FirstName = "Existing",
LastName = "User",
Email = "existinguser@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1985, 5, 15),
};
await _repository.Add(userAccount);
// Act
var retrievedUser = await _repository.GetById(userId);
// Assert
Assert.NotNull(retrievedUser);
Assert.Equal(userId, retrievedUser.UserAccountId);
}
[Fact]
public async Task Update_ShouldModifyUserAccount()
{
// Arrange
var userAccount = new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = "updatableuser",
FirstName = "Updatable",
LastName = "User",
Email = "updatableuser@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1992, 3, 10),
};
await _repository.Add(userAccount);
// Act
userAccount.FirstName = "Updated";
await _repository.Update(userAccount);
var updatedUser = await _repository.GetById(userAccount.UserAccountId);
// Assert
Assert.NotNull(updatedUser);
Assert.Equal("Updated", updatedUser.FirstName);
}
[Fact]
public async Task Delete_ShouldRemoveUserAccount()
{
// Arrange
var userAccount = new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = "deletableuser",
FirstName = "Deletable",
LastName = "User",
Email = "deletableuser@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1995, 7, 20),
};
await _repository.Add(userAccount);
// Act
await _repository.Delete(userAccount.UserAccountId);
var deletedUser = await _repository.GetById(userAccount.UserAccountId);
// Assert
Assert.Null(deletedUser);
}
[Fact]
public async Task GetAll_ShouldReturnAllUserAccounts()
{
// Arrange
var user1 = new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = "user1",
FirstName = "User",
LastName = "One",
Email = "user1@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1990, 1, 1),
};
var user2 = new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = "user2",
FirstName = "User",
LastName = "Two",
Email = "user2@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1992, 2, 2),
};
await _repository.Add(user1);
await _repository.Add(user2);
// Act
var allUsers = await _repository.GetAll(null, null);
// Assert
Assert.NotNull(allUsers);
Assert.True(allUsers.Count() >= 2);
}
[Fact]
public async Task GetAll_WithPagination_ShouldRespectLimit()
{
// Arrange
var users = new List<UserAccount>
{
new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = $"pageuser_{Guid.NewGuid():N}",
FirstName = "Page",
LastName = "User",
Email = $"pageuser_{Guid.NewGuid():N}@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1991, 4, 4),
},
new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = $"pageuser_{Guid.NewGuid():N}",
FirstName = "Page",
LastName = "User",
Email = $"pageuser_{Guid.NewGuid():N}@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1992, 5, 5),
},
new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = $"pageuser_{Guid.NewGuid():N}",
FirstName = "Page",
LastName = "User",
Email = $"pageuser_{Guid.NewGuid():N}@example.com",
CreatedAt = DateTime.UtcNow,
DateOfBirth = new DateTime(1993, 6, 6),
},
};
foreach (var user in users)
{
await _repository.Add(user);
}
// Act
var page = (await _repository.GetAll(2, 0)).ToList();
// Assert
Assert.Equal(2, page.Count);
}
[Fact]
public async Task GetAll_WithPagination_ShouldValidateArguments()
{
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
(await _repository.GetAll(0, 0)).ToList()
);
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
(await _repository.GetAll(1, -1)).ToList()
);
}
}
internal class InMemoryUserAccountRepository : IUserAccountRepository
{
private readonly Dictionary<Guid, UserAccount> _store = new();
public Task Add(UserAccount userAccount)
{
if (userAccount.UserAccountId == Guid.Empty)
{
userAccount.UserAccountId = Guid.NewGuid();
}
_store[userAccount.UserAccountId] = Clone(userAccount);
return Task.CompletedTask;
}
public Task<UserAccount?> GetById(Guid id)
{
_store.TryGetValue(id, out var user);
return Task.FromResult(user is null ? null : Clone(user));
}
public Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset)
{
if (limit.HasValue && limit.Value <= 0) throw new ArgumentOutOfRangeException(nameof(limit));
if (offset.HasValue && offset.Value < 0) throw new ArgumentOutOfRangeException(nameof(offset));
var query = _store.Values
.OrderBy(u => u.Username)
.Select(Clone);
if (offset.HasValue) query = query.Skip(offset.Value);
if (limit.HasValue) query = query.Take(limit.Value);
return Task.FromResult<IEnumerable<UserAccount>>(query.ToList());
}
public Task Update(UserAccount userAccount)
{
if (!_store.ContainsKey(userAccount.UserAccountId)) return Task.CompletedTask;
_store[userAccount.UserAccountId] = Clone(userAccount);
return Task.CompletedTask;
}
public Task Delete(Guid id)
{
_store.Remove(id);
return Task.CompletedTask;
}
public Task<UserAccount?> GetByUsername(string username)
{
var user = _store.Values.FirstOrDefault(u => u.Username == username);
return Task.FromResult(user is null ? null : Clone(user));
}
public Task<UserAccount?> GetByEmail(string email)
{
var user = _store.Values.FirstOrDefault(u => u.Email == email);
return Task.FromResult(user is null ? null : Clone(user));
}
private static UserAccount Clone(UserAccount u) => new()
{
UserAccountId = u.UserAccountId,
Username = u.Username,
FirstName = u.FirstName,
LastName = u.LastName,
Email = u.Email,
CreatedAt = u.CreatedAt,
UpdatedAt = u.UpdatedAt,
DateOfBirth = u.DateOfBirth,
Timer = u.Timer is null ? null : (byte[])u.Timer.Clone(),
};
}
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>BusinessLayer</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,10 @@
using DataAccessLayer.Entities;
namespace BusinessLayer.Services
{
public interface IUserService
{
Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null);
Task<UserAccount?> GetByIdAsync(Guid id);
}
}

View File

@@ -0,0 +1,18 @@
using DataAccessLayer.Entities;
using DataAccessLayer.Repositories;
namespace BusinessLayer.Services
{
public class UserService(IUserAccountRepository repository) : IUserService
{
public async Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null)
{
return await repository.GetAll(limit, offset);
}
public async Task<UserAccount?> GetByIdAsync(Guid id)
{
return await repository.GetById(id);
}
}
}

16
biergarten.slnx Normal file
View File

@@ -0,0 +1,16 @@
<Solution>
<Folder Name="/API/" >
<Project Path="API/API.Core/API.Core.csproj"/>
</Folder>
<Folder Name="/Database/">
<Project Path="Database/Database.Core/Database.Core.csproj" />
<Project Path="Database/Database.Seed/Database.Seed.csproj" />
</Folder>
<Folder Name="/Repository/" >
<Project Path="Repository/Repository.Core/Repository.Core.csproj"/>
<Project Path="Repository/Repository.Tests/Repository.Tests.csproj"/>
</Folder>
<Folder Name="/Service/">
<Project Path="Service/Service.Core/Service.Core.csproj"/>
</Folder>
</Solution>

29
docker-compose.yml Normal file
View File

@@ -0,0 +1,29 @@
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: sqlserver
env_file:
- .env
environment:
ACCEPT_EULA: "Y"
SA_PASSWORD: "${SA_PASSWORD}"
ports:
- "1433:1433"
volumes:
- sqlserverdata:/var/opt/mssql
healthcheck:
test: ["CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "${SA_PASSWORD}", "-Q", "SELECT 1" ]
interval: 10s
timeout: 5s
retries: 12
networks:
- devnet
volumes:
sqlserverdata:
nuget-cache:
networks:
devnet:
driver: bridge

2411
misc/raw-data/beers.csv Normal file

File diff suppressed because it is too large Load Diff

559
misc/raw-data/breweries.csv Normal file
View File

@@ -0,0 +1,559 @@
,name,city,state
0,NorthGate Brewing ,Minneapolis, MN
1,Against the Grain Brewery,Louisville, KY
2,Jack's Abby Craft Lagers,Framingham, MA
3,Mike Hess Brewing Company,San Diego, CA
4,Fort Point Beer Company,San Francisco, CA
5,COAST Brewing Company,Charleston, SC
6,Great Divide Brewing Company,Denver, CO
7,Tapistry Brewing,Bridgman, MI
8,Big Lake Brewing,Holland, MI
9,The Mitten Brewing Company,Grand Rapids, MI
10,Brewery Vivant,Grand Rapids, MI
11,Petoskey Brewing,Petoskey, MI
12,Blackrocks Brewery,Marquette, MI
13,Perrin Brewing Company,Comstock Park, MI
14,Witch's Hat Brewing Company,South Lyon, MI
15,Founders Brewing Company,Grand Rapids, MI
16,Flat 12 Bierwerks,Indianapolis, IN
17,Tin Man Brewing Company,Evansville, IN
18,Black Acre Brewing Co.,Indianapolis, IN
19,Brew Link Brewing,Plainfield, IN
20,Bare Hands Brewery,Granger, IN
21,Three Pints Brewing,Martinsville, IN
22,Four Fathers Brewing ,Valparaiso, IN
23,Indiana City Brewing,Indianapolis, IN
24,Burn 'Em Brewing,Michigan City, IN
25,Sun King Brewing Company,Indianapolis, IN
26,Evil Czech Brewery,Mishawaka, IN
27,450 North Brewing Company,Columbus, IN
28,Taxman Brewing Company,Bargersville, IN
29,Cedar Creek Brewery,Seven Points, TX
30,SanTan Brewing Company,Chandler, AZ
31,Boulevard Brewing Company,Kansas City, MO
32,James Page Brewing Company,Stevens Point, WI
33,The Dudes' Brewing Company,Torrance, CA
34,Ballast Point Brewing Company,San Diego, CA
35,Anchor Brewing Company,San Francisco, CA
36,Figueroa Mountain Brewing Company,Buellton, CA
37,Avery Brewing Company,Boulder, CO
38,Twisted X Brewing Company,Dripping Springs, TX
39,Gonzo's BiggDogg Brewing,Kalamazoo, MI
40,Big Muddy Brewing,Murphysboro, IL
41,Lost Nation Brewing,East Fairfield, VT
42,Rising Tide Brewing Company,Portland, ME
43,Rivertowne Brewing Company,Export, PA
44,Revolution Brewing Company,Chicago, IL
45,Tallgrass Brewing Company,Manhattan, KS
46,Sixpoint Craft Ales,Brooklyn, NY
47,White Birch Brewing,Hooksett, NH
48,Firestone Walker Brewing Company,Paso Robles, CA
49,SweetWater Brewing Company,Atlanta, GA
50,Flying Mouse Brewery,Troutville, VA
51,Upslope Brewing Company,Boulder, CO
52,Pipeworks Brewing Company,Chicago, IL
53,Bent Brewstillery,Roseville, MN
54,Flesk Brewing Company,Lombard, IL
55,Pollyanna Brewing Company,Lemont, IL
56,BuckleDown Brewing,Lyons, IL
57,Destihl Brewery,Bloomington, IL
58,Summit Brewing Company,St. Paul, MN
59,Latitude 42 Brewing Company,Portage, MI
60,4 Hands Brewing Company,Saint Louis, MO
61,Surly Brewing Company,Brooklyn Center, MN
62,Against The Grain Brewery,Louisville, KY
63,Crazy Mountain Brewing Company,Edwards, CO
64,SlapShot Brewing Company,Chicago, IL
65,Mikerphone Brewing,Chicago, IL
66,Freetail Brewing Company,San Antonio, TX
67,3 Daughters Brewing,St Petersburg, FL
68,Red Shedman Farm Brewery and Hop...,Mt. Airy, MD
69,Appalachian Mountain Brewery,Boone, NC
70,Birdsong Brewing Company,Charlotte, NC
71,Union Craft Brewing,Baltimore, MD
72,Atwater Brewery,Detroit, MI
73,Ale Asylum,Madison, WI
74,Two Brothers Brewing Company,Warrenville, IL
75,Bent Paddle Brewing Company,Duluth, MN
76,Bell's Brewery,Kalamazoo, MI
77,Blue Owl Brewing,Austin, TX
78,Speakasy Ales & Lagers,San Francisco, CA
79,Black Tooth Brewing Company,Sheridan, WY
80,Hopworks Urban Brewery,Portland, OR
81,Epic Brewing,Denver, CO
82,New Belgium Brewing Company,Fort Collins, CO
83,Sierra Nevada Brewing Company,Chico, CA
84,Keweenaw Brewing Company,Houghton, MI
85,Brewery Terra Firma,Traverse City, MI
86,Grey Sail Brewing Company,Westerly, RI
87,Kirkwood Station Brewing Company,Kirkwood, MO
88,Goose Island Brewing Company,Chicago, IL
89,Broad Brook Brewing LLC,East Windsor, CT
90,The Lion Brewery,Wilkes-Barre, PA
91,Madtree Brewing Company,Cincinnati, OH
92,Jackie O's Pub & Brewery,Athens, OH
93,Rhinegeist Brewery,Cincinnati, OH
94,Warped Wing Brewing Company,Dayton, OH
95,Blackrocks Brewery,Marquette, MA
96,Catawba Valley Brewing Company,Morganton, NC
97,Tröegs Brewing Company,Hershey, PA
98,Mission Brewery,San Diego, CA
99,Christian Moerlein Brewing Company,Cincinnati, OH
100,West Sixth Brewing,Lexington, KY
101,Coastal Extreme Brewing Company,Newport, RI
102,King Street Brewing Company,Anchorage, AK
103,Beer Works Brewery,Lowell, MA
104,Lone Tree Brewing Company,Lone Tree, CO
105,Four String Brewing Company,Columbus, OH
106,Glabrous Brewing Company,Pineland, ME
107,Bonfire Brewing Company,Eagle, CO
108,Thomas Hooker Brewing Company,Bloomfield, CT
109,"Woodstock Inn, Station & Brewery",North Woodstock, NH
110,Renegade Brewing Company,Denver, CO
111,Mother Earth Brew Company,Vista, CA
112,Black Market Brewing Company,Temecula, CA
113,Vault Brewing Company,Yardley, PA
114,Jailbreak Brewing Company,Laurel, MD
115,Smartmouth Brewing Company,Norfolk, VA
116,Base Camp Brewing Co.,Portland, OR
117,Alameda Brewing,Portland, OR
118,Southern Star Brewing Company,Conroe, TX
119,Steamworks Brewing Company,Durango, CO
120,Horny Goat Brew Pub,Milwaukee, WI
121,Cheboygan Brewing Company,Cheboygan, MI
122,Center of the Universe Brewing C...,Ashland, VA
123,Ipswich Ale Brewery,Ipswich, MA
124,Griffin Claw Brewing Company,Birmingham, MI
125,Karbach Brewing Company,Houston, TX
126,Uncle Billy's Brewery and Smokeh...,Austin, TX
127,Deep Ellum Brewing Company,Dallas, TX
128,Real Ale Brewing Company,Blanco, TX
129,Straub Brewery,St Mary's, PA
130,Shebeen Brewing Company,Wolcott, CT
131,Stevens Point Brewery,Stevens Point, WI
132,Weston Brewing Company,Weston, MO
133,Southern Prohibition Brewing Com...,Hattiesburg, MS
134,Minhas Craft Brewery,Monroe, WI
135,Pug Ryan's Brewery,Dillon, CO
136,Hops & Grains Brewing Company,Austin, TX
137,Sietsema Orchards and Cider Mill,Ada, MI
138,Summit Brewing Company,St Paul, MN
139,Core Brewing & Distilling Company,Springdale, AR
140,Independence Brewing Company,Austin, TX
141,Cigar City Brewing Company,Tampa, FL
142,Third Street Brewhouse,Cold Spring, MN
143,Narragansett Brewing Company,Providence, RI
144,Grimm Brothers Brewhouse,Loveland, CO
145,Cisco Brewers,Nantucket, MA
146,Angry Minnow,Hayward, WI
147,Platform Beer Company,Cleveland, OH
148,Odyssey Beerwerks,Arvada, CO
149,Lonerider Brewing Company,Raleigh, NC
150,Oakshire Brewing,Eugene, OR
151,Fort Pitt Brewing Company,Latrobe, PA
152,Tin Roof Brewing Company,Baton Rouge, LA
153,Three Creeks Brewing,Sisters, OR
154,2 Towns Ciderhouse,Corvallis, OR
155,Caldera Brewing Company,Ashland, OR
156,Greenbrier Valley Brewing Company,Lewisburg, WV
157,Phoenix Ale Brewery,Phoenix, AZ
158,Lumberyard Brewing Company,Flagstaff, AZ
159,Uinta Brewing Company,Salt Lake City, UT
160,Four Peaks Brewing Company,Tempe, AZ
161,Martin House Brewing Company,Fort Worth, TX
162,Right Brain Brewery,Traverse City, MI
163,Sly Fox Brewing Company,Phoenixville, PA
164,Round Guys Brewing,Lansdale, PA
165,Great Crescent Brewery,Aurora, IN
166,Oskar Blues Brewery,Longmont, CO
167,Boxcar Brewing Company,West Chester, PA
168,High Hops Brewery,Windsor, CO
169,Crooked Fence Brewing Company,Garden City, ID
170,Everybody's Brewing,White Salmon, WA
171,Anderson Valley Brewing Company,Boonville, CA
172,Fiddlehead Brewing Company,Shelburne, VT
173,Evil Twin Brewing,Brooklyn, NY
174,New Orleans Lager & Ale Brewing ...,New Orleans, LA
175,Spiteful Brewing Company,Chicago, IL
176,Rahr & Sons Brewing Company,Fort Worth, TX
177,18th Street Brewery,Gary, IN
178,Cambridge Brewing Company,Cambridge, MA
179,Carolina Brewery,Pittsboro, NC
180,Frog Level Brewing Company,Waynesville, NC
181,Wild Wolf Brewing Company,Nellysford, VA
182,COOP Ale Works,Oklahoma City, OK
183,Seventh Son Brewing Company,Columbus, OH
184,Oasis Texas Brewing Company,Austin, TX
185,Vander Mill Ciders,Spring Lake, MI
186,St. Julian Winery,Paw Paw, MI
187,Pedernales Brewing Company,Fredericksburg, TX
188,Mother's Brewing,Springfield, MO
189,Modern Monks Brewery,Lincoln, NE
190,Two Beers Brewing Company,Seattle, WA
191,Snake River Brewing Company,Jackson, WY
192,Capital Brewery,Middleton, WI
193,Anthem Brewing Company,Oklahoma City, OK
194,Goodlife Brewing Co.,Bend, OR
195,Breakside Brewery,Portland, OR
196,Goose Island Brewery Company,Chicago, IL
197,Burnside Brewing Co.,Portland, OR
198,Hop Valley Brewing Company,Springfield, OR
199,Worthy Brewing Company,Bend, OR
200,Occidental Brewing Company,Portland, OR
201,Fearless Brewing Company,Estacada, OR
202,Upland Brewing Company,Bloomington, IN
203,Mehana Brewing Co.,Hilo, HI
204,Hawai'i Nui Brewing Co.,Hilo, HI
205,People's Brewing Company,Lafayette, IN
206,Fort George Brewery,Astoria, OR
207,Branchline Brewing Company,San Antonio, TX
208,Kalona Brewing Company,Kalona, IA
209,Modern Times Beer,San Diego, CA
210,Temperance Beer Company,Evanston, IL
211,Wisconsin Brewing Company,Verona, WI
212,Crow Peak Brewing Company,Spearfish, SD
213,Grapevine Craft Brewery,Farmers Branch, TX
214,Buffalo Bayou Brewing Company,Houston, TX
215,Texian Brewing Co.,Richmond, TX
216,Orpheus Brewing,Atlanta, GA
217,Forgotten Boardwalk,Cherry Hill, NJ
218,Laughing Dog Brewing Company,Ponderay, ID
219,Bozeman Brewing Company,Bozeman, MT
220,Big Choice Brewing,Broomfield, CO
221,Big Storm Brewing Company,Odessa, FL
222,Carton Brewing Company,Atlantic Highlands, NJ
223,Midnight Sun Brewing Company,Anchorage, AK
224,Fat Head's Brewery,Middleburg Heights, OH
225,Refuge Brewery,Temecula, CA
226,Chatham Brewing,Chatham, NY
227,DC Brau Brewing Company,Washington, DC
228,Geneva Lake Brewing Company,Lake Geneva, WI
229,Rochester Mills Brewing Company,Rochester, MI
230,Cape Ann Brewing Company,Gloucester, MA
231,Borderlands Brewing Company,Tucson, AZ
232,College Street Brewhouse and Pub,Lake Havasu City, AZ
233,Joseph James Brewing Company,Henderson, NV
234,Harpoon Brewery,Boston, MA
235,Back East Brewing Company,Bloomfield, CT
236,Champion Brewing Company,Charlottesville, VA
237,Devil's Backbone Brewing Company,Lexington, VA
238,Newburgh Brewing Company,Newburgh, NY
239,Wiseacre Brewing Company,Memphis, TN
240,Golden Road Brewing,Los Angeles, CA
241,New Republic Brewing Company,College Station, TX
242,Infamous Brewing Company,Austin, TX
243,Two Henrys Brewing Company,Plant City, FL
244,Lift Bridge Brewing Company,Stillwater, MN
245,Lucky Town Brewing Company,Jackson, MS
246,Quest Brewing Company,Greenville, SC
247,Creature Comforts,Athens, GA
248,Half Full Brewery,Stamford, CT
249,Southampton Publick House,Southampton, NY
250,Chapman's Brewing,Angola, IN
251,Barrio Brewing Company,Tucson, AZ
252,Santa Cruz Mountain Brewing,Santa Cruz, CA
253,Frankenmuth Brewery,Frankenmuth, MI
254,Meckley's Cidery,Somerset Center, MI
255,Stillwater Artisanal Ales,Baltimore, MD
256,Finch's Beer Company,Chicago, IL
257,South Austin Brewery,South Austin, TX
258,Bauhaus Brew Labs,Minneapolis, MN
259,Ozark Beer Company,Rogers, AR
260,Mountain Town Brewing Company ,Mount Pleasant, MI
261,Otter Creek Brewing,Waterbury, VT
262,The Brewer's Art,Baltimore, MD
263,Denver Beer Company,Denver, CO
264,Ska Brewing Company,Durango, CO
265,Tractor Brewing Company,Albuquerque, NM
266,Peak Organic Brewing Company,Portland, ME
267,Cape Cod Beer,Hyannis, MA
268,Long Trail Brewing Company,Bridgewater Corners, VT
269,Great Raft Brewing Company,Shreveport, LA
270,Alaskan Brewing Company,Juneau, AK
271,Notch Brewing Company,Ipswich, MA
272,The Alchemist,Waterbury, VT
273,Three Notch'd Brewing Company,Charlottesville, VA
274,Portside Brewery,Cleveland, OH
275,Otter Creek Brewing,Middlebury, VT
276,Montauk Brewing Company,Montauk, NY
277,Indeed Brewing Company,Minneapolis, MN
278,Berkshire Brewing Company,South Deerfield, MA
279,Foolproof Brewing Company,Pawtucket, RI
280,Headlands Brewing Company,Mill Valley, CA
281,Bolero Snort Brewery,Ridgefield Park, NJ
282,Thunderhead Brewing Company,Kearney, NE
283,Defiance Brewing Company,Hays, KS
284,Milwaukee Brewing Company,Milwaukee, WI
285,Catawba Island Brewing,Port Clinton, OH
286,Back Forty Beer Company,Gadsden, AL
287,Four Corners Brewing Company,Dallas, TX
288,Saint Archer Brewery,San Diego, CA
289,Rogue Ales,Newport, OR
290,Hale's Ales,Seattle, WA
291,Tommyknocker Brewery,Idaho Springs, CO
292,Baxter Brewing Company,Lewiston, ME
293,Northampton Brewery,Northamtpon, MA
294,Black Shirt Brewing Company,Denver, CO
295,Wachusett Brewing Company,Westminster, MA
296,Widmer Brothers Brewing Company,Portland, OR
297,Hop Farm Brewing Company,Pittsburgh, PA
298,Liquid Hero Brewery,York, PA
299,Matt Brewing Company,Utica, NY
300,Boston Beer Company,Boston, MA
301,Old Forge Brewing Company,Danville, PA
302,Utah Brewers Cooperative,Salt Lake City, UT
303,Magic Hat Brewing Company,South Burlington, VT
304,Blue Hills Brewery,Canton, MA
305,Night Shift Brewing,Everett, MA
306,Beach Brewing Company,Virginia Beach, VA
307,Payette Brewing Company,Garden City, ID
308,Brew Bus Brewing,Tampa, FL
309,Sockeye Brewing Company,Boise, ID
310,Pine Street Brewery,San Francisco, CA
311,Dirty Bucket Brewing Company,Woodinville, WA
312,Jackalope Brewing Company,Nashville, TN
313,Slanted Rock Brewing Company,Meridian, ID
314,Piney River Brewing Company,Bucryus, MO
315,Cutters Brewing Company,Avon, IN
316,Iron Hill Brewery & Restaurant,Wilmington, DE
317,Marshall Wharf Brewing Company,Belfast, ME
318,Banner Beer Company,Williamsburg, MA
319,Dick's Brewing Company,Centralia, WA
320,Claremont Craft Ales,Claremont, CA
321,Rivertown Brewing Company,Lockland, OH
322,Voodoo Brewery,Meadville, PA
323,D.L. Geary Brewing Company,Portland, ME
324,Pisgah Brewing Company,Black Mountain, NC
325,Neshaminy Creek Brewing Company,Croydon, PA
326,Morgan Street Brewery,Saint Louis, MO
327,Half Acre Beer Company,Chicago, IL
328,The Just Beer Project,Burlington, VT
329,The Bronx Brewery,Bronx, NY
330,Dead Armadillo Craft Brewing,Tulsa, OK
331,Catawba Brewing Company,Morganton, NC
332,La Cumbre Brewing Company,Albuquerque, NM
333,David's Ale Works,Diamond Springs, CA
334,The Traveler Beer Company,Burlington, VT
335,Fargo Brewing Company,Fargo, ND
336,Big Sky Brewing Company,Missoula, MT
337,Nebraska Brewing Company,Papillion, NE
338,Uncle John's Fruit House Winery,St. John's, MI
339,Wormtown Brewery,Worcester, MA
340,Due South Brewing Company,Boynton Beach, FL
341,Palisade Brewing Company,Palisade, CO
342,KelSo Beer Company,Brooklyn, NY
343,Hardywood Park Craft Brewery,Richmond, VA
344,Wolf Hills Brewing Company,Abingdon, VA
345,Lavery Brewing Company,Erie, PA
346,Manzanita Brewing Company,Santee, CA
347,Fullsteam Brewery,Durham, NC
348,Four Horsemen Brewing Company,South Bend, IN
349,Hinterland Brewery,Green Bay, WI
350,Central Coast Brewing Company,San Luis Obispo, CA
351,Westfield River Brewing Company,Westfield, MA
352,Elevator Brewing Company,Columbus, OH
353,Aslan Brewing Company,Bellingham, WA
354,Kulshan Brewery,Bellingham, WA
355,Pikes Peak Brewing Company,Monument, CO
356,Manayunk Brewing Company,Philadelphia, PA
357,Buckeye Brewing,Cleveland, OH
358,Daredevil Brewing Company,Shelbyville, IN
359,NoDa Brewing Company,Charlotte, NC
360,Aviator Brewing Company,Fuquay-Varina, NC
361,Wild Onion Brewing Company,Lake Barrington, IL
362,Hilliard's Beer,Seattle, WA
363,Mikkeller,Pottstown, PA
364,Bohemian Brewery,Midvale, UT
365,Great River Brewery,Davenport, IA
366,Mustang Brewing Company,Mustang, OK
367,Airways Brewing Company,Kent, WA
368,21st Amendment Brewery,San Francisco, CA
369,Eddyline Brewery & Restaurant,Buena Vista, CO
370,Pizza Port Brewing Company,Carlsbad, CA
371,Sly Fox Brewing Company,Pottstown, PA
372,Spring House Brewing Company,Conestoga, PA
373,7venth Sun,Dunedin, FL
374,Astoria Brewing Company,Astoria, OR
375,Maui Brewing Company,Lahaina, HI
376,RoughTail Brewing Company,Midwest City, OK
377,Lucette Brewing Company,Menominee, WI
378,Bold City Brewery,Jacksonville, FL
379,Grey Sail Brewing of Rhode Island,Westerly, RI
380,Blue Blood Brewing Company,Lincoln, NE
381,Swashbuckler Brewing Company,Manheim, PA
382,Blue Mountain Brewery,Afton, VA
383,Starr Hill Brewery,Crozet, VA
384,Westbrook Brewing Company,Mt. Pleasant, SC
385,Shipyard Brewing Company,Portland, ME
386,Revolution Brewing,Paonia, CO
387,Natian Brewery,Portland, OR
388,Alltech's Lexington Brewing Company,Lexington, KY
389,Oskar Blues Brewery (North Carol...,Brevard, NC
390,Orlison Brewing Company,Airway Heights, WA
391,Breckenridge Brewery,Denver, CO
392,Santa Fe Brewing Company,Santa Fe, NM
393,Miami Brewing Company,Miami, FL
394,Schilling & Company,Seattle, WA
395,Hops & Grain Brewery,Austin, TX
396,White Flame Brewing Company,Hudsonville, MI
397,Ruhstaller Beer Company,Sacramento, CA
398,Saugatuck Brewing Company,Douglas, MI
399,Moab Brewery,Moab, UT
400,Macon Beer Company,Macon, GA
401,Amnesia Brewing Company,Washougal, WA
402,Wolverine State Brewing Company,Ann Arbor, MI
403,Red Tank Cider Company,Bend, OR
404,Cascadia Ciderworks United,Portland, OR
405,Fate Brewing Company,Boulder, CO
406,Lazy Monk Brewing,Eau Claire, WI
407,Bitter Root Brewing,Hamilton, MT
408,10 Barrel Brewing Company,Bend, OR
409,Tamarack Brewing Company,Lakeside, MT
410,New England Brewing Company,Woodbridge, CT
411,Seattle Cider Company,Seattle, WA
412,Straight to Ale,Huntsville, AL
413,Austin Beerworks,Austin, TX
414,Blue Mountain Brewery,Arrington, VA
415,Coastal Empire Beer Company,Savannah, GA
416,Jack's Hard Cider (Hauser Estate...,Biglerville, PA
417,Boulder Beer Company,Boulder, CO
418,Coalition Brewing Company,Portland, OR
419,Sanitas Brewing Company,Boulder, CO
420,Gore Range Brewery,Edwards, CO
421,Redstone Meadery,Boulder, CO
422,Blue Dog Mead,Eugene, OR
423,Hess Brewing Company,San Diego, CA
424,Wynkoop Brewing Company,Denver, CO
425,Ciderboys,Stevens Point, WI
426,Armadillo Ale Works,Denton, TX
427,Roanoke Railhouse Brewery,Roanoke, VA
428,Schlafly Brewing Company,Saint Louis, MO
429,Asher Brewing Company,Boulder, CO
430,Lost Rhino Brewing Company,Ashburn, VA
431,North Country Brewing Company,Slippery Rock, PA
432,Seabright Brewery,Santa Cruz, CA
433,French Broad Brewery,Asheville, NC
434,Angry Orchard Cider Company,Cincinnati, OH
435,Two Roads Brewing Company,Stratford, CT
436,Southern Oregon Brewing Company,Medford, OR
437,Brooklyn Brewery,Brooklyn, NY
438,The Right Brain Brewery,Traverse City, MI
439,Kona Brewing Company,Kona, HI
440,MillKing It Productions,Royal Oak, MI
441,Pateros Creek Brewing Company,Fort Collins, CO
442,O'Fallon Brewery,O'Fallon, MO
443,Marble Brewery,Albuquerque, NM
444,Big Wood Brewery,Vadnais Heights, MN
445,Howard Brewing Company,Lenoir, NC
446,Downeast Cider House,Leominster, MA
447,Swamp Head Brewery,Gainesville, FL
448,Mavericks Beer Company,Half Moon Bay, CA
449,TailGate Beer,San Diego, CA
450,Northwest Brewing Company,Pacific, WA
451,Dad & Dude's Breweria,Aurora, CO
452,Centennial Beer Company,Edwards, CO
453,Denali Brewing Company,Talkeetna, AK
454,Deschutes Brewery,Bend, OR
455,Sunken City Brewing Company,Hardy, VA
456,Lucette Brewing Company,Menominie, WI
457,The Black Tooth Brewing Company,Sheridan, WY
458,Kenai River Brewing Company,Soldotna, AK
459,River North Brewery,Denver, CO
460,Fremont Brewing Company,Seattle, WA
461,Armstrong Brewing Company,South San Francisco, CA
462,AC Golden Brewing Company,Golden, CO
463,Big Bend Brewing Company,Alpine, TX
464,Good Life Brewing Company,Bend, OR
465,Engine 15 Brewing,Jacksonville Beach, FL
466,Green Room Brewing,Jacksonville, FL
467,Brindle Dog Brewing Company,Tampa Bay, FL
468,Peace Tree Brewing Company,Knoxville, IA
469,Terrapin Brewing Company,Athens, GA
470,Pete's Brewing Company,San Antonio, TX
471,Okoboji Brewing Company,Spirit Lake, IA
472,Crystal Springs Brewing Company,Boulder, CO
473,Engine House 9,Tacoma, WA
474,Tonka Beer Company,Minnetonka, MN
475,Red Hare Brewing Company,Marietta, GA
476,Hangar 24 Craft Brewery,Redlands, CA
477,Big Elm Brewing,Sheffield, MA
478,Good People Brewing Company,Birmingham, AL
479,Heavy Seas Beer,Halethorpe, MD
480,Telluride Brewing Company,Telluride, CO
481,7 Seas Brewing Company,Gig Harbor, WA
482,Confluence Brewing Company,Des Moines, IA
483,Bale Breaker Brewing Company,Yakima, WA
484,The Manhattan Brewing Company,New York, NY
485,MacTarnahans Brewing Company,Portland, OR
486,Stillmank Beer Company,Green Bay, WI
487,Redhook Brewery,Woodinville, WA
488,Dock Street Brewery,Philadelphia, PA
489,Blue Point Brewing Company,Patchogue, NY
490,Tampa Bay Brewing Company,Tampa, FL
491,Devil's Canyon Brewery,Belmont, CA
492,Stone Coast Brewing Company,Portland, ME
493,Broken Tooth Brewing Company,Anchorage, AK
494,Seven Brides Brewery,Silverton, OR
495,Newburyport Brewing Company,Newburyport, MA
496,Dry Dock Brewing Company,Aurora, CO
497,Cans Bar and Canteen,Charlotte, NC
498,Sprecher Brewing Company,Glendale, WI
499,Wildwood Brewing Company,Stevensville, MT
500,High Noon Saloon And Brewery,Leavenworth, KS
501,Woodchuck Hard Cider,Middlebury, VT
502,Sea Dog Brewing Company,Portland, ME
503,Oskar Blues Brewery,Lyons, CO
504,Carolina Beer & Beverage,Mooresville, NC
505,Krebs Brewing Company (Pete's Pl...,Krebs, OK
506,Warbird Brewing Company,Fort Wayne, IN
507,Mudshark Brewing Company,Lake Havasu City, AZ
508,Spilker Ales,Cortland, NE
509,Wingman Brewers,Tacoma, WA
510,Kettle House Brewing Company,Missoula, MT
511,Sherwood Forest Brewers,Marlborough, MA
512,Cottrell Brewing,Pawcatuck, CT
513,Arctic Craft Brewery,Colorado Springs, CO
514,Monkey Paw Pub & Brewery,San Diego, CA
515,Crabtree Brewing Company,Greeley, CO
516,Emerald City Beer Company,Seattle, WA
517,Butcher's Brewing,Carlsbad, CA
518,New South Brewing Company,Myrtle Beach, SC
519,Big River Brewing Company,Chattanooga, TN
520,Twisted Pine Brewing Company,Boulder, CO
521,Flying Dog Brewery,Frederick, MD
522,Uncommon Brewers,Santa Cruz, CA
523,Aspen Brewing Company,Aspen, CO
524,Triangle Brewing Company,Durham, NC
525,Bomb Beer Company,New York, NY
526,Churchkey Can Company,Seattle, WA
527,Intuition Ale Works,Jacksonville, FL
528,Asheville Brewing Company,Asheville, NC
529,Northwoods Brewpub,Eau Claire, WI
530,Buckbean Brewing Company,Reno, NV
531,Dolores River Brewery,Dolores, CO
532,Flat Rock Brewing Company,Smithton, PA
533,Abita Brewing Company,Abita Springs, LA
534,Mammoth Brewing Company,Mammoth Lakes, CA
535,Harvest Moon Brewing Company,Belt, MT
536,Grand Canyon Brewing Company,Williams, AZ
537,Lewis and Clark Brewing Company,Helena, MT
538,Dundee Brewing Company,Rochester, NY
539,Twin Lakes Brewing Company,Greenville, DE
540,Mother Earth Brewing Company,Kinston, NC
541,Arcadia Brewing Company,Battle Creek, MI
542,Angry Minnow Brewing Company,Hayward, WI
543,Great Northern Brewing Company,Whitefish, MT
544,Pyramid Breweries,Seattle, WA
545,Lancaster Brewing Company,Lancaster, PA
546,Upstate Brewing Company,Elmira, NY
547,Moat Mountain Smoke House & Brew...,North Conway, NH
548,Prescott Brewing Company,Prescott, AZ
549,Mogollon Brewing Company,Flagstaff, AZ
550,Wind River Brewing Company,Pinedale, WY
551,Silverton Brewery,Silverton, CO
552,Mickey Finn's Brewery,Libertyville, IL
553,Covington Brewhouse,Covington, LA
554,Dave's Brewfarm,Wilson, WI
555,Ukiah Brewing Company,Ukiah, CA
556,Butternuts Beer and Ale,Garrattsville, NY
557,Sleeping Lady Brewing Company,Anchorage, AK
1 name city state
2 0 NorthGate Brewing Minneapolis MN
3 1 Against the Grain Brewery Louisville KY
4 2 Jack's Abby Craft Lagers Framingham MA
5 3 Mike Hess Brewing Company San Diego CA
6 4 Fort Point Beer Company San Francisco CA
7 5 COAST Brewing Company Charleston SC
8 6 Great Divide Brewing Company Denver CO
9 7 Tapistry Brewing Bridgman MI
10 8 Big Lake Brewing Holland MI
11 9 The Mitten Brewing Company Grand Rapids MI
12 10 Brewery Vivant Grand Rapids MI
13 11 Petoskey Brewing Petoskey MI
14 12 Blackrocks Brewery Marquette MI
15 13 Perrin Brewing Company Comstock Park MI
16 14 Witch's Hat Brewing Company South Lyon MI
17 15 Founders Brewing Company Grand Rapids MI
18 16 Flat 12 Bierwerks Indianapolis IN
19 17 Tin Man Brewing Company Evansville IN
20 18 Black Acre Brewing Co. Indianapolis IN
21 19 Brew Link Brewing Plainfield IN
22 20 Bare Hands Brewery Granger IN
23 21 Three Pints Brewing Martinsville IN
24 22 Four Fathers Brewing Valparaiso IN
25 23 Indiana City Brewing Indianapolis IN
26 24 Burn 'Em Brewing Michigan City IN
27 25 Sun King Brewing Company Indianapolis IN
28 26 Evil Czech Brewery Mishawaka IN
29 27 450 North Brewing Company Columbus IN
30 28 Taxman Brewing Company Bargersville IN
31 29 Cedar Creek Brewery Seven Points TX
32 30 SanTan Brewing Company Chandler AZ
33 31 Boulevard Brewing Company Kansas City MO
34 32 James Page Brewing Company Stevens Point WI
35 33 The Dudes' Brewing Company Torrance CA
36 34 Ballast Point Brewing Company San Diego CA
37 35 Anchor Brewing Company San Francisco CA
38 36 Figueroa Mountain Brewing Company Buellton CA
39 37 Avery Brewing Company Boulder CO
40 38 Twisted X Brewing Company Dripping Springs TX
41 39 Gonzo's BiggDogg Brewing Kalamazoo MI
42 40 Big Muddy Brewing Murphysboro IL
43 41 Lost Nation Brewing East Fairfield VT
44 42 Rising Tide Brewing Company Portland ME
45 43 Rivertowne Brewing Company Export PA
46 44 Revolution Brewing Company Chicago IL
47 45 Tallgrass Brewing Company Manhattan KS
48 46 Sixpoint Craft Ales Brooklyn NY
49 47 White Birch Brewing Hooksett NH
50 48 Firestone Walker Brewing Company Paso Robles CA
51 49 SweetWater Brewing Company Atlanta GA
52 50 Flying Mouse Brewery Troutville VA
53 51 Upslope Brewing Company Boulder CO
54 52 Pipeworks Brewing Company Chicago IL
55 53 Bent Brewstillery Roseville MN
56 54 Flesk Brewing Company Lombard IL
57 55 Pollyanna Brewing Company Lemont IL
58 56 BuckleDown Brewing Lyons IL
59 57 Destihl Brewery Bloomington IL
60 58 Summit Brewing Company St. Paul MN
61 59 Latitude 42 Brewing Company Portage MI
62 60 4 Hands Brewing Company Saint Louis MO
63 61 Surly Brewing Company Brooklyn Center MN
64 62 Against The Grain Brewery Louisville KY
65 63 Crazy Mountain Brewing Company Edwards CO
66 64 SlapShot Brewing Company Chicago IL
67 65 Mikerphone Brewing Chicago IL
68 66 Freetail Brewing Company San Antonio TX
69 67 3 Daughters Brewing St Petersburg FL
70 68 Red Shedman Farm Brewery and Hop... Mt. Airy MD
71 69 Appalachian Mountain Brewery Boone NC
72 70 Birdsong Brewing Company Charlotte NC
73 71 Union Craft Brewing Baltimore MD
74 72 Atwater Brewery Detroit MI
75 73 Ale Asylum Madison WI
76 74 Two Brothers Brewing Company Warrenville IL
77 75 Bent Paddle Brewing Company Duluth MN
78 76 Bell's Brewery Kalamazoo MI
79 77 Blue Owl Brewing Austin TX
80 78 Speakasy Ales & Lagers San Francisco CA
81 79 Black Tooth Brewing Company Sheridan WY
82 80 Hopworks Urban Brewery Portland OR
83 81 Epic Brewing Denver CO
84 82 New Belgium Brewing Company Fort Collins CO
85 83 Sierra Nevada Brewing Company Chico CA
86 84 Keweenaw Brewing Company Houghton MI
87 85 Brewery Terra Firma Traverse City MI
88 86 Grey Sail Brewing Company Westerly RI
89 87 Kirkwood Station Brewing Company Kirkwood MO
90 88 Goose Island Brewing Company Chicago IL
91 89 Broad Brook Brewing LLC East Windsor CT
92 90 The Lion Brewery Wilkes-Barre PA
93 91 Madtree Brewing Company Cincinnati OH
94 92 Jackie O's Pub & Brewery Athens OH
95 93 Rhinegeist Brewery Cincinnati OH
96 94 Warped Wing Brewing Company Dayton OH
97 95 Blackrocks Brewery Marquette MA
98 96 Catawba Valley Brewing Company Morganton NC
99 97 Tröegs Brewing Company Hershey PA
100 98 Mission Brewery San Diego CA
101 99 Christian Moerlein Brewing Company Cincinnati OH
102 100 West Sixth Brewing Lexington KY
103 101 Coastal Extreme Brewing Company Newport RI
104 102 King Street Brewing Company Anchorage AK
105 103 Beer Works Brewery Lowell MA
106 104 Lone Tree Brewing Company Lone Tree CO
107 105 Four String Brewing Company Columbus OH
108 106 Glabrous Brewing Company Pineland ME
109 107 Bonfire Brewing Company Eagle CO
110 108 Thomas Hooker Brewing Company Bloomfield CT
111 109 Woodstock Inn, Station & Brewery North Woodstock NH
112 110 Renegade Brewing Company Denver CO
113 111 Mother Earth Brew Company Vista CA
114 112 Black Market Brewing Company Temecula CA
115 113 Vault Brewing Company Yardley PA
116 114 Jailbreak Brewing Company Laurel MD
117 115 Smartmouth Brewing Company Norfolk VA
118 116 Base Camp Brewing Co. Portland OR
119 117 Alameda Brewing Portland OR
120 118 Southern Star Brewing Company Conroe TX
121 119 Steamworks Brewing Company Durango CO
122 120 Horny Goat Brew Pub Milwaukee WI
123 121 Cheboygan Brewing Company Cheboygan MI
124 122 Center of the Universe Brewing C... Ashland VA
125 123 Ipswich Ale Brewery Ipswich MA
126 124 Griffin Claw Brewing Company Birmingham MI
127 125 Karbach Brewing Company Houston TX
128 126 Uncle Billy's Brewery and Smokeh... Austin TX
129 127 Deep Ellum Brewing Company Dallas TX
130 128 Real Ale Brewing Company Blanco TX
131 129 Straub Brewery St Mary's PA
132 130 Shebeen Brewing Company Wolcott CT
133 131 Stevens Point Brewery Stevens Point WI
134 132 Weston Brewing Company Weston MO
135 133 Southern Prohibition Brewing Com... Hattiesburg MS
136 134 Minhas Craft Brewery Monroe WI
137 135 Pug Ryan's Brewery Dillon CO
138 136 Hops & Grains Brewing Company Austin TX
139 137 Sietsema Orchards and Cider Mill Ada MI
140 138 Summit Brewing Company St Paul MN
141 139 Core Brewing & Distilling Company Springdale AR
142 140 Independence Brewing Company Austin TX
143 141 Cigar City Brewing Company Tampa FL
144 142 Third Street Brewhouse Cold Spring MN
145 143 Narragansett Brewing Company Providence RI
146 144 Grimm Brothers Brewhouse Loveland CO
147 145 Cisco Brewers Nantucket MA
148 146 Angry Minnow Hayward WI
149 147 Platform Beer Company Cleveland OH
150 148 Odyssey Beerwerks Arvada CO
151 149 Lonerider Brewing Company Raleigh NC
152 150 Oakshire Brewing Eugene OR
153 151 Fort Pitt Brewing Company Latrobe PA
154 152 Tin Roof Brewing Company Baton Rouge LA
155 153 Three Creeks Brewing Sisters OR
156 154 2 Towns Ciderhouse Corvallis OR
157 155 Caldera Brewing Company Ashland OR
158 156 Greenbrier Valley Brewing Company Lewisburg WV
159 157 Phoenix Ale Brewery Phoenix AZ
160 158 Lumberyard Brewing Company Flagstaff AZ
161 159 Uinta Brewing Company Salt Lake City UT
162 160 Four Peaks Brewing Company Tempe AZ
163 161 Martin House Brewing Company Fort Worth TX
164 162 Right Brain Brewery Traverse City MI
165 163 Sly Fox Brewing Company Phoenixville PA
166 164 Round Guys Brewing Lansdale PA
167 165 Great Crescent Brewery Aurora IN
168 166 Oskar Blues Brewery Longmont CO
169 167 Boxcar Brewing Company West Chester PA
170 168 High Hops Brewery Windsor CO
171 169 Crooked Fence Brewing Company Garden City ID
172 170 Everybody's Brewing White Salmon WA
173 171 Anderson Valley Brewing Company Boonville CA
174 172 Fiddlehead Brewing Company Shelburne VT
175 173 Evil Twin Brewing Brooklyn NY
176 174 New Orleans Lager & Ale Brewing ... New Orleans LA
177 175 Spiteful Brewing Company Chicago IL
178 176 Rahr & Sons Brewing Company Fort Worth TX
179 177 18th Street Brewery Gary IN
180 178 Cambridge Brewing Company Cambridge MA
181 179 Carolina Brewery Pittsboro NC
182 180 Frog Level Brewing Company Waynesville NC
183 181 Wild Wolf Brewing Company Nellysford VA
184 182 COOP Ale Works Oklahoma City OK
185 183 Seventh Son Brewing Company Columbus OH
186 184 Oasis Texas Brewing Company Austin TX
187 185 Vander Mill Ciders Spring Lake MI
188 186 St. Julian Winery Paw Paw MI
189 187 Pedernales Brewing Company Fredericksburg TX
190 188 Mother's Brewing Springfield MO
191 189 Modern Monks Brewery Lincoln NE
192 190 Two Beers Brewing Company Seattle WA
193 191 Snake River Brewing Company Jackson WY
194 192 Capital Brewery Middleton WI
195 193 Anthem Brewing Company Oklahoma City OK
196 194 Goodlife Brewing Co. Bend OR
197 195 Breakside Brewery Portland OR
198 196 Goose Island Brewery Company Chicago IL
199 197 Burnside Brewing Co. Portland OR
200 198 Hop Valley Brewing Company Springfield OR
201 199 Worthy Brewing Company Bend OR
202 200 Occidental Brewing Company Portland OR
203 201 Fearless Brewing Company Estacada OR
204 202 Upland Brewing Company Bloomington IN
205 203 Mehana Brewing Co. Hilo HI
206 204 Hawai'i Nui Brewing Co. Hilo HI
207 205 People's Brewing Company Lafayette IN
208 206 Fort George Brewery Astoria OR
209 207 Branchline Brewing Company San Antonio TX
210 208 Kalona Brewing Company Kalona IA
211 209 Modern Times Beer San Diego CA
212 210 Temperance Beer Company Evanston IL
213 211 Wisconsin Brewing Company Verona WI
214 212 Crow Peak Brewing Company Spearfish SD
215 213 Grapevine Craft Brewery Farmers Branch TX
216 214 Buffalo Bayou Brewing Company Houston TX
217 215 Texian Brewing Co. Richmond TX
218 216 Orpheus Brewing Atlanta GA
219 217 Forgotten Boardwalk Cherry Hill NJ
220 218 Laughing Dog Brewing Company Ponderay ID
221 219 Bozeman Brewing Company Bozeman MT
222 220 Big Choice Brewing Broomfield CO
223 221 Big Storm Brewing Company Odessa FL
224 222 Carton Brewing Company Atlantic Highlands NJ
225 223 Midnight Sun Brewing Company Anchorage AK
226 224 Fat Head's Brewery Middleburg Heights OH
227 225 Refuge Brewery Temecula CA
228 226 Chatham Brewing Chatham NY
229 227 DC Brau Brewing Company Washington DC
230 228 Geneva Lake Brewing Company Lake Geneva WI
231 229 Rochester Mills Brewing Company Rochester MI
232 230 Cape Ann Brewing Company Gloucester MA
233 231 Borderlands Brewing Company Tucson AZ
234 232 College Street Brewhouse and Pub Lake Havasu City AZ
235 233 Joseph James Brewing Company Henderson NV
236 234 Harpoon Brewery Boston MA
237 235 Back East Brewing Company Bloomfield CT
238 236 Champion Brewing Company Charlottesville VA
239 237 Devil's Backbone Brewing Company Lexington VA
240 238 Newburgh Brewing Company Newburgh NY
241 239 Wiseacre Brewing Company Memphis TN
242 240 Golden Road Brewing Los Angeles CA
243 241 New Republic Brewing Company College Station TX
244 242 Infamous Brewing Company Austin TX
245 243 Two Henrys Brewing Company Plant City FL
246 244 Lift Bridge Brewing Company Stillwater MN
247 245 Lucky Town Brewing Company Jackson MS
248 246 Quest Brewing Company Greenville SC
249 247 Creature Comforts Athens GA
250 248 Half Full Brewery Stamford CT
251 249 Southampton Publick House Southampton NY
252 250 Chapman's Brewing Angola IN
253 251 Barrio Brewing Company Tucson AZ
254 252 Santa Cruz Mountain Brewing Santa Cruz CA
255 253 Frankenmuth Brewery Frankenmuth MI
256 254 Meckley's Cidery Somerset Center MI
257 255 Stillwater Artisanal Ales Baltimore MD
258 256 Finch's Beer Company Chicago IL
259 257 South Austin Brewery South Austin TX
260 258 Bauhaus Brew Labs Minneapolis MN
261 259 Ozark Beer Company Rogers AR
262 260 Mountain Town Brewing Company Mount Pleasant MI
263 261 Otter Creek Brewing Waterbury VT
264 262 The Brewer's Art Baltimore MD
265 263 Denver Beer Company Denver CO
266 264 Ska Brewing Company Durango CO
267 265 Tractor Brewing Company Albuquerque NM
268 266 Peak Organic Brewing Company Portland ME
269 267 Cape Cod Beer Hyannis MA
270 268 Long Trail Brewing Company Bridgewater Corners VT
271 269 Great Raft Brewing Company Shreveport LA
272 270 Alaskan Brewing Company Juneau AK
273 271 Notch Brewing Company Ipswich MA
274 272 The Alchemist Waterbury VT
275 273 Three Notch'd Brewing Company Charlottesville VA
276 274 Portside Brewery Cleveland OH
277 275 Otter Creek Brewing Middlebury VT
278 276 Montauk Brewing Company Montauk NY
279 277 Indeed Brewing Company Minneapolis MN
280 278 Berkshire Brewing Company South Deerfield MA
281 279 Foolproof Brewing Company Pawtucket RI
282 280 Headlands Brewing Company Mill Valley CA
283 281 Bolero Snort Brewery Ridgefield Park NJ
284 282 Thunderhead Brewing Company Kearney NE
285 283 Defiance Brewing Company Hays KS
286 284 Milwaukee Brewing Company Milwaukee WI
287 285 Catawba Island Brewing Port Clinton OH
288 286 Back Forty Beer Company Gadsden AL
289 287 Four Corners Brewing Company Dallas TX
290 288 Saint Archer Brewery San Diego CA
291 289 Rogue Ales Newport OR
292 290 Hale's Ales Seattle WA
293 291 Tommyknocker Brewery Idaho Springs CO
294 292 Baxter Brewing Company Lewiston ME
295 293 Northampton Brewery Northamtpon MA
296 294 Black Shirt Brewing Company Denver CO
297 295 Wachusett Brewing Company Westminster MA
298 296 Widmer Brothers Brewing Company Portland OR
299 297 Hop Farm Brewing Company Pittsburgh PA
300 298 Liquid Hero Brewery York PA
301 299 Matt Brewing Company Utica NY
302 300 Boston Beer Company Boston MA
303 301 Old Forge Brewing Company Danville PA
304 302 Utah Brewers Cooperative Salt Lake City UT
305 303 Magic Hat Brewing Company South Burlington VT
306 304 Blue Hills Brewery Canton MA
307 305 Night Shift Brewing Everett MA
308 306 Beach Brewing Company Virginia Beach VA
309 307 Payette Brewing Company Garden City ID
310 308 Brew Bus Brewing Tampa FL
311 309 Sockeye Brewing Company Boise ID
312 310 Pine Street Brewery San Francisco CA
313 311 Dirty Bucket Brewing Company Woodinville WA
314 312 Jackalope Brewing Company Nashville TN
315 313 Slanted Rock Brewing Company Meridian ID
316 314 Piney River Brewing Company Bucryus MO
317 315 Cutters Brewing Company Avon IN
318 316 Iron Hill Brewery & Restaurant Wilmington DE
319 317 Marshall Wharf Brewing Company Belfast ME
320 318 Banner Beer Company Williamsburg MA
321 319 Dick's Brewing Company Centralia WA
322 320 Claremont Craft Ales Claremont CA
323 321 Rivertown Brewing Company Lockland OH
324 322 Voodoo Brewery Meadville PA
325 323 D.L. Geary Brewing Company Portland ME
326 324 Pisgah Brewing Company Black Mountain NC
327 325 Neshaminy Creek Brewing Company Croydon PA
328 326 Morgan Street Brewery Saint Louis MO
329 327 Half Acre Beer Company Chicago IL
330 328 The Just Beer Project Burlington VT
331 329 The Bronx Brewery Bronx NY
332 330 Dead Armadillo Craft Brewing Tulsa OK
333 331 Catawba Brewing Company Morganton NC
334 332 La Cumbre Brewing Company Albuquerque NM
335 333 David's Ale Works Diamond Springs CA
336 334 The Traveler Beer Company Burlington VT
337 335 Fargo Brewing Company Fargo ND
338 336 Big Sky Brewing Company Missoula MT
339 337 Nebraska Brewing Company Papillion NE
340 338 Uncle John's Fruit House Winery St. John's MI
341 339 Wormtown Brewery Worcester MA
342 340 Due South Brewing Company Boynton Beach FL
343 341 Palisade Brewing Company Palisade CO
344 342 KelSo Beer Company Brooklyn NY
345 343 Hardywood Park Craft Brewery Richmond VA
346 344 Wolf Hills Brewing Company Abingdon VA
347 345 Lavery Brewing Company Erie PA
348 346 Manzanita Brewing Company Santee CA
349 347 Fullsteam Brewery Durham NC
350 348 Four Horsemen Brewing Company South Bend IN
351 349 Hinterland Brewery Green Bay WI
352 350 Central Coast Brewing Company San Luis Obispo CA
353 351 Westfield River Brewing Company Westfield MA
354 352 Elevator Brewing Company Columbus OH
355 353 Aslan Brewing Company Bellingham WA
356 354 Kulshan Brewery Bellingham WA
357 355 Pikes Peak Brewing Company Monument CO
358 356 Manayunk Brewing Company Philadelphia PA
359 357 Buckeye Brewing Cleveland OH
360 358 Daredevil Brewing Company Shelbyville IN
361 359 NoDa Brewing Company Charlotte NC
362 360 Aviator Brewing Company Fuquay-Varina NC
363 361 Wild Onion Brewing Company Lake Barrington IL
364 362 Hilliard's Beer Seattle WA
365 363 Mikkeller Pottstown PA
366 364 Bohemian Brewery Midvale UT
367 365 Great River Brewery Davenport IA
368 366 Mustang Brewing Company Mustang OK
369 367 Airways Brewing Company Kent WA
370 368 21st Amendment Brewery San Francisco CA
371 369 Eddyline Brewery & Restaurant Buena Vista CO
372 370 Pizza Port Brewing Company Carlsbad CA
373 371 Sly Fox Brewing Company Pottstown PA
374 372 Spring House Brewing Company Conestoga PA
375 373 7venth Sun Dunedin FL
376 374 Astoria Brewing Company Astoria OR
377 375 Maui Brewing Company Lahaina HI
378 376 RoughTail Brewing Company Midwest City OK
379 377 Lucette Brewing Company Menominee WI
380 378 Bold City Brewery Jacksonville FL
381 379 Grey Sail Brewing of Rhode Island Westerly RI
382 380 Blue Blood Brewing Company Lincoln NE
383 381 Swashbuckler Brewing Company Manheim PA
384 382 Blue Mountain Brewery Afton VA
385 383 Starr Hill Brewery Crozet VA
386 384 Westbrook Brewing Company Mt. Pleasant SC
387 385 Shipyard Brewing Company Portland ME
388 386 Revolution Brewing Paonia CO
389 387 Natian Brewery Portland OR
390 388 Alltech's Lexington Brewing Company Lexington KY
391 389 Oskar Blues Brewery (North Carol... Brevard NC
392 390 Orlison Brewing Company Airway Heights WA
393 391 Breckenridge Brewery Denver CO
394 392 Santa Fe Brewing Company Santa Fe NM
395 393 Miami Brewing Company Miami FL
396 394 Schilling & Company Seattle WA
397 395 Hops & Grain Brewery Austin TX
398 396 White Flame Brewing Company Hudsonville MI
399 397 Ruhstaller Beer Company Sacramento CA
400 398 Saugatuck Brewing Company Douglas MI
401 399 Moab Brewery Moab UT
402 400 Macon Beer Company Macon GA
403 401 Amnesia Brewing Company Washougal WA
404 402 Wolverine State Brewing Company Ann Arbor MI
405 403 Red Tank Cider Company Bend OR
406 404 Cascadia Ciderworks United Portland OR
407 405 Fate Brewing Company Boulder CO
408 406 Lazy Monk Brewing Eau Claire WI
409 407 Bitter Root Brewing Hamilton MT
410 408 10 Barrel Brewing Company Bend OR
411 409 Tamarack Brewing Company Lakeside MT
412 410 New England Brewing Company Woodbridge CT
413 411 Seattle Cider Company Seattle WA
414 412 Straight to Ale Huntsville AL
415 413 Austin Beerworks Austin TX
416 414 Blue Mountain Brewery Arrington VA
417 415 Coastal Empire Beer Company Savannah GA
418 416 Jack's Hard Cider (Hauser Estate... Biglerville PA
419 417 Boulder Beer Company Boulder CO
420 418 Coalition Brewing Company Portland OR
421 419 Sanitas Brewing Company Boulder CO
422 420 Gore Range Brewery Edwards CO
423 421 Redstone Meadery Boulder CO
424 422 Blue Dog Mead Eugene OR
425 423 Hess Brewing Company San Diego CA
426 424 Wynkoop Brewing Company Denver CO
427 425 Ciderboys Stevens Point WI
428 426 Armadillo Ale Works Denton TX
429 427 Roanoke Railhouse Brewery Roanoke VA
430 428 Schlafly Brewing Company Saint Louis MO
431 429 Asher Brewing Company Boulder CO
432 430 Lost Rhino Brewing Company Ashburn VA
433 431 North Country Brewing Company Slippery Rock PA
434 432 Seabright Brewery Santa Cruz CA
435 433 French Broad Brewery Asheville NC
436 434 Angry Orchard Cider Company Cincinnati OH
437 435 Two Roads Brewing Company Stratford CT
438 436 Southern Oregon Brewing Company Medford OR
439 437 Brooklyn Brewery Brooklyn NY
440 438 The Right Brain Brewery Traverse City MI
441 439 Kona Brewing Company Kona HI
442 440 MillKing It Productions Royal Oak MI
443 441 Pateros Creek Brewing Company Fort Collins CO
444 442 O'Fallon Brewery O'Fallon MO
445 443 Marble Brewery Albuquerque NM
446 444 Big Wood Brewery Vadnais Heights MN
447 445 Howard Brewing Company Lenoir NC
448 446 Downeast Cider House Leominster MA
449 447 Swamp Head Brewery Gainesville FL
450 448 Mavericks Beer Company Half Moon Bay CA
451 449 TailGate Beer San Diego CA
452 450 Northwest Brewing Company Pacific WA
453 451 Dad & Dude's Breweria Aurora CO
454 452 Centennial Beer Company Edwards CO
455 453 Denali Brewing Company Talkeetna AK
456 454 Deschutes Brewery Bend OR
457 455 Sunken City Brewing Company Hardy VA
458 456 Lucette Brewing Company Menominie WI
459 457 The Black Tooth Brewing Company Sheridan WY
460 458 Kenai River Brewing Company Soldotna AK
461 459 River North Brewery Denver CO
462 460 Fremont Brewing Company Seattle WA
463 461 Armstrong Brewing Company South San Francisco CA
464 462 AC Golden Brewing Company Golden CO
465 463 Big Bend Brewing Company Alpine TX
466 464 Good Life Brewing Company Bend OR
467 465 Engine 15 Brewing Jacksonville Beach FL
468 466 Green Room Brewing Jacksonville FL
469 467 Brindle Dog Brewing Company Tampa Bay FL
470 468 Peace Tree Brewing Company Knoxville IA
471 469 Terrapin Brewing Company Athens GA
472 470 Pete's Brewing Company San Antonio TX
473 471 Okoboji Brewing Company Spirit Lake IA
474 472 Crystal Springs Brewing Company Boulder CO
475 473 Engine House 9 Tacoma WA
476 474 Tonka Beer Company Minnetonka MN
477 475 Red Hare Brewing Company Marietta GA
478 476 Hangar 24 Craft Brewery Redlands CA
479 477 Big Elm Brewing Sheffield MA
480 478 Good People Brewing Company Birmingham AL
481 479 Heavy Seas Beer Halethorpe MD
482 480 Telluride Brewing Company Telluride CO
483 481 7 Seas Brewing Company Gig Harbor WA
484 482 Confluence Brewing Company Des Moines IA
485 483 Bale Breaker Brewing Company Yakima WA
486 484 The Manhattan Brewing Company New York NY
487 485 MacTarnahans Brewing Company Portland OR
488 486 Stillmank Beer Company Green Bay WI
489 487 Redhook Brewery Woodinville WA
490 488 Dock Street Brewery Philadelphia PA
491 489 Blue Point Brewing Company Patchogue NY
492 490 Tampa Bay Brewing Company Tampa FL
493 491 Devil's Canyon Brewery Belmont CA
494 492 Stone Coast Brewing Company Portland ME
495 493 Broken Tooth Brewing Company Anchorage AK
496 494 Seven Brides Brewery Silverton OR
497 495 Newburyport Brewing Company Newburyport MA
498 496 Dry Dock Brewing Company Aurora CO
499 497 Cans Bar and Canteen Charlotte NC
500 498 Sprecher Brewing Company Glendale WI
501 499 Wildwood Brewing Company Stevensville MT
502 500 High Noon Saloon And Brewery Leavenworth KS
503 501 Woodchuck Hard Cider Middlebury VT
504 502 Sea Dog Brewing Company Portland ME
505 503 Oskar Blues Brewery Lyons CO
506 504 Carolina Beer & Beverage Mooresville NC
507 505 Krebs Brewing Company (Pete's Pl... Krebs OK
508 506 Warbird Brewing Company Fort Wayne IN
509 507 Mudshark Brewing Company Lake Havasu City AZ
510 508 Spilker Ales Cortland NE
511 509 Wingman Brewers Tacoma WA
512 510 Kettle House Brewing Company Missoula MT
513 511 Sherwood Forest Brewers Marlborough MA
514 512 Cottrell Brewing Pawcatuck CT
515 513 Arctic Craft Brewery Colorado Springs CO
516 514 Monkey Paw Pub & Brewery San Diego CA
517 515 Crabtree Brewing Company Greeley CO
518 516 Emerald City Beer Company Seattle WA
519 517 Butcher's Brewing Carlsbad CA
520 518 New South Brewing Company Myrtle Beach SC
521 519 Big River Brewing Company Chattanooga TN
522 520 Twisted Pine Brewing Company Boulder CO
523 521 Flying Dog Brewery Frederick MD
524 522 Uncommon Brewers Santa Cruz CA
525 523 Aspen Brewing Company Aspen CO
526 524 Triangle Brewing Company Durham NC
527 525 Bomb Beer Company New York NY
528 526 Churchkey Can Company Seattle WA
529 527 Intuition Ale Works Jacksonville FL
530 528 Asheville Brewing Company Asheville NC
531 529 Northwoods Brewpub Eau Claire WI
532 530 Buckbean Brewing Company Reno NV
533 531 Dolores River Brewery Dolores CO
534 532 Flat Rock Brewing Company Smithton PA
535 533 Abita Brewing Company Abita Springs LA
536 534 Mammoth Brewing Company Mammoth Lakes CA
537 535 Harvest Moon Brewing Company Belt MT
538 536 Grand Canyon Brewing Company Williams AZ
539 537 Lewis and Clark Brewing Company Helena MT
540 538 Dundee Brewing Company Rochester NY
541 539 Twin Lakes Brewing Company Greenville DE
542 540 Mother Earth Brewing Company Kinston NC
543 541 Arcadia Brewing Company Battle Creek MI
544 542 Angry Minnow Brewing Company Hayward WI
545 543 Great Northern Brewing Company Whitefish MT
546 544 Pyramid Breweries Seattle WA
547 545 Lancaster Brewing Company Lancaster PA
548 546 Upstate Brewing Company Elmira NY
549 547 Moat Mountain Smoke House & Brew... North Conway NH
550 548 Prescott Brewing Company Prescott AZ
551 549 Mogollon Brewing Company Flagstaff AZ
552 550 Wind River Brewing Company Pinedale WY
553 551 Silverton Brewery Silverton CO
554 552 Mickey Finn's Brewery Libertyville IL
555 553 Covington Brewhouse Covington LA
556 554 Dave's Brewfarm Wilson WI
557 555 Ukiah Brewing Company Ukiah CA
558 556 Butternuts Beer and Ale Garrattsville NY
559 557 Sleeping Lady Brewing Company Anchorage AK

162686
misc/raw-data/breweries.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,578 @@
[
{
"text": "100 Acre Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/100-acre-brewing-company/"
},
{
"text": "All My Friends Beer Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/all-my-friends-beer-co/"
},
{
"text": "All or Nothing Brewhouse",
"href": "https://ontariocraftbrewers.com/brewery-profile/all-or-nothing-brewhouse/"
},
{
"text": "Anderson Craft Ales",
"href": "https://ontariocraftbrewers.com/brewery-profile/anderson-craft-ales/"
},
{
"text": "Badlands Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/badlands-brewing-company/"
},
{
"text": "Bancroft Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/bancroft-brewing-co/"
},
{
"text": "Banded Goose Brewing Comany",
"href": "https://ontariocraftbrewers.com/brewery-profile/banded-goose-brewing-comany/"
},
{
"text": "Beaus Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/beaus-brewery/"
},
{
"text": "BeerLab! London",
"href": "https://ontariocraftbrewers.com/brewery-profile/beerlab-london/"
},
{
"text": "Bellwoods Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/bellwoods-brewery/"
},
{
"text": "Bench Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/bench-brewing/"
},
{
"text": "Beyond The Pale Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/beyond-the-pale-brewing-co/"
},
{
"text": "Bicycle Craft Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/bicycle-craft-brewery/"
},
{
"text": "Big Rig Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/big-rig-brewery/"
},
{
"text": "Big Rock Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/big-rock-brewery/"
},
{
"text": "Black Gold Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/black-gold-brewery/"
},
{
"text": "Black Oak Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/black-oak-brewing-co/"
},
{
"text": "Block 3 Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/block-3-brewing-co/"
},
{
"text": "Blood Brothers Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/blood-brothers-brewing/"
},
{
"text": "Bobcaygeon Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/bobcaygeon-brewing-company/"
},
{
"text": "Boshkung Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/boshkung-brewing-co/"
},
{
"text": "Brauwerk Hoffman Rockland",
"href": "https://ontariocraftbrewers.com/brewery-profile/brauwerk-hoffman-rockland/"
},
{
"text": "Bridge Masters Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/bridge-masters-brewing-co/"
},
{
"text": "Broadhead Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/broadhead-brewery/"
},
{
"text": "Broken Rail Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/broken-rail-brewing/"
},
{
"text": "Burdock Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/burdock-brewery/"
},
{
"text": "Cest What Durham Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/cest-what-durham-brewing-company/"
},
{
"text": "Calabogie Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/calabogie-brewing/"
},
{
"text": "Camerons Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/camerons-brewing-company/"
},
{
"text": "Canvas Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/canvas-brewing-co/"
},
{
"text": "Caps Off Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/caps-off-brewing/"
},
{
"text": "Century Barn Brewing & Beverage Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/century-barn-brewing-and-beverage-company/"
},
{
"text": "Chronicle Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/chronicle-brewing-company/"
},
{
"text": "Clifford Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/clifford-brewing-co/"
},
{
"text": "Cold Bear Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/cold-bear-brewing-co/"
},
{
"text": "Collective Arts Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/collective-arts-brewing-ltd/"
},
{
"text": "Common Good Beer Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/common-good-beer-co/"
},
{
"text": "Couchiching Craft Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/couchiching-craft-brewing-company/"
},
{
"text": "Cowbell Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/cowbell-brewing-co/"
},
{
"text": "Cured Craft Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/cured-craft-brewing-co/"
},
{
"text": "Daft Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/daft-brewing-company/"
},
{
"text": "Dog House Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/dog-house-brewing-company/"
},
{
"text": "Dominion City Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/dominion-city-brewing-co/"
},
{
"text": "Eastbound Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/eastbound-brewing-co/"
},
{
"text": "Equals Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/equals-brewing-company/"
},
{
"text": "Fairweather Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/fairweather-brewing-company/"
},
{
"text": "Farm League Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/farm-league-brewing/"
},
{
"text": "Fixed Gear Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/fixed-gear-brewing-co/"
},
{
"text": "Flying Monkeys Craft Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/flying-monkeys-craft-brewery/"
},
{
"text": "Focal Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/focal-brewing-co/"
},
{
"text": "Foundry Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/foundry-brewing/"
},
{
"text": "Four Fathers Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/four-fathers-brewing-co/"
},
{
"text": "Frank Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/frank-brewing-co/"
},
{
"text": "Freddys",
"href": "https://ontariocraftbrewers.com/brewery-profile/freddys/"
},
{
"text": "Full Beard Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/1068-2/"
},
{
"text": "Furnace Room Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/furnace-room-brewery/"
},
{
"text": "Gateway City Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/gateway-city-brewery/"
},
{
"text": "Glasstown Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/glasstown-brewing-company/"
},
{
"text": "Godspeed Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/godspeed-brewery/"
},
{
"text": "Goldenfield Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/goldenfield-brewing/"
},
{
"text": "Grand River Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/grand-river-brewery/"
},
{
"text": "Granite Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/granite-brewery/"
},
{
"text": "Great Lakes Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/great-lakes-brewery/"
},
{
"text": "Haliburton Highlands Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/haliburton-highlands-brewery/"
},
{
"text": "Imperial City Brew House",
"href": "https://ontariocraftbrewers.com/brewery-profile/imperial-city-brew-house/"
},
{
"text": "Indie Ale House",
"href": "https://ontariocraftbrewers.com/brewery-profile/indie-ale-house/"
},
{
"text": "Jobsite Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/jobsite-brewing-co/"
},
{
"text": "Kichesippi Beer Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/kichesippi-beer-co/"
},
{
"text": "Kick and Push Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/kick-and-push-brewing-company/"
},
{
"text": "Lake of Bays Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/lake-of-bays-brewing-co/"
},
{
"text": "Lake Of The Woods Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/lake-of-the-woods-brewing-company/"
},
{
"text": "Left Field Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/left-field-brewery/"
},
{
"text": "Lightcaster Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/lightcaster-brewery/"
},
{
"text": "MacKinnon Brothers Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/mackinnon-brothers-brewing-co/"
},
{
"text": "Macleans Ales Inc.",
"href": "https://ontariocraftbrewers.com/brewery-profile/macleans-ales-inc/"
},
{
"text": "Magnotta Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/magnotta-brewery/"
},
{
"text": "Market Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/market-brewing/"
},
{
"text": "Mascot Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/mascot-brewery/"
},
{
"text": "Matron Fine Beer",
"href": "https://ontariocraftbrewers.com/brewery-profile/matron-fine-beer/"
},
{
"text": "Meyers Creek Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/meyers-creek-brewing-company/"
},
{
"text": "Midtown Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/midtown-brewing-co/"
},
{
"text": "Miski Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/miski-brewing/"
},
{
"text": "Muddy York Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/muddy-york-brewing-co/"
},
{
"text": "Muskoka Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/muskoka-brewery/"
},
{
"text": "Natterjack Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/natterjack-brewing-company/"
},
{
"text": "Newark Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/newark-brewing-co/"
},
{
"text": "Niagara Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/niagara-brewing-company/"
},
{
"text": "Niagara College Teaching Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/niagara-college-teaching-brewery/"
},
{
"text": "Niagara Oast House Brewers",
"href": "https://ontariocraftbrewers.com/brewery-profile/niagara-oast-house-brewers/"
},
{
"text": "Nickel Brook Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/nickel-brook-brewing-co/"
},
{
"text": "Northern Superior Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/northern-superior-brewing-co/"
},
{
"text": "Old Credit Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/old-credit-brewing-co/"
},
{
"text": "Old Flame Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/1239-2/"
},
{
"text": "Orléans Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/orleans-brewing-co/"
},
{
"text": "Overflow Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/overflow-brewing-co/"
},
{
"text": "Parsons Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/parsons-brewing-company/"
},
{
"text": "Perth Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/perth-brewery/"
},
{
"text": "Prince Eddys Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/prince-eddys-brewing-company/"
},
{
"text": "Quayles Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/quayles-brewery/"
},
{
"text": "Quetico Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/quetico-brewing-company/"
},
{
"text": "Railway City Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/railway-city-brewing-co/"
},
{
"text": "Ramblin Road Brewery Farm",
"href": "https://ontariocraftbrewers.com/brewery-profile/ramblin-road-brewery-farm/"
},
{
"text": "Red Barn Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/red-barn-brewing-company/"
},
{
"text": "Refined Fool Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/refined-fool-brewing-co/"
},
{
"text": "Rouge River Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/rouge-river-brewing/"
},
{
"text": "Royal City Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/royal-city-brewing-co/"
},
{
"text": "Sassy Britches Brewing Co Ltd",
"href": "https://ontariocraftbrewers.com/brewery-profile/sassy-britches-brewing-co-ltd/"
},
{
"text": "Sawdust City Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/sawdust-city-brewing/"
},
{
"text": "Shawn & Ed Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/shawn-ed-brewing-co/"
},
{
"text": "Silversmith Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/silversmith-brewing-company/"
},
{
"text": "Slake Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/slake-brewing/"
},
{
"text": "Sleeping Giant Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/sleeping-giant-brewing-co/"
},
{
"text": "Something in the Water Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/something-in-the-water-brewing-co/"
},
{
"text": "Sonnen Hill Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/sonnen-hill-brewing/"
},
{
"text": "Sons of Kent Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/sons-of-kent-brewing-company/"
},
{
"text": "Spark Beer",
"href": "https://ontariocraftbrewers.com/brewery-profile/spark-beer/"
},
{
"text": "Split Rail Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/split-rail-brewing-company/"
},
{
"text": "Stack Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/stack-brewing/"
},
{
"text": "Steam Whistle Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/steam-whistle-brewing/"
},
{
"text": "Steel Wheel Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/steel-wheel-brewery/"
},
{
"text": "Stonehooker Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/stonehooker-brewing-company/"
},
{
"text": "Stonepicker Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/stonepicker-brewery/"
},
{
"text": "Stray Dog Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/stray-dog-brewing-company/"
},
{
"text": "The Exchange Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/the-exchange-brewery/"
},
{
"text": "The Grove Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/the-grove-brewing-company/"
},
{
"text": "The Second Wedge Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/the-second-wedge-brewing-co/"
},
{
"text": "Thornbury Village Craft Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/thornbury-village-craft-brewery/"
},
{
"text": "Three Sheets Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/three-sheets-brewing/"
},
{
"text": "Tooth and Nail Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/tooth-and-nail-brewing-company/"
},
{
"text": "Torched Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/torched-brewing/"
},
{
"text": "Town Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/town-brewery/"
},
{
"text": "Trestle Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/trestle-brewing-company/"
},
{
"text": "True History Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/true-history-brewing/"
},
{
"text": "Upper Thames Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/upper-thames-brewing-co/"
},
{
"text": "Vimy Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/vimy-brewing-company/"
},
{
"text": "Walkerville Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/walkerville-brewery/"
},
{
"text": "Wave Maker Craft Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/wave-maker-craft-brewery/"
},
{
"text": "Wellington Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/wellington-brewery/"
},
{
"text": "Whiprsnapr Brewing Co.",
"href": "https://ontariocraftbrewers.com/brewery-profile/whiprsnapr-brewing-co/"
},
{
"text": "Whiskeyjack Beer Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/whiskeyjack-beer-company/"
},
{
"text": "Whitewater Brewing Company",
"href": "https://ontariocraftbrewers.com/brewery-profile/whitewater-brewing-company/"
},
{
"text": "Willibald Farm Distillery & Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/willibald-farm-distillery-brewery/"
},
{
"text": "Windmill Brewery",
"href": "https://ontariocraftbrewers.com/brewery-profile/windmill-brewery/"
},
{
"text": "Wishbone Brewing",
"href": "https://ontariocraftbrewers.com/brewery-profile/wishbone-brewing/"
}
]