Ported progress from SPA and began scaffolding Identity UI.

Laid some groundwork in backend.

Began customizing Identity UI.
This commit is contained in:
Harrison Deng 2021-07-21 01:58:49 -05:00
parent 57f67391f1
commit b43d7bab84
44 changed files with 1225 additions and 620 deletions

View File

@ -37,6 +37,15 @@
"/consoleloggerparameters:NoSummary" "/consoleloggerparameters:NoSummary"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
},
{
"label": "reset database",
"command": "py",
"type": "process",
"args": [
"./scripts/reset_db.py"
],
"problemMatcher": []
} }
] ]
} }

View File

@ -0,0 +1,7 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
if (userId == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
return Page();
}
}
}

View File

@ -5,65 +5,54 @@
ViewData["Title"] = "Log in"; ViewData["Title"] = "Log in";
} }
<h1>@ViewData["Title"]</h1> <div class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="row"> <div class="jumbotron border-top border-bottom">
<div class="col-md-4"> <h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
<section> <div class="my-3 row justify-content-md-center">
<form id="account" method="post"> <div class="col-md-4">
<h4>Use a local account to log in.</h4> <form id="account" method="post">
<hr /> <h4>Use a local account to log in.</h4>
<div asp-validation-summary="All" class="text-danger"></div> <hr />
<div class="form-group"> <div asp-validation-summary="All" class="text-danger"></div>
<label asp-for="Input.Email"></label> <div class="mb-3">
<input asp-for="Input.Email" class="form-control" /> <label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span> <input asp-for="Input.Email" class="form-control" />
</div> <span asp-validation-for="Input.Email" class="text-danger"></span>
<div class="form-group"> </div>
<label asp-for="Input.Password"></label> <div class="mb-3">
<input asp-for="Input.Password" class="form-control" /> <label asp-for="Input.Password" class="form-label"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span> <input asp-for="Input.Password" class="form-control" />
</div> <span asp-validation-for="Input.Password" class="text-danger"></span>
<div class="form-group"> </div>
<div class="checkbox"> <div class="mb-3">
<label asp-for="Input.RememberMe"> <div class="form-check">
<input asp-for="Input.RememberMe" /> <input asp-for="Input.RememberMe" class="form-check-input" />
@Html.DisplayNameFor(m => m.Input.RememberMe) <label asp-for="Input.RememberMe" class="form-check-label">
</label> @Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Log in</button>
</div> </div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Log in</button>
</div>
<div class="form-group">
<p>
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
<p>
<a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
</p>
</div>
</form>
</section>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to log in.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div> <div>
<p> <div class="my-1">
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a> <a class="link-secondary" id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
for details on setting up this ASP.NET application to support logging in via external services. </div>
</p> <div class="my-1">
<a class="link-secondary" asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</div>
<div class="my-1">
<a class="link-secondary" id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
</div>
</div> </div>
} </form>
else </div>
{ @if ((Model.ExternalLogins?.Count ?? 0) != 0)
{
<div class="col-md-6 md-offset-2">
<h4>Use another service to log in.</h4>
<hr />
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div> <div>
<p> <p>
@ -74,12 +63,13 @@
</p> </p>
</div> </div>
</form> </form>
} </div>
} }
</section> </div>
</div> </div>
</div> </div>
@section Scripts { @section Scripts {
<partial name="_ValidationScriptsPartial" /> <partial name="_ValidationScriptsPartial" />
} }

View File

