🎉 Added files

This commit is contained in:
Edgar 2024-12-06 10:26:27 +01:00
commit 953f596db1
No known key found for this signature in database
GPG Key ID: 3C2E1F2C1C353131
29 changed files with 1288 additions and 0 deletions

487
.gitignore vendored Normal file
View File

@ -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/

5
App.config Normal file
View File

@ -0,0 +1,5 @@
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>

15
App.razor Normal file
View File

@ -0,0 +1,15 @@
@using The_Metaverse_Engine.Pages.Shared
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

37
Config.cs Normal file
View File

@ -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<ConfigData>(_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
{
}

19
Dockerfile Normal file
View File

@ -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"]

View File

@ -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<ApiKeyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<ApiKeyHandler> _logger;
private readonly IAsyncDocumentSession _session;
public ApiKeyHandler(ILogger<ApiKeyHandler> 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<ApiKey>().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();
}
}

42
Pages/Error.cshtml Normal file
View File

@ -0,0 +1,42 @@
@page
@model ErrorModel
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Error</title>
<link href="~/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="~/css/site.css" rel="stylesheet" asp-append-version="true" />
</head>
<body>
<div class="main">
<div class="content px-4">
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
</div>
</div>
</body>
</html>

25
Pages/Error.cshtml.cs Normal file
View File

@ -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<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger)
{
_logger = logger;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}

12
Pages/Index.razor Normal file
View File

@ -0,0 +1,12 @@
@using The_Metaverse_Engine.Services
@using Raven.Client.Documents.Session
@page "/"
@inject IDocumentSession Session
<PageTitle>Index</PageTitle>
@code {
}

View File

@ -0,0 +1,63 @@
@using Microsoft.FluentUI.AspNetCore.Components.Extensions
@typeparam TEnum where TEnum : struct, Enum
<FluentSelect
TOption="Option<int>"
Items="_enumOptions"
Label="@Label"
OptionText="@(i => i.Text)"
OptionValue="@(i => i.Value.ToString())"
OptionSelected="@(i => i.Selected)"
@bind-Value="_selectedValue"
Placeholder="@Placeholder"
AdditionalAttributes="@AdditionalAttributes"
/>
@code {
[Parameter] public TEnum? EnumSelectedValue { get; set; }
[Parameter] public EventCallback<TEnum?> 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<string, object> AdditionalAttributes { get; set; } = new();
private List<Option<int>> _enumOptions = [];
private string? _selectedValue
{
get { return _enumOptions.FirstOrDefault(e => e.Value.Equals(GetEnumInt(EnumSelectedValue)))?.Value.ToString(); }
set
{
if (value is null || !Enum.TryParse<TEnum>(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<TEnum>()
.Where(e => !ExcludeEnumItems.Contains(e))
.Select(e => new Option<int>
{
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));
}
}

View File

@ -0,0 +1,12 @@
<AuthorizeView>
<Authorized>
<a style="padding: 2em; color: aliceblue;" href="Identity/Account/Manage">Hello,
@context.User.Identity?.Name</a>
<form method="post" action="Identity/Account/Logout">
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Lightweight">Log out</FluentButton>
</form>
</Authorized>
<NotAuthorized>
<a href="Identity/Account/Login">Log in</a>
</NotAuthorized>
</AuthorizeView>

View File

@ -0,0 +1,23 @@
@inherits LayoutComponentBase
<PageTitle>The-Metaverse-Engine</PageTitle>
<FluentMainLayout NavMenuTitle="Navigation menu">
<Header>
<h2 style="margin: 0.4em;">The-Metaverse-Engine</h2>
<FluentSpacer />
<LoginDisplay />
</Header>
<Body>
<FluentMessageBarProvider Section="MESSAGES_TOP" />
@Body
</Body>
<NavMenuContent>
<NavMenu />
</NavMenuContent>
</FluentMainLayout>
<FluentToastProvider MaxToastCount="10" />
<FluentDialogProvider />

View File

View File

@ -0,0 +1,8 @@
<FluentNavLink Icon="@(new Icons.Regular.Size24.Home())" Href="/" Match="NavLinkMatch.All">Home</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="apikeys">ApiKeys</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="heats">Heats</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="challenges">Challenges</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="final">Final</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="players">Players</FluentNavLink>
<FluentNavLink Icon="@(new Icons.Regular.Size24.VehicleCarCollision())" Href="dangerzone">Danger zone</FluentNavLink>

View File

@ -0,0 +1,5 @@
.navmenu {
background: var(--neutral-layer-3);
display: flex;
padding: 10px;
}

View File

@ -0,0 +1,28 @@
@using Microsoft.AspNetCore.Identity
@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello
@User.Identity?.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

50
Pages/_Host.cshtml Normal file
View File

@ -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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="~/" />
<link href="css/site.css" rel="stylesheet" />
<link href="The-Metaverse-Engine.styles.css" rel="stylesheet" />
<link href="_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css" rel="stylesheet" />
<link rel="icon" type="image/png" href="favicon.png" />
<component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
<script app-name="The-Metaverse-Engine"
src="./_content/Microsoft.FluentUI.AspNetCore.Components/js/initializersLoader.webview.js"></script>
<script src="js/Sortable.min.js"></script>
</head>
<body>
<component type="typeof(App)" render-mode="ServerPrerendered" />
<div id="blazor-error-ui">
<environment include="Staging,Production">
An error has occurred. This application may no longer respond until reloaded.
</environment>
<environment include="Development">
An unhandled exception has occurred. See browser dev tools for details.
</environment>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.server.js"></script>
<script>
window.copyToClipboard = (data) => {
console.log(data)
navigator.clipboard.writeText(data);
}
</script>
</body>
</html>

20
Pocos/AppUser.cs Normal file
View File

@ -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; }
}

149
Program.cs Normal file
View File

@ -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<UnobservedTaskExceptionEventArgs>((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<AppendAuthorizeToSummaryOperationFilter>();
//this filter does add if an API is secured based upon the Authorize attribute
o.OperationFilter<SecurityRequirementsOperationFilter>(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<AppUser, Raven.Identity.IdentityRole>()
// 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<AppUser, Raven.Identity.IdentityRole>()
.AddTokenProvider<DataProtectorTokenProvider<AppUser>>(TokenOptions.DefaultProvider)
.AddDefaultUI();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<AppUser>>();
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<IAuthorizationHandler, ApiKeyHandler>();
builder.Services.AddFluentUIComponents();
builder.Services.AddHttpClient();
builder.WebHost.UseStaticWebAssets();
builder.Services.AddSingleton<Config>();
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<IDocumentStore>();
// 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<HypetrainBroadcaster>("/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();

View File

@ -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"
}
}
}
}

