From 15ffe475c7363aeb59044fb7d22c34ed6e14bc2f Mon Sep 17 00:00:00 2001 From: dan Date: Tue, 16 Jul 2019 21:58:35 -0600 Subject: [PATCH] Initial Commit --- .gitignore | 275 +++++++++++++++ OFBButte.Api/Controllers/ValuesController.cs | 45 +++ OFBButte.Api/OFBButte.Api.csproj | 20 ++ OFBButte.Api/Program.cs | 24 ++ OFBButte.Api/Properties/launchSettings.json | 30 ++ OFBButte.Api/Startup.cs | 61 ++++ OFBButte.Api/appsettings.Development.json | 15 + OFBButte.Api/appsettings.json | 16 + OFBButte.Application/Base/AuthorizeCommand.cs | 39 +++ OFBButte.Application/Base/IAccess.cs | 12 + OFBButte.Application/Codes/ICodeGenerator.cs | 11 + .../Configuration/AppSettings.cs | 11 + .../Configuration/ConnectionStrings.cs | 11 + .../Configuration/Permission.cs | 46 +++ OFBButte.Application/Database/IOFBContext.cs | 18 + OFBButte.Application/Email/EmailMessage.cs | 30 ++ OFBButte.Application/Email/IEmailSender.cs | 12 + .../Email/PasswordResetEmailMessage.cs | 14 + .../Templates/PasswordResetEmailMessage.html | 4 + .../Email/Templates/VerifyEmailMessage.html | 4 + .../Email/Templates/base.html | 12 + .../Email/VerifyEmailMessage.cs | 15 + .../Exceptions/ValidationException.cs | 14 + .../Missionary/AddMissionarySupportForm.cs | 25 ++ .../Missionary/GetMissionarySupportForm.cs | 23 ++ .../OFBButte.Application.csproj | 15 + OFBButte.Application/Users/AddProfile.cs | 55 +++ OFBButte.Application/Users/AddUser.cs | 42 +++ OFBButte.Application/Users/DeleteUser.cs | 27 ++ OFBButte.Application/Users/FederateProfile.cs | 34 ++ OFBButte.Application/Users/IPasswordHasher.cs | 12 + OFBButte.Application/Users/LoginUser.cs | 36 ++ OFBButte.Application/Users/ResetPassword.cs | 34 ++ .../Users/SendEmailValidation.cs | 57 ++++ .../Users/SendResetPasswordEmail.cs | 55 +++ OFBButte.Application/Users/UpdateProfile.cs | 39 +++ OFBButte.Application/Users/VerifyEmail.cs | 29 ++ OFBButte.Console/App.cs | 61 ++++ OFBButte.Console/OFBButte.Console.csproj | 18 + OFBButte.Console/Program.cs | 44 +++ .../20190717032510_Initial.Designer.cs | 318 ++++++++++++++++++ .../Migrations/20190717032510_Initial.cs | 287 ++++++++++++++++ .../Migrations/OFBContextModelSnapshot.cs | 316 +++++++++++++++++ OFBButte.Database/OFBButte.Database.csproj | 16 + OFBButte.Database/OFBContext/OFBContext.cs | 66 ++++ OFBButte.Entities/EmailVerificationCode.cs | 13 + OFBButte.Entities/Enums/BibleReading.cs | 9 + OFBButte.Entities/Enums/ChildrenSchool.cs | 10 + OFBButte.Entities/Enums/FemaleShorts.cs | 10 + OFBButte.Entities/Enums/FemaleSlacks.cs | 10 + OFBButte.Entities/MissionaryChild.cs | 12 + .../MissionaryCollegeRecommendation.cs | 12 + OFBButte.Entities/MissionarySupport.cs | 69 ++++ OFBButte.Entities/OFBButte.Entities.csproj | 7 + OFBButte.Entities/PasswordResetCode.cs | 13 + OFBButte.Entities/Profile.cs | 21 ++ OFBButte.Entities/ProfileFederationCode.cs | 13 + OFBButte.Entities/User.cs | 21 ++ .../Codes/CodeGenerator.cs | 19 ++ OFBButte.Infrastructure/Email/EmailSender.cs | 44 +++ .../Hashing/PasswordHasher.cs | 215 ++++++++++++ .../Hashing/PasswordHasherOptions.cs | 30 ++ .../Hashing/PasswordVerificationResult.cs | 28 ++ .../OFBButte.Infrastructure.csproj | 16 + OFBButte.sln | 55 +++ 65 files changed, 2975 insertions(+) create mode 100644 .gitignore create mode 100644 OFBButte.Api/Controllers/ValuesController.cs create mode 100644 OFBButte.Api/OFBButte.Api.csproj create mode 100644 OFBButte.Api/Program.cs create mode 100644 OFBButte.Api/Properties/launchSettings.json create mode 100644 OFBButte.Api/Startup.cs create mode 100644 OFBButte.Api/appsettings.Development.json create mode 100644 OFBButte.Api/appsettings.json create mode 100644 OFBButte.Application/Base/AuthorizeCommand.cs create mode 100644 OFBButte.Application/Base/IAccess.cs create mode 100644 OFBButte.Application/Codes/ICodeGenerator.cs create mode 100644 OFBButte.Application/Configuration/AppSettings.cs create mode 100644 OFBButte.Application/Configuration/ConnectionStrings.cs create mode 100644 OFBButte.Application/Configuration/Permission.cs create mode 100644 OFBButte.Application/Database/IOFBContext.cs create mode 100644 OFBButte.Application/Email/EmailMessage.cs create mode 100644 OFBButte.Application/Email/IEmailSender.cs create mode 100644 OFBButte.Application/Email/PasswordResetEmailMessage.cs create mode 100644 OFBButte.Application/Email/Templates/PasswordResetEmailMessage.html create mode 100644 OFBButte.Application/Email/Templates/VerifyEmailMessage.html create mode 100644 OFBButte.Application/Email/Templates/base.html create mode 100644 OFBButte.Application/Email/VerifyEmailMessage.cs create mode 100644 OFBButte.Application/Exceptions/ValidationException.cs create mode 100644 OFBButte.Application/Missionary/AddMissionarySupportForm.cs create mode 100644 OFBButte.Application/Missionary/GetMissionarySupportForm.cs create mode 100644 OFBButte.Application/OFBButte.Application.csproj create mode 100644 OFBButte.Application/Users/AddProfile.cs create mode 100644 OFBButte.Application/Users/AddUser.cs create mode 100644 OFBButte.Application/Users/DeleteUser.cs create mode 100644 OFBButte.Application/Users/FederateProfile.cs create mode 100644 OFBButte.Application/Users/IPasswordHasher.cs create mode 100644 OFBButte.Application/Users/LoginUser.cs create mode 100644 OFBButte.Application/Users/ResetPassword.cs create mode 100644 OFBButte.Application/Users/SendEmailValidation.cs create mode 100644 OFBButte.Application/Users/SendResetPasswordEmail.cs create mode 100644 OFBButte.Application/Users/UpdateProfile.cs create mode 100644 OFBButte.Application/Users/VerifyEmail.cs create mode 100644 OFBButte.Console/App.cs create mode 100644 OFBButte.Console/OFBButte.Console.csproj create mode 100644 OFBButte.Console/Program.cs create mode 100644 OFBButte.Database/Migrations/20190717032510_Initial.Designer.cs create mode 100644 OFBButte.Database/Migrations/20190717032510_Initial.cs create mode 100644 OFBButte.Database/Migrations/OFBContextModelSnapshot.cs create mode 100644 OFBButte.Database/OFBButte.Database.csproj create mode 100644 OFBButte.Database/OFBContext/OFBContext.cs create mode 100644 OFBButte.Entities/EmailVerificationCode.cs create mode 100644 OFBButte.Entities/Enums/BibleReading.cs create mode 100644 OFBButte.Entities/Enums/ChildrenSchool.cs create mode 100644 OFBButte.Entities/Enums/FemaleShorts.cs create mode 100644 OFBButte.Entities/Enums/FemaleSlacks.cs create mode 100644 OFBButte.Entities/MissionaryChild.cs create mode 100644 OFBButte.Entities/MissionaryCollegeRecommendation.cs create mode 100644 OFBButte.Entities/MissionarySupport.cs create mode 100644 OFBButte.Entities/OFBButte.Entities.csproj create mode 100644 OFBButte.Entities/PasswordResetCode.cs create mode 100644 OFBButte.Entities/Profile.cs create mode 100644 OFBButte.Entities/ProfileFederationCode.cs create mode 100644 OFBButte.Entities/User.cs create mode 100644 OFBButte.Infrastructure/Codes/CodeGenerator.cs create mode 100644 OFBButte.Infrastructure/Email/EmailSender.cs create mode 100644 OFBButte.Infrastructure/Hashing/PasswordHasher.cs create mode 100644 OFBButte.Infrastructure/Hashing/PasswordHasherOptions.cs create mode 100644 OFBButte.Infrastructure/Hashing/PasswordVerificationResult.cs create mode 100644 OFBButte.Infrastructure/OFBButte.Infrastructure.csproj create mode 100644 OFBButte.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d415c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,275 @@ + +# Created by https://www.gitignore.io/api/aspnetcore +# Edit at https://www.gitignore.io/?templates=aspnetcore + +### ASPNETCore ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.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 + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# 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 +# TODO: 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 +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable 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 + +# 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 +node_modules/ +orleans.codegen.cs + +# 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 + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# 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/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +# End of https://www.gitignore.io/api/aspnetcore \ No newline at end of file diff --git a/OFBButte.Api/Controllers/ValuesController.cs b/OFBButte.Api/Controllers/ValuesController.cs new file mode 100644 index 0000000..5275a69 --- /dev/null +++ b/OFBButte.Api/Controllers/ValuesController.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace OFBButte.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class ValuesController : ControllerBase + { + // GET api/values + [HttpGet] + public ActionResult> Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public ActionResult Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody] string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody] string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/OFBButte.Api/OFBButte.Api.csproj b/OFBButte.Api/OFBButte.Api.csproj new file mode 100644 index 0000000..d1ad426 --- /dev/null +++ b/OFBButte.Api/OFBButte.Api.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.2 + InProcess + + + + + + + + + + + + + + + diff --git a/OFBButte.Api/Program.cs b/OFBButte.Api/Program.cs new file mode 100644 index 0000000..2855c71 --- /dev/null +++ b/OFBButte.Api/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace OFBButte.Api +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/OFBButte.Api/Properties/launchSettings.json b/OFBButte.Api/Properties/launchSettings.json new file mode 100644 index 0000000..346f4a0 --- /dev/null +++ b/OFBButte.Api/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50405", + "sslPort": 44314 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/values", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "OFBButte.Api": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/values", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/OFBButte.Api/Startup.cs b/OFBButte.Api/Startup.cs new file mode 100644 index 0000000..c6b57e8 --- /dev/null +++ b/OFBButte.Api/Startup.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using OFBButte.Application.Configuration; +using OFBButte.Application.Database; +using OFBButte.Database; + +namespace OFBButte.Api +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + // App Settings + services.Configure(Configuration.GetSection("App")); + + // DB Context + services.AddDbContext(o => + { + OFBContext.UseMySql(o, Configuration.GetConnectionString("OFBContext")); + }); + services.AddScoped(s => s.GetService()); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseMvc(); + } + } +} diff --git a/OFBButte.Api/appsettings.Development.json b/OFBButte.Api/appsettings.Development.json new file mode 100644 index 0000000..63f6029 --- /dev/null +++ b/OFBButte.Api/appsettings.Development.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + }, + "App": { + "Environment": "Dev" + }, + "ConnectionStrings": { + "OFBContext": "server=localhost;database=ofbtest;user=ofbapi;password=87hjdusiodksyeunsjkdis7" + } +} diff --git a/OFBButte.Api/appsettings.json b/OFBButte.Api/appsettings.json new file mode 100644 index 0000000..b8470b3 --- /dev/null +++ b/OFBButte.Api/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "App": { + "Environment": "Local" + }, + "ConnectionStrings": { + "OFBContext": "server=localhost;database=ofbtest;user=ofbapi;password=87hjdusiodksyeunsjkdis7" + } +} diff --git a/OFBButte.Application/Base/AuthorizeCommand.cs b/OFBButte.Application/Base/AuthorizeCommand.cs new file mode 100644 index 0000000..ecffd7d --- /dev/null +++ b/OFBButte.Application/Base/AuthorizeCommand.cs @@ -0,0 +1,39 @@ +using OFBButte.Application.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Base +{ + public class AuthorizeCommand + { + private readonly IAccess access; + protected readonly IEnumerable allowedPermissions; + private AuthorizeCommand(IAccess access) + { + this.access = access; + } + public AuthorizeCommand(IAccess access, Permission permission): this(access) + { + this.allowedPermissions = new Permission[] { permission }; + } + public AuthorizeCommand(IAccess access, IEnumerable permissions): this(access) + { + this.allowedPermissions = permissions; + } + + public void Authorize() + { + if (allowedPermissions == null) + { + throw new UnauthorizedAccessException("Permission Denied"); + } + foreach(var permission in allowedPermissions) + { + if (access.CanDo(permission)) + return; + } + throw new UnauthorizedAccessException($"Permission Denied: {string.Join(", ", allowedPermissions)}"); + } + } +} diff --git a/OFBButte.Application/Base/IAccess.cs b/OFBButte.Application/Base/IAccess.cs new file mode 100644 index 0000000..5f04607 --- /dev/null +++ b/OFBButte.Application/Base/IAccess.cs @@ -0,0 +1,12 @@ +using OFBButte.Application.Configuration; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Base +{ + public interface IAccess + { + bool CanDo(Permission permission); + } +} diff --git a/OFBButte.Application/Codes/ICodeGenerator.cs b/OFBButte.Application/Codes/ICodeGenerator.cs new file mode 100644 index 0000000..aa44ec2 --- /dev/null +++ b/OFBButte.Application/Codes/ICodeGenerator.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Codes +{ + public interface ICodeGenerator + { + string Generate(int byteSize = 16); + } +} diff --git a/OFBButte.Application/Configuration/AppSettings.cs b/OFBButte.Application/Configuration/AppSettings.cs new file mode 100644 index 0000000..515f98c --- /dev/null +++ b/OFBButte.Application/Configuration/AppSettings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Configuration +{ + public class AppSettings + { + public string Environment { get; set; } + } +} diff --git a/OFBButte.Application/Configuration/ConnectionStrings.cs b/OFBButte.Application/Configuration/ConnectionStrings.cs new file mode 100644 index 0000000..e869742 --- /dev/null +++ b/OFBButte.Application/Configuration/ConnectionStrings.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Configuration +{ + public class ConnectionStrings + { + public string OFBContext { get; set; } + } +} diff --git a/OFBButte.Application/Configuration/Permission.cs b/OFBButte.Application/Configuration/Permission.cs new file mode 100644 index 0000000..e668693 --- /dev/null +++ b/OFBButte.Application/Configuration/Permission.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Configuration +{ + public class Permission + { + public string Value { get; private set; } + private Permission(string permission) + { + Value = permission; + } + + public static Permission AddProfile { get; } = new Permission("Add Profile"); + + private static Dictionary values = typeof(Permission).GetProperties(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).Where(p => p.PropertyType == typeof(Permission)).ToDictionary(p => p.Name, p => (Permission)p.GetValue(null)); + public static bool TryCast(string input, out Permission permission) + { + permission = null; + if (values.TryGetValue(input, out Permission perm)) + { + permission = perm; + return true; + } + return false; + } + + public static implicit operator string(Permission input) + { + return input.Value; + } + + public static explicit operator Permission(string input) + { + if (TryCast(input, out Permission permission)) + { + return permission; + } else + { + throw new InvalidCastException($"Could not cast {input} to {typeof(Permission).ToString()}"); + } + } + } +} diff --git a/OFBButte.Application/Database/IOFBContext.cs b/OFBButte.Application/Database/IOFBContext.cs new file mode 100644 index 0000000..c97bbb1 --- /dev/null +++ b/OFBButte.Application/Database/IOFBContext.cs @@ -0,0 +1,18 @@ +using Microsoft.EntityFrameworkCore; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Database +{ + public interface IOFBContext + { + DbSet Users { get; set; } + DbSet Profiles { get; set; } + DbSet EmailVerificationCodes { get; set; } + DbSet PasswordResetCodes { get; set; } + DbSet MissionarySupportForms { get; set; } + int SaveChanges(); + } +} diff --git a/OFBButte.Application/Email/EmailMessage.cs b/OFBButte.Application/Email/EmailMessage.cs new file mode 100644 index 0000000..3cef1a0 --- /dev/null +++ b/OFBButte.Application/Email/EmailMessage.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Email +{ + public abstract class EmailMessage + { + public virtual string To { get; set; } + public virtual string From { get; set; } = "donotreply@ofbbutte.com"; + public virtual string Subject { get; set; } + public virtual string Body { get; set; } + public abstract string TemplateName { get; set; } + + public void SetBodyFromTemplate(bool isTest = true) + { + string baseHtml = System.IO.File.ReadAllText($@"Email\Templates\base.html"); + string template = System.IO.File.ReadAllText($@"Email\Templates\{TemplateName}.html"); + baseHtml = baseHtml.Replace("{{TEST}}", isTest ? "THIS IS A TEST" : ""); + template = baseHtml.Replace("{{CONTENT}}", template); + var props = GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).ToDictionary(p => p.Name, p => p.GetValue(this)?.ToString() ?? ""); + foreach (var property in props) + { + template = template.Replace($"{{{{{property.Key}}}}}", property.Value); + } + Body = template; + } + } +} diff --git a/OFBButte.Application/Email/IEmailSender.cs b/OFBButte.Application/Email/IEmailSender.cs new file mode 100644 index 0000000..061db4d --- /dev/null +++ b/OFBButte.Application/Email/IEmailSender.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OFBButte.Application.Email +{ + public interface IEmailSender + { + Task Send(EmailMessage message); + } +} diff --git a/OFBButte.Application/Email/PasswordResetEmailMessage.cs b/OFBButte.Application/Email/PasswordResetEmailMessage.cs new file mode 100644 index 0000000..94bbf51 --- /dev/null +++ b/OFBButte.Application/Email/PasswordResetEmailMessage.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Email +{ + public class PasswordResetEmailMessage : EmailMessage + { + public override string TemplateName { get; set; } = "PasswordResetEmailMessage"; + public override string Subject { get; set; } = "OFB Butte: Password Reset"; + + public string Url { get; set; } + } +} diff --git a/OFBButte.Application/Email/Templates/PasswordResetEmailMessage.html b/OFBButte.Application/Email/Templates/PasswordResetEmailMessage.html new file mode 100644 index 0000000..d0722b5 --- /dev/null +++ b/OFBButte.Application/Email/Templates/PasswordResetEmailMessage.html @@ -0,0 +1,4 @@ +