@ -4,14 +4,15 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Props.Models; using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account namespace Props.Areas.Identity.Pages.Account
{ {

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Props.Models; using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account namespace Props.Areas.Identity.Pages.Account
{ {

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Props.Areas.Identity.Pages.Account.Manage
{
public static class ManageNavPages
{
public static string Index => "Index";
public static string Email => "Email";
public static string ChangePassword => "ChangePassword";
public static string DownloadPersonalData => "DownloadPersonalData";
public static string DeletePersonalData => "DeletePersonalData";
public static string ExternalLogins => "ExternalLogins";
public static string PersonalData => "PersonalData";
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
private static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
}
}

View File

@ -0,0 +1,32 @@
@{
if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
{
Layout = (string)parentLayout;
}
else
{
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
}
}
<div class="container">
<h1>Manage your account</h1>
<div>
<h4>Change your account settings</h4>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}

View File

@ -0,0 +1,15 @@
@inject SignInManager<ApplicationUser> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>

View File

@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@Model
</div>
}

View File

@ -0,0 +1 @@
@using Props.Areas.Identity.Pages.Account.Manage

View File

@ -4,48 +4,38 @@
ViewData["Title"] = "Register"; ViewData["Title"] = "Register";
} }
<h1>@ViewData["Title"]</h1> <div class="flex-grow-1 d-flex flex-column justify-content-center">
<div class="jumbotron border-top border-bottom">
<div class="row"> <h1 class="mx-auto mt-3 mb-4 text-center">@ViewData["Title"]</h1>
<div class="col-md-4"> <div class="my-3 row justify-content-md-center">
<form asp-route-returnUrl="@Model.ReturnUrl" method="post"> <div class="col-md-4">
<h4>Create a new account.</h4> <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
<hr /> <h4>Create a new account.</h4>
<div asp-validation-summary="All" class="text-danger"></div> <hr />
<div class="form-group"> <div asp-validation-summary="All" class="text-danger"></div>
<label asp-for="Input.Email"></label> <div class="mb-3">
<input asp-for="Input.Email" class="form-control" /> <label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span> <input asp-for="Input.Email" class="form-control" />
</div> <span asp-validation-for="Input.Email" class="text-danger"></span>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.ConfirmPassword"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h4>Use another service to register.</h4>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div> </div>
} <div class="mb-3">
else <label asp-for="Input.Password" class="form-label"></label>
{ <input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
<input asp-for="Input.ConfirmPassword" class="form-control" />
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
@if ((Model.ExternalLogins?.Count ?? 0) != 0)
{
<div class="col-md-6 md-offset-2">
<h4>Use another service to register.</h4>
<hr />
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal"> <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div> <div>
<p> <p>
@ -56,12 +46,13 @@
</p> </p>
</div> </div>
</form> </form>
} </div>
} }
</section> </div>
</div> </div>
</div> </div>
@section Scripts { @section Scripts {
<partial name="_ValidationScriptsPartial" /> <partial name="_ValidationScriptsPartial" />
} }

View File

