From 953f596db1bb0241e4ec432a2b88c821e4b93d33 Mon Sep 17 00:00:00 2001 From: Edgar Date: Fri, 6 Dec 2024 10:26:27 +0100 Subject: [PATCH] :tada: Added files --- .gitignore | 487 ++++++++++++++++++ App.config | 5 + App.razor | 15 + Config.cs | 37 ++ Dockerfile | 19 + Handlers/ApiKeyAuthHandler.cs | 56 ++ Pages/Error.cshtml | 42 ++ Pages/Error.cshtml.cs | 25 + Pages/Index.razor | 12 + Pages/Shared/FluentSelectEnum.razor | 63 +++ Pages/Shared/LoginDisplay.razor | 12 + Pages/Shared/MainLayout.razor | 23 + Pages/Shared/MainLayout.razor.css | 0 Pages/Shared/NavMenu.razor | 8 + Pages/Shared/NavMenu.razor.css | 5 + Pages/Shared/_LoginPartial.cshtml | 28 + Pages/_Host.cshtml | 50 ++ Pocos/AppUser.cs | 20 + Program.cs | 149 ++++++ Properties/launchSettings.json | 38 ++ ...tingIdentityAuthenticationStateProvider.cs | 69 +++ The-Metaverse-Engine.csproj | 26 + _Imports.razor | 12 + appsettings.Development.json | 17 + appsettings.json | 18 + libman.json | 13 + wwwroot/css/site.css | 37 ++ wwwroot/favicon.ico | Bin 0 -> 5430 bytes wwwroot/js/Sortable.min.js | 2 + 29 files changed, 1288 insertions(+) create mode 100644 .gitignore create mode 100644 App.config create mode 100644 App.razor create mode 100644 Config.cs create mode 100644 Dockerfile create mode 100644 Handlers/ApiKeyAuthHandler.cs create mode 100644 Pages/Error.cshtml create mode 100644 Pages/Error.cshtml.cs create mode 100644 Pages/Index.razor create mode 100644 Pages/Shared/FluentSelectEnum.razor create mode 100644 Pages/Shared/LoginDisplay.razor create mode 100644 Pages/Shared/MainLayout.razor create mode 100644 Pages/Shared/MainLayout.razor.css create mode 100644 Pages/Shared/NavMenu.razor create mode 100644 Pages/Shared/NavMenu.razor.css create mode 100644 Pages/Shared/_LoginPartial.cshtml create mode 100644 Pages/_Host.cshtml create mode 100644 Pocos/AppUser.cs create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 Services/RevalidatingIdentityAuthenticationStateProvider.cs create mode 100644 The-Metaverse-Engine.csproj create mode 100644 _Imports.razor create mode 100644 appsettings.Development.json create mode 100644 appsettings.json create mode 100644 libman.json create mode 100644 wwwroot/css/site.css create mode 100644 wwwroot/favicon.ico create mode 100644 wwwroot/js/Sortable.min.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7c719d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,487 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp + + +tmp/ \ No newline at end of file diff --git a/App.config b/App.config new file mode 100644 index 0000000..4d8b4c2 --- /dev/null +++ b/App.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/App.razor b/App.razor new file mode 100644 index 0000000..1512946 --- /dev/null +++ b/App.razor @@ -0,0 +1,15 @@ +@using The_Metaverse_Engine.Pages.Shared + + + + + + + + Not found + +

Sorry, there's nothing at this address.