View File

@ -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<TUser>
: RevalidatingServerAuthenticationStateProvider where TUser : class
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IdentityOptions _options;
public RevalidatingIdentityAuthenticationStateProvider(
ILoggerFactory loggerFactory,
IServiceScopeFactory scopeFactory,
IOptions<IdentityOptions> optionsAccessor)
: base(loggerFactory)
{
_scopeFactory = scopeFactory;
_options = optionsAccessor.Value;
}
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
protected override async Task<bool> 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<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> 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;
}
}
}

View File

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<DefineConstants>$(DefineConstants);USE_APIKEY_AUTH</DefineConstants>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.10.4" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Emoji" Version="4.6.0" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.10.4" />
<PackageReference Include="RavenDB.Client" Version="6.2.1" />
<PackageReference Include="RavenDB.Identity" Version="9.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
</ItemGroup>
</Project>

12
_Imports.razor Normal file
View File

@ -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

View File

@ -0,0 +1,17 @@
{
"DetailedErrors": true,
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
},
"RavenSettings": {
"Urls": [
"http://localhost:8080"
],
"DatabaseName": "tme_dev",
"CertFilePath": "",
"CertPassword": ""
}
}

18
appsettings.json Normal file
View File

@ -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": ""
}
}

13
libman.json Normal file
View File

@ -0,0 +1,13 @@
{
"version": "1.0",
"defaultProvider": "jsdelivr",
"libraries": [
{
"library": "sortablejs@1.15.3",
"destination": "wwwroot/js/",
"files": [
"Sortable.min.js"
]
}
]
}

37
wwwroot/css/site.css Normal file
View File

@ -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."
}

BIN
wwwroot/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

2
wwwroot/js/Sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long