@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Props.Models; using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account namespace Props.Areas.Identity.Pages.Account
{ {

View File

@ -8,15 +8,16 @@
@{ @{
if (@Model.DisplayConfirmAccountLink) if (@Model.DisplayConfirmAccountLink)
{ {
<p> <p>
This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender. This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a> Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a>
</p> </p>
} }
else else
{ {
<p> <p>
Please check your email to confirm your account. Please check your email to confirm your account.
</p> </p>
} }
} }
@* TODO: https://aka.ms/aspaccountconf *@

View File

@ -1,12 +1,13 @@
using Microsoft.AspNetCore.Authorization;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities; using Microsoft.AspNetCore.WebUtilities;
using Props.Models; using Props.Models;
using Props.Models.User;
namespace Props.Areas.Identity.Pages.Account namespace Props.Areas.Identity.Pages.Account
{ {

View File

@ -2,4 +2,4 @@
@using Props.Areas.Identity @using Props.Areas.Identity
@using Props.Areas.Identity.Pages @using Props.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using Props.Models @using Props.Models.User

View File

@ -1,27 +0,0 @@
{
"features": {
"description": "Props strives to be a platform where people can find and organize the material they need to complete their projects. Our features are therefore tailored to the community helping each other, as well as smart tools to quickly compare different products from different stores.",
"list": [
{
"title": "Shopping List",
"subtitle": "We'll do it so you don't need to.",
"text": "We'll help you track the components you need. You can add descriptions, and mark things as purchased, or shipped at your convenience. You can even add components from stores that Props doesn't know about yet!"
},
{
"title": "Product Comparison",
"subtitle": "So many shops to look through...",
"text": "There's so many online retailers nowadays that it's becoming more and more work to check all of them for what you need. With us, we'll do the searching for you. All you need to do is decide if the shipping time, cost, ratings and reviews are suitable!"
},
{
"title": "Auto Listing Search",
"subtitle": "Need a starting point?",
"text": "Some project parts commonly used. We'll try and find these parts for you automatically. This means you can create a shopping list, and we'll try and the best fitting products for you based on your shopping list entries."
},
{
"title": "Share It!",
"subtitle": "Show off your work!",
"text": "Have a project that you're excited about? Want to tell all your friends? We'll help. Each of your projects has a privacy setting. You can have it publically listed, unlisted, or completely private. Short link included."
}
]
}
}

View File

@ -4,5 +4,31 @@
"title": "Getting Started", "title": "Getting Started",
"searchIntroduction": "Props is a site designed to help with the online project component shopping experience. Create project component lists and search across multiple commonly used online retail stores to find the ideal purchase.", "searchIntroduction": "Props is a site designed to help with the online project component shopping experience. Create project component lists and search across multiple commonly used online retail stores to find the ideal purchase.",
"additionalInfo": "Take advantage of our project component manager, where you can add detailed descriptions of what things are for, as well as overall project summaries." "additionalInfo": "Take advantage of our project component manager, where you can add detailed descriptions of what things are for, as well as overall project summaries."
},
"mission": "Our mission is to bring become digital project workshop for anyone who likes Do It Yourself (DIY), Internet Of Things (IOT), or really any project that needs a home for all the materials, ideas, and thoughts. We also strive to build a community of builders, crafters and creators in hopes that we can all learn from each other's projects.",
"features": {
"description": "Props strives to be a platform where people can find and organize the material they need to complete their projects. Our features are therefore tailored to the community helping each other, as well as smart tools to quickly compare different products from different stores.",
"list": [
{
"title": "Shopping List",
"subtitle": "We'll do it so you don't need to.",
"text": "We'll help you track the components you need. You can add descriptions, and mark things as purchased, or shipped at your convenience. You can even add components from stores that Props doesn't know about yet!"
},
{
"title": "Product Comparison",
"subtitle": "So many shops to look through...",
"text": "There's so many online retailers nowadays that it's becoming more and more work to check all of them for what you need. With us, we'll do the searching for you. All you need to do is decide if the shipping time, cost, ratings and reviews are suitable!"
},
{
"title": "Auto Listing Search",
"subtitle": "Need a starting point?",
"text": "Some project parts commonly used. We'll try and find these parts for you automatically. This means you can create a shopping list, and we'll try and the best fitting products for you based on your shopping list entries."
},
{
"title": "Share It!",
"subtitle": "Show off your work!",
"text": "Have a project that you're excited about? Want to tell all your friends? We'll help. Each of your projects has a privacy setting. You can have it publically listed, unlisted, or completely private. Short link included."
}
]
} }
} }

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using Props.Models; using Props.Models;
using Props.Models.User;
using Props.Shop.Framework; using Props.Shop.Framework;
namespace Props.Data namespace Props.Data

View File

