mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Merge remote-tracking branch 'dotnet/main' into integrate-dotnet-backend-repo
This commit is contained in:
13
.config/dotnet-tools.json
Normal file
13
.config/dotnet-tools.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"csharpier": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"commands": [
|
||||||
|
"csharpier"
|
||||||
|
],
|
||||||
|
"rollForward": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
.csharpierrc.json
Normal file
10
.csharpierrc.json
Normal 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
|
||||||
|
}
|
||||||
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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
434
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
22
API/API.Core/API.Core.csproj
Normal file
22
API/API.Core/API.Core.csproj
Normal 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>
|
||||||
16
API/API.Core/Controllers/NotFoundController.cs
Normal file
16
API/API.Core/Controllers/NotFoundController.cs
Normal 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." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
API/API.Core/Controllers/UserController.cs
Normal file
26
API/API.Core/Controllers/UserController.cs
Normal 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
25
API/API.Core/Program.cs
Normal 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();
|
||||||
23
API/API.Core/Properties/launchSettings.json
Normal file
23
API/API.Core/Properties/launchSettings.json
Normal 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
64
API/API.Core/WebAPI.http
Normal 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}}
|
||||||
8
API/API.Core/appsettings.Development.json
Normal file
8
API/API.Core/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
API/API.Core/appsettings.json
Normal file
9
API/API.Core/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
17
Database/Database.Core/Database.Core.csproj
Normal file
17
Database/Database.Core/Database.Core.csproj
Normal 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>
|
||||||
32
Database/Database.Core/Program.cs
Normal file
32
Database/Database.Core/Program.cs
Normal 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;
|
||||||
552
Database/Database.Core/scripts/01-schema/schema.sql
Normal file
552
Database/Database.Core/scripts/01-schema/schema.sql
Normal 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)
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
22
Database/Database.Seed/Database.Seed.csproj
Normal file
22
Database/Database.Seed/Database.Seed.csproj
Normal 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>
|
||||||
9
Database/Database.Seed/ISeeder.cs
Normal file
9
Database/Database.Seed/ISeeder.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace DBSeed
|
||||||
|
{
|
||||||
|
internal interface ISeeder
|
||||||
|
{
|
||||||
|
Task SeedAsync(SqlConnection connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
328
Database/Database.Seed/LocationSeeder.cs
Normal file
328
Database/Database.Seed/LocationSeeder.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Database/Database.Seed/Program.cs
Normal file
40
Database/Database.Seed/Program.cs
Normal 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;
|
||||||
|
}
|
||||||
274
Database/Database.Seed/UserSeeder.cs
Normal file
274
Database/Database.Seed/UserSeeder.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Repository/Repository.Core/Entities/UserAccount.cs
Normal file
14
Repository/Repository.Core/Entities/UserAccount.cs
Normal 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; }
|
||||||
|
}
|
||||||
11
Repository/Repository.Core/Entities/UserCredential.cs
Normal file
11
Repository/Repository.Core/Entities/UserCredential.cs
Normal 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; }
|
||||||
|
}
|
||||||
9
Repository/Repository.Core/Entities/UserVerification.cs
Normal file
9
Repository/Repository.Core/Entities/UserVerification.cs
Normal 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; }
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Repository/Repository.Core/Repositories/Repository.cs
Normal file
24
Repository/Repository.Core/Repositories/Repository.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Repository/Repository.Core/Repositories/UserAccountRepository.cs
Normal file
134
Repository/Repository.Core/Repositories/UserAccountRepository.cs
Normal 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"]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Repository/Repository.Core/Repository.Core.csproj
Normal file
17
Repository/Repository.Core/Repository.Core.csproj
Normal 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>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Repository/Repository.Core/Sql/ISqlConnectionFactory.cs
Normal file
9
Repository/Repository.Core/Sql/ISqlConnectionFactory.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Microsoft.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace DataAccessLayer.Sql
|
||||||
|
{
|
||||||
|
public interface ISqlConnectionFactory
|
||||||
|
{
|
||||||
|
SqlConnection CreateConnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Repository/Repository.Tests/Repository.Tests.csproj
Normal file
24
Repository/Repository.Tests/Repository.Tests.csproj
Normal 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>
|
||||||
280
Repository/Repository.Tests/UserAccountRepositoryTests.cs
Normal file
280
Repository/Repository.Tests/UserAccountRepositoryTests.cs
Normal 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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Service/Service.Core/Service.Core.csproj
Normal file
12
Service/Service.Core/Service.Core.csproj
Normal 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>
|
||||||
10
Service/Service.Core/Services/IUserService.cs
Normal file
10
Service/Service.Core/Services/IUserService.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Service/Service.Core/Services/UserService.cs
Normal file
18
Service/Service.Core/Services/UserService.cs
Normal 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
16
biergarten.slnx
Normal 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
29
docker-compose.yml
Normal 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
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
559
misc/raw-data/breweries.csv
Normal 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
|
||||||
|
162686
misc/raw-data/breweries.json
Normal file
162686
misc/raw-data/breweries.json
Normal file
File diff suppressed because it is too large
Load Diff
578
misc/raw-data/ontariobreweries.json
Normal file
578
misc/raw-data/ontariobreweries.json
Normal 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": "Beau’s 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": "C’est 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": "Cameron’s 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": "Freddy’s",
|
||||||
|
"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": "Maclean’s 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 Eddy’s Brewing Company",
|
||||||
|
"href": "https://ontariocraftbrewers.com/brewery-profile/prince-eddys-brewing-company/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Quayle’s 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/"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user