+
+
+
+
\ No newline at end of file diff --git a/Config.cs b/Config.cs new file mode 100644 index 0000000..53c31ef --- /dev/null +++ b/Config.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using Raven.Client.Documents; + +public class Config +{ + private string _db_key = "Config"; + + public ConfigData Data; + + private readonly IDocumentStore db; + + public Config(IDocumentStore d) + { + db = d; + using var session = db.OpenSession(); + Data = session.Load(_db_key); + if (Data == null) + { + Data = new ConfigData(); + session.Store(Data, _db_key); + session.SaveChanges(); + } + // session.Store(Data, _db_key_backup);// Backup data on startup + } + + internal void Save() + { + using var session = db.OpenSession(); + session.Store(Data, _db_key); + session.SaveChanges(); + } +} + +public class ConfigData +{ + +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca5d588 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# https://hub.docker.com/_/microsoft-dotnet +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /source + +# copy csproj and restore as distinct layers +COPY *.sln . +COPY *.csproj . +RUN dotnet restore + +# copy everything else and build app +COPY . . +WORKDIR /source/ +RUN dotnet publish -c release -o /app --no-restore + +# final stage/image +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +WORKDIR /app +COPY --from=build /app ./ +ENTRYPOINT ["dotnet", "The_Metaverse_Engine.dll"] \ No newline at end of file diff --git a/Handlers/ApiKeyAuthHandler.cs b/Handlers/ApiKeyAuthHandler.cs new file mode 100644 index 0000000..80c7f63 --- /dev/null +++ b/Handlers/ApiKeyAuthHandler.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Authorization; +using Raven.Client.Documents; +using Raven.Client.Documents.Session; + +namespace The_Metaverse_Engine.Handlers; + +public class ApiKeyRequirement : IAuthorizationRequirement +{ +} + +public class ApiKeyHandler : AuthorizationHandler +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ILogger _logger; + + private readonly IAsyncDocumentSession _session; + public ApiKeyHandler(ILogger logger, IHttpContextAccessor httpContextAccessor, IAsyncDocumentSession session) + { + _logger = logger; + _httpContextAccessor = httpContextAccessor; + _session = session; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext ctx, ApiKeyRequirement req) + { + // string apiKey = _httpContextAccessor?.HttpContext?.Request.Headers[Constants.ApiKeyHeaderName].ToString(); + var apiKey = _httpContextAccessor?.HttpContext?.Request.Query["apikey"].ToString(); + if (string.IsNullOrWhiteSpace(apiKey)) + { + ctx.Fail(); + return; + } + try + { + var res = await _session.Query().Where(k => k.Key == apiKey).SingleOrDefaultAsync(); + + if (res != null) + { + ctx.Succeed(req); + // Only update every 1 minute to reduce DB load + if (res.LastUse + TimeSpan.FromMinutes(1) < DateTime.Now) + { + res.LastUse = DateTime.Now; + await _session.SaveChangesAsync(); + } + return; + } + _logger.LogWarning($"Invalid API key: {apiKey}"); + } + catch (Exception e) + { + _logger.LogError(e.Message); + } + + ctx.Fail(); + } +} diff --git a/Pages/Error.cshtml b/Pages/Error.cshtml new file mode 100644 index 0000000..b66c05d --- /dev/null +++ b/Pages/Error.cshtml @@ -0,0 +1,42 @@ +@page +@model ErrorModel + + + + + + + + Error + + + + + +
+
+

Error.

+

An error occurred while processing your request.

+ + @if (Model.ShowRequestId) + { +

+ Request ID: @Model.RequestId +

+ } + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+
+
+ + + diff --git a/Pages/Error.cshtml.cs b/Pages/Error.cshtml.cs new file mode 100644 index 0000000..525e4d2 --- /dev/null +++ b/Pages/Error.cshtml.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using System.Diagnostics; + + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel +{ + public string? RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } +} diff --git a/Pages/Index.razor b/Pages/Index.razor new file mode 100644 index 0000000..19e9ee6 --- /dev/null +++ b/Pages/Index.razor @@ -0,0 +1,12 @@ +@using The_Metaverse_Engine.Services +@using Raven.Client.Documents.Session +@page "/" +@inject IDocumentSession Session + +Index + + + +@code { + +} \ No newline at end of file diff --git a/Pages/Shared/FluentSelectEnum.razor b/Pages/Shared/FluentSelectEnum.razor new file mode 100644 index 0000000..f8fe8db --- /dev/null +++ b/Pages/Shared/FluentSelectEnum.razor @@ -0,0 +1,63 @@ +@using Microsoft.FluentUI.AspNetCore.Components.Extensions +@typeparam TEnum where TEnum : struct, Enum + +@code { + [Parameter] public TEnum? EnumSelectedValue { get; set; } + [Parameter] public EventCallback EnumSelectedValueChanged { get; set; } + [Parameter] public TEnum[] ExcludeEnumItems { get; set; } = []; + [Parameter] public string? Placeholder { get; set; } + [Parameter] public string? Label { get; set; } + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary AdditionalAttributes { get; set; } = new(); + + private List> _enumOptions = []; + private string? _selectedValue + { + get { return _enumOptions.FirstOrDefault(e => e.Value.Equals(GetEnumInt(EnumSelectedValue)))?.Value.ToString(); } + set + { + if (value is null || !Enum.TryParse(value, out var result)) return; + EnumSelectedValue = result; + EnumSelectedValueChanged.InvokeAsync(result); + } + } + + protected override void OnInitialized() + { + if (!typeof(TEnum).IsEnum) + { + throw new InvalidOperationException($"{typeof(TEnum).Name} is not an enum type"); + } + } + + protected override void OnParametersSet() + { + _enumOptions = Enum.GetValues(typeof(TEnum)) + .Cast() + .Where(e => !ExcludeEnumItems.Contains(e)) + .Select(e => new Option + { + Value = GetEnumInt(e).GetValueOrDefault(), + Text = e.GetDescription(false), + Selected = e.Equals(EnumSelectedValue) + }) + .ToList(); + } + + private static int? GetEnumInt(TEnum? value) + { + if (!typeof(TEnum).IsEnum || value is null) + return null; + return (int)Convert.ChangeType(value, typeof(TEnum)); + } +} \ No newline at end of file diff --git a/Pages/Shared/LoginDisplay.razor b/Pages/Shared/LoginDisplay.razor new file mode 100644 index 0000000..65a39cd --- /dev/null +++ b/Pages/Shared/LoginDisplay.razor @@ -0,0 +1,12 @@ + + + Hello, + @context.User.Identity?.Name +
+ Log out +
+
+ + Log in + +
diff --git a/Pages/Shared/MainLayout.razor b/Pages/Shared/MainLayout.razor new file mode 100644 index 0000000..2b9e2ca --- /dev/null +++ b/Pages/Shared/MainLayout.razor @@ -0,0 +1,23 @@ +@inherits LayoutComponentBase + +The-Metaverse-Engine + + + +
+

The-Metaverse-Engine

+ + +
+ + + + @Body + + + + + +
+ + \ No newline at end of file diff --git a/Pages/Shared/MainLayout.razor.css b/Pages/Shared/MainLayout.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/Pages/Shared/NavMenu.razor b/Pages/Shared/NavMenu.razor new file mode 100644 index 0000000..451a6d0 --- /dev/null +++ b/Pages/Shared/NavMenu.razor @@ -0,0 +1,8 @@ +Home +ApiKeys +Heats +Challenges +Final +Players +Danger zone + diff --git a/Pages/Shared/NavMenu.razor.css b/Pages/Shared/NavMenu.razor.css new file mode 100644 index 0000000..1ce263a --- /dev/null +++ b/Pages/Shared/NavMenu.razor.css @@ -0,0 +1,5 @@ +.navmenu { + background: var(--neutral-layer-3); + display: flex; + padding: 10px; +} \ No newline at end of file diff --git a/Pages/Shared/_LoginPartial.cshtml b/Pages/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000..07c8bb0 --- /dev/null +++ b/Pages/Shared/_LoginPartial.cshtml @@ -0,0 +1,28 @@ +@using Microsoft.AspNetCore.Identity +@inject SignInManager SignInManager +@inject UserManager UserManager +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + diff --git a/Pages/_Host.cshtml b/Pages/_Host.cshtml new file mode 100644 index 0000000..0549bf6 --- /dev/null +++ b/Pages/_Host.cshtml @@ -0,0 +1,50 @@ +@page "/" +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using The_Metaverse_Engine +@namespace The_Metaverse_Engine.Dashboard.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + + + + + + + + + + + + + + + + + + +
+ + An error has occurred. This application may no longer respond until reloaded. + + + An unhandled exception has occurred. See browser dev tools for details. + + Reload + 🗙 +
+ + + + + + + + diff --git a/Pocos/AppUser.cs b/Pocos/AppUser.cs new file mode 100644 index 0000000..4eadaf5 --- /dev/null +++ b/Pocos/AppUser.cs @@ -0,0 +1,20 @@ +public class AppUser : Raven.Identity.IdentityUser +{ + public string Username { get; set; } = ""; + public AuthScopes Scope { get; set; } = AuthScopes.User; +} + +public enum AuthScopes +{ + User, + Moderator, + Admin +} + +public class ApiKey +{ + public string? Id { get; set; } + public string Name { get; set; } = ""; + public string Key { get; set; } = Guid.NewGuid().ToString(); + public DateTime LastUse { get; set; } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..19e173b --- /dev/null +++ b/Program.cs @@ -0,0 +1,149 @@ +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.FluentUI.AspNetCore.Components; +using Microsoft.OpenApi.Models; +using Raven.Client.Documents; +using Raven.DependencyInjection; +using Raven.Identity; +using Swashbuckle.AspNetCore.Filters; +using The_Metaverse_Engine.Handlers; +using The_Metaverse_Engine.Services; + +AppContext.SetSwitch("Microsoft.AspNetCore.Authentication.SuppressAutoDefaultScheme", false); + +var builder = WebApplication.CreateBuilder(args); + +var handler = new EventHandler((s, e) => throw e.Exception); +TaskScheduler.UnobservedTaskException += handler; + +// Add services to the container. + +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); +}); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); + +builder.Services.AddSwaggerGen(o => + { + var sc = new OpenApiSecurityScheme + { + In = ParameterLocation.Query, + Name = "apikey", + Type = SecuritySchemeType.ApiKey, + Description = "Enter API key", + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "apikey" + } + }; + o.AddSecurityDefinition(sc.Reference.Id, sc); + + //to indicate the entire API is secured, add this. NOTE it does NOT secure it, just indicates it is. + // o.AddSecurityRequirement(new OpenApiSecurityRequirement { { sc, new string[] { } } }); + o.OperationFilter(); + //this filter does add if an API is secured based upon the Authorize attribute + o.OperationFilter(true, sc.Reference.Id); + } +); + +// Create an IDocumentStore singleton from the RavenSettings. +builder.Services.AddRavenDbDocStore(); +// Create a RavenDB IAsyncDocumentSession for each request. You're responsible for calling .SaveChanges after each request. +builder.Services.AddRavenDbSession(); +builder.Services.AddRavenDbAsyncSession(); +// Adds an identity system to ASP.NET Core +builder.Services.AddIdentity() + // Use RavenDB as the store for identity users and roles. Specify your app user type here, and your role type. If you don't have a role type, use Raven.Identity.IdentityRole. + .AddRavenDbIdentityStores() + .AddTokenProvider>(TokenOptions.DefaultProvider) + .AddDefaultUI(); + +builder.Services.AddScoped>(); + +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy.WithOrigins("*"); + policy.AllowAnyHeader(); + policy.AllowAnyMethod(); + }); +}); + +builder.Services.AddStackExchangeRedisCache(options => + { + options.Configuration = builder.Configuration.GetConnectionString("Redis"); + options.InstanceName = "tme"; + }); + +builder.Services.AddAuthentication().AddJwtBearer(); + +builder.Services.AddAuthorization(options => +{ + options.AddPolicy("ApiKey", policy => + { + policy.AddAuthenticationSchemes([JwtBearerDefaults.AuthenticationScheme]); + policy.Requirements.Add(new ApiKeyRequirement()); + }); + + options.FallbackPolicy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); +}); + +builder.Services.AddScoped(); + +builder.Services.AddFluentUIComponents(); + +builder.Services.AddHttpClient(); +builder.WebHost.UseStaticWebAssets(); + +builder.Services.AddSingleton(); + +builder.Services.AddRazorPages(); +builder.Services.AddServerSideBlazor().AddCircuitOptions(option => +{ + option.DetailedErrors = builder.Environment.IsDevelopment(); +});; + + +var app = builder.Build(); + +// using (var scope = app.Services.CreateScope()) +// { +// var db = scope.ServiceProvider.GetRequiredService(); +// new Rank_ById().Execute(db); +// } + +app.UseCors(); // UseCors must be called before MapHub. + +// Configure the HTTP request pipeline. +#if DEBUG +app.UseSwagger(); +app.UseSwaggerUI(); +#endif + +// app.MapHub("/hypetrain"); + +app.UseStaticFiles(); + +// app.UseHttpsRedirection(); + +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); + + +app.MapControllers(); +app.MapGet("/api/ok", () => "OK").AllowAnonymous(); + +app.MapBlazorHub(); +app.MapFallbackToPage("/_Host"); + +app.Run(); diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..04f558a --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38904", + "sslPort": 44377 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7111;http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": false, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Services/RevalidatingIdentityAuthenticationStateProvider.cs b/Services/RevalidatingIdentityAuthenticationStateProvider.cs new file mode 100644 index 0000000..8084f56 --- /dev/null +++ b/Services/RevalidatingIdentityAuthenticationStateProvider.cs @@ -0,0 +1,69 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Server; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using System.Security.Claims; + +namespace The_Metaverse_Engine.Services; + +public class RevalidatingIdentityAuthenticationStateProvider + : RevalidatingServerAuthenticationStateProvider where TUser : class +{ + private readonly IServiceScopeFactory _scopeFactory; + private readonly IdentityOptions _options; + + public RevalidatingIdentityAuthenticationStateProvider( + ILoggerFactory loggerFactory, + IServiceScopeFactory scopeFactory, + IOptions optionsAccessor) + : base(loggerFactory) + { + _scopeFactory = scopeFactory; + _options = optionsAccessor.Value; + } + + protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); + + protected override async Task ValidateAuthenticationStateAsync( + AuthenticationState authenticationState, CancellationToken cancellationToken) + { + // Get the user manager from a new scope to ensure it fetches fresh data + var scope = _scopeFactory.CreateScope(); + try + { + var userManager = scope.ServiceProvider.GetRequiredService>(); + return await ValidateSecurityStampAsync(userManager, authenticationState.User); + } + finally + { + if (scope is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + else + { + scope.Dispose(); + } + } + } + + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + { + var user = await userManager.GetUserAsync(principal); + if (user == null) + { + return false; + } + else if (!userManager.SupportsUserSecurityStamp) + { + return true; + } + else + { + var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType); + var userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; + } + } +} \ No newline at end of file diff --git a/The-Metaverse-Engine.csproj b/The-Metaverse-Engine.csproj new file mode 100644 index 0000000..9e43d44 --- /dev/null +++ b/The-Metaverse-Engine.csproj @@ -0,0 +1,26 @@ + + + + net9.0 + enable + $(DefineConstants);USE_APIKEY_AUTH + enable + latest + + + + + + + + + + + + + + + + + + diff --git a/_Imports.razor b/_Imports.razor new file mode 100644 index 0000000..a2d2e49 --- /dev/null +++ b/_Imports.razor @@ -0,0 +1,12 @@ +@using The_Metaverse_Engine +@using The_Metaverse_Engine.Pages +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Identity +@using Microsoft.FluentUI.AspNetCore.Components +@using Microsoft.JSInterop +@using System.Net.Http \ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..df442ab --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,17 @@ +{ + "DetailedErrors": true, + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft.AspNetCore": "Warning" + } + }, + "RavenSettings": { + "Urls": [ + "http://localhost:8080" + ], + "DatabaseName": "tme_dev", + "CertFilePath": "", + "CertPassword": "" + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..884890d --- /dev/null +++ b/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "Urls": "http://0.0.0.0:9000", + "AllowedHosts": "*", + "RavenSettings": { + "Urls": [ + "http://localhost:8080" + ], + "DatabaseName": "tme", + "CertFilePath": "", + "CertPassword": "" + } +} \ No newline at end of file diff --git a/libman.json b/libman.json new file mode 100644 index 0000000..3a38dde --- /dev/null +++ b/libman.json @@ -0,0 +1,13 @@ +{ + "version": "1.0", + "defaultProvider": "jsdelivr", + "libraries": [ + { + "library": "sortablejs@1.15.3", + "destination": "wwwroot/js/", + "files": [ + "Sortable.min.js" + ] + } + ] +} \ No newline at end of file diff --git a/wwwroot/css/site.css b/wwwroot/css/site.css new file mode 100644 index 0000000..7945b64 --- /dev/null +++ b/wwwroot/css/site.css @@ -0,0 +1,37 @@ +html, +body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.body-content { + Padding: 1.5em; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + +.blazor-error-boundary::after { + content: "An error has occurred." +} diff --git a/wwwroot/favicon.ico b/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..63e859b476eff5055e0e557aaa151ca8223fbeef GIT binary patch literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gjat.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function g(t){return t.host&&t!==document&&t.host.nodeType?t.host:t.parentNode}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=g(t)))}return null}var m,v=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(v," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(v," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function b(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function E(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Ft(t){Z&&Z.parentNode[K]._isOutsideThisEl(t.target)}function jt(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return kt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==jt.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in z.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in Rt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&It,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),St.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,A())}function Ht(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Lt(t){t.draggable=!1}function Kt(){xt=!1}function Wt(t){return setTimeout(t,0)}function zt(t){return clearTimeout(t)}jt.prototype={constructor:jt,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(vt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,Z):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){Ot.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&Ot.push(o)}}(o),!Z&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||et===l)){if(it=j(l),at=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return V({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),U("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return V({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),U("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!Z&&n.parentNode===r&&(o=X(n),J=r,$=(Z=n).parentNode,tt=Z.nextSibling,et=n,st=a.group,ut={target:jt.dragged=Z,clientX:(e||t).clientX,clientY:(e||t).clientY},pt=ut.clientX-o.left,gt=ut.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,Z.style["will-change"]="all",o=function(){U("delayEnded",i,{evt:t}),jt.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(Z.draggable=!0),i._triggerDragStart(t,e),V({sortable:i,name:"choose",originalEvent:t}),k(Z,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){E(Z,t.trim(),Lt)}),h(l,"dragover",Bt),h(l,"mousemove",Bt),h(l,"touchmove",Bt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,Z.draggable=!0),U("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():jt.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){Z&&Lt(Z),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(Z,"dragend",this),h(J,"dragstart",this._onDragStart));try{document.selection?Wt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;Et=!1,J&&Z?(U("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Ft),n=this.options,t||k(Z,n.dragClass,!1),k(Z,n.ghostClass,!0),jt.active=this,t&&this._appendGhost(),V({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(dt){this._lastX=dt.clientX,this._lastY=dt.clientY,Xt();for(var t=document.elementFromPoint(dt.clientX,dt.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(dt.clientX,dt.clientY))!==e;)e=t;if(Z.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:dt.clientX,clientY:dt.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=g(t=e));Yt()}},_onTouchMove:function(t){if(ut){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=Q&&b(Q,!0),a=Q&&r&&r.a,l=Q&&r&&r.d,e=At&&wt&&D(wt),a=(i.clientX-ut.clientX+o.x)/(a||1)+(e?e[0]-Tt[0]:0)/(a||1),l=(i.clientY-ut.clientY+o.y)/(l||1)+(e?e[1]-Tt[1]:0)/(l||1);if(!jt.active&&!Et){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))D.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>D.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,Q),e?t.clientX<_.left-10||t.clientY