@ -1,217 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Props.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -1,22 +1,22 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using Props.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Props.Data;
namespace Props.Data.Migrations namespace Props.Data.Migrations
{ {
[DbContext(typeof(ApplicationDbContext))] [DbContext(typeof(ApplicationDbContext))]
[Migration("00000000000000_CreateIdentitySchema")] [Migration("20210721064503_InitialCreate")]
partial class CreateIdentitySchema partial class InitialCreate
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.0.0"); .HasAnnotation("ProductVersion", "5.0.8");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{ {
@ -28,18 +28,18 @@ namespace Props.Data.Migrations
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT") .HasMaxLength(256)
.HasMaxLength(256); .HasColumnType("TEXT");
b.Property<string>("NormalizedName") b.Property<string>("NormalizedName")
.HasColumnType("TEXT") .HasMaxLength(256)
.HasMaxLength(256); .HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("NormalizedName") b.HasIndex("NormalizedName")
.IsUnique() .IsUnique()
.HasName("RoleNameIndex"); .HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles"); b.ToTable("AspNetRoles");
}); });
@ -67,70 +67,6 @@ namespace Props.Data.Migrations
b.ToTable("AspNetRoleClaims"); b.ToTable("AspNetRoleClaims");
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -157,12 +93,12 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{ {
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("ProviderKey") b.Property<string>("ProviderKey")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName") b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -199,12 +135,12 @@ namespace Props.Data.Migrations
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("Value") b.Property<string>("Value")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -214,6 +150,144 @@ namespace Props.Data.Migrations
b.ToTable("AspNetUserTokens"); b.ToTable("AspNetUserTokens");
}); });
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Order")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("ResultsPreferences");
});
modelBuilder.Entity("Props.Models.SearchOutline", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Filters")
.HasColumnType("TEXT");
b.Property<int>("MaxResults")
.HasColumnType("INTEGER");
b.Property<string>("ShopStates")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("SearchOutline");
});
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("DarkMode")
.HasColumnType("INTEGER");
b.Property<bool>("EnableSearchHistory")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("ApplicationPreferences");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
@ -225,7 +299,7 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -234,7 +308,7 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -249,7 +323,7 @@ namespace Props.Data.Migrations
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -258,12 +332,51 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("ResultsPreferences")
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Models.SearchOutline", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("SearchOutline")
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("ApplicationPreferences")
.HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
{
b.Navigation("ApplicationPreferences")
.IsRequired();
b.Navigation("ResultsPreferences")
.IsRequired();
b.Navigation("SearchOutline")
.IsRequired();
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -0,0 +1,307 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Props.Data.Migrations
{
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
RoleId = table.Column<string>(type: "TEXT", nullable: false),
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ApplicationPreferences",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
EnableSearchHistory = table.Column<bool>(type: "INTEGER", nullable: false),
DarkMode = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApplicationPreferences", x => x.Id);
table.ForeignKey(
name: "FK_ApplicationPreferences_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
UserId = table.Column<string>(type: "TEXT", nullable: false),
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
UserId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(type: "TEXT", nullable: false),
RoleId = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(type: "TEXT", nullable: false),
LoginProvider = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
Name = table.Column<string>(type: "TEXT", maxLength: 128, nullable: false),
Value = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ResultsPreferences",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
Order = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ResultsPreferences", x => x.Id);
table.ForeignKey(
name: "FK_ResultsPreferences_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "SearchOutline",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ApplicationUserId = table.Column<string>(type: "TEXT", nullable: false),
Filters = table.Column<string>(type: "TEXT", nullable: true),
MaxResults = table.Column<int>(type: "INTEGER", nullable: false),
ShopStates = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SearchOutline", x => x.Id);
table.ForeignKey(
name: "FK_SearchOutline_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ApplicationPreferences_ApplicationUserId",
table: "ApplicationPreferences",
column: "ApplicationUserId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ResultsPreferences_ApplicationUserId",
table: "ResultsPreferences",
column: "ApplicationUserId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_SearchOutline_ApplicationUserId",
table: "SearchOutline",
column: "ApplicationUserId",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApplicationPreferences");
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "ResultsPreferences");
migrationBuilder.DropTable(
name: "SearchOutline");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

View File

@ -1,9 +1,9 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using Props.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Props.Data;
namespace Props.Data.Migrations namespace Props.Data.Migrations
{ {
@ -14,7 +14,7 @@ namespace Props.Data.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "3.0.0"); .HasAnnotation("ProductVersion", "5.0.8");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{ {
@ -26,18 +26,18 @@ namespace Props.Data.Migrations
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT") .HasMaxLength(256)
.HasMaxLength(256); .HasColumnType("TEXT");
b.Property<string>("NormalizedName") b.Property<string>("NormalizedName")
.HasColumnType("TEXT") .HasMaxLength(256)
.HasMaxLength(256); .HasColumnType("TEXT");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("NormalizedName") b.HasIndex("NormalizedName")
.IsUnique() .IsUnique()
.HasName("RoleNameIndex"); .HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles"); b.ToTable("AspNetRoles");
}); });
@ -65,70 +65,6 @@ namespace Props.Data.Migrations
b.ToTable("AspNetRoleClaims"); b.ToTable("AspNetRoleClaims");
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -155,12 +91,12 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{ {
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("ProviderKey") b.Property<string>("ProviderKey")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName") b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -197,12 +133,12 @@ namespace Props.Data.Migrations
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("LoginProvider") b.Property<string>("LoginProvider")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT") .HasMaxLength(128)
.HasMaxLength(128); .HasColumnType("TEXT");
b.Property<string>("Value") b.Property<string>("Value")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -212,6 +148,144 @@ namespace Props.Data.Migrations
b.ToTable("AspNetUserTokens"); b.ToTable("AspNetUserTokens");
}); });
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Order")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("ResultsPreferences");
});
modelBuilder.Entity("Props.Models.SearchOutline", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Filters")
.HasColumnType("TEXT");
b.Property<int>("MaxResults")
.HasColumnType("INTEGER");
b.Property<string>("ShopStates")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("SearchOutline");
});
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ApplicationUserId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("DarkMode")
.HasColumnType("INTEGER");
b.Property<bool>("EnableSearchHistory")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ApplicationUserId")
.IsUnique();
b.ToTable("ApplicationPreferences");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
@ -223,7 +297,7 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -232,7 +306,7 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -247,7 +321,7 @@ namespace Props.Data.Migrations
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
@ -256,12 +330,51 @@ namespace Props.Data.Migrations
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{ {
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) b.HasOne("Props.Models.ApplicationUser", null)
.WithMany() .WithMany()
.HasForeignKey("UserId") .HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
}); });
modelBuilder.Entity("Props.Models.ResultsPreferences", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("ResultsPreferences")
.HasForeignKey("Props.Models.ResultsPreferences", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Models.SearchOutline", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("SearchOutline")
.HasForeignKey("Props.Models.SearchOutline", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Shared.Models.User.ApplicationPreferences", b =>
{
b.HasOne("Props.Models.ApplicationUser", null)
.WithOne("ApplicationPreferences")
.HasForeignKey("Props.Shared.Models.User.ApplicationPreferences", "ApplicationUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Props.Models.ApplicationUser", b =>
{
b.Navigation("ApplicationPreferences")
.IsRequired();
b.Navigation("ResultsPreferences")
.IsRequired();
b.Navigation("SearchOutline")
.IsRequired();
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@ -1,6 +1,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
namespace Props.Shared.Models namespace Props.Shared.Models.User
{ {
public class ApplicationPreferences public class ApplicationPreferences
{ {
@ -10,5 +10,6 @@ namespace Props.Shared.Models
public string ApplicationUserId { get; set; } public string ApplicationUserId { get; set; }
public bool EnableSearchHistory { get; set; } = true; public bool EnableSearchHistory { get; set; } = true;
public bool DarkMode { get; set; } = false;
} }
} }

View File

@ -4,9 +4,9 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Props.Shared.Models; using Props.Shared.Models.User;
namespace Props.Models namespace Props.Models.User
{ {
public class ApplicationUser : IdentityUser public class ApplicationUser : IdentityUser
{ {

View File

@ -1,33 +0,0 @@
@page
@using Props.Services
@inject IContentManager<AboutModel> ContentManager
@{
ViewData["Title"] = "About";
}
<div class="container d-flex flex-column align-items-center my-3">
<div class="concise text-center">
<h2 class="mb-3 mt-4">Features</h2>
<p>
@ContentManager.Content.features.description
</p>
</div>
<div style="width: 100%;" data-simplebar>
<div class="row px-2 py-4 flex-nowrap">
@foreach (dynamic feature in ContentManager.Content.features.list)
{
<div class="card mx-2" style="width: 32rem;">
<div class="card-body">
<h5 class="card-title">@feature.title</h5>
<h6 class="card-subtitle mb-3 text-muted">
<slot name="subtitle">@feature.subtitle</slot>
</h6>
<p class="card-text">
@feature.text
</p>
</div>
</div>
}
</div>
</div>
</div>

View File

@ -1,26 +1,27 @@
@page @page
@using Props.Services @using Props.Services.Content
@model IndexModel @model IndexModel
@inject IContentManager<IndexModel> ContentManager @inject IContentManager<IndexModel> ContentManager
@{ @{
ViewData["Title"] = "Home page"; ViewData["Title"] = "Home page";
} }
<div class="jumbotron d-flex flex-column align-items-center"> <section class="jumbotron d-flex flex-column align-items-center">
<div> <div>
<img alt="Props logo" src="./images/logo.svg" class="img-fluid" style="max-height: 540px;" /> <img alt="Props logo" src="./images/logo.svg" class="img-fluid" style="max-height: 540px;" />
</div> </div>
<div class="text-center px-3 my-2 concise"> <div class="text-center px-3 my-2 concise">
<h1 class="my-2">Props</h1> <h1 class="my-2">Props</h1>
<p> <p>
@ContentManager.Content.description @ContentManager.Json.description
</p> </p>
</div> </div>
</div> </section>
<div class="jumbotron sub">
<div class="container d-flex flex-column align-items-center py-3 concise"> <section class="jumbotron sub">
<div class="container d-flex flex-column align-items-center py-2 concise">
<i class="bi bi-search" style="font-size: 5rem;"></i> <i class="bi bi-search" style="font-size: 5rem;"></i>
<h2 class="mb-3 mt-4">@ContentManager.Content.help.title</h2> <h2 class="mb-3 mt-4">@ContentManager.Json.help.title</h2>
<form class="concise my-4"> <form class="concise my-4">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn"> <input type="text" class="form-control" placeholder="What are you looking for?" aria-label="Search" aria-describedby="search-btn">
@ -28,10 +29,46 @@
</div> </div>
</form> </form>
<p class="text-center"> <p class="text-center">
@ContentManager.Content.help.searchIntroduction @ContentManager.Json.help.searchIntroduction
</p> </p>
<p class="text-center"> <p class="text-center">
@ContentManager.Content.help.additionalInfo @ContentManager.Json.help.additionalInfo
</p> </p>
</div> </div>
</div> </section>
<section class="container d-flex flex-column align-items-center my-3 less-concise">
<h2 class="mb-3 mt-4">Our Mission</h2>
<p class="text-center">
@ContentManager.Json.mission
</p>
</section>
<hr class="concise">
<section class="container d-flex flex-column align-items-center">
<div class="less-concise d-flex flex-column align-items-center">
<h2 class="mb-3 mt-4">Features</h2>
<p class="center">
@ContentManager.Json.features.description
</p>
</div>
<div style="width: 100%;" data-simplebar>
<div class="row px-2 py-3 flex-nowrap">
@foreach (dynamic feature in ContentManager.Json.features.list)
{
<div class="card mx-2" style="width: 32rem;">
<div class="card-body">
<h5 class="card-title">@feature.title</h5>
<h6 class="card-subtitle mb-3 text-muted">
<slot name="subtitle">@feature.subtitle</slot>
</h6>
<p class="card-text">
@feature.text
</p>
</div>
</div>
}
</div>
</div>
</section>

View File

@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Props.Pages namespace Props.Pages
{ {
public class AboutModel : PageModel public class SearchModel : PageModel
{ {
} }

View File

@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Props.Models @using Props.Models.User
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager
@ -21,27 +21,24 @@
<header> <header>
<nav id="nav"> <nav id="nav">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" asp-page="/">Props</a> <a class="navbar-brand" asp-area="" asp-page="/Index">Props</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent">
<i class="bi bi-list" style="width: 100%; height: auto;"></i> <i class="bi bi-list" style="width: 100%; height: auto;"></i>
</button> </button>
<div class="collapse navbar-collapse" id="navbarContent"> <div class="collapse navbar-collapse" id="navbarContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<nav-link class="nav-link" asp-page="Index">Home</nav-link> <nav-link class="nav-link" asp-area="" asp-page="/Index">Home</nav-link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<nav-link class="nav-link" asp-page="Search">Search</nav-link> <nav-link class="nav-link" asp-area="" asp-page="/Search">Search</nav-link>
</li>
<li class="nav-item">
<nav-link class="nav-link" asp-page="About">About</nav-link>
</li> </li>
</ul> </ul>
<ul class="navbar-nav mb-2 mb-lg-0"> <ul class="navbar-nav mb-2 mb-lg-0">
@if (SignInManager.IsSignedIn(User)) @if (SignInManager.IsSignedIn(User))
{ {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a> <nav-link class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</nav-link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post"> <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
@ -52,10 +49,10 @@
else else
{ {
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</a> <nav-link class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</nav-link>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</a> <nav-link class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</nav-link>
</li> </li>
} }
</ul> </ul>

View File

@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Identity
@using Props.Models @using Props.Models.User
@inject SignInManager<ApplicationUser> SignInManager @inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> UserManager @inject UserManager<ApplicationUser> UserManager

View File

@ -17,6 +17,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.8" /> <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.8">
@ -31,7 +32,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include=".\Content\**\*"> <Content Include=".\content\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View File

@ -1,15 +1,15 @@
using System.IO; using System.IO;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Props.Services namespace Props.Services.Content
{ {
public class JsonContentManager<Page> : IContentManager<Page> public class ContentManager<Page> : IContentManager<Page>
{ {
private dynamic data; private dynamic data;
private readonly string directory; private readonly string directory;
private readonly string fileName; private readonly string fileName;
dynamic IContentManager<Page>.Content dynamic IContentManager<Page>.Json
{ {
get get
{ {
@ -18,7 +18,7 @@ namespace Props.Services
} }
} }
public JsonContentManager(string directory = "Content") public ContentManager(string directory = "content")
{ {
this.directory = directory; this.directory = directory;
this.fileName = typeof(Page).Name.Replace("Model", "") + ".json"; this.fileName = typeof(Page).Name.Replace("Model", "") + ".json";

View File

@ -1,7 +1,7 @@
namespace Props.Services namespace Props.Services.Content
{ {
public interface IContentManager<out TModel> public interface IContentManager<out TModel>
{ {
dynamic Content { get; } dynamic Json { get; }
} }
} }

View File

@ -0,0 +1,29 @@
using System;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace Props.Services.Modules
{
internal class ShopAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver resolver;
public ShopAssemblyLoadContext(string path)
{
resolver = new AssemblyDependencyResolver(path);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
return assemblyPath != null ? LoadFromAssemblyPath(assemblyPath) : null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libPath = resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return libPath != null ? LoadUnmanagedDllFromPath(libPath) : IntPtr.Zero;
}
}
}

View File

@ -13,8 +13,8 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Props.Data; using Props.Data;
using Props.Models; using Props.Models.User;
using Props.Services; using Props.Services.Content;
namespace Props namespace Props
{ {
@ -35,21 +35,25 @@ namespace Props
if (environment.IsDevelopment()) if (environment.IsDevelopment())
{ {
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite( {
Configuration.GetConnectionString("DefaultConnection"))); options.UseLazyLoadingProxies();
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection"));
});
services.AddDatabaseDeveloperPageExceptionFilter(); services.AddDatabaseDeveloperPageExceptionFilter();
} }
else else
{ {
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer( {
Configuration.GetConnectionString("DefaultConnection"))); options.UseLazyLoadingProxies();
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
} }
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>(); .AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages(); services.AddRazorPages();
services.AddSingleton(typeof(IContentManager<>), typeof(JsonContentManager<>)); services.AddSingleton(typeof(IContentManager<>), typeof(ContentManager<>));
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -9,12 +9,8 @@
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
}, },
"webOptimizer": { "Shops": {
"enableCaching": true, "Path": "./shops"
"enableMemoryCache": true,
"enableDiskCache": true,
"enableTagHelperBundling": true,
"allowEmptyBundle": false
}, },
"AllowedHosts": "*" "AllowedHosts": "*"
} }

View File

@ -1,3 +1,3 @@
$themes: ( $themes: (
"light": ("background": #f4f4f4, "navbar": #FFF8F8, "main": #BDF2D5, "footer": #F2F2F2,"sub": #F2FCFC, "bold": #647b9b, "text": #1A1A1A, "muted": #797a7e), "light": ("background": #f4f4f4, "navbar": #FFF8F8, "main": #BDF2D5, "footer": #F2F2F2,"sub": #F4FCFC, "bold": #647b9b, "text": #1A1A1A, "muted": #797a7e),
); );

View File

@ -29,17 +29,16 @@ header > nav {
} }
} }
body { main {
@include themer.themed { flex-grow: 1;
background-color: themer.color-of("background"); display: flex;
color: themer.color-of("text"); flex-direction: column;
}
} }
footer { footer {
@extend .py-2;
@extend .text-center; @extend .text-center;
@extend .border-top; @extend .border-top;
@extend .py-2;
@include themer.themed { @include themer.themed {
background-color: themer.color-of("footer"); background-color: themer.color-of("footer");
color: themer.color-of("muted"); color: themer.color-of("muted");
@ -51,8 +50,6 @@ footer {
} }
} }
position: absolute;
bottom: 0;
width: 100%; width: 100%;
} }
@ -75,6 +72,11 @@ footer {
width: inherit; width: inherit;
} }
.less-concise {
max-width: 720px;
width: inherit;
}
h1 { h1 {
font-size: 5em; font-size: 5em;
} }
@ -83,14 +85,31 @@ h2 {
font-size: 3em; font-size: 3em;
} }
hr { hr.concise {
@extend .my-3; @extend .my-3;
@include themer.themed { @include themer.themed {
color: themer.color-of("bold"); color: themer.color-of("bold");
} }
width: 15%;
max-width: 160px;
min-width: 32px;
margin-left: auto;
margin-right: auto;
height: 2px;
} }
html { html {
min-height: 100%; min-height: 100%;
position: relative; display: flex;
flex-direction: column;
}
body {
display: flex;
flex-direction: column;
flex-grow: 1;
@include themer.themed {
background-color: themer.color-of("background");
color: themer.color-of("text");
}
} }

View File

@ -2371,6 +2371,14 @@
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
}, },
"framesync": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz",
"integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==",
"requires": {
"tslib": "^2.1.0"
}
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -2506,6 +2514,11 @@
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true "dev": true
}, },
"hey-listen": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
},
"human-signals": { "human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -3072,6 +3085,17 @@
"find-up": "^4.0.0" "find-up": "^4.0.0"
} }
}, },
"popmotion": {
"version": "9.4.0",
"resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.4.0.tgz",
"integrity": "sha512-FGnHjc8iDMrAwuZhka8eNx0yzcaufDqyZzW9vjJebRuC6BryR5ICyBmUH+wCgUuuaFSSU4r6oT2WtnbnDGcr3g==",
"requires": {
"framesync": "5.3.0",
"hey-listen": "^1.0.8",
"style-value-types": "4.1.4",
"tslib": "^2.1.0"
}
},
"postcss": { "postcss": {
"version": "8.3.5", "version": "8.3.5",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.5.tgz",
@ -3525,6 +3549,15 @@
"integrity": "sha512-HYVvBMX3RX7zx71pquZV6EcnPN7Deba+zQteSxCLqt3bxYRphmeMr+2mZMrIZjZ7IMa6aOUhNGn8cXGvWMjClw==", "integrity": "sha512-HYVvBMX3RX7zx71pquZV6EcnPN7Deba+zQteSxCLqt3bxYRphmeMr+2mZMrIZjZ7IMa6aOUhNGn8cXGvWMjClw==",
"dev": true "dev": true
}, },
"style-value-types": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz",
"integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==",
"requires": {
"hey-listen": "^1.0.8",
"tslib": "^2.1.0"
}
},
"supports-color": { "supports-color": {
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@ -3628,6 +3661,11 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"tslib": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
},
"type-check": { "type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -26,6 +26,7 @@
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap": "^5.0.2", "bootstrap": "^5.0.2",
"bootstrap-icons": "^1.5.0", "bootstrap-icons": "^1.5.0",
"popmotion": "^9.4.0",
"simplebar": "^5.3.5" "simplebar": "^5.3.5"
} }
} }