+ Please click the link below to reset your password. +

+{{Url}} \ No newline at end of file diff --git a/OFBButte.Application/Email/Templates/VerifyEmailMessage.html b/OFBButte.Application/Email/Templates/VerifyEmailMessage.html new file mode 100644 index 0000000..38a955d --- /dev/null +++ b/OFBButte.Application/Email/Templates/VerifyEmailMessage.html @@ -0,0 +1,4 @@ +

+ Please verify your email by clicking the link below. +

+{{VerificationUrl}} \ No newline at end of file diff --git a/OFBButte.Application/Email/Templates/base.html b/OFBButte.Application/Email/Templates/base.html new file mode 100644 index 0000000..6ba6e5d --- /dev/null +++ b/OFBButte.Application/Email/Templates/base.html @@ -0,0 +1,12 @@ + + + + + + + + + {{TEST}} + {{CONTENT}} + + \ No newline at end of file diff --git a/OFBButte.Application/Email/VerifyEmailMessage.cs b/OFBButte.Application/Email/VerifyEmailMessage.cs new file mode 100644 index 0000000..487d1e4 --- /dev/null +++ b/OFBButte.Application/Email/VerifyEmailMessage.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Email +{ + public class VerifyEmailMessage : EmailMessage + { + public override string TemplateName { get; set; } = "VerifyEmailMessage"; + public override string Subject { get; set; } = "OFB Butte: Email Verification"; + + // Email Properties + public string VerificationUrl { get; set; } + } +} diff --git a/OFBButte.Application/Exceptions/ValidationException.cs b/OFBButte.Application/Exceptions/ValidationException.cs new file mode 100644 index 0000000..ade89d0 --- /dev/null +++ b/OFBButte.Application/Exceptions/ValidationException.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Exceptions +{ + public class ValidationException: Exception + { + public ValidationException(string message): base(message) + { + + } + } +} diff --git a/OFBButte.Application/Missionary/AddMissionarySupportForm.cs b/OFBButte.Application/Missionary/AddMissionarySupportForm.cs new file mode 100644 index 0000000..7f79a53 --- /dev/null +++ b/OFBButte.Application/Missionary/AddMissionarySupportForm.cs @@ -0,0 +1,25 @@ +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Missionary +{ + public class AddMissionarySupportForm + { + private readonly IOFBContext context; + public AddMissionarySupportForm(IOFBContext context) + { + this.context = context; + } + + public MissionarySupport Handle(MissionarySupport support) + { + support.Id = 0; + context.MissionarySupportForms.Add(support); + context.SaveChanges(); + return support; + } + } +} diff --git a/OFBButte.Application/Missionary/GetMissionarySupportForm.cs b/OFBButte.Application/Missionary/GetMissionarySupportForm.cs new file mode 100644 index 0000000..cd2eba6 --- /dev/null +++ b/OFBButte.Application/Missionary/GetMissionarySupportForm.cs @@ -0,0 +1,23 @@ +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Missionary +{ + public class GetMissionarySupportForm + { + private readonly IOFBContext context; + public GetMissionarySupportForm(IOFBContext context) + { + this.context = context; + } + + public MissionarySupport Handle(int id) + { + return context.MissionarySupportForms.FirstOrDefault(m => m.Id == id); + } + } +} diff --git a/OFBButte.Application/OFBButte.Application.csproj b/OFBButte.Application/OFBButte.Application.csproj new file mode 100644 index 0000000..cada972 --- /dev/null +++ b/OFBButte.Application/OFBButte.Application.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp2.2 + + + + + + + + + + + diff --git a/OFBButte.Application/Users/AddProfile.cs b/OFBButte.Application/Users/AddProfile.cs new file mode 100644 index 0000000..8dc1069 --- /dev/null +++ b/OFBButte.Application/Users/AddProfile.cs @@ -0,0 +1,55 @@ +using OFBButte.Application.Base; +using OFBButte.Application.Codes; +using OFBButte.Application.Configuration; +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class AddProfile: AuthorizeCommand + { + private readonly IOFBContext context; + private readonly ICodeGenerator codes; + public AddProfile(IAccess access, IOFBContext context, ICodeGenerator codes) + :base(access, Permission.AddProfile) + { + this.context = context; + this.codes = codes; + } + + public Profile Handle(Profile profile) + { + base.Authorize(); + + return Handle(profile.FirstName, profile.LastName, profile.Street, profile.City, profile.State, profile.Zip, profile.Country); + } + + public Profile Handle(string first, string last, string street, string city, string state, string zip, string country) + { + base.Authorize(); + + var profile = new Profile() + { + FirstName = first, + LastName = last, + Street = street, + City = city, + State = state, + Zip = zip, + Country = country, + ModifiedDate = DateTime.Now, + ProfileFederationCode = new ProfileFederationCode() + { + CreatedDate = DateTime.Now, + Code = codes.Generate() + } + }; + context.Profiles.Add(profile); + context.SaveChanges(); + return profile; + } + } +} diff --git a/OFBButte.Application/Users/AddUser.cs b/OFBButte.Application/Users/AddUser.cs new file mode 100644 index 0000000..8731e56 --- /dev/null +++ b/OFBButte.Application/Users/AddUser.cs @@ -0,0 +1,42 @@ +using OFBButte.Application.Database; +using OFBButte.Application.Exceptions; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class AddUser + { + private readonly IOFBContext context; + private readonly IPasswordHasher hasher; + public AddUser(IOFBContext context, IPasswordHasher hasher) + { + this.context = context; + this.hasher = hasher; + } + + public User Handle(string email, string password) + { + // Check to see if email already exists + var existingUser = context.Users.FirstOrDefault(u => u.Email == email); + if (existingUser != null) + throw new ValidationException("User with email already exists"); + + // Create the new user + var user = new Entities.User() + { + Email = email, + CreatedDate = DateTime.Now, + Password = hasher.HashPassword(password) + }; + + // Save to db + context.Users.Add(user); + context.SaveChanges(); + return user; + } + } +} diff --git a/OFBButte.Application/Users/DeleteUser.cs b/OFBButte.Application/Users/DeleteUser.cs new file mode 100644 index 0000000..935741e --- /dev/null +++ b/OFBButte.Application/Users/DeleteUser.cs @@ -0,0 +1,27 @@ +using OFBButte.Application.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class DeleteUser + { + private readonly IOFBContext context; + public DeleteUser(IOFBContext context) + { + this.context = context; + } + public void Handle(string email) + { + // Get the user + var user = context.Users.FirstOrDefault(u => u.Email == email && u.DeletedDate == null); + if (user == null) + return; + + user.DeletedDate = DateTime.Now; + context.SaveChanges(); + } + } +} diff --git a/OFBButte.Application/Users/FederateProfile.cs b/OFBButte.Application/Users/FederateProfile.cs new file mode 100644 index 0000000..dac1fec --- /dev/null +++ b/OFBButte.Application/Users/FederateProfile.cs @@ -0,0 +1,34 @@ +using OFBButte.Application.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class FederateProfile + { + private readonly IOFBContext context; + public FederateProfile(IOFBContext context) + { + this.context = context; + } + + public bool Handle(string email, string lastName, string federationCode) + { + // Get the user + var user = context.Users.FirstOrDefault(u => u.Email == email && u.Profile == null); + if (user == null) + return false; + + // Get the profile + var profile = context.Profiles.FirstOrDefault(p => p.ProfileFederationCode.Code == federationCode && p.LastName == lastName); + if (profile == null) + return false; + + user.Profile = profile; + context.SaveChanges(); + return true; + } + } +} diff --git a/OFBButte.Application/Users/IPasswordHasher.cs b/OFBButte.Application/Users/IPasswordHasher.cs new file mode 100644 index 0000000..4aeeed2 --- /dev/null +++ b/OFBButte.Application/Users/IPasswordHasher.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Application.Users +{ + public interface IPasswordHasher + { + string HashPassword(string password); + bool VerifyPassword(string hashedPassword, string password); + } +} diff --git a/OFBButte.Application/Users/LoginUser.cs b/OFBButte.Application/Users/LoginUser.cs new file mode 100644 index 0000000..040e15b --- /dev/null +++ b/OFBButte.Application/Users/LoginUser.cs @@ -0,0 +1,36 @@ +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class LoginUser + { + private readonly IOFBContext context; + private readonly IPasswordHasher hasher; + public LoginUser(IOFBContext context, IPasswordHasher hasher) + { + this.context = context; + this.hasher = hasher; + } + public User Handle(string email, string password) + { + var user = context.Users.FirstOrDefault(u => u.Email == email); + if (user == null) + throw new Exception("Could not find email: " + email); + + var validPassword = hasher.VerifyPassword(user.Password, password); + + if (validPassword) + { + return user; + } else + { + return null; + } + } + } +} diff --git a/OFBButte.Application/Users/ResetPassword.cs b/OFBButte.Application/Users/ResetPassword.cs new file mode 100644 index 0000000..3f83b27 --- /dev/null +++ b/OFBButte.Application/Users/ResetPassword.cs @@ -0,0 +1,34 @@ +using OFBButte.Application.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class ResetPassword + { + private readonly IOFBContext context; + private readonly IPasswordHasher hasher; + public ResetPassword(IOFBContext context, IPasswordHasher hasher) + { + this.context = context; + this.hasher = hasher; + } + + public bool Handle(int codeId, string code, string newPassword) + { + // Get user + var dte = DateTime.Now.AddHours(-1); + var user = context.Users.FirstOrDefault(u => u.PassswordResetCode.Id == codeId && u.PassswordResetCode.Code == code && u.PassswordResetCode.CreatedDate >= dte); + if (user == null) + return false; + + user.Password = hasher.HashPassword(newPassword); + context.PasswordResetCodes.Remove(user.PassswordResetCode); + user.PassswordResetCode = null; + context.SaveChanges(); + return true; + } + } +} diff --git a/OFBButte.Application/Users/SendEmailValidation.cs b/OFBButte.Application/Users/SendEmailValidation.cs new file mode 100644 index 0000000..dbdf0d5 --- /dev/null +++ b/OFBButte.Application/Users/SendEmailValidation.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore; +using OFBButte.Application.Codes; +using OFBButte.Application.Database; +using OFBButte.Application.Email; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace OFBButte.Application.Users +{ + public class SendEmailValidation + { + private readonly IOFBContext context; + private readonly IEmailSender emailSender; + private readonly ICodeGenerator codes; + public SendEmailValidation(IOFBContext context, IEmailSender emailSender, ICodeGenerator codes) + { + this.context = context; + this.emailSender = emailSender; + this.codes = codes; + } + + public async Task Handle(string email) + { + var user = context.Users.Include(u => u.EmailVerificationCode).FirstOrDefault(u => u.Email == email); + if (user == null) + throw new Exception($"Could not find user with email: {email}"); + + if (user.EmailVerificationCode != null) + { + context.EmailVerificationCodes.Remove(user.EmailVerificationCode); + } + + // Generate verification code + user.EmailVerificationCode = new Entities.EmailVerificationCode() + { + Code = codes.Generate(), + CreatedDate = DateTime.Now + }; + + context.SaveChanges(); + + // Create the email + var emailMessage = new VerifyEmailMessage() + { + To = email, + From = "donotreply@ofbbutte.com", + VerificationUrl = $"https://ofbbutte.com/user/emailverification?c={user.EmailVerificationCode.Id}&code={user.EmailVerificationCode.Code}" + }; + + await emailSender.Send(emailMessage); + } + } +} diff --git a/OFBButte.Application/Users/SendResetPasswordEmail.cs b/OFBButte.Application/Users/SendResetPasswordEmail.cs new file mode 100644 index 0000000..feff0ec --- /dev/null +++ b/OFBButte.Application/Users/SendResetPasswordEmail.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using OFBButte.Application.Codes; +using OFBButte.Application.Database; +using OFBButte.Application.Email; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OFBButte.Application.Users +{ + public class SendResetPasswordEmail + { + private readonly IOFBContext context; + private readonly IEmailSender emailSender; + private readonly ICodeGenerator codes; + + public SendResetPasswordEmail(IOFBContext context, IEmailSender emailSender, ICodeGenerator codes) + { + this.context = context; + this.emailSender = emailSender; + this.codes = codes; + } + + public async Task Handle(string email, string lastName) + { + // Get the user + var user = context.Users.Include(u => u.PassswordResetCode).FirstOrDefault(u => u.Email == email && u.Profile.LastName == lastName); + if (user == null) + return; + + if (user.PassswordResetCode != null) + { + context.PasswordResetCodes.Remove(user.PassswordResetCode); + } + + user.PassswordResetCode = new Entities.PasswordResetCode() + { + CreatedDate = DateTime.Now, + Code = codes.Generate() + }; + + context.SaveChanges(); + + var message = new PasswordResetEmailMessage() + { + To = user.Email, + Url = $"http://ofbbutte.com/user/resetpassword?c={user.PassswordResetCode.Id}&code={user.PassswordResetCode.Code}" + }; + + await emailSender.Send(message); + } + } +} diff --git a/OFBButte.Application/Users/UpdateProfile.cs b/OFBButte.Application/Users/UpdateProfile.cs new file mode 100644 index 0000000..941b034 --- /dev/null +++ b/OFBButte.Application/Users/UpdateProfile.cs @@ -0,0 +1,39 @@ +using Microsoft.EntityFrameworkCore; +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class UpdateProfile + { + private readonly IOFBContext context; + public UpdateProfile(IOFBContext context) + { + this.context = context; + } + + public Profile Handle(string email, Profile profile) + { + // Get the user + var user = context.Users.Include(u => u.Profile).FirstOrDefault(u => u.Email == email); + if (user == null || user.Profile == null) + return null; + + user.Profile.City = profile.City; + user.Profile.Country = profile.Country; + user.Profile.FirstName = profile.FirstName; + user.Profile.LastName = profile.LastName; + user.Profile.ModifiedDate = DateTime.Now; + user.Profile.State = profile.State; + user.Profile.Street = profile.Street; + user.Profile.Zip = profile.Zip; + + context.SaveChanges(); + return user.Profile; + } + } +} diff --git a/OFBButte.Application/Users/VerifyEmail.cs b/OFBButte.Application/Users/VerifyEmail.cs new file mode 100644 index 0000000..94ab16f --- /dev/null +++ b/OFBButte.Application/Users/VerifyEmail.cs @@ -0,0 +1,29 @@ +using OFBButte.Application.Database; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OFBButte.Application.Users +{ + public class VerifyEmail + { + private readonly IOFBContext context; + public VerifyEmail(IOFBContext context) + { + this.context = context; + } + + public bool Handle(int codeId, string code) + { + var dte = DateTime.Now.AddHours(-1); + var user = context.Users.FirstOrDefault(u => u.EmailVerificationCode.Id == codeId && u.EmailVerificationCode.Code == code && u.EmailVerifiedDate > dte); + if (user == null) + return false; + + user.EmailVerifiedDate = DateTime.Now; + context.SaveChanges(); + return true; + } + } +} diff --git a/OFBButte.Console/App.cs b/OFBButte.Console/App.cs new file mode 100644 index 0000000..4c93fe2 --- /dev/null +++ b/OFBButte.Console/App.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Options; +using OFBButte.Application.Configuration; +using OFBButte.Application.Database; +using OFBButte.Application.Email; +using OFBButte.Application.Users; +using OFBButte.Infrastructure.Hashing; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OFBButte.Console +{ + public class App + { + private readonly AppSettings appSettings; + private readonly IEmailSender emailSender; + private readonly IOFBContext context; + private string user = "djabsher@gmail.com"; + private string pass = "my password"; + + public App(IOptions appSettings, IOFBContext context, IEmailSender emailSender) + { + this.appSettings = appSettings.Value; + this.emailSender = emailSender; + this.context = context; + } + + public async Task Run() + { + var env = appSettings.Environment; + try + { + await SendVerificationEmail(); + } + catch (Exception ex) + { + throw ex; + } + + } + + private void AddUser() + { + var adder = new AddUser(context, new PasswordHasher()); + var result = adder.Handle(user, pass); + } + + private async Task SendVerificationEmail() + { + var adder = new SendEmailValidation(context, emailSender, new Infrastructure.Codes.CodeGenerator()); + await adder.Handle(user); + } + + private void LoginUser() + { + var login = new LoginUser(context, new PasswordHasher()); + var result = login.Handle(user, pass); + } + } +} diff --git a/OFBButte.Console/OFBButte.Console.csproj b/OFBButte.Console/OFBButte.Console.csproj new file mode 100644 index 0000000..792922e --- /dev/null +++ b/OFBButte.Console/OFBButte.Console.csproj @@ -0,0 +1,18 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + + + + + diff --git a/OFBButte.Console/Program.cs b/OFBButte.Console/Program.cs new file mode 100644 index 0000000..8615ae5 --- /dev/null +++ b/OFBButte.Console/Program.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using OFBButte.Application.Configuration; +using OFBButte.Application.Database; +using OFBButte.Application.Email; +using OFBButte.Database; +using OFBButte.Infrastructure.Email; +using System; +using System.Threading.Tasks; + +namespace OFBButte.Console +{ + class Program + { + + static ServiceProvider services; + + static async Task Main(string[] args) + { + SetupDI(); + + await services.GetService().Run(); + } + + static void SetupDI() + { + var builder = new ConfigurationBuilder(); + builder.AddJsonFile("appsettings.json", false, true); + var config = builder.Build(); + + + var serviceCollection = new ServiceCollection(); + serviceCollection.Configure(a => config.GetSection("app").Bind(a)); + + serviceCollection.AddSingleton(new EmailSender()); + serviceCollection.AddDbContext(o => OFBContext.UseMySql(o, config.GetConnectionString("OFBContext"))); + serviceCollection.AddScoped(s => s.GetService()); + serviceCollection.AddTransient(); + services = serviceCollection.BuildServiceProvider(); + } + + + } +} diff --git a/OFBButte.Database/Migrations/20190717032510_Initial.Designer.cs b/OFBButte.Database/Migrations/20190717032510_Initial.Designer.cs new file mode 100644 index 0000000..e548fbb --- /dev/null +++ b/OFBButte.Database/Migrations/20190717032510_Initial.Designer.cs @@ -0,0 +1,318 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OFBButte.Database; + +namespace OFBButte.Database.Migrations +{ + [DbContext(typeof(OFBContext))] + [Migration("20190717032510_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("OFBButte.Entities.EmailVerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("EmailVerificationCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryChild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionarySupportId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("MissionarySupportId"); + + b.ToTable("MissionaryChild"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryCollegeRecommendation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionarySupportId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("MissionarySupportId"); + + b.ToTable("MissionaryCollegeRecommendation"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionarySupport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdmittedWrong"); + + b.Property("Alcohol"); + + b.Property("AloneOrTeam"); + + b.Property("BibleVersionOpinion"); + + b.Property("BibleVersionsUsed"); + + b.Property("BillsOnTime"); + + b.Property("CallToField"); + + b.Property("CellPhone"); + + b.Property("Charasmaticism"); + + b.Property("ChildrenSchool"); + + b.Property("ContemporaryMusic"); + + b.Property("CorrectWrongOfAnotherMissionary"); + + b.Property("CurrentMonthlySupport"); + + b.Property("DailyBible"); + + b.Property("Dance"); + + b.Property("Divorced"); + + b.Property("EvaluationOfNationals"); + + b.Property("FellowshipAssociation"); + + b.Property("FemaleDressStandard"); + + b.Property("FemaleShorts"); + + b.Property("FemaleSlacks"); + + b.Property("FieldOfService"); + + b.Property("FieldPhone"); + + b.Property("FinancialStatementPrevYear"); + + b.Property("Fundamentalist"); + + b.Property("GroundsForRemarry"); + + b.Property("HomePhone"); + + b.Property("LateBillActionTaken"); + + b.Property("LateBills"); + + b.Property("MaleHair"); + + b.Property("MarryADivorcee"); + + b.Property("MasonicLodge"); + + b.Property("MonthlySupportNeeded"); + + b.Property("MovieTheaters"); + + b.Property("Name"); + + b.Property("NumberLedToChrist"); + + b.Property("NumberWeeklyTracts"); + + b.Property("NumberWitnessedTo"); + + b.Property("Plans"); + + b.Property("PotentialHarvest"); + + b.Property("Predestination"); + + b.Property("RateOfSuccess"); + + b.Property("RepentanceDefinition"); + + b.Property("RepentanceNecessary"); + + b.Property("RestAndRelaxation"); + + b.Property("SendingChurch"); + + b.Property("Smoking"); + + b.Property("SwimmingClothing"); + + b.Property("Television"); + + b.Property("Testimony"); + + b.Property("TimeInCountry"); + + b.Property("Tongues"); + + b.Property("WifesName"); + + b.Property("WorldlyMusic"); + + b.HasKey("Id"); + + b.ToTable("MissionarySupportForms"); + }); + + modelBuilder.Entity("OFBButte.Entities.PasswordResetCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("PasswordResetCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("City"); + + b.Property("Country"); + + b.Property("FirstName"); + + b.Property("LastName"); + + b.Property("ModifiedDate"); + + b.Property("ProfileFederationCodeId"); + + b.Property("State"); + + b.Property("Street"); + + b.Property("Zip"); + + b.HasKey("Id"); + + b.HasIndex("ProfileFederationCodeId"); + + b.ToTable("Profiles"); + }); + + modelBuilder.Entity("OFBButte.Entities.ProfileFederationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("ProfileFederationCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatedDate"); + + b.Property("DeletedDate"); + + b.Property("Email") + .IsRequired(); + + b.Property("EmailVerificationCodeId"); + + b.Property("EmailVerifiedDate"); + + b.Property("PassswordResetCodeId"); + + b.Property("Password") + .IsRequired(); + + b.Property("ProfileId"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("EmailVerificationCodeId"); + + b.HasIndex("PassswordResetCodeId"); + + b.HasIndex("ProfileId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryChild", b => + { + b.HasOne("OFBButte.Entities.MissionarySupport") + .WithMany("Children") + .HasForeignKey("MissionarySupportId"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryCollegeRecommendation", b => + { + b.HasOne("OFBButte.Entities.MissionarySupport") + .WithMany("CollegeRecommendations") + .HasForeignKey("MissionarySupportId"); + }); + + modelBuilder.Entity("OFBButte.Entities.Profile", b => + { + b.HasOne("OFBButte.Entities.ProfileFederationCode", "ProfileFederationCode") + .WithMany() + .HasForeignKey("ProfileFederationCodeId"); + }); + + modelBuilder.Entity("OFBButte.Entities.User", b => + { + b.HasOne("OFBButte.Entities.EmailVerificationCode", "EmailVerificationCode") + .WithMany() + .HasForeignKey("EmailVerificationCodeId"); + + b.HasOne("OFBButte.Entities.PasswordResetCode", "PassswordResetCode") + .WithMany() + .HasForeignKey("PassswordResetCodeId"); + + b.HasOne("OFBButte.Entities.Profile", "Profile") + .WithMany() + .HasForeignKey("ProfileId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OFBButte.Database/Migrations/20190717032510_Initial.cs b/OFBButte.Database/Migrations/20190717032510_Initial.cs new file mode 100644 index 0000000..670f398 --- /dev/null +++ b/OFBButte.Database/Migrations/20190717032510_Initial.cs @@ -0,0 +1,287 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace OFBButte.Database.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "EmailVerificationCodes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Code = table.Column(nullable: true), + CreatedDate = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_EmailVerificationCodes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MissionarySupportForms", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + HomePhone = table.Column(nullable: true), + CellPhone = table.Column(nullable: true), + FieldPhone = table.Column(nullable: true), + WifesName = table.Column(nullable: true), + Testimony = table.Column(nullable: true), + CallToField = table.Column(nullable: true), + SendingChurch = table.Column(nullable: true), + FieldOfService = table.Column(nullable: true), + Plans = table.Column(nullable: true), + EvaluationOfNationals = table.Column(nullable: true), + TimeInCountry = table.Column(nullable: true), + CorrectWrongOfAnotherMissionary = table.Column(nullable: true), + FinancialStatementPrevYear = table.Column(nullable: true), + CurrentMonthlySupport = table.Column(nullable: false), + MonthlySupportNeeded = table.Column(nullable: false), + RestAndRelaxation = table.Column(nullable: true), + AloneOrTeam = table.Column(nullable: true), + ChildrenSchool = table.Column(nullable: false), + Dance = table.Column(nullable: false), + WorldlyMusic = table.Column(nullable: false), + MovieTheaters = table.Column(nullable: false), + Alcohol = table.Column(nullable: false), + Smoking = table.Column(nullable: false), + MaleHair = table.Column(nullable: false), + FemaleSlacks = table.Column(nullable: false), + FemaleShorts = table.Column(nullable: false), + FemaleDressStandard = table.Column(nullable: false), + SwimmingClothing = table.Column(nullable: true), + Television = table.Column(nullable: true), + DailyBible = table.Column(nullable: false), + NumberLedToChrist = table.Column(nullable: false), + NumberWitnessedTo = table.Column(nullable: false), + NumberWeeklyTracts = table.Column(nullable: false), + RateOfSuccess = table.Column(nullable: true), + Predestination = table.Column(nullable: true), + FellowshipAssociation = table.Column(nullable: true), + AdmittedWrong = table.Column(nullable: true), + Divorced = table.Column(nullable: false), + GroundsForRemarry = table.Column(nullable: false), + MarryADivorcee = table.Column(nullable: false), + MasonicLodge = table.Column(nullable: false), + BibleVersionsUsed = table.Column(nullable: true), + BibleVersionOpinion = table.Column(nullable: true), + ContemporaryMusic = table.Column(nullable: false), + Charasmaticism = table.Column(nullable: true), + Tongues = table.Column(nullable: false), + RepentanceNecessary = table.Column(nullable: false), + RepentanceDefinition = table.Column(nullable: true), + Fundamentalist = table.Column(nullable: false), + BillsOnTime = table.Column(nullable: true), + LateBills = table.Column(nullable: true), + LateBillActionTaken = table.Column(nullable: true), + PotentialHarvest = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MissionarySupportForms", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "PasswordResetCodes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Code = table.Column(nullable: true), + CreatedDate = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PasswordResetCodes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ProfileFederationCodes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Code = table.Column(nullable: true), + CreatedDate = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProfileFederationCodes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MissionaryChild", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + MissionarySupportId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MissionaryChild", x => x.Id); + table.ForeignKey( + name: "FK_MissionaryChild_MissionarySupportForms_MissionarySupportId", + column: x => x.MissionarySupportId, + principalTable: "MissionarySupportForms", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "MissionaryCollegeRecommendation", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + MissionarySupportId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MissionaryCollegeRecommendation", x => x.Id); + table.ForeignKey( + name: "FK_MissionaryCollegeRecommendation_MissionarySupportForms_Missi~", + column: x => x.MissionarySupportId, + principalTable: "MissionarySupportForms", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Profiles", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ModifiedDate = table.Column(nullable: false), + FirstName = table.Column(nullable: true), + LastName = table.Column(nullable: true), + Street = table.Column(nullable: true), + City = table.Column(nullable: true), + State = table.Column(nullable: true), + Zip = table.Column(nullable: true), + Country = table.Column(nullable: true), + ProfileFederationCodeId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Profiles", x => x.Id); + table.ForeignKey( + name: "FK_Profiles_ProfileFederationCodes_ProfileFederationCodeId", + column: x => x.ProfileFederationCodeId, + principalTable: "ProfileFederationCodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Email = table.Column(nullable: false), + Password = table.Column(nullable: false), + EmailVerifiedDate = table.Column(nullable: true), + CreatedDate = table.Column(nullable: false), + DeletedDate = table.Column(nullable: true), + ProfileId = table.Column(nullable: true), + EmailVerificationCodeId = table.Column(nullable: true), + PassswordResetCodeId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_EmailVerificationCodes_EmailVerificationCodeId", + column: x => x.EmailVerificationCodeId, + principalTable: "EmailVerificationCodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Users_PasswordResetCodes_PassswordResetCodeId", + column: x => x.PassswordResetCodeId, + principalTable: "PasswordResetCodes", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_Users_Profiles_ProfileId", + column: x => x.ProfileId, + principalTable: "Profiles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_MissionaryChild_MissionarySupportId", + table: "MissionaryChild", + column: "MissionarySupportId"); + + migrationBuilder.CreateIndex( + name: "IX_MissionaryCollegeRecommendation_MissionarySupportId", + table: "MissionaryCollegeRecommendation", + column: "MissionarySupportId"); + + migrationBuilder.CreateIndex( + name: "IX_Profiles_ProfileFederationCodeId", + table: "Profiles", + column: "ProfileFederationCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email"); + + migrationBuilder.CreateIndex( + name: "IX_Users_EmailVerificationCodeId", + table: "Users", + column: "EmailVerificationCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_PassswordResetCodeId", + table: "Users", + column: "PassswordResetCodeId"); + + migrationBuilder.CreateIndex( + name: "IX_Users_ProfileId", + table: "Users", + column: "ProfileId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MissionaryChild"); + + migrationBuilder.DropTable( + name: "MissionaryCollegeRecommendation"); + + migrationBuilder.DropTable( + name: "Users"); + + migrationBuilder.DropTable( + name: "MissionarySupportForms"); + + migrationBuilder.DropTable( + name: "EmailVerificationCodes"); + + migrationBuilder.DropTable( + name: "PasswordResetCodes"); + + migrationBuilder.DropTable( + name: "Profiles"); + + migrationBuilder.DropTable( + name: "ProfileFederationCodes"); + } + } +} diff --git a/OFBButte.Database/Migrations/OFBContextModelSnapshot.cs b/OFBButte.Database/Migrations/OFBContextModelSnapshot.cs new file mode 100644 index 0000000..431169d --- /dev/null +++ b/OFBButte.Database/Migrations/OFBContextModelSnapshot.cs @@ -0,0 +1,316 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using OFBButte.Database; + +namespace OFBButte.Database.Migrations +{ + [DbContext(typeof(OFBContext))] + partial class OFBContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("OFBButte.Entities.EmailVerificationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("EmailVerificationCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryChild", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionarySupportId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("MissionarySupportId"); + + b.ToTable("MissionaryChild"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryCollegeRecommendation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MissionarySupportId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("MissionarySupportId"); + + b.ToTable("MissionaryCollegeRecommendation"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionarySupport", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdmittedWrong"); + + b.Property("Alcohol"); + + b.Property("AloneOrTeam"); + + b.Property("BibleVersionOpinion"); + + b.Property("BibleVersionsUsed"); + + b.Property("BillsOnTime"); + + b.Property("CallToField"); + + b.Property("CellPhone"); + + b.Property("Charasmaticism"); + + b.Property("ChildrenSchool"); + + b.Property("ContemporaryMusic"); + + b.Property("CorrectWrongOfAnotherMissionary"); + + b.Property("CurrentMonthlySupport"); + + b.Property("DailyBible"); + + b.Property("Dance"); + + b.Property("Divorced"); + + b.Property("EvaluationOfNationals"); + + b.Property("FellowshipAssociation"); + + b.Property("FemaleDressStandard"); + + b.Property("FemaleShorts"); + + b.Property("FemaleSlacks"); + + b.Property("FieldOfService"); + + b.Property("FieldPhone"); + + b.Property("FinancialStatementPrevYear"); + + b.Property("Fundamentalist"); + + b.Property("GroundsForRemarry"); + + b.Property("HomePhone"); + + b.Property("LateBillActionTaken"); + + b.Property("LateBills"); + + b.Property("MaleHair"); + + b.Property("MarryADivorcee"); + + b.Property("MasonicLodge"); + + b.Property("MonthlySupportNeeded"); + + b.Property("MovieTheaters"); + + b.Property("Name"); + + b.Property("NumberLedToChrist"); + + b.Property("NumberWeeklyTracts"); + + b.Property("NumberWitnessedTo"); + + b.Property("Plans"); + + b.Property("PotentialHarvest"); + + b.Property("Predestination"); + + b.Property("RateOfSuccess"); + + b.Property("RepentanceDefinition"); + + b.Property("RepentanceNecessary"); + + b.Property("RestAndRelaxation"); + + b.Property("SendingChurch"); + + b.Property("Smoking"); + + b.Property("SwimmingClothing"); + + b.Property("Television"); + + b.Property("Testimony"); + + b.Property("TimeInCountry"); + + b.Property("Tongues"); + + b.Property("WifesName"); + + b.Property("WorldlyMusic"); + + b.HasKey("Id"); + + b.ToTable("MissionarySupportForms"); + }); + + modelBuilder.Entity("OFBButte.Entities.PasswordResetCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("PasswordResetCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.Profile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("City"); + + b.Property("Country"); + + b.Property("FirstName"); + + b.Property("LastName"); + + b.Property("ModifiedDate"); + + b.Property("ProfileFederationCodeId"); + + b.Property("State"); + + b.Property("Street"); + + b.Property("Zip"); + + b.HasKey("Id"); + + b.HasIndex("ProfileFederationCodeId"); + + b.ToTable("Profiles"); + }); + + modelBuilder.Entity("OFBButte.Entities.ProfileFederationCode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Code"); + + b.Property("CreatedDate"); + + b.HasKey("Id"); + + b.ToTable("ProfileFederationCodes"); + }); + + modelBuilder.Entity("OFBButte.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CreatedDate"); + + b.Property("DeletedDate"); + + b.Property("Email") + .IsRequired(); + + b.Property("EmailVerificationCodeId"); + + b.Property("EmailVerifiedDate"); + + b.Property("PassswordResetCodeId"); + + b.Property("Password") + .IsRequired(); + + b.Property("ProfileId"); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("EmailVerificationCodeId"); + + b.HasIndex("PassswordResetCodeId"); + + b.HasIndex("ProfileId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryChild", b => + { + b.HasOne("OFBButte.Entities.MissionarySupport") + .WithMany("Children") + .HasForeignKey("MissionarySupportId"); + }); + + modelBuilder.Entity("OFBButte.Entities.MissionaryCollegeRecommendation", b => + { + b.HasOne("OFBButte.Entities.MissionarySupport") + .WithMany("CollegeRecommendations") + .HasForeignKey("MissionarySupportId"); + }); + + modelBuilder.Entity("OFBButte.Entities.Profile", b => + { + b.HasOne("OFBButte.Entities.ProfileFederationCode", "ProfileFederationCode") + .WithMany() + .HasForeignKey("ProfileFederationCodeId"); + }); + + modelBuilder.Entity("OFBButte.Entities.User", b => + { + b.HasOne("OFBButte.Entities.EmailVerificationCode", "EmailVerificationCode") + .WithMany() + .HasForeignKey("EmailVerificationCodeId"); + + b.HasOne("OFBButte.Entities.PasswordResetCode", "PassswordResetCode") + .WithMany() + .HasForeignKey("PassswordResetCodeId"); + + b.HasOne("OFBButte.Entities.Profile", "Profile") + .WithMany() + .HasForeignKey("ProfileId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/OFBButte.Database/OFBButte.Database.csproj b/OFBButte.Database/OFBButte.Database.csproj new file mode 100644 index 0000000..0ef879c --- /dev/null +++ b/OFBButte.Database/OFBButte.Database.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + diff --git a/OFBButte.Database/OFBContext/OFBContext.cs b/OFBButte.Database/OFBContext/OFBContext.cs new file mode 100644 index 0000000..878c0ea --- /dev/null +++ b/OFBButte.Database/OFBContext/OFBContext.cs @@ -0,0 +1,66 @@ +using Microsoft.EntityFrameworkCore; +using OFBButte.Application.Database; +using OFBButte.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Database +{ + public class OFBContext : DbContext, IOFBContext + { + public OFBContext(DbContextOptions options) + : base(options) + { } + + public static void UseMySql(DbContextOptionsBuilder optionsBuilder, string connString) + { + optionsBuilder.UseMySql(connString); + } + + public DbSet Users { get; set; } + public DbSet Profiles { get; set; } + public DbSet ProfileFederationCodes { get; set; } + public DbSet EmailVerificationCodes { get; set; } + public DbSet PasswordResetCodes { get; set; } + public DbSet MissionarySupportForms { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(e => + { + e.HasKey(x => x.Id); + e.HasIndex(x => x.Email); + e.Property(x => x.Email).IsRequired(); + e.Property(x => x.Password).IsRequired(); + e.Property(x => x.CreatedDate).IsRequired(); + e.HasOne(x => x.EmailVerificationCode); + e.HasOne(x => x.Profile); + e.HasOne(x => x.PassswordResetCode); + }); + + modelBuilder.Entity(e => { + e.HasKey(x => x.Id); + }); + + modelBuilder.Entity(e => { + e.HasKey(x => x.Id); + e.HasOne(x => x.ProfileFederationCode); + }); + + modelBuilder.Entity(e => { + e.HasKey(x => x.Id); + }); + + modelBuilder.Entity(e => { + e.HasKey(x => x.Id); + }); + + modelBuilder.Entity(e => { + e.HasKey(x => x.Id); + }); + } + } +} diff --git a/OFBButte.Entities/EmailVerificationCode.cs b/OFBButte.Entities/EmailVerificationCode.cs new file mode 100644 index 0000000..22fcce1 --- /dev/null +++ b/OFBButte.Entities/EmailVerificationCode.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class EmailVerificationCode + { + public int Id { get; set; } + public string Code { get; set; } + public DateTime CreatedDate { get; set; } + } +} diff --git a/OFBButte.Entities/Enums/BibleReading.cs b/OFBButte.Entities/Enums/BibleReading.cs new file mode 100644 index 0000000..a1eb016 --- /dev/null +++ b/OFBButte.Entities/Enums/BibleReading.cs @@ -0,0 +1,9 @@ +namespace OFBButte.Entities +{ + public enum BibleReading + { + Always = 1, + Sometimes = 2, + Rarely = 3 + } +} diff --git a/OFBButte.Entities/Enums/ChildrenSchool.cs b/OFBButte.Entities/Enums/ChildrenSchool.cs new file mode 100644 index 0000000..89e6df8 --- /dev/null +++ b/OFBButte.Entities/Enums/ChildrenSchool.cs @@ -0,0 +1,10 @@ +namespace OFBButte.Entities +{ + public enum ChildrenSchool + { + Christian = 1, + Public = 2, + Home = 3, + Other = 5 + } +} diff --git a/OFBButte.Entities/Enums/FemaleShorts.cs b/OFBButte.Entities/Enums/FemaleShorts.cs new file mode 100644 index 0000000..59e4661 --- /dev/null +++ b/OFBButte.Entities/Enums/FemaleShorts.cs @@ -0,0 +1,10 @@ +namespace OFBButte.Entities +{ + public enum FemaleShorts + { + Rarely = 1, + Often = 2, + Sports = 3, + Never = 4 + } +} diff --git a/OFBButte.Entities/Enums/FemaleSlacks.cs b/OFBButte.Entities/Enums/FemaleSlacks.cs new file mode 100644 index 0000000..ba3b0a4 --- /dev/null +++ b/OFBButte.Entities/Enums/FemaleSlacks.cs @@ -0,0 +1,10 @@ +namespace OFBButte.Entities +{ + public enum FemaleSlacks + { + Rarely = 1, + Often = 2, + Sports = 3, + Never = 4 + } +} diff --git a/OFBButte.Entities/MissionaryChild.cs b/OFBButte.Entities/MissionaryChild.cs new file mode 100644 index 0000000..72b1583 --- /dev/null +++ b/OFBButte.Entities/MissionaryChild.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class MissionaryChild + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/OFBButte.Entities/MissionaryCollegeRecommendation.cs b/OFBButte.Entities/MissionaryCollegeRecommendation.cs new file mode 100644 index 0000000..759d0c5 --- /dev/null +++ b/OFBButte.Entities/MissionaryCollegeRecommendation.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class MissionaryCollegeRecommendation + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/OFBButte.Entities/MissionarySupport.cs b/OFBButte.Entities/MissionarySupport.cs new file mode 100644 index 0000000..f711a94 --- /dev/null +++ b/OFBButte.Entities/MissionarySupport.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class MissionarySupport + { + public int Id { get; set; } + public string Name { get; set; } + public string HomePhone { get; set; } + public string CellPhone { get; set; } + public string FieldPhone { get; set; } + public string WifesName { get; set; } + public MissionaryChild[] Children { get; set; } + public string Testimony { get; set; } + public string CallToField { get; set; } + public string SendingChurch { get; set; } + public string FieldOfService { get; set; } + public string Plans { get; set; } + public string EvaluationOfNationals { get; set; } + public string TimeInCountry { get; set; } + public string CorrectWrongOfAnotherMissionary { get; set; } + public string FinancialStatementPrevYear { get; set; } + public decimal CurrentMonthlySupport { get; set; } + public decimal MonthlySupportNeeded { get; set; } + public string RestAndRelaxation { get; set; } + public string AloneOrTeam { get; set; } + public ChildrenSchool ChildrenSchool { get; set; } + public bool Dance { get; set; } + public bool WorldlyMusic { get; set; } + public bool MovieTheaters { get; set; } + public bool Alcohol { get; set; } + public bool Smoking { get; set; } + public bool MaleHair { get; set; } + public FemaleSlacks FemaleSlacks { get; set; } + public FemaleShorts FemaleShorts { get; set; } + public bool FemaleDressStandard { get; set; } + public string SwimmingClothing { get; set; } + public string Television { get; set; } + public BibleReading DailyBible { get; set; } + public double NumberLedToChrist { get; set; } + public double NumberWitnessedTo { get; set; } + public double NumberWeeklyTracts { get; set; } + public string RateOfSuccess { get; set; } + public string Predestination { get; set; } + public string FellowshipAssociation { get; set; } + public MissionaryCollegeRecommendation[] CollegeRecommendations { get; set; } + public string AdmittedWrong { get; set; } + public bool Divorced { get; set; } + public bool GroundsForRemarry { get; set; } + public bool MarryADivorcee { get; set; } + public bool MasonicLodge { get; set; } + public string BibleVersionsUsed { get; set; } + public string BibleVersionOpinion { get; set; } + public bool ContemporaryMusic { get; set; } + public string Charasmaticism { get; set; } + public bool Tongues { get; set; } + public bool RepentanceNecessary { get; set; } + public string RepentanceDefinition { get; set; } + public bool Fundamentalist { get; set; } + public string BillsOnTime { get; set; } + public string LateBills { get; set; } + public string LateBillActionTaken { get; set; } + public string PotentialHarvest { get; set; } + + + } +} diff --git a/OFBButte.Entities/OFBButte.Entities.csproj b/OFBButte.Entities/OFBButte.Entities.csproj new file mode 100644 index 0000000..c16c6d5 --- /dev/null +++ b/OFBButte.Entities/OFBButte.Entities.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.2 + + + diff --git a/OFBButte.Entities/PasswordResetCode.cs b/OFBButte.Entities/PasswordResetCode.cs new file mode 100644 index 0000000..6867725 --- /dev/null +++ b/OFBButte.Entities/PasswordResetCode.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class PasswordResetCode + { + public int Id { get; set; } + public string Code { get; set; } + public DateTime CreatedDate { get; set; } + } +} diff --git a/OFBButte.Entities/Profile.cs b/OFBButte.Entities/Profile.cs new file mode 100644 index 0000000..0d149be --- /dev/null +++ b/OFBButte.Entities/Profile.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class Profile + { + public int Id { get; set; } + public DateTime ModifiedDate { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Street { get; set; } + public string City { get; set; } + public string State { get; set; } + public string Zip { get; set; } + public string Country { get; set; } + + public ProfileFederationCode ProfileFederationCode { get; set; } + } +} diff --git a/OFBButte.Entities/ProfileFederationCode.cs b/OFBButte.Entities/ProfileFederationCode.cs new file mode 100644 index 0000000..57d88e0 --- /dev/null +++ b/OFBButte.Entities/ProfileFederationCode.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class ProfileFederationCode + { + public int Id { get; set; } + public string Code { get; set; } + public DateTime CreatedDate { get; set; } + } +} diff --git a/OFBButte.Entities/User.cs b/OFBButte.Entities/User.cs new file mode 100644 index 0000000..59e5e66 --- /dev/null +++ b/OFBButte.Entities/User.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Entities +{ + public class User + { + public int Id { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public DateTime? EmailVerifiedDate { get; set; } + + public DateTime CreatedDate { get; set; } + public DateTime? DeletedDate { get; set; } + + public Profile Profile { get; set; } + public EmailVerificationCode EmailVerificationCode { get; set; } + public PasswordResetCode PassswordResetCode { get; set; } + } +} diff --git a/OFBButte.Infrastructure/Codes/CodeGenerator.cs b/OFBButte.Infrastructure/Codes/CodeGenerator.cs new file mode 100644 index 0000000..5f3c485 --- /dev/null +++ b/OFBButte.Infrastructure/Codes/CodeGenerator.cs @@ -0,0 +1,19 @@ +using OFBButte.Application.Codes; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace OFBButte.Infrastructure.Codes +{ + public class CodeGenerator: ICodeGenerator + { + public string Generate(int byteSize = 16) + { + byte[] codeBytes = new byte[byteSize]; + RandomNumberGenerator.Create().GetBytes(codeBytes); + string code = Convert.ToBase64String(codeBytes).TrimEnd('=').Replace('+', '-').Replace('/', '_'); + return code; + } + } +} diff --git a/OFBButte.Infrastructure/Email/EmailSender.cs b/OFBButte.Infrastructure/Email/EmailSender.cs new file mode 100644 index 0000000..abcc5db --- /dev/null +++ b/OFBButte.Infrastructure/Email/EmailSender.cs @@ -0,0 +1,44 @@ +using MailKit.Net.Smtp; +using MimeKit; +using OFBButte.Application.Email; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OFBButte.Infrastructure.Email +{ + public class EmailSender : IEmailSender, IDisposable + { + private readonly SmtpClient client; + private readonly bool isTest; + + public EmailSender(bool isTest = true) + { + this.isTest = isTest; + client = new SmtpClient(); + } + + public async Task Send(EmailMessage message) + { + message.SetBodyFromTemplate(isTest); + var mm = new MimeMessage(); + mm.From.Add(new MailboxAddress(message.From)); + mm.To.Add(new MailboxAddress(message.To)); + mm.Subject = message.Subject; + var bodyBuilder = new BodyBuilder(); + bodyBuilder.HtmlBody = message.Body; + mm.Body = bodyBuilder.ToMessageBody(); + + client.Connect("smtp.webfaction.com", 465, true); + client.Authenticate("ofbcontact", "2014OfbPwd"); + await client.SendAsync(mm); + client.Disconnect(true); + } + + public void Dispose() + { + client.Dispose(); + } + } +} diff --git a/OFBButte.Infrastructure/Hashing/PasswordHasher.cs b/OFBButte.Infrastructure/Hashing/PasswordHasher.cs new file mode 100644 index 0000000..f86c121 --- /dev/null +++ b/OFBButte.Infrastructure/Hashing/PasswordHasher.cs @@ -0,0 +1,215 @@ +using Microsoft.AspNetCore.Cryptography.KeyDerivation; +using OFBButte.Application.Users; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; + +namespace OFBButte.Infrastructure.Hashing +{ + /// + /// Implements the standard Identity password hashing. + /// + public class PasswordHasher: IPasswordHasher + { + /* ======================= + * HASHED PASSWORD FORMATS + * ======================= + * + * Version 3: + * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. + * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } + * (All UInt32s are stored big-endian.) + */ + + private readonly int _iterCount; + private readonly RandomNumberGenerator _rng; + + /// + /// Creates a new instance of . + /// + /// The options for this instance. + public PasswordHasher(PasswordHasherOptions optionsAccessor = null) + { + var options = optionsAccessor ?? new PasswordHasherOptions(); + + _iterCount = options.IterationCount; + _rng = options.Rng; + } + + // Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized. + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static bool ByteArraysEqual(byte[] a, byte[] b) + { + if (a == null && b == null) + { + return true; + } + if (a == null || b == null || a.Length != b.Length) + { + return false; + } + var areSame = true; + for (var i = 0; i < a.Length; i++) + { + areSame &= (a[i] == b[i]); + } + return areSame; + } + + /// + /// Returns a hashed representation of the supplied for the specified . + /// + /// The password to hash. + /// A hashed representation of the supplied for the specified . + public virtual string HashPassword(string password) + { + if (password == null) + { + throw new ArgumentNullException(nameof(password)); + } + + return Convert.ToBase64String(HashPasswordV3(password, _rng)); + } + + private byte[] HashPasswordV3(string password, RandomNumberGenerator rng) + { + return HashPasswordV3(password, rng, + prf: KeyDerivationPrf.HMACSHA256, + iterCount: _iterCount, + saltSize: 128 / 8, + numBytesRequested: 256 / 8); + } + + private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng, KeyDerivationPrf prf, int iterCount, int saltSize, int numBytesRequested) + { + // Produce a version 3 (see comment above) text hash. + byte[] salt = new byte[saltSize]; + rng.GetBytes(salt); + byte[] subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested); + + var outputBytes = new byte[13 + salt.Length + subkey.Length]; + outputBytes[0] = 0x01; // format marker + WriteNetworkByteOrder(outputBytes, 1, (uint)prf); + WriteNetworkByteOrder(outputBytes, 5, (uint)iterCount); + WriteNetworkByteOrder(outputBytes, 9, (uint)saltSize); + Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length); + Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length); + return outputBytes; + } + + private static uint ReadNetworkByteOrder(byte[] buffer, int offset) + { + return ((uint)(buffer[offset + 0]) << 24) + | ((uint)(buffer[offset + 1]) << 16) + | ((uint)(buffer[offset + 2]) << 8) + | ((uint)(buffer[offset + 3])); + } + + public bool VerifyPassword(string hashedPassword, string password) + { + var result = VerifyHashedPassword(hashedPassword, password); + return result == PasswordVerificationResult.Success; + } + + /// + /// Returns a indicating the result of a password hash comparison. + /// + /// The user whose password should be verified. + /// The hash value for a user's stored password. + /// The password supplied for comparison. + /// A indicating the result of a password hash comparison. + /// Implementations of this method should be time consistent. + public virtual PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) + { + if (hashedPassword == null) + { + throw new ArgumentNullException(nameof(hashedPassword)); + } + if (providedPassword == null) + { + throw new ArgumentNullException(nameof(providedPassword)); + } + + byte[] decodedHashedPassword = Convert.FromBase64String(hashedPassword); + + // read the format marker from the hashed password + if (decodedHashedPassword.Length == 0) + { + return PasswordVerificationResult.Failed; + } + switch (decodedHashedPassword[0]) + { + case 0x00: + return PasswordVerificationResult.Failed; + + case 0x01: + int embeddedIterCount; + if (VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, out embeddedIterCount)) + { + // If this hasher was configured with a higher iteration count, change the entry now. + return (embeddedIterCount < _iterCount) + ? PasswordVerificationResult.SuccessRehashNeeded + : PasswordVerificationResult.Success; + } + else + { + return PasswordVerificationResult.Failed; + } + + default: + return PasswordVerificationResult.Failed; // unknown format marker + } + } + + private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, out int iterCount) + { + iterCount = default(int); + + try + { + // Read header information + KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 1); + iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5); + int saltLength = (int)ReadNetworkByteOrder(hashedPassword, 9); + + // Read the salt: must be >= 128 bits + if (saltLength < 128 / 8) + { + return false; + } + byte[] salt = new byte[saltLength]; + Buffer.BlockCopy(hashedPassword, 13, salt, 0, salt.Length); + + // Read the subkey (the rest of the payload): must be >= 128 bits + int subkeyLength = hashedPassword.Length - 13 - salt.Length; + if (subkeyLength < 128 / 8) + { + return false; + } + byte[] expectedSubkey = new byte[subkeyLength]; + Buffer.BlockCopy(hashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length); + + // Hash the incoming password and verify it + byte[] actualSubkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, subkeyLength); + return ByteArraysEqual(actualSubkey, expectedSubkey); + } + catch + { + // This should never occur except in the case of a malformed payload, where + // we might go off the end of the array. Regardless, a malformed payload + // implies verification failed. + return false; + } + } + + private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value) + { + buffer[offset + 0] = (byte)(value >> 24); + buffer[offset + 1] = (byte)(value >> 16); + buffer[offset + 2] = (byte)(value >> 8); + buffer[offset + 3] = (byte)(value >> 0); + } + } +} diff --git a/OFBButte.Infrastructure/Hashing/PasswordHasherOptions.cs b/OFBButte.Infrastructure/Hashing/PasswordHasherOptions.cs new file mode 100644 index 0000000..f82e7ad --- /dev/null +++ b/OFBButte.Infrastructure/Hashing/PasswordHasherOptions.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace OFBButte.Infrastructure.Hashing +{ + /// + /// Specifies options for password hashing. + /// + public class PasswordHasherOptions + { + private static readonly RandomNumberGenerator _defaultRng = RandomNumberGenerator.Create(); // secure PRNG + + /// + /// Gets or sets the number of iterations used when hashing passwords using PBKDF2. Default is 10,000. + /// + /// + /// The number of iterations used when hashing passwords using PBKDF2. + /// + /// + /// This value is only used when the compatibility mode is set to 'V3'. + /// The value must be a positive integer. + /// + public int IterationCount { get; set; } = 10000; + + // for unit testing + internal RandomNumberGenerator Rng { get; set; } = _defaultRng; + } +} diff --git a/OFBButte.Infrastructure/Hashing/PasswordVerificationResult.cs b/OFBButte.Infrastructure/Hashing/PasswordVerificationResult.cs new file mode 100644 index 0000000..54b016d --- /dev/null +++ b/OFBButte.Infrastructure/Hashing/PasswordVerificationResult.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace OFBButte.Infrastructure.Hashing +{ + // + /// Specifies the results for password verification. + /// + public enum PasswordVerificationResult + { + /// + /// Indicates password verification failed. + /// + Failed = 0, + + /// + /// Indicates password verification was successful. + /// + Success = 1, + + /// + /// Indicates password verification was successful however the password was encoded using a deprecated algorithm + /// and should be rehashed and updated. + /// + SuccessRehashNeeded = 2 + } +} diff --git a/OFBButte.Infrastructure/OFBButte.Infrastructure.csproj b/OFBButte.Infrastructure/OFBButte.Infrastructure.csproj new file mode 100644 index 0000000..4264bf8 --- /dev/null +++ b/OFBButte.Infrastructure/OFBButte.Infrastructure.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + diff --git a/OFBButte.sln b/OFBButte.sln new file mode 100644 index 0000000..003b3f5 --- /dev/null +++ b/OFBButte.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29102.190 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Api", "OFBButte.Api\OFBButte.Api.csproj", "{FF5F4D78-6419-47E4-A7CE-5B25503BE2D7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Application", "OFBButte.Application\OFBButte.Application.csproj", "{8BCA31C6-C10D-4865-BAE8-6DC932B4797D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Console", "OFBButte.Console\OFBButte.Console.csproj", "{82FA36EC-1CBC-4F9A-B436-A61D9AA6D1C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Database", "OFBButte.Database\OFBButte.Database.csproj", "{64A05F44-4BB2-45CE-A343-6307EA8F099A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Infrastructure", "OFBButte.Infrastructure\OFBButte.Infrastructure.csproj", "{157A1CB3-9320-4FE6-ABC5-1C9EF1952540}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OFBButte.Entities", "OFBButte.Entities\OFBButte.Entities.csproj", "{39A5BAF9-E108-4B81-8DBB-A7C1A6CEA9EF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FF5F4D78-6419-47E4-A7CE-5B25503BE2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF5F4D78-6419-47E4-A7CE-5B25503BE2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF5F4D78-6419-47E4-A7CE-5B25503BE2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF5F4D78-6419-47E4-A7CE-5B25503BE2D7}.Release|Any CPU.Build.0 = Release|Any CPU + {8BCA31C6-C10D-4865-BAE8-6DC932B4797D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BCA31C6-C10D-4865-BAE8-6DC932B4797D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BCA31C6-C10D-4865-BAE8-6DC932B4797D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BCA31C6-C10D-4865-BAE8-6DC932B4797D}.Release|Any CPU.Build.0 = Release|Any CPU + {82FA36EC-1CBC-4F9A-B436-A61D9AA6D1C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82FA36EC-1CBC-4F9A-B436-A61D9AA6D1C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82FA36EC-1CBC-4F9A-B436-A61D9AA6D1C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82FA36EC-1CBC-4F9A-B436-A61D9AA6D1C7}.Release|Any CPU.Build.0 = Release|Any CPU + {64A05F44-4BB2-45CE-A343-6307EA8F099A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64A05F44-4BB2-45CE-A343-6307EA8F099A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64A05F44-4BB2-45CE-A343-6307EA8F099A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64A05F44-4BB2-45CE-A343-6307EA8F099A}.Release|Any CPU.Build.0 = Release|Any CPU + {157A1CB3-9320-4FE6-ABC5-1C9EF1952540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {157A1CB3-9320-4FE6-ABC5-1C9EF1952540}.Debug|Any CPU.Build.0 = Debug|Any CPU + {157A1CB3-9320-4FE6-ABC5-1C9EF1952540}.Release|Any CPU.ActiveCfg = Release|Any CPU + {157A1CB3-9320-4FE6-ABC5-1C9EF1952540}.Release|Any CPU.Build.0 = Release|Any CPU + {39A5BAF9-E108-4B81-8DBB-A7C1A6CEA9EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39A5BAF9-E108-4B81-8DBB-A7C1A6CEA9EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39A5BAF9-E108-4B81-8DBB-A7C1A6CEA9EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39A5BAF9-E108-4B81-8DBB-A7C1A6CEA9EF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CD719D07-AABB-44D8-84CD-705EDF224EB9} + EndGlobalSection +EndGlobal