28
Props/scripts/reset_db.py Normal file
View File

@ -0,0 +1,28 @@
import os
import shutil
SERVER_DIR = "./"
DATA_DIR = "Data"
DB_MIGRATE_CMD = "dotnet ef migrations add InitialCreate -o {0}"
DB_UPDATE_CMD = "dotnet ef database update"
os.chdir(os.path.dirname(os.path.realpath(__file__)))
os.chdir("..")
os.chdir(SERVER_DIR)
print("Working in: " + os.getcwd())
migrationsDir = os.path.join(DATA_DIR, "Migrations")
print("Deleting current migrations directory if it exists.")
shutil.rmtree(migrationsDir, ignore_errors=True)
print("Deleting old app.db if it exists.")
if os.path.exists("app.db"):
os.remove("app.db")
print("Creating migration.")
os.system(DB_MIGRATE_CMD.format(migrationsDir))
print("Updating database.")
os.system(DB_UPDATE_CMD)

View File

@ -2,8 +2,7 @@ const CopyPlugin = require("copy-webpack-plugin");
const { glob } = require("glob"); const { glob } = require("glob");
const path = require("path"); const path = require("path");
module.exports = { let config = {
mode: "development",
entry: glob.sync(path.resolve("./assets/js/specific/*.js")).reduce((obj, elem) => { entry: glob.sync(path.resolve("./assets/js/specific/*.js")).reduce((obj, elem) => {
let name = elem.substr(path.resolve("./assets/js/").length); let name = elem.substr(path.resolve("./assets/js/").length);
name = name.substring(0, name.length - path.extname(name).length); name = name.substring(0, name.length - path.extname(name).length);
@ -57,3 +56,17 @@ module.exports = {
}) })
] ]
}; };
module.exports = (env, argv) => {
if (argv.mode === "development") {
// Development specific configuration.
config.devtool = "eval-source-map";
}
if (argv.mode === "production") {
// Production specific configuration.
config.devtool = "nosources-source-map";
}
return config;
};