Compare commits
196 Commits
v2
...
v2.1_Repla
| Author | SHA1 | Date | |
|---|---|---|---|
| ab92edb008 | |||
| 60178dc54b | |||
| 8a63a3448a | |||
| f5336f63ce | |||
| 97583349df | |||
| 9bec336323 | |||
| 123b64a666 | |||
| 5a215231fa | |||
| 6a29b5386c | |||
| 7cd138b09f | |||
| 504837fe77 | |||
| 7dde1d86f8 | |||
| a480ae9c50 | |||
| 2c9a87bf3e | |||
| 03ea94b858 | |||
| 9e42985ec8 | |||
| dbf17aee3b | |||
| 2b91d9a798 | |||
| 74d18419a6 | |||
| e3f007b568 | |||
| 6690f09a32 | |||
| 7c496b4c7f | |||
| 77abdd807d | |||
| 7599a7d60a | |||
| 14993b9fe7 | |||
| acba3a9f53 | |||
| 2b62fc141d | |||
| 4baa0bbe77 | |||
| d88376f2fb | |||
| eaece9e334 | |||
| 7e158d48c6 | |||
| 4ed153ac5b | |||
| 6b17296139 | |||
| bb5dffe395 | |||
| 0fa844f856 | |||
| 4fea77c837 | |||
| f76c797a7f | |||
| ad81fd9199 | |||
| f732c80230 | |||
| eb97d15f03 | |||
| be590d8e2b | |||
| a41845e45c | |||
| a1cb4f243a | |||
| 110e055e24 | |||
| f3a35bd62a | |||
| 565d9a5a4d | |||
| dbc29046f5 | |||
| 0152502c1b | |||
| 0520e3d7e5 | |||
| dc49e168ab | |||
| da55f2b19e | |||
| 424f2a8779 | |||
| 255bfba9e3 | |||
| 593a79c506 | |||
| d4a7600c14 | |||
|
|
7ead76fbcc | ||
|
|
f87e4a0800 | ||
|
|
eab03d7f5a | ||
| 9dc77e6dde | |||
| 3a804c99ce | |||
|
|
c49b000521 | ||
|
|
ebc1b9441e | ||
| 0e5402c347 | |||
| 6922242b4f | |||
| 73ca5e57e8 | |||
| a579dd4759 | |||
|
|
609a0297d5 | ||
|
|
0743bad93c | ||
|
|
49848db96f | ||
|
|
ba736d2b19 | ||
|
|
753cb04dfe | ||
|
|
bc25210fe4 | ||
|
|
a4e0175173 | ||
|
|
bd8ea67017 | ||
| 8126250d1a | |||
|
|
8ac2d20d8a | ||
| 1b4d32eed3 | |||
| 1a67fcffb4 | |||
| bbc6c0fbd3 | |||
| 6657bae0cd | |||
| 65ea5985d3 | |||
| 7defc9a6a9 | |||
| 504cb8e950 | |||
| f81b84e4b3 | |||
| c12e1e38b8 | |||
| db7ac8d174 | |||
| 0b0c9304b1 | |||
| f56f592c4c | |||
| b0fe27c643 | |||
| 55bc825cb7 | |||
| 7fa46ef245 | |||
| e1c0722fce | |||
| 3f511cefa8 | |||
| f1adba4fa6 | |||
| a9d3a30782 | |||
| 55a8cfad46 | |||
| 420ff46ceb | |||
| 75f037da02 | |||
| 1238095f09 | |||
| 3084bb268b | |||
| f1c0d3b896 | |||
| 340cf738dc | |||
| f23320eb1c | |||
| 9fb1667bf0 | |||
| 1f95577eb7 | |||
| 45ccb6fc4c | |||
| b1092985ff | |||
| 6c5e4c2a1e | |||
| caa8d47af2 | |||
| cdc4744f28 | |||
| f4a0aabb61 | |||
| 64f4a3a58c | |||
| a23c3b0fdd | |||
| 3dd5d2958a | |||
| 69df761bf4 | |||
| 4a571e1944 | |||
| fa86b26f46 | |||
| 480d118014 | |||
| 2e5d0dcd73 | |||
| b6b488edf6 | |||
| bf5a744499 | |||
| 1a4864ba00 | |||
| e299cde6da | |||
| 8372cfad1b | |||
| d477e803ab | |||
| bf89ef16f7 | |||
| 744d977454 | |||
| b95f89687f | |||
| 143ba3c138 | |||
| 094af845a0 | |||
| 5efeb5fba6 | |||
| 42ab052699 | |||
| e6c9feed6b | |||
| e63a3db8b9 | |||
| 75cefea4fa | |||
| 150a18cc0b | |||
| 64b20e26ac | |||
| f08c8f013d | |||
| 721570927f | |||
| 62fe6089f7 | |||
| 0a76e64d2f | |||
| 2e98d166ec | |||
|
|
bc737c830f | ||
|
|
23a74bdfc6 | ||
|
|
f702167d6e | ||
|
|
fe31c01a06 | ||
|
|
2d0a0e53c0 | ||
|
|
072adb5bb1 | ||
|
|
e5555c815b | ||
|
|
a074f0c4f0 | ||
|
|
d92f996169 | ||
|
|
96bb3a5c0f | ||
|
|
18810766ed | ||
|
|
764ebe3586 | ||
|
|
add4c3e99f | ||
|
|
f9c4ec1d31 | ||
|
|
f2d653563c | ||
|
|
288b0c8d97 | ||
|
|
69e5e1c75b | ||
|
|
399fbbaab5 | ||
|
|
06f7731011 | ||
|
|
81d8bc45ee | ||
|
|
17a4e7ec14 | ||
|
|
b73c3ebfb3 | ||
|
|
b0a044db97 | ||
|
|
fce44f49b6 | ||
|
|
6d0c75ceff | ||
|
|
324bf6891a | ||
|
|
e5f29e4725 | ||
|
|
54e0675ba9 | ||
|
|
e02af774a9 | ||
|
|
f6ed12fc7a | ||
|
|
c15f18108d | ||
|
|
7239182e83 | ||
|
|
eba6e00251 | ||
|
|
6f3341e6ad | ||
|
|
910f190c86 | ||
|
|
c4c3d1bd60 | ||
|
|
f166de1a43 | ||
|
|
71dc81c4dc | ||
|
|
8883b521e9 | ||
|
|
6be3b8338d | ||
|
|
9d1351527d | ||
|
|
13daa3cbac | ||
|
|
19afc5d055 | ||
|
|
bb29177e41 | ||
|
|
f48e5d4b19 | ||
|
|
cf25e4e1e6 | ||
|
|
966e67afee | ||
|
|
a0432eec68 | ||
|
|
522d0c1471 | ||
|
|
ca1b7a84c9 | ||
|
|
e32e35d3af | ||
|
|
ef2e6c9a20 | ||
|
|
fa748494f6 | ||
|
|
73bf27d222 |
@@ -1,25 +0,0 @@
|
|||||||
**/.dockerignore
|
|
||||||
**/.env
|
|
||||||
**/.git
|
|
||||||
**/.gitignore
|
|
||||||
**/.project
|
|
||||||
**/.settings
|
|
||||||
**/.toolstarget
|
|
||||||
**/.vs
|
|
||||||
**/.vscode
|
|
||||||
**/.idea
|
|
||||||
**/*.*proj.user
|
|
||||||
**/*.dbmdl
|
|
||||||
**/*.jfm
|
|
||||||
**/azds.yaml
|
|
||||||
**/bin
|
|
||||||
**/charts
|
|
||||||
**/docker-compose*
|
|
||||||
**/Dockerfile*
|
|
||||||
**/node_modules
|
|
||||||
**/npm-debug.log
|
|
||||||
**/obj
|
|
||||||
**/secrets.dev.yaml
|
|
||||||
**/values.dev.yaml
|
|
||||||
LICENSE
|
|
||||||
README.md
|
|
||||||
35
.github/workflows/development-build.yml
vendored
35
.github/workflows/development-build.yml
vendored
@@ -1,35 +0,0 @@
|
|||||||
name: Development Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- closed
|
|
||||||
branches: [ "v2" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current date
|
|
||||||
id: date
|
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login into docker hub
|
|
||||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PW }}
|
|
||||||
- name: Build and Push Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Moonlight/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: moonlightpanel/moonlight:dev
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: |
|
|
||||||
"BUILD_CHANNEL=${{ github.ref_name }}"
|
|
||||||
"BUILD_COMMIT_HASH=${{ github.sha }}"
|
|
||||||
"BUILD_NAME=devbuild ${{ steps.date.outputs.date }}"
|
|
||||||
"BUILD_VERSION=unknown"
|
|
||||||
39
.github/workflows/publish-dev-packages.yml
vendored
Normal file
39
.github/workflows/publish-dev-packages.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Build and Publish NuGet Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ v2_ChangeArchitecture,v2.1 ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: debian-12
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
project:
|
||||||
|
- Moonlight.Client
|
||||||
|
- Moonlight.ApiServer
|
||||||
|
- Moonlight.Shared
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Step 1: Clean environment
|
||||||
|
- name: Clean up Environment
|
||||||
|
run: |
|
||||||
|
rm -rf ./*
|
||||||
|
rm -rf ./.??*
|
||||||
|
|
||||||
|
# Step 2: Checkout the code
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Step 3: Build project
|
||||||
|
- name: "Build project"
|
||||||
|
run: dotnet build --configuration Debug ${{ matrix.project }}/${{ matrix.project }}.csproj
|
||||||
|
|
||||||
|
# Step 4: Pack project
|
||||||
|
- name: "Pack project"
|
||||||
|
run: dotnet pack --configuration Debug --no-build --output . ${{ matrix.project }}/${{ matrix.project }}.csproj
|
||||||
|
|
||||||
|
# Step 5: Publish on package registry
|
||||||
|
- name: Publish on package registry"
|
||||||
|
run: dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{secrets.GH_PACKAGES_READWRITE}} --source https://nuget.pkg.github.com/Moonlight-Panel/index.json
|
||||||
35
.github/workflows/release-build.yml
vendored
35
.github/workflows/release-build.yml
vendored
@@ -1,35 +0,0 @@
|
|||||||
name: Release Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
codeName:
|
|
||||||
description: 'Code Name'
|
|
||||||
required: true
|
|
||||||
versionName:
|
|
||||||
description: 'Version Name'
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
- name: Login into docker hub
|
|
||||||
run: docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PW }}
|
|
||||||
- name: Build and Push Docker image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Moonlight/Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: moonlightpanel/moonlight:latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: |
|
|
||||||
"BUILD_CHANNEL=${{ github.ref_name }}"
|
|
||||||
"BUILD_COMMIT_HASH=${{ github.sha }}"
|
|
||||||
"BUILD_NAME=${{ github.event.inputs.codeName }}"
|
|
||||||
"BUILD_VERSION=${{ github.event.inputs.versionName }}"
|
|
||||||
38
.gitignore
vendored
38
.gitignore
vendored
@@ -397,8 +397,38 @@ FodyWeavers.xsd
|
|||||||
# JetBrains Rider
|
# JetBrains Rider
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Moonlight
|
||||||
storage/
|
storage/
|
||||||
.idea/.idea.Moonlight/.idea/dataSources.xml
|
**/.idea/**
|
||||||
Moonlight/Assets/Core/css/theme.css
|
style.min.css
|
||||||
Moonlight/Assets/Core/css/theme.css.map
|
|
||||||
.idea/.idea.Moonlight/.idea/discord.xml
|
# Build script for nuget packages
|
||||||
|
finalPackages/
|
||||||
|
nupkgs/
|
||||||
|
|
||||||
|
# Scripts
|
||||||
|
**/bin/**
|
||||||
|
**/obj/**
|
||||||
13
.idea/.idea.Moonlight/.idea/.gitignore
generated
vendored
13
.idea/.idea.Moonlight/.idea/.gitignore
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Rider ignored files
|
|
||||||
/modules.xml
|
|
||||||
/contentModel.xml
|
|
||||||
/projectSettingsUpdater.xml
|
|
||||||
/.idea.Moonlight.iml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
12
.idea/.idea.Moonlight/.idea/discord.xml
generated
12
.idea/.idea.Moonlight/.idea/discord.xml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="DiscordProjectSettings">
|
|
||||||
<option name="show" value="PROJECT_FILES" />
|
|
||||||
<option name="description" value="" />
|
|
||||||
<option name="theme" value="material" />
|
|
||||||
<option name="button1Title" value="" />
|
|
||||||
<option name="button1Url" value="" />
|
|
||||||
<option name="button2Title" value="" />
|
|
||||||
<option name="button2Url" value="" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
4
.idea/.idea.Moonlight/.idea/encodings.xml
generated
4
.idea/.idea.Moonlight/.idea/encodings.xml
generated
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
|
||||||
</project>
|
|
||||||
8
.idea/.idea.Moonlight/.idea/indexLayout.xml
generated
8
.idea/.idea.Moonlight/.idea/indexLayout.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="UserContentModel">
|
|
||||||
<attachedFolders />
|
|
||||||
<explicitIncludes />
|
|
||||||
<explicitExcludes />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="MaterialThemeProjectNewConfig">
|
|
||||||
<option name="metadata">
|
|
||||||
<MTProjectMetadataState>
|
|
||||||
<option name="migrated" value="true" />
|
|
||||||
<option name="pristineConfig" value="false" />
|
|
||||||
<option name="userId" value="52a374ed:18c1029d858:-8000" />
|
|
||||||
<option name="version" value="8.13.2" />
|
|
||||||
</MTProjectMetadataState>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/.idea.Moonlight/.idea/vcs.xml
generated
6
.idea/.idea.Moonlight/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Moonlight.ApiServer\Moonlight.ApiServer.csproj" />
|
||||||
|
<ProjectReference Include="..\Moonlight.Client.Runtime\Moonlight.Client.Runtime.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.7" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="Plugins.props" />
|
||||||
|
|
||||||
|
</Project>
|
||||||
10
Moonlight.ApiServer.Runtime/PluginLoader.cs
Normal file
10
Moonlight.ApiServer.Runtime/PluginLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using MoonCore.PluginFramework;
|
||||||
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Runtime;
|
||||||
|
|
||||||
|
[PluginLoader]
|
||||||
|
public partial class PluginLoader : IPluginStartup
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
4
Moonlight.ApiServer.Runtime/Plugins.props
Normal file
4
Moonlight.ApiServer.Runtime/Plugins.props
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<Project>
|
||||||
|
<ItemGroup>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
35
Moonlight.ApiServer.Runtime/Program.cs
Normal file
35
Moonlight.ApiServer.Runtime/Program.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Moonlight.ApiServer.Runtime;
|
||||||
|
using Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
var pluginLoader = new PluginLoader();
|
||||||
|
pluginLoader.Initialize();
|
||||||
|
/*
|
||||||
|
await startup.Run(args, pluginLoader.Instances);
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
var cs = new Startup();
|
||||||
|
|
||||||
|
await cs.Initialize(args, pluginLoader.Instances);
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
await cs.AddMoonlight(builder);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
await cs.AddMoonlight(app);
|
||||||
|
|
||||||
|
// Handle setup of wasm app hosting in the runtime
|
||||||
|
// so the Moonlight.ApiServer doesn't need the wasm package
|
||||||
|
if (cs.Configuration.Frontend.EnableHosting)
|
||||||
|
{
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
app.UseWebAssemblyDebugging();
|
||||||
|
|
||||||
|
app.UseBlazorFrameworkFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await app.RunAsync();
|
||||||
29
Moonlight.ApiServer.Runtime/Properties/launchSettings.json
Normal file
29
Moonlight.ApiServer.Runtime/Properties/launchSettings.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5165",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"HTTP_PROXY": "",
|
||||||
|
"HTTPS_PROXY": ""
|
||||||
|
},
|
||||||
|
"hotReloadEnabled": true
|
||||||
|
},
|
||||||
|
"WASM Debug": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5165",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"HTTP_PROXY": "",
|
||||||
|
"HTTPS_PROXY": ""
|
||||||
|
},
|
||||||
|
"hotReloadEnabled": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
102
Moonlight.ApiServer/Configuration/AppConfiguration.cs
Normal file
102
Moonlight.ApiServer/Configuration/AppConfiguration.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using MoonCore.Helpers;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Configuration;
|
||||||
|
|
||||||
|
public record AppConfiguration
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "The public url your instance should be accessible through")]
|
||||||
|
public string PublicUrl { get; set; } = "http://localhost:5165";
|
||||||
|
|
||||||
|
[YamlMember(Description = "The credentials of the postgres which moonlight should use")]
|
||||||
|
public DatabaseConfig Database { get; set; } = new();
|
||||||
|
|
||||||
|
[YamlMember(Description = "Settings regarding authentication")]
|
||||||
|
public AuthenticationConfig Authentication { get; set; } = new();
|
||||||
|
|
||||||
|
[YamlMember(Description = "These options are only meant for development purposes")]
|
||||||
|
public DevelopmentConfig Development { get; set; } = new();
|
||||||
|
public FrontendData Frontend { get; set; } = new();
|
||||||
|
public KestrelConfig Kestrel { get; set; } = new();
|
||||||
|
public MetricsData Metrics { get; set; } = new();
|
||||||
|
|
||||||
|
public static AppConfiguration CreateEmpty()
|
||||||
|
{
|
||||||
|
return new AppConfiguration()
|
||||||
|
{
|
||||||
|
// Set arrays as empty here
|
||||||
|
|
||||||
|
Kestrel = new()
|
||||||
|
{
|
||||||
|
AllowedOrigins = []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FrontendData
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "Enable the hosting of the frontend. Disable this if you only want to run the api server")]
|
||||||
|
public bool EnableHosting { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DatabaseConfig
|
||||||
|
{
|
||||||
|
public string Host { get; set; } = "your-database-host.name";
|
||||||
|
public int Port { get; set; } = 5432;
|
||||||
|
|
||||||
|
public string Username { get; set; } = "db_user";
|
||||||
|
public string Password { get; set; } = "db_password";
|
||||||
|
|
||||||
|
public string Database { get; set; } = "db_name";
|
||||||
|
}
|
||||||
|
|
||||||
|
public record AuthenticationConfig
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "The secret token to use for creating jwts and encrypting things. This needs to be at least 32 characters long")]
|
||||||
|
public string Secret { get; set; } = Formatter.GenerateString(32);
|
||||||
|
|
||||||
|
[YamlMember(Description = "The lifespan of generated user tokens in hours")]
|
||||||
|
public int TokenDuration { get; set; } = 24 * 10;
|
||||||
|
|
||||||
|
[YamlMember(Description = "This enables the use of the local oauth2 provider, so moonlight will use itself as an oauth2 provider")]
|
||||||
|
public bool EnableLocalOAuth2 { get; set; } = true;
|
||||||
|
public OAuth2Data OAuth2 { get; set; } = new();
|
||||||
|
|
||||||
|
public record OAuth2Data
|
||||||
|
{
|
||||||
|
public string Secret { get; set; } = Formatter.GenerateString(32);
|
||||||
|
public string ClientId { get; set; } = Formatter.GenerateString(8);
|
||||||
|
public string ClientSecret { get; set; } = Formatter.GenerateString(32);
|
||||||
|
public string? AuthorizationEndpoint { get; set; }
|
||||||
|
public string? AccessEndpoint { get; set; }
|
||||||
|
public string? AuthorizationRedirect { get; set; }
|
||||||
|
|
||||||
|
[YamlMember(Description = "This specifies if the first registered user will become an admin automatically. This only works when using local oauth2")]
|
||||||
|
public bool FirstUserAdmin { get; set; } = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record DevelopmentConfig
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "This toggles the availability of the api docs via /api/swagger")]
|
||||||
|
public bool EnableApiDocs { get; set; } = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record KestrelConfig
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "The upload limit in megabytes for the api server")]
|
||||||
|
public int UploadLimit { get; set; } = 100;
|
||||||
|
|
||||||
|
[YamlMember(Description = "The allowed origins for the api server. Use * to allow all origins (which is not advised)")]
|
||||||
|
public string[] AllowedOrigins { get; set; } = ["*"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public record MetricsData
|
||||||
|
{
|
||||||
|
[YamlMember(Description = "This enables the collecting of metrics and allows access to the /metrics endpoint")]
|
||||||
|
public bool Enable { get; set; } = false;
|
||||||
|
|
||||||
|
[YamlMember(Description = "The interval in which metrics are created, specified in seconds")]
|
||||||
|
public int Interval { get; set; } = 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Moonlight.ApiServer/Database/CoreDataContext.cs
Normal file
40
Moonlight.ApiServer/Database/CoreDataContext.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Extended.SingleDb;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database;
|
||||||
|
|
||||||
|
public class CoreDataContext : DatabaseContext
|
||||||
|
{
|
||||||
|
public override string Prefix { get; } = "Core";
|
||||||
|
|
||||||
|
public DbSet<User> Users { get; set; }
|
||||||
|
public DbSet<ApiKey> ApiKeys { get; set; }
|
||||||
|
public DbSet<Theme> Themes { get; set; }
|
||||||
|
|
||||||
|
public CoreDataContext(AppConfiguration configuration)
|
||||||
|
{
|
||||||
|
Options = new()
|
||||||
|
{
|
||||||
|
Host = configuration.Database.Host,
|
||||||
|
Port = configuration.Database.Port,
|
||||||
|
Username = configuration.Database.Username,
|
||||||
|
Password = configuration.Database.Password,
|
||||||
|
Database = configuration.Database.Database
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Ignore<ApplicationTheme>();
|
||||||
|
modelBuilder.Entity<Theme>()
|
||||||
|
.OwnsOne(x => x.Content, builder =>
|
||||||
|
{
|
||||||
|
builder.ToJson();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Moonlight.ApiServer/Database/Entities/ApiKey.cs
Normal file
14
Moonlight.ApiServer/Database/Entities/ApiKey.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
public class ApiKey
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
public string[] Permissions { get; set; } = [];
|
||||||
|
|
||||||
|
public DateTimeOffset ExpiresAt { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
|
||||||
|
}
|
||||||
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
19
Moonlight.ApiServer/Database/Entities/Theme.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
public class Theme
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Author { get; set; }
|
||||||
|
public string Version { get; set; }
|
||||||
|
|
||||||
|
public string? UpdateUrl { get; set; }
|
||||||
|
public string? DonateUrl { get; set; }
|
||||||
|
|
||||||
|
public ApplicationTheme Content { get; set; }
|
||||||
|
}
|
||||||
12
Moonlight.ApiServer/Database/Entities/User.cs
Normal file
12
Moonlight.ApiServer/Database/Entities/User.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
public DateTimeOffset TokenValidTimestamp { get; set; } = DateTimeOffset.MinValue;
|
||||||
|
public string[] Permissions { get; set; } = [];
|
||||||
|
}
|
||||||
90
Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.Designer.cs
generated
Normal file
90
Moonlight.ApiServer/Database/Migrations/20250226080942_AddedUsersAndApiKey.Designer.cs
generated
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250226080942_AddedUsersAndApiKey")]
|
||||||
|
partial class AddedUsersAndApiKey
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.11")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("Secret")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedUsersAndApiKey : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Core_ApiKeys",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Secret = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: false),
|
||||||
|
PermissionsJson = table.Column<string>(type: "jsonb", nullable: false),
|
||||||
|
ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Core_ApiKeys", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Core_Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Username = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Password = table.Column<string>(type: "text", nullable: false),
|
||||||
|
TokenValidTimestamp = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
PermissionsJson = table.Column<string>(type: "jsonb", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Core_Users", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Core_Users");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Moonlight.ApiServer/Database/Migrations/20250314095412_ModifiedApiKeyEntity.Designer.cs
generated
Normal file
89
Moonlight.ApiServer/Database/Migrations/20250314095412_ModifiedApiKeyEntity.Designer.cs
generated
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250314095412_ModifiedApiKeyEntity")]
|
||||||
|
partial class ModifiedApiKeyEntity
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.11")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ModifiedApiKeyEntity : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Secret",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "CreatedAt",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "timestamp with time zone",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "CreatedAt",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Secret",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "text",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
393
Moonlight.ApiServer/Database/Migrations/20250405172522_AddedHangfireTables.Designer.cs
generated
Normal file
393
Moonlight.ApiServer/Database/Migrations/20250405172522_AddedHangfireTables.Designer.cs
generated
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250405172522_AddedHangfireTables")]
|
||||||
|
partial class AddedHangfireTables
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "8.0.11")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<long>("Value")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Value");
|
||||||
|
|
||||||
|
b.ToTable("HangfireCounter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Field")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Field");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireHash");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("InvocationData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("StateName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("StateId");
|
||||||
|
|
||||||
|
b.HasIndex("StateName");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("JobId", "Name");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJobParameter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Position");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireList");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AcquiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HangfireLock");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FetchedAt")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Queue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.HasIndex("Queue", "FetchedAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireQueuedJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Heartbeat")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Queues")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("WorkerCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Heartbeat");
|
||||||
|
|
||||||
|
b.ToTable("HangfireServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("Score")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Value");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Score");
|
||||||
|
|
||||||
|
b.ToTable("HangfireSet");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.ToTable("HangfireState");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PermissionsJson")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<DateTime>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StateId");
|
||||||
|
|
||||||
|
b.Navigation("State");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("Parameters")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("QueuedJobs")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("States")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Parameters");
|
||||||
|
|
||||||
|
b.Navigation("QueuedJobs");
|
||||||
|
|
||||||
|
b.Navigation("States");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedHangfireTables : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireCounter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Value = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireCounter", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireHash",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Field = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireHash", x => new { x.Key, x.Field });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireList",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Position = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireList", x => new { x.Key, x.Position });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireLock",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
AcquiredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireLock", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireServer",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
StartedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Heartbeat = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
WorkerCount = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Queues = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireServer", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireSet",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Score = table.Column<double>(type: "double precision", nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireSet", x => new { x.Key, x.Value });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireJob",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
StateId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
StateName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
InvocationData = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireJob", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireJobParameter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireJobParameter", x => new { x.JobId, x.Name });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireJobParameter_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireQueuedJob",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Queue = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
FetchedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireQueuedJob", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireQueuedJob_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireState",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Reason = table.Column<string>(type: "text", nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Data = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireState", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireState_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireCounter_ExpireAt",
|
||||||
|
table: "HangfireCounter",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireCounter_Key_Value",
|
||||||
|
table: "HangfireCounter",
|
||||||
|
columns: new[] { "Key", "Value" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireHash_ExpireAt",
|
||||||
|
table: "HangfireHash",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_ExpireAt",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_StateId",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_StateName",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateName");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireList_ExpireAt",
|
||||||
|
table: "HangfireList",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireQueuedJob_JobId",
|
||||||
|
table: "HangfireQueuedJob",
|
||||||
|
column: "JobId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireQueuedJob_Queue_FetchedAt",
|
||||||
|
table: "HangfireQueuedJob",
|
||||||
|
columns: new[] { "Queue", "FetchedAt" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireServer_Heartbeat",
|
||||||
|
table: "HangfireServer",
|
||||||
|
column: "Heartbeat");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireSet_ExpireAt",
|
||||||
|
table: "HangfireSet",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireSet_Key_Score",
|
||||||
|
table: "HangfireSet",
|
||||||
|
columns: new[] { "Key", "Score" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireState_JobId",
|
||||||
|
table: "HangfireState",
|
||||||
|
column: "JobId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_HangfireJob_HangfireState_StateId",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateId",
|
||||||
|
principalTable: "HangfireState",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_HangfireJob_HangfireState_StateId",
|
||||||
|
table: "HangfireJob");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireCounter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireHash");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireJobParameter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireList");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireLock");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireQueuedJob");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireServer");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireSet");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireState");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireJob");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
393
Moonlight.ApiServer/Database/Migrations/20250712202608_SwitchedToPgArraysForPermissions.Designer.cs
generated
Normal file
393
Moonlight.ApiServer/Database/Migrations/20250712202608_SwitchedToPgArraysForPermissions.Designer.cs
generated
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250712202608_SwitchedToPgArraysForPermissions")]
|
||||||
|
partial class SwitchedToPgArraysForPermissions
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<long>("Value")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Value");
|
||||||
|
|
||||||
|
b.ToTable("HangfireCounter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Field")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Field");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireHash");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("InvocationData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("StateName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("StateId");
|
||||||
|
|
||||||
|
b.HasIndex("StateName");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("JobId", "Name");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJobParameter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Position");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireList");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AcquiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HangfireLock");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FetchedAt")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Queue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.HasIndex("Queue", "FetchedAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireQueuedJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Heartbeat")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Queues")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("WorkerCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Heartbeat");
|
||||||
|
|
||||||
|
b.ToTable("HangfireServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("Score")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Value");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Score");
|
||||||
|
|
||||||
|
b.ToTable("HangfireSet");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.ToTable("HangfireState");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StateId");
|
||||||
|
|
||||||
|
b.Navigation("State");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("Parameters")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("QueuedJobs")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("States")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Parameters");
|
||||||
|
|
||||||
|
b.Navigation("QueuedJobs");
|
||||||
|
|
||||||
|
b.Navigation("States");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class SwitchedToPgArraysForPermissions : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_Users",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new string[0]);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string[]>(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "text[]",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new string[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Permissions",
|
||||||
|
table: "Core_ApiKeys");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_Users",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "PermissionsJson",
|
||||||
|
table: "Core_ApiKeys",
|
||||||
|
type: "jsonb",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
564
Moonlight.ApiServer/Database/Migrations/20250720203346_AddedThemes.Designer.cs
generated
Normal file
@@ -0,0 +1,564 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250720203346_AddedThemes")]
|
||||||
|
partial class AddedThemes
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<long>("Value")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Value");
|
||||||
|
|
||||||
|
b.ToTable("HangfireCounter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Field")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Field");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireHash");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("InvocationData")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long?>("StateId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("StateName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("StateId");
|
||||||
|
|
||||||
|
b.HasIndex("StateName");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("JobId", "Name");
|
||||||
|
|
||||||
|
b.ToTable("HangfireJobParameter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<int>("Position")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Position");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireList");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("AcquiredAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("HangfireLock");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime?>("FetchedAt")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Queue")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.HasIndex("Queue", "FetchedAt");
|
||||||
|
|
||||||
|
b.ToTable("HangfireQueuedJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Heartbeat")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Queues")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("StartedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("WorkerCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Heartbeat");
|
||||||
|
|
||||||
|
b.ToTable("HangfireServer");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("character varying(100)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExpireAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<double>("Score")
|
||||||
|
.HasColumnType("double precision");
|
||||||
|
|
||||||
|
b.HasKey("Key", "Value");
|
||||||
|
|
||||||
|
b.HasIndex("ExpireAt");
|
||||||
|
|
||||||
|
b.HasIndex("Key", "Score");
|
||||||
|
|
||||||
|
b.ToTable("HangfireSet");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.Property<long>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Data")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("JobId")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("character varying(256)");
|
||||||
|
|
||||||
|
b.Property<string>("Reason")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("JobId");
|
||||||
|
|
||||||
|
b.ToTable("HangfireState");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Author")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DonateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UpdateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Themes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("StateId");
|
||||||
|
|
||||||
|
b.Navigation("State");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("Parameters")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("QueuedJobs")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job")
|
||||||
|
.WithMany("States")
|
||||||
|
.HasForeignKey("JobId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Job");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("Border")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccentContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBackground")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase100")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase150")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase200")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase250")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase300")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBaseContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorError")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorErrorContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfo")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfoContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutral")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutralContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccess")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccessContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarningContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<float>("Depth")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("Noise")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusBox")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.HasKey("ThemeId");
|
||||||
|
|
||||||
|
b1.ToTable("Core_Themes");
|
||||||
|
|
||||||
|
b1.ToJson("Content");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ThemeId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Content")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Parameters");
|
||||||
|
|
||||||
|
b.Navigation("QueuedJobs");
|
||||||
|
|
||||||
|
b.Navigation("States");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedThemes : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Core_Themes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
IsEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Author = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Version = table.Column<string>(type: "text", nullable: false),
|
||||||
|
UpdateUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
DonateUrl = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Content = table.Column<string>(type: "jsonb", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Core_Themes", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Core_Themes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
260
Moonlight.ApiServer/Database/Migrations/20250819195904_RemovedHangfire.Designer.cs
generated
Normal file
260
Moonlight.ApiServer/Database/Migrations/20250819195904_RemovedHangfire.Designer.cs
generated
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
[Migration("20250819195904_RemovedHangfire")]
|
||||||
|
partial class RemovedHangfire
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Author")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DonateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UpdateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Themes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("Border")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccentContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBackground")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase100")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase150")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase200")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase250")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase300")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBaseContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorError")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorErrorContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfo")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfoContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutral")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutralContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccess")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccessContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarningContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<int>("Depth")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<int>("Noise")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusBox")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.HasKey("ThemeId");
|
||||||
|
|
||||||
|
b1.ToTable("Core_Themes");
|
||||||
|
|
||||||
|
b1.ToJson("Content");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ThemeId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Content")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RemovedHangfire : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_HangfireJob_HangfireState_StateId",
|
||||||
|
table: "HangfireJob");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireCounter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireHash");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireJobParameter");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireList");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireLock");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireQueuedJob");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireServer");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireSet");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireState");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "HangfireJob");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireCounter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Value = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireCounter", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireHash",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Field = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireHash", x => new { x.Key, x.Field });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireList",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Position = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireList", x => new { x.Key, x.Position });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireLock",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
AcquiredAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireLock", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireServer",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Heartbeat = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Queues = table.Column<string>(type: "text", nullable: false),
|
||||||
|
StartedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
WorkerCount = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireServer", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireSet",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Score = table.Column<double>(type: "double precision", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireSet", x => new { x.Key, x.Value });
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireJob",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
StateId = table.Column<long>(type: "bigint", nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
ExpireAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
InvocationData = table.Column<string>(type: "text", nullable: false),
|
||||||
|
StateName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireJob", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireJobParameter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireJobParameter", x => new { x.JobId, x.Name });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireJobParameter_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireQueuedJob",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
FetchedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Queue = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireQueuedJob", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireQueuedJob_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "HangfireState",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
JobId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Data = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||||
|
Reason = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_HangfireState", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_HangfireState_HangfireJob_JobId",
|
||||||
|
column: x => x.JobId,
|
||||||
|
principalTable: "HangfireJob",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireCounter_ExpireAt",
|
||||||
|
table: "HangfireCounter",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireCounter_Key_Value",
|
||||||
|
table: "HangfireCounter",
|
||||||
|
columns: new[] { "Key", "Value" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireHash_ExpireAt",
|
||||||
|
table: "HangfireHash",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_ExpireAt",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_StateId",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireJob_StateName",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateName");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireList_ExpireAt",
|
||||||
|
table: "HangfireList",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireQueuedJob_JobId",
|
||||||
|
table: "HangfireQueuedJob",
|
||||||
|
column: "JobId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireQueuedJob_Queue_FetchedAt",
|
||||||
|
table: "HangfireQueuedJob",
|
||||||
|
columns: new[] { "Queue", "FetchedAt" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireServer_Heartbeat",
|
||||||
|
table: "HangfireServer",
|
||||||
|
column: "Heartbeat");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireSet_ExpireAt",
|
||||||
|
table: "HangfireSet",
|
||||||
|
column: "ExpireAt");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireSet_Key_Score",
|
||||||
|
table: "HangfireSet",
|
||||||
|
columns: new[] { "Key", "Score" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_HangfireState_JobId",
|
||||||
|
table: "HangfireState",
|
||||||
|
column: "JobId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_HangfireJob_HangfireState_StateId",
|
||||||
|
table: "HangfireJob",
|
||||||
|
column: "StateId",
|
||||||
|
principalTable: "HangfireState",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
222
Moonlight.ApiServer/Database/Migrations/20250819200221_AddedTickerQ.Designer.cs
generated
Normal file
222
Moonlight.ApiServer/Database/Migrations/20250819200221_AddedTickerQ.Designer.cs
generated
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TickerDataContext))]
|
||||||
|
[Migration("20250819200221_AddedTickerQ")]
|
||||||
|
partial class AddedTickerQ
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Expression")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Function")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("InitIdentifier")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Request")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("Retries")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<int[]>("RetryIntervals")
|
||||||
|
.HasColumnType("integer[]");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Expression")
|
||||||
|
.HasDatabaseName("IX_CronTickers_Expression");
|
||||||
|
|
||||||
|
b.ToTable("CronTickers", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerOccurrenceEntity<TickerQ.EntityFrameworkCore.Entities.CronTickerEntity>", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("CronTickerId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<long>("ElapsedTime")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Exception")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExecutedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExecutionTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("LockHolder")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LockedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("RetryCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CronTickerId")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
|
||||||
|
|
||||||
|
b.HasIndex("ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("CronTickerId", "ExecutionTime")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UQ_CronTickerId_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("Status", "ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
|
||||||
|
|
||||||
|
b.ToTable("CronTickerOccurrences", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("BatchParent")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int?>("BatchRunCondition")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("ElapsedTime")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Exception")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExecutedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExecutionTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Function")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("InitIdentifier")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("LockHolder")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LockedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Request")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("Retries")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RetryCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<int[]>("RetryIntervals")
|
||||||
|
.HasColumnType("integer[]");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BatchParent");
|
||||||
|
|
||||||
|
b.HasIndex("ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_TimeTicker_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("Status", "ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_TimeTicker_Status_ExecutionTime");
|
||||||
|
|
||||||
|
b.ToTable("TimeTickers", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerOccurrenceEntity<TickerQ.EntityFrameworkCore.Entities.CronTickerEntity>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TickerQ.EntityFrameworkCore.Entities.CronTickerEntity", "CronTicker")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CronTickerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("CronTicker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", "ParentJob")
|
||||||
|
.WithMany("ChildJobs")
|
||||||
|
.HasForeignKey("BatchParent")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("ParentJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ChildJobs");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedTickerQ : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.EnsureSchema(
|
||||||
|
name: "ticker");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CronTickers",
|
||||||
|
schema: "ticker",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Expression = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Request = table.Column<byte[]>(type: "bytea", nullable: true),
|
||||||
|
Retries = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RetryIntervals = table.Column<int[]>(type: "integer[]", nullable: true),
|
||||||
|
Function = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: true),
|
||||||
|
InitIdentifier = table.Column<string>(type: "text", nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CronTickers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "TimeTickers",
|
||||||
|
schema: "ticker",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
LockHolder = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Request = table.Column<byte[]>(type: "bytea", nullable: true),
|
||||||
|
ExecutionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
LockedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
ExecutedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Exception = table.Column<string>(type: "text", nullable: true),
|
||||||
|
ElapsedTime = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
Retries = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RetryCount = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
RetryIntervals = table.Column<int[]>(type: "integer[]", nullable: true),
|
||||||
|
BatchParent = table.Column<Guid>(type: "uuid", nullable: true),
|
||||||
|
BatchRunCondition = table.Column<int>(type: "integer", nullable: true),
|
||||||
|
Function = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: true),
|
||||||
|
InitIdentifier = table.Column<string>(type: "text", nullable: true),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_TimeTickers", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_TimeTickers_TimeTickers_BatchParent",
|
||||||
|
column: x => x.BatchParent,
|
||||||
|
principalSchema: "ticker",
|
||||||
|
principalTable: "TimeTickers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CronTickerOccurrences",
|
||||||
|
schema: "ticker",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
LockHolder = table.Column<string>(type: "text", nullable: true),
|
||||||
|
ExecutionTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
CronTickerId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
LockedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
ExecutedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Exception = table.Column<string>(type: "text", nullable: true),
|
||||||
|
ElapsedTime = table.Column<long>(type: "bigint", nullable: false),
|
||||||
|
RetryCount = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CronTickerOccurrences", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CronTickerOccurrences_CronTickers_CronTickerId",
|
||||||
|
column: x => x.CronTickerId,
|
||||||
|
principalSchema: "ticker",
|
||||||
|
principalTable: "CronTickers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CronTickerOccurrence_CronTickerId",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "CronTickerOccurrences",
|
||||||
|
column: "CronTickerId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CronTickerOccurrence_ExecutionTime",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "CronTickerOccurrences",
|
||||||
|
column: "ExecutionTime");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CronTickerOccurrence_Status_ExecutionTime",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "CronTickerOccurrences",
|
||||||
|
columns: new[] { "Status", "ExecutionTime" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "UQ_CronTickerId_ExecutionTime",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "CronTickerOccurrences",
|
||||||
|
columns: new[] { "CronTickerId", "ExecutionTime" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CronTickers_Expression",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "CronTickers",
|
||||||
|
column: "Expression");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TimeTicker_ExecutionTime",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "TimeTickers",
|
||||||
|
column: "ExecutionTime");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TimeTicker_Status_ExecutionTime",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "TimeTickers",
|
||||||
|
columns: new[] { "Status", "ExecutionTime" });
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_TimeTickers_BatchParent",
|
||||||
|
schema: "ticker",
|
||||||
|
table: "TimeTickers",
|
||||||
|
column: "BatchParent");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CronTickerOccurrences",
|
||||||
|
schema: "ticker");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "TimeTickers",
|
||||||
|
schema: "ticker");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CronTickers",
|
||||||
|
schema: "ticker");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(CoreDataContext))]
|
||||||
|
partial class CoreDataContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("ExpiresAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_ApiKeys", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Author")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("DonateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("IsEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("UpdateUrl")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Version")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Themes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text[]");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("TokenValidTimestamp")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Core_Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b =>
|
||||||
|
{
|
||||||
|
b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("ThemeId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("Border")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorAccentContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBackground")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase100")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase150")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase200")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase250")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBase300")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorBaseContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorError")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorErrorContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfo")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorInfoContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutral")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorNeutralContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorPrimaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondary")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSecondaryContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccess")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorSuccessContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarning")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<string>("ColorWarningContent")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b1.Property<int>("Depth")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<int>("Noise")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusBox")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("RadiusSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeField")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.Property<float>("SizeSelector")
|
||||||
|
.HasColumnType("real");
|
||||||
|
|
||||||
|
b1.HasKey("ThemeId");
|
||||||
|
|
||||||
|
b1.ToTable("Core_Themes");
|
||||||
|
|
||||||
|
b1.ToJson("Content");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("ThemeId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Content")
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(TickerDataContext))]
|
||||||
|
partial class TickerDataContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "9.0.7")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Expression")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Function")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("InitIdentifier")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Request")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("Retries")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<int[]>("RetryIntervals")
|
||||||
|
.HasColumnType("integer[]");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Expression")
|
||||||
|
.HasDatabaseName("IX_CronTickers_Expression");
|
||||||
|
|
||||||
|
b.ToTable("CronTickers", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerOccurrenceEntity<TickerQ.EntityFrameworkCore.Entities.CronTickerEntity>", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("CronTickerId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<long>("ElapsedTime")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Exception")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExecutedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExecutionTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("LockHolder")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LockedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<int>("RetryCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("CronTickerId")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_CronTickerId");
|
||||||
|
|
||||||
|
b.HasIndex("ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("CronTickerId", "ExecutionTime")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UQ_CronTickerId_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("Status", "ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_CronTickerOccurrence_Status_ExecutionTime");
|
||||||
|
|
||||||
|
b.ToTable("CronTickerOccurrences", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid?>("BatchParent")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int?>("BatchRunCondition")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<long>("ElapsedTime")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("Exception")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("ExecutedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ExecutionTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Function")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("InitIdentifier")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("LockHolder")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LockedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Request")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("Retries")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("RetryCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<int[]>("RetryIntervals")
|
||||||
|
.HasColumnType("integer[]");
|
||||||
|
|
||||||
|
b.Property<int>("Status")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime>("UpdatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BatchParent");
|
||||||
|
|
||||||
|
b.HasIndex("ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_TimeTicker_ExecutionTime");
|
||||||
|
|
||||||
|
b.HasIndex("Status", "ExecutionTime")
|
||||||
|
.HasDatabaseName("IX_TimeTicker_Status_ExecutionTime");
|
||||||
|
|
||||||
|
b.ToTable("TimeTickers", "ticker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.CronTickerOccurrenceEntity<TickerQ.EntityFrameworkCore.Entities.CronTickerEntity>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TickerQ.EntityFrameworkCore.Entities.CronTickerEntity", "CronTicker")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CronTickerId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("CronTicker");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", "ParentJob")
|
||||||
|
.WithMany("ChildJobs")
|
||||||
|
.HasForeignKey("BatchParent")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("ParentJob");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("TickerQ.EntityFrameworkCore.Entities.TimeTickerEntity", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ChildJobs");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Moonlight.ApiServer/Database/TickerDataContext.cs
Normal file
31
Moonlight.ApiServer/Database/TickerDataContext.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Extended.SingleDb;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using TickerQ.EntityFrameworkCore.Configurations;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Database;
|
||||||
|
|
||||||
|
public class TickerDataContext : DatabaseContext
|
||||||
|
{
|
||||||
|
public override string Prefix => "Ticker";
|
||||||
|
|
||||||
|
public TickerDataContext(AppConfiguration configuration)
|
||||||
|
{
|
||||||
|
Options = new()
|
||||||
|
{
|
||||||
|
Host = configuration.Database.Host,
|
||||||
|
Port = configuration.Database.Port,
|
||||||
|
Username = configuration.Database.Username,
|
||||||
|
Password = configuration.Database.Password,
|
||||||
|
Database = configuration.Database.Database
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
// Apply TickerQ entity configurations explicitly
|
||||||
|
modelBuilder.ApplyConfiguration(new TimeTickerConfigurations());
|
||||||
|
modelBuilder.ApplyConfiguration(new CronTickerConfigurations());
|
||||||
|
modelBuilder.ApplyConfiguration(new CronTickerOccurrenceConfigurations());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Moonlight.Core.Extensions;
|
namespace Moonlight.ApiServer.Extensions;
|
||||||
|
|
||||||
public static class ZipArchiveExtensions
|
public static class ZipArchiveExtensions
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Models;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.ApiKeys;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.ApiKeys;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.ApiKeys;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/apikeys")]
|
||||||
|
public class ApiKeysController : Controller
|
||||||
|
{
|
||||||
|
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
||||||
|
private readonly ApiKeyService ApiKeyService;
|
||||||
|
|
||||||
|
public ApiKeysController(DatabaseRepository<ApiKey> apiKeyRepository, ApiKeyService apiKeyService)
|
||||||
|
{
|
||||||
|
ApiKeyRepository = apiKeyRepository;
|
||||||
|
ApiKeyService = apiKeyService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.apikeys.get")]
|
||||||
|
public async Task<IPagedData<ApiKeyResponse>> Get(
|
||||||
|
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||||
|
[FromQuery] [Range(1, 100)] int pageSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var count = await ApiKeyRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var apiKeys = await ApiKeyRepository
|
||||||
|
.Get()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(page * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var mappedApiKey = apiKeys
|
||||||
|
.Select(x => new ApiKeyResponse()
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
Permissions = x.Permissions,
|
||||||
|
Description = x.Description,
|
||||||
|
ExpiresAt = x.ExpiresAt
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new PagedData<ApiKeyResponse>()
|
||||||
|
{
|
||||||
|
CurrentPage = page,
|
||||||
|
Items = mappedApiKey,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalItems = count,
|
||||||
|
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.apikeys.get")]
|
||||||
|
public async Task<ApiKeyResponse> GetSingle(int id)
|
||||||
|
{
|
||||||
|
var apiKey = await ApiKeyRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (apiKey == null)
|
||||||
|
throw new HttpApiException("No api key with that id found", 404);
|
||||||
|
|
||||||
|
return new ApiKeyResponse()
|
||||||
|
{
|
||||||
|
Id = apiKey.Id,
|
||||||
|
Permissions = apiKey.Permissions,
|
||||||
|
Description = apiKey.Description,
|
||||||
|
ExpiresAt = apiKey.ExpiresAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "permissions:admin.apikeys.create")]
|
||||||
|
public async Task<CreateApiKeyResponse> Create([FromBody] CreateApiKeyRequest request)
|
||||||
|
{
|
||||||
|
var apiKey = new ApiKey()
|
||||||
|
{
|
||||||
|
Description = request.Description,
|
||||||
|
Permissions = request.Permissions,
|
||||||
|
ExpiresAt = request.ExpiresAt
|
||||||
|
};
|
||||||
|
|
||||||
|
var finalApiKey = await ApiKeyRepository.Add(apiKey);
|
||||||
|
|
||||||
|
var response = new CreateApiKeyResponse
|
||||||
|
{
|
||||||
|
Id = finalApiKey.Id,
|
||||||
|
Permissions = finalApiKey.Permissions,
|
||||||
|
Description = finalApiKey.Description,
|
||||||
|
ExpiresAt = finalApiKey.ExpiresAt,
|
||||||
|
Secret = ApiKeyService.GenerateJwt(finalApiKey)
|
||||||
|
};
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.apikeys.update")]
|
||||||
|
public async Task<ApiKeyResponse> Update([FromRoute] int id, [FromBody] UpdateApiKeyRequest request)
|
||||||
|
{
|
||||||
|
var apiKey = await ApiKeyRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (apiKey == null)
|
||||||
|
throw new HttpApiException("No api key with that id found", 404);
|
||||||
|
|
||||||
|
apiKey.Description = request.Description;
|
||||||
|
|
||||||
|
await ApiKeyRepository.Update(apiKey);
|
||||||
|
|
||||||
|
return new ApiKeyResponse()
|
||||||
|
{
|
||||||
|
Id = apiKey.Id,
|
||||||
|
Description = apiKey.Description,
|
||||||
|
Permissions = apiKey.Permissions,
|
||||||
|
ExpiresAt = apiKey.ExpiresAt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.apikeys.delete")]
|
||||||
|
public async Task Delete([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var apiKey = await ApiKeyRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (apiKey == null)
|
||||||
|
throw new HttpApiException("No api key with that id found", 404);
|
||||||
|
|
||||||
|
await ApiKeyRepository.Remove(apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/system/advanced")]
|
||||||
|
public class AdvancedController : Controller
|
||||||
|
{
|
||||||
|
private readonly FrontendService FrontendService;
|
||||||
|
|
||||||
|
public AdvancedController(FrontendService frontendService)
|
||||||
|
{
|
||||||
|
FrontendService = frontendService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("frontend")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.advanced.frontend")]
|
||||||
|
public async Task Frontend()
|
||||||
|
{
|
||||||
|
var stream = await FrontendService.GenerateZip();
|
||||||
|
await Results.File(stream, fileDownloadName: "frontend.zip").ExecuteAsync(HttpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Models;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Mappers;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Customisation;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/system/customisation/themes")]
|
||||||
|
public class ThemesController : Controller
|
||||||
|
{
|
||||||
|
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||||
|
|
||||||
|
public ThemesController(DatabaseRepository<Theme> themeRepository)
|
||||||
|
{
|
||||||
|
ThemeRepository = themeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.customisation.themes.read")]
|
||||||
|
public async Task<PagedData<ThemeResponse>> Get(
|
||||||
|
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||||
|
[FromQuery] [Range(1, 100)] int pageSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var count = await ThemeRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var items = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.Skip(page * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var mappedItems = items
|
||||||
|
.Select(ThemeMapper.ToResponse)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new PagedData<ThemeResponse>()
|
||||||
|
{
|
||||||
|
CurrentPage = page,
|
||||||
|
Items = mappedItems,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalItems = count,
|
||||||
|
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id:int}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.customisation.themes.read")]
|
||||||
|
public async Task<ThemeResponse> GetSingle([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var theme = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == id);
|
||||||
|
|
||||||
|
if (theme == null)
|
||||||
|
throw new HttpApiException("Theme with this id not found", 404);
|
||||||
|
|
||||||
|
return ThemeMapper.ToResponse(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.customisation.themes.write")]
|
||||||
|
public async Task<ThemeResponse> Create([FromBody] CreateThemeRequest request)
|
||||||
|
{
|
||||||
|
var theme = ThemeMapper.ToTheme(request);
|
||||||
|
|
||||||
|
var finalTheme = await ThemeRepository.Add(theme);
|
||||||
|
|
||||||
|
return ThemeMapper.ToResponse(finalTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id:int}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.customisation.themes.write")]
|
||||||
|
public async Task<ThemeResponse> Update([FromRoute] int id, [FromBody] UpdateThemeRequest request)
|
||||||
|
{
|
||||||
|
var theme = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(t => t.Id == id);
|
||||||
|
|
||||||
|
if (theme == null)
|
||||||
|
throw new HttpApiException("Theme with this id not found", 404);
|
||||||
|
|
||||||
|
// Disable all other enabled themes if we are enabling the current theme.
|
||||||
|
// This ensures only one theme is enabled at the time
|
||||||
|
if (request.IsEnabled)
|
||||||
|
{
|
||||||
|
var otherThemes = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.IsEnabled && x.Id != id)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
foreach (var otherTheme in otherThemes)
|
||||||
|
otherTheme.IsEnabled = false;
|
||||||
|
|
||||||
|
await ThemeRepository.RunTransaction(set =>
|
||||||
|
{
|
||||||
|
set.UpdateRange(otherThemes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeMapper.Merge(theme, request);
|
||||||
|
await ThemeRepository.Update(theme);
|
||||||
|
|
||||||
|
return ThemeMapper.ToResponse(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.customisation.themes.write")]
|
||||||
|
public async Task Delete([FromRoute] int id)
|
||||||
|
{
|
||||||
|
var theme = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (theme == null)
|
||||||
|
throw new HttpApiException("Theme with this id not found", 404);
|
||||||
|
|
||||||
|
await ThemeRepository.Remove(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.Sys;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/system/diagnose")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.diagnose")]
|
||||||
|
public class DiagnoseController : Controller
|
||||||
|
{
|
||||||
|
private readonly DiagnoseService DiagnoseService;
|
||||||
|
|
||||||
|
public DiagnoseController(DiagnoseService diagnoseService)
|
||||||
|
{
|
||||||
|
DiagnoseService = diagnoseService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task Diagnose([FromBody] GenerateDiagnoseRequest request)
|
||||||
|
{
|
||||||
|
var stream = await DiagnoseService.GenerateDiagnose(request.Providers);
|
||||||
|
|
||||||
|
await Results.Stream(
|
||||||
|
stream,
|
||||||
|
contentType: "application/zip",
|
||||||
|
fileDownloadName: "diagnose.zip"
|
||||||
|
)
|
||||||
|
.ExecuteAsync(HttpContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("providers")]
|
||||||
|
public async Task<DiagnoseProvideResponse[]> GetProviders()
|
||||||
|
{
|
||||||
|
return await DiagnoseService.GetProviders();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
using System.Text;
|
||||||
|
using ICSharpCode.SharpZipLib.GZip;
|
||||||
|
using ICSharpCode.SharpZipLib.Tar;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.Sys.Files;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/system/files")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.files")]
|
||||||
|
public class FilesController : Controller
|
||||||
|
{
|
||||||
|
private readonly string BaseDirectory = "storage";
|
||||||
|
private readonly long MaxChunkSize = ByteConverter.FromMegaBytes(20).Bytes;
|
||||||
|
|
||||||
|
[HttpPost("touch")]
|
||||||
|
public async Task CreateFile([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(physicalPath))
|
||||||
|
throw new HttpApiException("A file already exists at that path", 400);
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
throw new HttpApiException("A folder already exists at that path", 400);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Create(physicalPath);
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("mkdir")]
|
||||||
|
public Task CreateFolder([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
throw new HttpApiException("A folder already exists at that path", 400);
|
||||||
|
|
||||||
|
if (System.IO.File.Exists(physicalPath))
|
||||||
|
throw new HttpApiException("A file already exists at that path", 400);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(physicalPath);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("list")]
|
||||||
|
public Task<FileSystemEntryResponse[]> List([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
var entries = new List<FileSystemEntryResponse>();
|
||||||
|
|
||||||
|
var files = Directory.GetFiles(physicalPath);
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(file);
|
||||||
|
|
||||||
|
entries.Add(new FileSystemEntryResponse()
|
||||||
|
{
|
||||||
|
Name = fi.Name,
|
||||||
|
Size = fi.Length,
|
||||||
|
CreatedAt = fi.CreationTimeUtc,
|
||||||
|
IsFolder = false,
|
||||||
|
UpdatedAt = fi.LastWriteTimeUtc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var directories = Directory.GetDirectories(physicalPath);
|
||||||
|
|
||||||
|
foreach (var directory in directories)
|
||||||
|
{
|
||||||
|
var di = new DirectoryInfo(directory);
|
||||||
|
|
||||||
|
entries.Add(new FileSystemEntryResponse()
|
||||||
|
{
|
||||||
|
Name = di.Name,
|
||||||
|
Size = 0,
|
||||||
|
CreatedAt = di.CreationTimeUtc,
|
||||||
|
UpdatedAt = di.LastWriteTimeUtc,
|
||||||
|
IsFolder = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(
|
||||||
|
entries.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("upload")]
|
||||||
|
public async Task Upload([FromQuery] string path, [FromQuery] long chunkSize, [FromQuery] long totalSize, [FromQuery] int chunkId)
|
||||||
|
{
|
||||||
|
if (Request.Form.Files.Count != 1)
|
||||||
|
throw new HttpApiException("You need to provide exactly one file", 400);
|
||||||
|
|
||||||
|
var file = Request.Form.Files[0];
|
||||||
|
|
||||||
|
if (file.Length > chunkSize)
|
||||||
|
throw new HttpApiException("The provided data exceeds the chunk size limit", 400);
|
||||||
|
|
||||||
|
var chunks = totalSize / chunkSize;
|
||||||
|
chunks += totalSize % chunkSize > 0 ? 1 : 0;
|
||||||
|
|
||||||
|
if (chunkId > chunks)
|
||||||
|
throw new HttpApiException("Invalid chunk id: Out of bounds", 400);
|
||||||
|
|
||||||
|
var positionToSkipTo = chunkSize * chunkId;
|
||||||
|
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
var baseDir = Path.GetDirectoryName(physicalPath);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(baseDir))
|
||||||
|
Directory.CreateDirectory(baseDir);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Open(physicalPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||||
|
|
||||||
|
// This resizes the file to the correct size so we can handle the chunk if it didnt exist
|
||||||
|
|
||||||
|
if (fs.Length != totalSize)
|
||||||
|
fs.SetLength(totalSize);
|
||||||
|
|
||||||
|
fs.Position = positionToSkipTo;
|
||||||
|
|
||||||
|
var dataStream = file.OpenReadStream();
|
||||||
|
|
||||||
|
await dataStream.CopyToAsync(fs);
|
||||||
|
await fs.FlushAsync();
|
||||||
|
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("move")]
|
||||||
|
public Task Move([FromQuery] string oldPath, [FromQuery] string newPath)
|
||||||
|
{
|
||||||
|
var oldSafePath = SanitizePath(oldPath);
|
||||||
|
var newSafePath = SanitizePath(newPath);
|
||||||
|
|
||||||
|
var oldPhysicalDirPath = Path.Combine(BaseDirectory, oldSafePath);
|
||||||
|
|
||||||
|
if (Directory.Exists(oldPhysicalDirPath))
|
||||||
|
{
|
||||||
|
var newPhysicalDirPath = Path.Combine(BaseDirectory, newSafePath);
|
||||||
|
|
||||||
|
Directory.Move(
|
||||||
|
oldPhysicalDirPath,
|
||||||
|
newPhysicalDirPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var oldPhysicalFilePath = Path.Combine(BaseDirectory, oldSafePath);
|
||||||
|
var newPhysicalFilePath = Path.Combine(BaseDirectory, newSafePath);
|
||||||
|
|
||||||
|
System.IO.File.Move(
|
||||||
|
oldPhysicalFilePath,
|
||||||
|
newPhysicalFilePath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("delete")]
|
||||||
|
public Task Delete([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalDirPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
if (Directory.Exists(physicalDirPath))
|
||||||
|
Directory.Delete(physicalDirPath, true);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var physicalFilePath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
System.IO.File.Delete(physicalFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("download")]
|
||||||
|
public async Task Download([FromQuery] string path)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var physicalPath = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Open(physicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
await fs.CopyToAsync(Response.Body);
|
||||||
|
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("compress")]
|
||||||
|
public async Task Compress([FromBody] CompressRequest request)
|
||||||
|
{
|
||||||
|
if (request.Type == "tar.gz")
|
||||||
|
await CompressTarGz(request.Path, request.ItemsToCompress);
|
||||||
|
else if (request.Type == "zip")
|
||||||
|
await CompressZip(request.Path, request.ItemsToCompress);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Tar Gz
|
||||||
|
|
||||||
|
private async Task CompressTarGz(string path, string[] itemsToCompress)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var destination = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
await using var outStream = System.IO.File.Create(destination);
|
||||||
|
await using var gzoStream = new GZipOutputStream(outStream);
|
||||||
|
await using var tarStream = new TarOutputStream(gzoStream, Encoding.UTF8);
|
||||||
|
|
||||||
|
foreach (var itemName in itemsToCompress)
|
||||||
|
{
|
||||||
|
var safeFilePath = SanitizePath(itemName);
|
||||||
|
var filePath = Path.Combine(BaseDirectory, safeFilePath);
|
||||||
|
|
||||||
|
var fi = new FileInfo(filePath);
|
||||||
|
|
||||||
|
if (fi.Exists)
|
||||||
|
await AddFileToTarGz(tarStream, filePath);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var safeDirePath = SanitizePath(itemName);
|
||||||
|
var dirPath = Path.Combine(BaseDirectory, safeDirePath);
|
||||||
|
|
||||||
|
await AddDirectoryToTarGz(tarStream, dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await tarStream.FlushAsync();
|
||||||
|
await gzoStream.FlushAsync();
|
||||||
|
await outStream.FlushAsync();
|
||||||
|
|
||||||
|
tarStream.Close();
|
||||||
|
gzoStream.Close();
|
||||||
|
outStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddDirectoryToTarGz(TarOutputStream tarOutputStream, string root)
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(root))
|
||||||
|
await AddFileToTarGz(tarOutputStream, file);
|
||||||
|
|
||||||
|
foreach (var directory in Directory.GetDirectories(root))
|
||||||
|
await AddDirectoryToTarGz(tarOutputStream, directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddFileToTarGz(TarOutputStream tarOutputStream, string file)
|
||||||
|
{
|
||||||
|
// Open file stream
|
||||||
|
var fs = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
|
||||||
|
// Meta
|
||||||
|
var entry = TarEntry.CreateTarEntry(file);
|
||||||
|
|
||||||
|
// Fix path
|
||||||
|
entry.Name = Formatter
|
||||||
|
.ReplaceStart(entry.Name, BaseDirectory, "")
|
||||||
|
.TrimStart('/');
|
||||||
|
|
||||||
|
entry.Size = fs.Length;
|
||||||
|
|
||||||
|
// Write entry
|
||||||
|
await tarOutputStream.PutNextEntryAsync(entry, CancellationToken.None);
|
||||||
|
|
||||||
|
// Copy file content to tar stream
|
||||||
|
await fs.CopyToAsync(tarOutputStream);
|
||||||
|
fs.Close();
|
||||||
|
|
||||||
|
// Close the entry
|
||||||
|
tarOutputStream.CloseEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ZIP
|
||||||
|
|
||||||
|
private async Task CompressZip(string path, string[] itemsToCompress)
|
||||||
|
{
|
||||||
|
var safePath = SanitizePath(path);
|
||||||
|
var destination = Path.Combine(BaseDirectory, safePath);
|
||||||
|
|
||||||
|
await using var outStream = System.IO.File.Create(destination);
|
||||||
|
await using var zipOutputStream = new ZipOutputStream(outStream);
|
||||||
|
|
||||||
|
foreach (var itemName in itemsToCompress)
|
||||||
|
{
|
||||||
|
var safeFilePath = SanitizePath(itemName);
|
||||||
|
var filePath = Path.Combine(BaseDirectory, safeFilePath);
|
||||||
|
|
||||||
|
var fi = new FileInfo(filePath);
|
||||||
|
|
||||||
|
if (fi.Exists)
|
||||||
|
await AddFileToZip(zipOutputStream, filePath);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var safeDirePath = SanitizePath(itemName);
|
||||||
|
var dirPath = Path.Combine(BaseDirectory, safeDirePath);
|
||||||
|
|
||||||
|
await AddDirectoryToZip(zipOutputStream, dirPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await zipOutputStream.FlushAsync();
|
||||||
|
await outStream.FlushAsync();
|
||||||
|
|
||||||
|
zipOutputStream.Close();
|
||||||
|
outStream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddFileToZip(ZipOutputStream zipOutputStream, string path)
|
||||||
|
{
|
||||||
|
// Open file stream
|
||||||
|
var fs = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
|
||||||
|
// Fix path
|
||||||
|
var name = Formatter
|
||||||
|
.ReplaceStart(path, BaseDirectory, "")
|
||||||
|
.TrimStart('/');
|
||||||
|
|
||||||
|
// Meta
|
||||||
|
var entry = new ZipEntry(name);
|
||||||
|
|
||||||
|
entry.Size = fs.Length;
|
||||||
|
|
||||||
|
// Write entry
|
||||||
|
await zipOutputStream.PutNextEntryAsync(entry, CancellationToken.None);
|
||||||
|
|
||||||
|
// Copy file content to tar stream
|
||||||
|
await fs.CopyToAsync(zipOutputStream);
|
||||||
|
fs.Close();
|
||||||
|
|
||||||
|
// Close the entry
|
||||||
|
zipOutputStream.CloseEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddDirectoryToZip(ZipOutputStream zipOutputStream, string root)
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(root))
|
||||||
|
await AddFileToZip(zipOutputStream, file);
|
||||||
|
|
||||||
|
foreach (var directory in Directory.GetDirectories(root))
|
||||||
|
await AddDirectoryToZip(zipOutputStream, directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
[HttpPost("decompress")]
|
||||||
|
public async Task Decompress([FromBody] DecompressRequest request)
|
||||||
|
{
|
||||||
|
if (request.Type == "tar.gz")
|
||||||
|
await DecompressTarGz(request.Path, request.Destination);
|
||||||
|
else if (request.Type == "zip")
|
||||||
|
await DecompressZip(request.Path, request.Destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Tar Gz
|
||||||
|
|
||||||
|
private async Task DecompressTarGz(string path, string destination)
|
||||||
|
{
|
||||||
|
var safeDestination = SanitizePath(destination);
|
||||||
|
|
||||||
|
var safeArchivePath = SanitizePath(path);
|
||||||
|
var archivePath = Path.Combine(BaseDirectory, safeArchivePath);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Open(archivePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
await using var gzipInputStream = new GZipInputStream(fs);
|
||||||
|
await using var tarInputStream = new TarInputStream(gzipInputStream, Encoding.UTF8);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var entry = await tarInputStream.GetNextEntryAsync(CancellationToken.None);
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var safeFilePath = SanitizePath(entry.Name);
|
||||||
|
var fileDestination = Path.Combine(BaseDirectory, safeDestination, safeFilePath);
|
||||||
|
var parentFolder = Path.GetDirectoryName(fileDestination);
|
||||||
|
|
||||||
|
// Ensure parent directory exists, if it's not the base directory
|
||||||
|
if (parentFolder != null && parentFolder != BaseDirectory)
|
||||||
|
Directory.CreateDirectory(parentFolder);
|
||||||
|
|
||||||
|
await using var fileDestinationFs =
|
||||||
|
System.IO.File.Open(fileDestination, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
|
await tarInputStream.CopyToAsync(fileDestinationFs, CancellationToken.None);
|
||||||
|
|
||||||
|
await fileDestinationFs.FlushAsync();
|
||||||
|
fileDestinationFs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
tarInputStream.Close();
|
||||||
|
gzipInputStream.Close();
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Zip
|
||||||
|
|
||||||
|
private async Task DecompressZip(string path, string destination)
|
||||||
|
{
|
||||||
|
var safeDestination = SanitizePath(destination);
|
||||||
|
|
||||||
|
var safeArchivePath = SanitizePath(path);
|
||||||
|
var archivePath = Path.Combine(BaseDirectory, safeArchivePath);
|
||||||
|
|
||||||
|
await using var fs = System.IO.File.Open(archivePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
await using var zipInputStream = new ZipInputStream(fs);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var entry = zipInputStream.GetNextEntry();
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (entry.IsDirectory)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var safeFilePath = SanitizePath(entry.Name);
|
||||||
|
var fileDestination = Path.Combine(BaseDirectory, safeDestination, safeFilePath);
|
||||||
|
var parentFolder = Path.GetDirectoryName(fileDestination);
|
||||||
|
|
||||||
|
// Ensure parent directory exists, if it's not the base directory
|
||||||
|
if (parentFolder != null && parentFolder != BaseDirectory)
|
||||||
|
Directory.CreateDirectory(parentFolder);
|
||||||
|
|
||||||
|
await using var fileDestinationFs =
|
||||||
|
System.IO.File.Open(fileDestination, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
|
||||||
|
await zipInputStream.CopyToAsync(fileDestinationFs, CancellationToken.None);
|
||||||
|
|
||||||
|
await fileDestinationFs.FlushAsync();
|
||||||
|
fileDestinationFs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInputStream.Close();
|
||||||
|
fs.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private string SanitizePath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
// Normalize separators
|
||||||
|
path = path.Replace('\\', '/');
|
||||||
|
|
||||||
|
// Remove ".." and "."
|
||||||
|
var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Where(part => part != ".." && part != ".");
|
||||||
|
|
||||||
|
var sanitized = string.Join("/", parts);
|
||||||
|
|
||||||
|
// Ensure it does not start with a slash
|
||||||
|
if (sanitized.StartsWith('/'))
|
||||||
|
sanitized = sanitized.TrimStart('/');
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/system")]
|
||||||
|
public class SystemController : Controller
|
||||||
|
{
|
||||||
|
private readonly ApplicationService ApplicationService;
|
||||||
|
|
||||||
|
public SystemController(ApplicationService applicationService)
|
||||||
|
{
|
||||||
|
ApplicationService = applicationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.overview")]
|
||||||
|
public async Task<SystemOverviewResponse> GetOverview()
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Uptime = await ApplicationService.GetUptime(),
|
||||||
|
CpuUsage = await ApplicationService.GetCpuUsage(),
|
||||||
|
MemoryUsage = await ApplicationService.GetMemoryUsage(),
|
||||||
|
OperatingSystem = await ApplicationService.GetOsName()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("shutdown")]
|
||||||
|
[Authorize(Policy = "permissions:admin.system.shutdown")]
|
||||||
|
public async Task Shutdown()
|
||||||
|
{
|
||||||
|
await ApplicationService.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Extended.Helpers;
|
||||||
|
using MoonCore.Models;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.Users;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Users;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Admin.Users;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/admin/users")]
|
||||||
|
public class UsersController : Controller
|
||||||
|
{
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
|
||||||
|
public UsersController(DatabaseRepository<User> userRepository)
|
||||||
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Policy = "permissions:admin.users.get")]
|
||||||
|
public async Task<IPagedData<UserResponse>> Get(
|
||||||
|
[FromQuery] [Range(0, int.MaxValue)] int page,
|
||||||
|
[FromQuery] [Range(1, 100)] int pageSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var count = await UserRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
var users = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.Skip(page * pageSize)
|
||||||
|
.Take(pageSize)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var mappedUsers = users
|
||||||
|
.Select(x => new UserResponse()
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
Email = x.Email,
|
||||||
|
Username = x.Username,
|
||||||
|
Permissions = x.Permissions
|
||||||
|
})
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return new PagedData<UserResponse>()
|
||||||
|
{
|
||||||
|
CurrentPage = page,
|
||||||
|
Items = mappedUsers,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalItems = count,
|
||||||
|
TotalPages = count == 0 ? 0 : (count - 1) / pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.users.get")]
|
||||||
|
public async Task<UserResponse> GetSingle(int id)
|
||||||
|
{
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("No user with that id found", 404);
|
||||||
|
|
||||||
|
return new UserResponse()
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Email = user.Email,
|
||||||
|
Username = user.Username,
|
||||||
|
Permissions = user.Permissions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Policy = "permissions:admin.users.create")]
|
||||||
|
public async Task<UserResponse> Create([FromBody] CreateUserRequest request)
|
||||||
|
{
|
||||||
|
// Reformat values
|
||||||
|
request.Username = request.Username.ToLower().Trim();
|
||||||
|
request.Email = request.Email.ToLower().Trim();
|
||||||
|
|
||||||
|
// Check for users with the same values
|
||||||
|
if (UserRepository.Get().Any(x => x.Username == request.Username))
|
||||||
|
throw new HttpApiException("A user with that username already exists", 400);
|
||||||
|
|
||||||
|
if (UserRepository.Get().Any(x => x.Email == request.Email))
|
||||||
|
throw new HttpApiException("A user with that email address already exists", 400);
|
||||||
|
|
||||||
|
var hashedPassword = HashHelper.Hash(request.Password);
|
||||||
|
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Email = request.Email,
|
||||||
|
Username = request.Username,
|
||||||
|
Password = hashedPassword,
|
||||||
|
Permissions = request.Permissions
|
||||||
|
};
|
||||||
|
|
||||||
|
var finalUser = await UserRepository.Add(user);
|
||||||
|
|
||||||
|
return new UserResponse()
|
||||||
|
{
|
||||||
|
Id = finalUser.Id,
|
||||||
|
Email = finalUser.Email,
|
||||||
|
Username = finalUser.Username,
|
||||||
|
Permissions = finalUser.Permissions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.users.update")]
|
||||||
|
public async Task<UserResponse> Update([FromRoute] int id, [FromBody] UpdateUserRequest request)
|
||||||
|
{
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("No user with that id found", 404);
|
||||||
|
|
||||||
|
// Reformat values
|
||||||
|
request.Username = request.Username.ToLower().Trim();
|
||||||
|
request.Email = request.Email.ToLower().Trim();
|
||||||
|
|
||||||
|
// Check for users with the same values
|
||||||
|
if (UserRepository.Get().Any(x => x.Username == request.Username && x.Id != user.Id))
|
||||||
|
throw new HttpApiException("A user with that username already exists", 400);
|
||||||
|
|
||||||
|
if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id))
|
||||||
|
throw new HttpApiException("A user with that email address already exists", 400);
|
||||||
|
|
||||||
|
// Perform hashing the password if required
|
||||||
|
if (!string.IsNullOrEmpty(request.Password))
|
||||||
|
{
|
||||||
|
user.Password = HashHelper.Hash(request.Password);
|
||||||
|
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after password change
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Permissions.Any(x => !user.Permissions.Contains(x)))
|
||||||
|
{
|
||||||
|
user.Permissions = request.Permissions;
|
||||||
|
user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after permission change
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Email = request.Email;
|
||||||
|
user.Username = request.Username;
|
||||||
|
|
||||||
|
await UserRepository.Update(user);
|
||||||
|
|
||||||
|
return new UserResponse()
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
Email = user.Email,
|
||||||
|
Username = user.Username,
|
||||||
|
Permissions = user.Permissions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize(Policy = "permissions:admin.users.delete")]
|
||||||
|
public async Task Delete([FromRoute] int id, [FromQuery] bool force = false)
|
||||||
|
{
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("No user with that id found", 404);
|
||||||
|
|
||||||
|
var deletionService = HttpContext.RequestServices.GetRequiredService<UserDeletionService>();
|
||||||
|
|
||||||
|
if (!force)
|
||||||
|
{
|
||||||
|
var validationResult = await deletionService.Validate(user);
|
||||||
|
|
||||||
|
if (!validationResult.IsAllowed)
|
||||||
|
throw new HttpApiException($"Unable to delete user", 400, validationResult.Reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
await deletionService.Delete(user, force);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs
Normal file
111
Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.Shared.Http.Requests.Auth;
|
||||||
|
using Moonlight.Shared.Http.Responses.Auth;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Auth;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/auth")]
|
||||||
|
public class AuthController : Controller
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
private readonly IOAuth2Provider OAuth2Provider;
|
||||||
|
|
||||||
|
public AuthController(
|
||||||
|
AppConfiguration configuration,
|
||||||
|
DatabaseRepository<User> userRepository,
|
||||||
|
IOAuth2Provider oAuth2Provider
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
OAuth2Provider = oAuth2Provider;
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("start")]
|
||||||
|
public async Task<LoginStartResponse> Start()
|
||||||
|
{
|
||||||
|
var url = await OAuth2Provider.Start();
|
||||||
|
|
||||||
|
return new LoginStartResponse()
|
||||||
|
{
|
||||||
|
Url = url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("complete")]
|
||||||
|
public async Task<LoginCompleteResponse> Complete([FromBody] LoginCompleteRequest request)
|
||||||
|
{
|
||||||
|
var user = await OAuth2Provider.Complete(request.Code);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("Unable to load user data", 500);
|
||||||
|
|
||||||
|
// Generate token
|
||||||
|
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
||||||
|
{
|
||||||
|
Expires = DateTime.Now.AddHours(Configuration.Authentication.TokenDuration),
|
||||||
|
IssuedAt = DateTime.Now,
|
||||||
|
NotBefore = DateTime.Now.AddMinutes(-1),
|
||||||
|
Claims = new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"userId",
|
||||||
|
user.Id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permissions",
|
||||||
|
string.Join(";", user.Permissions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(Configuration.Authentication.Secret)
|
||||||
|
),
|
||||||
|
SecurityAlgorithms.HmacSha256
|
||||||
|
),
|
||||||
|
Issuer = Configuration.PublicUrl,
|
||||||
|
Audience = Configuration.PublicUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
|
||||||
|
|
||||||
|
var jwt = jwtSecurityTokenHandler.WriteToken(securityToken);
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
AccessToken = jwt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("check")]
|
||||||
|
public async Task<CheckResponse> Check()
|
||||||
|
{
|
||||||
|
var userIdStr = User.FindFirstValue("userId")!;
|
||||||
|
var userId = int.Parse(userIdStr);
|
||||||
|
var user = await UserRepository.Get().FirstAsync(x => x.Id == userId);
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Email = user.Email,
|
||||||
|
Username = user.Username,
|
||||||
|
Permissions = user.Permissions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using Moonlight.Shared.Misc;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Frontend;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("/")]
|
||||||
|
public class FrontendController : Controller
|
||||||
|
{
|
||||||
|
private readonly FrontendService FrontendService;
|
||||||
|
|
||||||
|
public FrontendController(FrontendService frontendService)
|
||||||
|
{
|
||||||
|
FrontendService = frontendService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("frontend.json")]
|
||||||
|
public async Task<FrontendConfiguration> GetConfiguration()
|
||||||
|
=> await FrontendService.GetConfiguration();
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IResult> Index()
|
||||||
|
{
|
||||||
|
var content = await FrontendService.GenerateIndexHtml();
|
||||||
|
|
||||||
|
return Results.Text(content, "text/html", Encoding.UTF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor
Normal file
106
Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
@using Moonlight.ApiServer.Database.Entities
|
||||||
|
@using Moonlight.Shared.Misc
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="bg-background text-base-content font-inter">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>@Title</title>
|
||||||
|
<base href="/"/>
|
||||||
|
|
||||||
|
@foreach (var style in Styles)
|
||||||
|
{
|
||||||
|
<link rel="stylesheet" href="@style"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<link href="manifest.webmanifest" rel="manifest"/>
|
||||||
|
<link rel="apple-touch-icon" sizes="512x512" href="/_content/Moonlight.Client/img/icon-512.png"/>
|
||||||
|
<link rel="apple-touch-icon" sizes="192x192" href="/_content/Moonlight.Client/img/icon-192.png"/>
|
||||||
|
|
||||||
|
@if (Theme != null)
|
||||||
|
{
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--mooncore-color-background: @(Theme.Content.ColorBackground);
|
||||||
|
--mooncore-color-base-100: @(Theme.Content.ColorBase100);
|
||||||
|
--mooncore-color-base-150: @(Theme.Content.ColorBase150);
|
||||||
|
--mooncore-color-base-200: @(Theme.Content.ColorBase200);
|
||||||
|
--mooncore-color-base-250: @(Theme.Content.ColorBase250);
|
||||||
|
--mooncore-color-base-300: @(Theme.Content.ColorBase300);
|
||||||
|
--mooncore-color-base-content: @(Theme.Content.ColorBaseContent);
|
||||||
|
|
||||||
|
--mooncore-color-primary: @(Theme.Content.ColorPrimary);
|
||||||
|
--mooncore-color-primary-content: @(Theme.Content.ColorPrimaryContent);
|
||||||
|
|
||||||
|
--mooncore-color-secondary: @(Theme.Content.ColorSecondary);
|
||||||
|
--mooncore-color-secondary-content: @(Theme.Content.ColorSecondaryContent);
|
||||||
|
|
||||||
|
--mooncore-color-accent: @(Theme.Content.ColorAccent);
|
||||||
|
--mooncore-color-accent-content: @(Theme.Content.ColorAccentContent);
|
||||||
|
|
||||||
|
--mooncore-color-neutral: @(Theme.Content.ColorNeutral);
|
||||||
|
--mooncore-color-neutral-content: @(Theme.Content.ColorNeutralContent);
|
||||||
|
|
||||||
|
--mooncore-color-info: @(Theme.Content.ColorInfo);
|
||||||
|
--mooncore-color-info-content: @(Theme.Content.ColorInfoContent);
|
||||||
|
|
||||||
|
--mooncore-color-success: @(Theme.Content.ColorSuccess);
|
||||||
|
--mooncore-color-success-content: @(Theme.Content.ColorSuccessContent);
|
||||||
|
|
||||||
|
--mooncore-color-warning: @(Theme.Content.ColorWarning);
|
||||||
|
--mooncore-color-warning-content: @(Theme.Content.ColorWarningContent);
|
||||||
|
|
||||||
|
--mooncore-color-error: @(Theme.Content.ColorError);
|
||||||
|
--mooncore-color-error-content: @(Theme.Content.ColorErrorContent);
|
||||||
|
|
||||||
|
--mooncore-radius-selector: @(Theme.Content.RadiusSelector)rem;
|
||||||
|
--mooncore-radius-field: @(Theme.Content.RadiusField)rem;
|
||||||
|
--mooncore-radius-box: @(Theme.Content.RadiusBox)rem;
|
||||||
|
|
||||||
|
--mooncore-size-selector: @(Theme.Content.SizeSelector)rem;
|
||||||
|
--mooncore-size-field: @(Theme.Content.SizeField)rem;
|
||||||
|
|
||||||
|
--mooncore-border: @(Theme.Content.Border)px;
|
||||||
|
--mooncore-depth: @(Theme.Content.Depth);
|
||||||
|
--mooncore-noise: @(Theme.Content.Noise);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
|
||||||
|
<div class="flex h-screen justify-center items-center">
|
||||||
|
<div class="sm:max-w-lg">
|
||||||
|
<div id="blazor-loader-label" class="text-center mb-2 text-lg font-semibold"></div>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="progress h-3 min-w-sm md:min-w-md" role="progressbar" aria-valuemin="0" aria-valuemax="100">
|
||||||
|
<div id="blazor-loader-progress" class="progress-bar progress-primary"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@foreach (var script in Scripts)
|
||||||
|
{
|
||||||
|
<script src="@script"></script>
|
||||||
|
}
|
||||||
|
|
||||||
|
<script src="/_framework/blazor.webassembly.js"></script>
|
||||||
|
<script>navigator.serviceWorker.register('service-worker.js');</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public string Title { get; set; }
|
||||||
|
[Parameter] public string[] Scripts { get; set; }
|
||||||
|
[Parameter] public string[] Styles { get; set; }
|
||||||
|
[Parameter] public Theme? Theme { get; set; }
|
||||||
|
}
|
||||||
62
Moonlight.ApiServer/Http/Controllers/OAuth2/Login.razor
Normal file
62
Moonlight.ApiServer/Http/Controllers/OAuth2/Login.razor
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<html lang="en" class="h-full bg-background">
|
||||||
|
<head>
|
||||||
|
<title>Login into your account</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.min.css"/>
|
||||||
|
</head>
|
||||||
|
<body class="h-full">
|
||||||
|
|
||||||
|
<div class="flex h-auto min-h-screen items-center justify-center overflow-x-hidden py-10">
|
||||||
|
<div class="relative flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div
|
||||||
|
class="bg-base-100 shadow-base-300/20 z-1 w-full space-y-6 rounded-xl p-6 shadow-md sm:min-w-md lg:p-8">
|
||||||
|
<div class="flex justify-center items-center gap-3">
|
||||||
|
<img src="/_content/Moonlight.Client/svg/logo.svg" class="size-12" alt="brand-logo"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-base-content mb-1.5 text-2xl font-semibold">Login into your account</h3>
|
||||||
|
<p class="text-base-content/80">After logging in you will be able to manage your services</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-error text-center">
|
||||||
|
@ErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form class="mb-4 space-y-4" method="post">
|
||||||
|
<div>
|
||||||
|
<label class="label-text" for="email">Email address</label>
|
||||||
|
<input type="email" name="email" placeholder="Enter your email address" class="input" id="email"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-text" for="password">Password</label>
|
||||||
|
<input class="input" name="password" id="password" type="password" placeholder="············"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary btn-gradient btn-block">Login</button>
|
||||||
|
</form>
|
||||||
|
<p class="text-base-content/80 mb-4 text-center">
|
||||||
|
No account?
|
||||||
|
<a href="?client_id=@(ClientId)&redirect_uri=@(RedirectUri)&response_type=@(ResponseType)&view=register"
|
||||||
|
class="link link-animated link-primary font-normal">Create an account</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public string ClientId { get; set; }
|
||||||
|
[Parameter] public string RedirectUri { get; set; }
|
||||||
|
[Parameter] public string ResponseType { get; set; }
|
||||||
|
[Parameter] public string? ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
317
Moonlight.ApiServer/Http/Controllers/OAuth2/OAuth2Controller.cs
Normal file
317
Moonlight.ApiServer/Http/Controllers/OAuth2/OAuth2Controller.cs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Extended.Helpers;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.Shared.Http.Responses.OAuth2;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.OAuth2;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("oauth2")]
|
||||||
|
public partial class OAuth2Controller : Controller
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
|
||||||
|
private readonly string ExpectedRedirectUri;
|
||||||
|
|
||||||
|
public OAuth2Controller(AppConfiguration configuration, DatabaseRepository<User> userRepository)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
UserRepository = userRepository;
|
||||||
|
|
||||||
|
ExpectedRedirectUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationRedirect)
|
||||||
|
? Configuration.PublicUrl
|
||||||
|
: Configuration.Authentication.OAuth2.AuthorizationRedirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpGet("authorize")]
|
||||||
|
public async Task Authorize(
|
||||||
|
[FromQuery(Name = "client_id")] string clientId,
|
||||||
|
[FromQuery(Name = "redirect_uri")] string redirectUri,
|
||||||
|
[FromQuery(Name = "response_type")] string responseType,
|
||||||
|
[FromQuery(Name = "view")] string view = "login"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!Configuration.Authentication.EnableLocalOAuth2)
|
||||||
|
throw new HttpApiException("Local OAuth2 has been disabled", 403);
|
||||||
|
|
||||||
|
if (Configuration.Authentication.OAuth2.ClientId != clientId ||
|
||||||
|
redirectUri != ExpectedRedirectUri ||
|
||||||
|
responseType != "code")
|
||||||
|
{
|
||||||
|
throw new HttpApiException("Invalid oauth2 request", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
string html;
|
||||||
|
|
||||||
|
if (view == "register")
|
||||||
|
{
|
||||||
|
html = await ComponentHelper.RenderComponent<Register>(HttpContext.RequestServices, parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("ClientId", clientId);
|
||||||
|
parameters.Add("RedirectUri", redirectUri);
|
||||||
|
parameters.Add("ResponseType", responseType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
html = await ComponentHelper.RenderComponent<Login>(HttpContext.RequestServices, parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("ClientId", clientId);
|
||||||
|
parameters.Add("RedirectUri", redirectUri);
|
||||||
|
parameters.Add("ResponseType", responseType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Results
|
||||||
|
.Text(html, "text/html", Encoding.UTF8)
|
||||||
|
.ExecuteAsync(HttpContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("authorize")]
|
||||||
|
public async Task AuthorizePost(
|
||||||
|
[FromQuery(Name = "client_id")] string clientId,
|
||||||
|
[FromQuery(Name = "redirect_uri")] string redirectUri,
|
||||||
|
[FromQuery(Name = "response_type")] string responseType,
|
||||||
|
[FromForm(Name = "email")] [EmailAddress(ErrorMessage = "You need to provide a valid email address")] string email,
|
||||||
|
[FromForm(Name = "password")] string password,
|
||||||
|
[FromForm(Name = "username")] string username = "",
|
||||||
|
[FromQuery(Name = "view")] string view = "login"
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!Configuration.Authentication.EnableLocalOAuth2)
|
||||||
|
throw new HttpApiException("Local OAuth2 has been disabled", 403);
|
||||||
|
|
||||||
|
if (Configuration.Authentication.OAuth2.ClientId != clientId ||
|
||||||
|
redirectUri != ExpectedRedirectUri ||
|
||||||
|
responseType != "code")
|
||||||
|
{
|
||||||
|
throw new HttpApiException("Invalid oauth2 request", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (view == "register" && string.IsNullOrEmpty(username))
|
||||||
|
throw new HttpApiException("You need to provide a username", 400);
|
||||||
|
|
||||||
|
string? errorMessage = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (view == "register")
|
||||||
|
{
|
||||||
|
var user = await Register(username, email, password);
|
||||||
|
var code = await GenerateCode(user);
|
||||||
|
|
||||||
|
Response.Redirect($"{redirectUri}?code={code}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var user = await Login(email, password);
|
||||||
|
var code = await GenerateCode(user);
|
||||||
|
|
||||||
|
Response.Redirect($"{redirectUri}?code={code}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (HttpApiException e)
|
||||||
|
{
|
||||||
|
errorMessage = e.Title;
|
||||||
|
|
||||||
|
string html;
|
||||||
|
|
||||||
|
if (view == "register")
|
||||||
|
{
|
||||||
|
html = await ComponentHelper.RenderComponent<Register>(HttpContext.RequestServices, parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("ClientId", clientId);
|
||||||
|
parameters.Add("RedirectUri", redirectUri);
|
||||||
|
parameters.Add("ResponseType", responseType);
|
||||||
|
parameters.Add("ErrorMessage", errorMessage!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
html = await ComponentHelper.RenderComponent<Login>(HttpContext.RequestServices, parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("ClientId", clientId);
|
||||||
|
parameters.Add("RedirectUri", redirectUri);
|
||||||
|
parameters.Add("ResponseType", responseType);
|
||||||
|
parameters.Add("ErrorMessage", errorMessage!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await Results
|
||||||
|
.Text(html, "text/html", Encoding.UTF8)
|
||||||
|
.ExecuteAsync(HttpContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("handle")]
|
||||||
|
public async Task<OAuth2HandleResponse> Handle(
|
||||||
|
[FromForm(Name = "grant_type")] string grantType,
|
||||||
|
[FromForm(Name = "code")] string code,
|
||||||
|
[FromForm(Name = "redirect_uri")] string redirectUri,
|
||||||
|
[FromForm(Name = "client_id")] string clientId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!Configuration.Authentication.EnableLocalOAuth2)
|
||||||
|
throw new HttpApiException("Local OAuth2 has been disabled", 403);
|
||||||
|
|
||||||
|
// Check header
|
||||||
|
if (!Request.Headers.ContainsKey("Authorization"))
|
||||||
|
throw new HttpApiException("You are missing the Authorization header", 400);
|
||||||
|
|
||||||
|
var authorizationHeaderValue = Request.Headers["Authorization"].FirstOrDefault() ?? "";
|
||||||
|
|
||||||
|
if (authorizationHeaderValue != $"Basic {Configuration.Authentication.OAuth2.ClientSecret}")
|
||||||
|
throw new HttpApiException("Invalid Authorization header value", 400);
|
||||||
|
|
||||||
|
// Check form
|
||||||
|
if (grantType != "authorization_code")
|
||||||
|
throw new HttpApiException("Invalid grant type provided", 400);
|
||||||
|
|
||||||
|
if (clientId != Configuration.Authentication.OAuth2.ClientId)
|
||||||
|
throw new HttpApiException("Invalid client id provided", 400);
|
||||||
|
|
||||||
|
if (redirectUri != ExpectedRedirectUri)
|
||||||
|
throw new HttpApiException("Invalid redirect uri provided", 400);
|
||||||
|
|
||||||
|
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
|
||||||
|
ClaimsPrincipal? codeData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
codeData = jwtSecurityTokenHandler.ValidateToken(code, new TokenValidationParameters()
|
||||||
|
{
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
|
||||||
|
Configuration.Authentication.OAuth2.Secret
|
||||||
|
)),
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ClockSkew = TimeSpan.Zero,
|
||||||
|
ValidateAudience = false,
|
||||||
|
ValidateIssuer = false
|
||||||
|
}, out _);
|
||||||
|
}
|
||||||
|
catch (SecurityTokenException)
|
||||||
|
{
|
||||||
|
throw new HttpApiException("Invalid code provided", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeData == null)
|
||||||
|
throw new HttpApiException("Invalid code provided", 400);
|
||||||
|
|
||||||
|
var userIdClaim = codeData.Claims.FirstOrDefault(x => x.Type == "id");
|
||||||
|
|
||||||
|
if (userIdClaim == null)
|
||||||
|
throw new HttpApiException("Malformed code provided", 400);
|
||||||
|
|
||||||
|
if (!int.TryParse(userIdClaim.Value, out var userId))
|
||||||
|
throw new HttpApiException("Malformed code provided", 400);
|
||||||
|
|
||||||
|
var user = UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.Id == userId);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("Malformed code provided", 400);
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
UserId = user.Id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<string> GenerateCode(User user)
|
||||||
|
{
|
||||||
|
var securityTokenDescriptor = new SecurityTokenDescriptor()
|
||||||
|
{
|
||||||
|
Expires = DateTime.Now.AddMinutes(1),
|
||||||
|
IssuedAt = DateTime.Now,
|
||||||
|
NotBefore = DateTime.Now.AddMinutes(-1),
|
||||||
|
Claims = new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"id",
|
||||||
|
user.Id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(Configuration.Authentication.OAuth2.Secret)
|
||||||
|
),
|
||||||
|
SecurityAlgorithms.HmacSha256
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
var securityToken = jwtSecurityTokenHandler.CreateToken(securityTokenDescriptor);
|
||||||
|
|
||||||
|
return Task.FromResult(
|
||||||
|
jwtSecurityTokenHandler.WriteToken(securityToken)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<User> Register(string username, string email, string password)
|
||||||
|
{
|
||||||
|
if (await UserRepository.Get().AnyAsync(x => x.Username == username))
|
||||||
|
throw new HttpApiException("A account with that username already exists", 400);
|
||||||
|
|
||||||
|
if (await UserRepository.Get().AnyAsync(x => x.Email == email))
|
||||||
|
throw new HttpApiException("A account with that email already exists", 400);
|
||||||
|
|
||||||
|
if (!UsernameRegex().IsMatch(username))
|
||||||
|
throw new HttpApiException("The username is only allowed to be contained out of small characters and numbers", 400);
|
||||||
|
|
||||||
|
var user = new User()
|
||||||
|
{
|
||||||
|
Username = username,
|
||||||
|
Email = email,
|
||||||
|
Password = HashHelper.Hash(password),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Configuration.Authentication.OAuth2.FirstUserAdmin)
|
||||||
|
{
|
||||||
|
var userCount = await UserRepository.Get().CountAsync();
|
||||||
|
|
||||||
|
if (userCount == 0)
|
||||||
|
user.Permissions = ["*"];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return await UserRepository.Add(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<User> Login(string email, string password)
|
||||||
|
{
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Email == email);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new HttpApiException("Invalid combination of email and password", 400);
|
||||||
|
|
||||||
|
if (!HashHelper.Verify(password, user.Password))
|
||||||
|
throw new HttpApiException("Invalid combination of email and password", 400);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("^[a-z][a-z0-9]*$")]
|
||||||
|
private static partial Regex UsernameRegex();
|
||||||
|
}
|
||||||
66
Moonlight.ApiServer/Http/Controllers/OAuth2/Register.razor
Normal file
66
Moonlight.ApiServer/Http/Controllers/OAuth2/Register.razor
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<html lang="en" class="h-full bg-background">
|
||||||
|
<head>
|
||||||
|
<title>Register a new account</title>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/style.min.css"/>
|
||||||
|
</head>
|
||||||
|
<body class="h-full">
|
||||||
|
|
||||||
|
<div class="flex h-auto min-h-screen items-center justify-center overflow-x-hidden py-10">
|
||||||
|
<div class="relative flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div
|
||||||
|
class="bg-base-100 shadow-base-300/20 z-1 w-full space-y-6 rounded-xl p-6 shadow-md sm:min-w-md lg:p-8">
|
||||||
|
<div class="flex justify-center items-center gap-3">
|
||||||
|
<img src="/_content/Moonlight.Client/svg/logo.svg" class="size-12" alt="brand-logo"/>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3 class="text-base-content mb-1.5 text-2xl font-semibold">Register a new account</h3>
|
||||||
|
<p class="text-base-content/80">After signing up you will be able to manage your services</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-error text-center">
|
||||||
|
@ErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<form class="mb-4 space-y-4" method="post">
|
||||||
|
<div>
|
||||||
|
<label class="label-text" for="username">Username</label>
|
||||||
|
<input type="text" name="username" placeholder="Enter your username" class="input" id="username"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-text" for="email">Email address</label>
|
||||||
|
<input type="email" name="email" placeholder="Enter your email address" class="input" id="email"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="label-text" for="password">Password</label>
|
||||||
|
<input class="input" name="password" id="password" type="password" placeholder="············"
|
||||||
|
required/>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-lg btn-primary btn-gradient btn-block">Register</button>
|
||||||
|
</form>
|
||||||
|
<p class="text-base-content/80 mb-4 text-center">
|
||||||
|
Already registered?
|
||||||
|
<a href="?client_id=@(ClientId)&redirect_uri=@(RedirectUri)&response_type=@(ResponseType)&view=login"
|
||||||
|
class="link link-animated link-primary font-normal">Login into your account</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public string ClientId { get; set; }
|
||||||
|
[Parameter] public string RedirectUri { get; set; }
|
||||||
|
[Parameter] public string ResponseType { get; set; }
|
||||||
|
[Parameter] public string? ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Http.Controllers.Swagger;
|
||||||
|
|
||||||
|
[Route("api/swagger")]
|
||||||
|
public class SwaggerController : Controller
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly IServiceProvider ServiceProvider;
|
||||||
|
|
||||||
|
public SwaggerController(
|
||||||
|
AppConfiguration configuration,
|
||||||
|
IServiceProvider serviceProvider
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
ServiceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult> Get()
|
||||||
|
{
|
||||||
|
if (!Configuration.Development.EnableApiDocs)
|
||||||
|
return BadRequest("Api docs are disabled");
|
||||||
|
|
||||||
|
var options = new ApiDocsOptions();
|
||||||
|
var optionsJson = JsonSerializer.Serialize(options);
|
||||||
|
|
||||||
|
var html = await ComponentHelper.RenderComponent<SwaggerPage>(
|
||||||
|
ServiceProvider,
|
||||||
|
parameters =>
|
||||||
|
{
|
||||||
|
parameters.Add("Options", optionsJson);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return Content(html, "text/html");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Moonlight Api Reference</title>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script id="api-reference" data-url="/api/swagger/main"></script>
|
||||||
|
<script>
|
||||||
|
const configuration = @(Options)
|
||||||
|
|
||||||
|
document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration)
|
||||||
|
</script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@@scalar/api-reference"></script>
|
||||||
|
<style>.light-mode {
|
||||||
|
--scalar-background-1: #fff;
|
||||||
|
--scalar-background-2: #f8fafc;
|
||||||
|
--scalar-background-3: #e7e7e7;
|
||||||
|
--scalar-background-accent: #8ab4f81f;
|
||||||
|
--scalar-color-1: #000;
|
||||||
|
--scalar-color-2: #6b7280;
|
||||||
|
--scalar-color-3: #9ca3af;
|
||||||
|
--scalar-color-accent: #00c16a;
|
||||||
|
--scalar-border-color: #e5e7eb;
|
||||||
|
--scalar-color-green: #069061;
|
||||||
|
--scalar-color-red: #ef4444;
|
||||||
|
--scalar-color-yellow: #f59e0b;
|
||||||
|
--scalar-color-blue: #1d4ed8;
|
||||||
|
--scalar-color-orange: #fb892c;
|
||||||
|
--scalar-color-purple: #6d28d9;
|
||||||
|
--scalar-button-1: #000;
|
||||||
|
--scalar-button-1-hover: rgba(0, 0, 0, 0.9);
|
||||||
|
--scalar-button-1-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode {
|
||||||
|
--scalar-background-1: #020420;
|
||||||
|
--scalar-background-2: #121a31;
|
||||||
|
--scalar-background-3: #1e293b;
|
||||||
|
--scalar-background-accent: #8ab4f81f;
|
||||||
|
--scalar-color-1: #fff;
|
||||||
|
--scalar-color-2: #cbd5e1;
|
||||||
|
--scalar-color-3: #94a3b8;
|
||||||
|
--scalar-color-accent: #00dc82;
|
||||||
|
--scalar-border-color: #1e293b;
|
||||||
|
--scalar-color-green: #069061;
|
||||||
|
--scalar-color-red: #f87171;
|
||||||
|
--scalar-color-yellow: #fde68a;
|
||||||
|
--scalar-color-blue: #60a5fa;
|
||||||
|
--scalar-color-orange: #fb892c;
|
||||||
|
--scalar-color-purple: #ddd6fe;
|
||||||
|
--scalar-button-1: hsla(0, 0%, 100%, 0.9);
|
||||||
|
--scalar-button-1-hover: hsla(0, 0%, 100%, 0.8);
|
||||||
|
--scalar-button-1-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-mode .t-doc__sidebar,
|
||||||
|
.light-mode .t-doc__sidebar {
|
||||||
|
--scalar-sidebar-background-1: var(--scalar-background-1);
|
||||||
|
--scalar-sidebar-color-1: var(--scalar-color-1);
|
||||||
|
--scalar-sidebar-color-2: var(--scalar-color-3);
|
||||||
|
--scalar-sidebar-border-color: var(--scalar-border-color);
|
||||||
|
--scalar-sidebar-item-hover-background: transparent;
|
||||||
|
--scalar-sidebar-item-hover-color: var(--scalar-color-1);
|
||||||
|
--scalar-sidebar-item-active-background: transparent;
|
||||||
|
--scalar-sidebar-color-active: var(--scalar-color-accent);
|
||||||
|
--scalar-sidebar-search-background: transparent;
|
||||||
|
--scalar-sidebar-search-color: var(--scalar-color-3);
|
||||||
|
--scalar-sidebar-search-border-color: var(--scalar-border-color);
|
||||||
|
--scalar-sidebar-indent-border: var(--scalar-border-color);
|
||||||
|
--scalar-sidebar-indent-border-hover: var(--scalar-color-1);
|
||||||
|
--scalar-sidebar-indent-border-active: var(--scalar-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scalar-card .request-card-footer {
|
||||||
|
--scalar-background-3: var(--scalar-background-2);
|
||||||
|
--scalar-button-1: #0f172a;
|
||||||
|
--scalar-button-1-hover: rgba(30, 41, 59, 0.5);
|
||||||
|
--scalar-button-1-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scalar-card .show-api-client-button {
|
||||||
|
border: 1px solid #334155 !important;
|
||||||
|
}</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
[Parameter] public string Options { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Extensions;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations.Diagnose;
|
||||||
|
|
||||||
|
public class CoreConfigDiagnoseProvider : IDiagnoseProvider
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Config;
|
||||||
|
|
||||||
|
public CoreConfigDiagnoseProvider(AppConfiguration config)
|
||||||
|
{
|
||||||
|
Config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string CheckForNullOrEmpty(string? content)
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(content)
|
||||||
|
? "ISEMPTY"
|
||||||
|
: "ISNOTEMPTY";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ModifyZipArchive(ZipArchive archive)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(Config);
|
||||||
|
var config = JsonSerializer.Deserialize<AppConfiguration>(json);
|
||||||
|
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
await archive.AddText("core/config.txt", "Could not fetch config.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Database.Password = CheckForNullOrEmpty(config.Database.Password);
|
||||||
|
|
||||||
|
config.Authentication.OAuth2.ClientSecret = CheckForNullOrEmpty(config.Authentication.OAuth2.ClientSecret);
|
||||||
|
|
||||||
|
config.Authentication.OAuth2.Secret = CheckForNullOrEmpty(config.Authentication.OAuth2.Secret);
|
||||||
|
|
||||||
|
config.Authentication.Secret = CheckForNullOrEmpty(config.Authentication.Secret);
|
||||||
|
|
||||||
|
config.Authentication.OAuth2.ClientId = CheckForNullOrEmpty(config.Authentication.OAuth2.ClientId);
|
||||||
|
|
||||||
|
await archive.AddText(
|
||||||
|
"core/config.txt",
|
||||||
|
JsonSerializer.Serialize(
|
||||||
|
config,
|
||||||
|
new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
WriteIndented = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using Moonlight.ApiServer.Extensions;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations.Diagnose;
|
||||||
|
|
||||||
|
public class LogsDiagnoseProvider : IDiagnoseProvider
|
||||||
|
{
|
||||||
|
public async Task ModifyZipArchive(ZipArchive archive)
|
||||||
|
{
|
||||||
|
var path = Path.Combine("storage", "logs", "latest.log");
|
||||||
|
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
await archive.AddText("logs.txt", "Logs file latest.log has not been found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var logsContent = await File.ReadAllTextAsync(path);
|
||||||
|
await archive.AddText("logs.txt", logsContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Moonlight.ApiServer/Implementations/LocalOAuth2Provider.cs
Normal file
106
Moonlight.ApiServer/Implementations/LocalOAuth2Provider.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.Shared.Http.Responses.OAuth2;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations;
|
||||||
|
|
||||||
|
public class LocalOAuth2Provider : IOAuth2Provider
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly ILogger<LocalOAuth2Provider> Logger;
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
|
||||||
|
public LocalOAuth2Provider(
|
||||||
|
AppConfiguration configuration,
|
||||||
|
ILogger<LocalOAuth2Provider> logger,
|
||||||
|
DatabaseRepository<User> userRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
Configuration = configuration;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> Start()
|
||||||
|
{
|
||||||
|
var redirectUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationRedirect)
|
||||||
|
? Configuration.PublicUrl
|
||||||
|
: Configuration.Authentication.OAuth2.AuthorizationRedirect;
|
||||||
|
|
||||||
|
var endpoint = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationEndpoint)
|
||||||
|
? Configuration.PublicUrl + "/oauth2/authorize"
|
||||||
|
: Configuration.Authentication.OAuth2.AuthorizationEndpoint;
|
||||||
|
|
||||||
|
var clientId = Configuration.Authentication.OAuth2.ClientId;
|
||||||
|
|
||||||
|
var url = $"{endpoint}" +
|
||||||
|
$"?client_id={clientId}" +
|
||||||
|
$"&redirect_uri={redirectUri}" +
|
||||||
|
$"&response_type=code";
|
||||||
|
|
||||||
|
return Task.FromResult(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User?> Complete(string code)
|
||||||
|
{
|
||||||
|
// Create http client to call the auth provider
|
||||||
|
var httpClient = new HttpClient();
|
||||||
|
using var httpApiClient = new HttpApiClient(httpClient);
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Authorization",
|
||||||
|
$"Basic {Configuration.Authentication.OAuth2.ClientSecret}");
|
||||||
|
|
||||||
|
// Build access endpoint
|
||||||
|
var accessEndpoint = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AccessEndpoint)
|
||||||
|
? $"{Configuration.PublicUrl}/oauth2/handle"
|
||||||
|
: Configuration.Authentication.OAuth2.AccessEndpoint;
|
||||||
|
|
||||||
|
// Build redirect uri
|
||||||
|
var redirectUri = string.IsNullOrEmpty(Configuration.Authentication.OAuth2.AuthorizationRedirect)
|
||||||
|
? Configuration.PublicUrl
|
||||||
|
: Configuration.Authentication.OAuth2.AuthorizationRedirect;
|
||||||
|
|
||||||
|
// Call the auth provider
|
||||||
|
OAuth2HandleResponse handleData;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
handleData = await httpApiClient.PostJson<OAuth2HandleResponse>(accessEndpoint, new FormUrlEncodedContent(
|
||||||
|
[
|
||||||
|
new KeyValuePair<string, string>("grant_type", "authorization_code"),
|
||||||
|
new KeyValuePair<string, string>("code", code),
|
||||||
|
new KeyValuePair<string, string>("redirect_uri", redirectUri),
|
||||||
|
new KeyValuePair<string, string>("client_id", Configuration.Authentication.OAuth2.ClientId)
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
catch (HttpApiException e)
|
||||||
|
{
|
||||||
|
if (e.Status == 400)
|
||||||
|
Logger.LogTrace("The auth server returned an error: {e}", e);
|
||||||
|
else
|
||||||
|
Logger.LogCritical("The auth server returned an error: {e}", e);
|
||||||
|
|
||||||
|
throw new HttpApiException("Unable to request user data", 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notice: We just look up the user id here
|
||||||
|
// which works as our oauth2 provider is using the same db.
|
||||||
|
// a real oauth2 provider would create a user here
|
||||||
|
|
||||||
|
// Handle the returned data
|
||||||
|
var userId = handleData.UserId;
|
||||||
|
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations.Metrics;
|
||||||
|
|
||||||
|
public class ApplicationMetric : IMetric
|
||||||
|
{
|
||||||
|
private Gauge<long> MemoryUsage;
|
||||||
|
private Gauge<int> CpuUsage;
|
||||||
|
private Gauge<double> Uptime;
|
||||||
|
|
||||||
|
public Task Initialize(Meter meter)
|
||||||
|
{
|
||||||
|
MemoryUsage = meter.CreateGauge<long>("moonlight_memory_usage");
|
||||||
|
CpuUsage = meter.CreateGauge<int>("moonlight_cpu_usage");
|
||||||
|
Uptime = meter.CreateGauge<double>("moonlight_uptime");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run(IServiceProvider provider, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var applicationService = provider.GetRequiredService<ApplicationService>();
|
||||||
|
|
||||||
|
var memory = await applicationService.GetMemoryUsage();
|
||||||
|
MemoryUsage.Record(memory);
|
||||||
|
|
||||||
|
var uptime = await applicationService.GetUptime();
|
||||||
|
Uptime.Record(uptime.TotalSeconds);
|
||||||
|
|
||||||
|
var cpu = await applicationService.GetCpuUsage();
|
||||||
|
CpuUsage.Record(cpu);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Moonlight.ApiServer/Implementations/Metrics/UsersMetric.cs
Normal file
28
Moonlight.ApiServer/Implementations/Metrics/UsersMetric.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations.Metrics;
|
||||||
|
|
||||||
|
public class UsersMetric : IMetric
|
||||||
|
{
|
||||||
|
private Gauge<int> Users;
|
||||||
|
|
||||||
|
public Task Initialize(Meter meter)
|
||||||
|
{
|
||||||
|
Users = meter.CreateGauge<int>("moonlight_users");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run(IServiceProvider provider, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var usersRepo = provider.GetRequiredService<DatabaseRepository<User>>();
|
||||||
|
var count = await usersRepo.Get().CountAsync(cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
Users.Record(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
130
Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs
Normal file
130
Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Moonlight.ApiServer.Implementations.Diagnose;
|
||||||
|
using Moonlight.ApiServer.Implementations.Metrics;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
using OpenTelemetry.Metrics;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations.Startup;
|
||||||
|
|
||||||
|
public class CoreStartup : IPluginStartup
|
||||||
|
{
|
||||||
|
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
|
#region Api Docs
|
||||||
|
|
||||||
|
if (configuration.Development.EnableApiDocs)
|
||||||
|
{
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
|
||||||
|
// Configure swagger api specification generator and set the document title for the api docs to use
|
||||||
|
builder.Services.AddSwaggerGen(options =>
|
||||||
|
{
|
||||||
|
options.SwaggerDoc("main", new OpenApiInfo()
|
||||||
|
{
|
||||||
|
Title = "Moonlight API"
|
||||||
|
});
|
||||||
|
|
||||||
|
options.CustomSchemaIds(x => x.FullName);
|
||||||
|
|
||||||
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Name = "Authorization",
|
||||||
|
In = ParameterLocation.Header,
|
||||||
|
Type = SecuritySchemeType.ApiKey,
|
||||||
|
Scheme = "Bearer"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Database
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<CoreDataContext>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Diagnose
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IDiagnoseProvider, CoreConfigDiagnoseProvider>();
|
||||||
|
builder.Services.AddSingleton<IDiagnoseProvider, LogsDiagnoseProvider>();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Prometheus
|
||||||
|
|
||||||
|
if (configuration.Metrics.Enable)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<MetricsBackgroundService>();
|
||||||
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<MetricsBackgroundService>());
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IMetric, ApplicationMetric>();
|
||||||
|
builder.Services.AddSingleton<IMetric, UsersMetric>();
|
||||||
|
|
||||||
|
builder.Services.AddOpenTelemetry()
|
||||||
|
.WithMetrics(providerBuilder =>
|
||||||
|
{
|
||||||
|
providerBuilder.AddPrometheusExporter();
|
||||||
|
providerBuilder.AddAspNetCoreInstrumentation();
|
||||||
|
|
||||||
|
providerBuilder.AddMeter("moonlight");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Client / Frontend
|
||||||
|
|
||||||
|
if (configuration.Frontend.EnableHosting)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton(new FrontendConfigurationOption()
|
||||||
|
{
|
||||||
|
Scripts =
|
||||||
|
[
|
||||||
|
"/_content/Moonlight.Client/js/moonlight.js", "/_content/MoonCore.Blazor.FlyonUi/moonCore.js",
|
||||||
|
"/_content/MoonCore.Blazor.FlyonUi/ace/ace.js"
|
||||||
|
],
|
||||||
|
Styles = ["/css/style.min.css"]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
|
#region Prometheus
|
||||||
|
|
||||||
|
if (configuration.Metrics.Enable)
|
||||||
|
app.UseOpenTelemetryPrometheusScrapingEndpoint();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder)
|
||||||
|
{
|
||||||
|
var configuration = serviceProvider.GetRequiredService<AppConfiguration>();
|
||||||
|
|
||||||
|
if (configuration.Development.EnableApiDocs)
|
||||||
|
routeBuilder.MapSwagger("/api/swagger/{documentName}");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using TickerQ.Utilities.Enums;
|
||||||
|
using TickerQ.Utilities.Interfaces;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations;
|
||||||
|
|
||||||
|
public class TickerExceptionHandler : ITickerExceptionHandler
|
||||||
|
{
|
||||||
|
private readonly ILogger<TickerExceptionHandler> Logger;
|
||||||
|
|
||||||
|
public TickerExceptionHandler(ILogger<TickerExceptionHandler> logger)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleExceptionAsync(Exception exception, Guid tickerId, TickerType tickerType)
|
||||||
|
{
|
||||||
|
Logger.LogError(exception, "An unhandled error occured while running ticker {id} ({type})", tickerId, tickerType);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task HandleCanceledExceptionAsync(Exception exception, Guid tickerId, TickerType tickerType)
|
||||||
|
{
|
||||||
|
Logger.LogError(exception, "An unhandled error occured while handling canceled ticker {id} ({type})", tickerId, tickerType);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Moonlight.ApiServer/Implementations/UserAuthInvalidation.cs
Normal file
62
Moonlight.ApiServer/Implementations/UserAuthInvalidation.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Extended.JwtInvalidation;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Implementations;
|
||||||
|
|
||||||
|
public class UserAuthInvalidation : IJwtInvalidateHandler
|
||||||
|
{
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
private readonly DatabaseRepository<ApiKey> ApiKeyRepository;
|
||||||
|
|
||||||
|
public UserAuthInvalidation(
|
||||||
|
DatabaseRepository<User> userRepository,
|
||||||
|
DatabaseRepository<ApiKey> apiKeyRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
ApiKeyRepository = apiKeyRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> Handle(ClaimsPrincipal principal)
|
||||||
|
{
|
||||||
|
var userIdClaim = principal.FindFirstValue("userId");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(userIdClaim))
|
||||||
|
{
|
||||||
|
var userId = int.Parse(userIdClaim);
|
||||||
|
|
||||||
|
var user = await UserRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == userId);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return true; // User is deleted, invalidate session
|
||||||
|
|
||||||
|
var iatStr = principal.FindFirstValue("iat")!;
|
||||||
|
var iat = DateTimeOffset.FromUnixTimeSeconds(long.Parse(iatStr));
|
||||||
|
|
||||||
|
// If the token has been issued before the token valid time, its expired, and we want to invalidate it
|
||||||
|
return user.TokenValidTimestamp > iat;
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiKeyIdClaim = principal.FindFirstValue("apiKeyId");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiKeyIdClaim))
|
||||||
|
{
|
||||||
|
var apiKeyId = int.Parse(apiKeyIdClaim);
|
||||||
|
|
||||||
|
var apiKey = await ApiKeyRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.Id == apiKeyId);
|
||||||
|
|
||||||
|
// If the api key exists, we don't want to invalidate the request.
|
||||||
|
// If it doesn't exist we want to invalidate the request
|
||||||
|
return apiKey == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs
Normal file
8
Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
public interface IDiagnoseProvider
|
||||||
|
{
|
||||||
|
public Task ModifyZipArchive(ZipArchive archive);
|
||||||
|
}
|
||||||
9
Moonlight.ApiServer/Interfaces/IMetric.cs
Normal file
9
Moonlight.ApiServer/Interfaces/IMetric.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
public interface IMetric
|
||||||
|
{
|
||||||
|
public Task Initialize(Meter meter);
|
||||||
|
public Task Run(IServiceProvider provider, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
10
Moonlight.ApiServer/Interfaces/IOAuth2Provider.cs
Normal file
10
Moonlight.ApiServer/Interfaces/IOAuth2Provider.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
public interface IOAuth2Provider
|
||||||
|
{
|
||||||
|
public Task<string> Start();
|
||||||
|
|
||||||
|
public Task<User?> Complete(string code);
|
||||||
|
}
|
||||||
10
Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs
Normal file
10
Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
public interface IUserDeleteHandler
|
||||||
|
{
|
||||||
|
public Task<UserDeleteValidationResult> Validate(User user);
|
||||||
|
public Task Delete(User user, bool force);
|
||||||
|
}
|
||||||
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
14
Moonlight.ApiServer/Mappers/ThemeMapper.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.Shared.Http.Requests.Admin.Sys.Theme;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin;
|
||||||
|
using Riok.Mapperly.Abstractions;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Mappers;
|
||||||
|
|
||||||
|
[Mapper]
|
||||||
|
public static partial class ThemeMapper
|
||||||
|
{
|
||||||
|
public static partial ThemeResponse ToResponse(Theme theme);
|
||||||
|
public static partial Theme ToTheme(CreateThemeRequest request);
|
||||||
|
public static partial void Merge([MappingTarget] Theme theme, UpdateThemeRequest request);
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
namespace Moonlight.Core.Models;
|
namespace Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
// From https://github.com/scalar/scalar/blob/main/packages/scalar.aspnetcore/ScalarOptions.cs
|
// From https://github.com/scalar/scalar/blob/main/packages/scalar.aspnetcore/ScalarOptions.cs
|
||||||
|
|
||||||
public class ScalarOptions
|
public class ApiDocsOptions
|
||||||
{
|
{
|
||||||
public string Theme { get; set; } = "purple";
|
public string Theme { get; set; } = "purple";
|
||||||
|
|
||||||
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
49
Moonlight.ApiServer/Models/ApplicationTheme.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
namespace Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
public class ApplicationTheme
|
||||||
|
{
|
||||||
|
public string ColorBackground { get; set; }
|
||||||
|
|
||||||
|
public string ColorBase100 { get; set; }
|
||||||
|
public string ColorBase150 { get; set; }
|
||||||
|
public string ColorBase200 { get; set; }
|
||||||
|
public string ColorBase250 { get; set; }
|
||||||
|
public string ColorBase300 { get; set; }
|
||||||
|
|
||||||
|
public string ColorBaseContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorPrimary { get; set; }
|
||||||
|
public string ColorPrimaryContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorSecondary { get; set; }
|
||||||
|
public string ColorSecondaryContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorAccent { get; set; }
|
||||||
|
public string ColorAccentContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorNeutral { get; set; }
|
||||||
|
public string ColorNeutralContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorInfo { get; set; }
|
||||||
|
public string ColorInfoContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorSuccess { get; set; }
|
||||||
|
public string ColorSuccessContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorWarning { get; set; }
|
||||||
|
public string ColorWarningContent { get; set; }
|
||||||
|
|
||||||
|
public string ColorError { get; set; }
|
||||||
|
public string ColorErrorContent { get; set; }
|
||||||
|
|
||||||
|
public float RadiusSelector { get; set; }
|
||||||
|
public float RadiusField { get; set; }
|
||||||
|
public float RadiusBox { get; set; }
|
||||||
|
|
||||||
|
public float SizeSelector { get; set; }
|
||||||
|
public float SizeField { get; set; }
|
||||||
|
|
||||||
|
public float Border { get; set; }
|
||||||
|
public int Depth { get; set; }
|
||||||
|
public int Noise { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
public class FrontendConfigurationOption
|
||||||
|
{
|
||||||
|
public string[] Scripts { get; set; } = [];
|
||||||
|
public string[] Styles { get; set; } = [];
|
||||||
|
}
|
||||||
27
Moonlight.ApiServer/Models/UserDeleteValidateResult.cs
Normal file
27
Moonlight.ApiServer/Models/UserDeleteValidateResult.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
public class UserDeleteValidationResult
|
||||||
|
{
|
||||||
|
public bool IsAllowed { get; set; }
|
||||||
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
public static UserDeleteValidationResult Allow()
|
||||||
|
{
|
||||||
|
return new UserDeleteValidationResult()
|
||||||
|
{
|
||||||
|
IsAllowed = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserDeleteValidationResult Deny()
|
||||||
|
=> Deny("No reason provided");
|
||||||
|
|
||||||
|
public static UserDeleteValidationResult Deny(string reason)
|
||||||
|
{
|
||||||
|
return new UserDeleteValidationResult()
|
||||||
|
{
|
||||||
|
IsAllowed = false,
|
||||||
|
Reason = reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
46
Moonlight.ApiServer/Moonlight.ApiServer.csproj
Normal file
46
Moonlight.ApiServer/Moonlight.ApiServer.csproj
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Moonlight.Shared\Moonlight.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Database\Migrations\" />
|
||||||
|
<Folder Include="Helpers\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<PackageId>Moonlight.ApiServer</PackageId>
|
||||||
|
<Version>2.1.7</Version>
|
||||||
|
<Authors>Moonlight Panel</Authors>
|
||||||
|
<Description>A build of the api server for moonlight development</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/Moonlight-Panel/Moonlight</PackageProjectUrl>
|
||||||
|
<DevelopmentDependency>true</DevelopmentDependency>
|
||||||
|
<PackageTags>apiserver</PackageTags>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
|
||||||
|
<PackageReference Include="MoonCore" Version="1.9.2" />
|
||||||
|
<PackageReference Include="MoonCore.Extended" Version="1.3.5" />
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.12.0-beta.1" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
|
||||||
|
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
|
||||||
|
<PackageReference Include="Riok.Mapperly" Version="4.3.0-next.2" />
|
||||||
|
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
|
||||||
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
|
<PackageReference Include="TickerQ" Version="2.4.4" />
|
||||||
|
<PackageReference Include="TickerQ.EntityFrameworkCore" Version="2.4.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="storage\**\*" />
|
||||||
|
<Content Remove="storage\**\*" />
|
||||||
|
<None Remove="storage\**\*" />
|
||||||
|
<None Remove="Properties\launchSettings.json" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
12
Moonlight.ApiServer/Plugins/IPluginStartup.cs
Normal file
12
Moonlight.ApiServer/Plugins/IPluginStartup.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
public interface IPluginStartup
|
||||||
|
{
|
||||||
|
public Task BuildApplication(IServiceProvider serviceProvider, IHostApplicationBuilder builder);
|
||||||
|
public Task ConfigureApplication(IServiceProvider serviceProvider, IApplicationBuilder app);
|
||||||
|
public Task ConfigureEndpoints(IServiceProvider serviceProvider, IEndpointRouteBuilder routeBuilder);
|
||||||
|
}
|
||||||
53
Moonlight.ApiServer/Services/ApiKeyService.cs
Normal file
53
Moonlight.ApiServer/Services/ApiKeyService.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
[Singleton]
|
||||||
|
public class ApiKeyService
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
|
||||||
|
public ApiKeyService(AppConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateJwt(ApiKey apiKey)
|
||||||
|
{
|
||||||
|
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
|
||||||
|
|
||||||
|
var descriptor = new SecurityTokenDescriptor()
|
||||||
|
{
|
||||||
|
Expires = apiKey.ExpiresAt.UtcDateTime,
|
||||||
|
IssuedAt = DateTime.Now,
|
||||||
|
NotBefore = DateTime.Now.AddMinutes(-1),
|
||||||
|
Claims = new Dictionary<string, object>()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"apiKeyId",
|
||||||
|
apiKey.Id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permissions",
|
||||||
|
string.Join(";", apiKey.Permissions)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SigningCredentials = new SigningCredentials(
|
||||||
|
new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(Configuration.Authentication.Secret)
|
||||||
|
),
|
||||||
|
SecurityAlgorithms.HmacSha256
|
||||||
|
),
|
||||||
|
Issuer = Configuration.PublicUrl,
|
||||||
|
Audience = Configuration.PublicUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(descriptor);
|
||||||
|
return jwtSecurityTokenHandler.WriteToken(securityToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
120
Moonlight.ApiServer/Services/ApplicationService.cs
Normal file
120
Moonlight.ApiServer/Services/ApplicationService.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
[Singleton]
|
||||||
|
public class ApplicationService
|
||||||
|
{
|
||||||
|
private ILogger<ApplicationService> Logger;
|
||||||
|
private readonly IHost Host;
|
||||||
|
|
||||||
|
public ApplicationService(ILogger<ApplicationService> logger, IHost host)
|
||||||
|
{
|
||||||
|
Logger = logger;
|
||||||
|
Host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string> GetOsName()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Windows platform detected
|
||||||
|
var osVersion = Environment.OSVersion.Version;
|
||||||
|
return Task.FromResult($"Windows {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
var releaseRaw = File
|
||||||
|
.ReadAllLines("/etc/os-release")
|
||||||
|
.FirstOrDefault(x => x.StartsWith("PRETTY_NAME="));
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(releaseRaw))
|
||||||
|
return Task.FromResult("Linux (unknown release)");
|
||||||
|
|
||||||
|
var release = releaseRaw
|
||||||
|
.Replace("PRETTY_NAME=", "")
|
||||||
|
.Replace("\"", "");
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(release))
|
||||||
|
return Task.FromResult("Linux (unknown release)");
|
||||||
|
|
||||||
|
return Task.FromResult(release);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
// macOS platform detected
|
||||||
|
var osVersion = Environment.OSVersion.Version;
|
||||||
|
return Task.FromResult($"macOS {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown platform
|
||||||
|
return Task.FromResult("N/A");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<long> GetMemoryUsage()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
var process = Process.GetCurrentProcess();
|
||||||
|
return process.PrivateMemorySize64;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var lines = await File.ReadAllLinesAsync("/proc/self/smaps");
|
||||||
|
var kilobytes = 0;
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
if(!line.StartsWith("pss:", StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var valueString = line
|
||||||
|
.Replace("pss:", "", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
.Replace("kb", "", StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
.Trim();
|
||||||
|
|
||||||
|
kilobytes += int.Parse(valueString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ByteConverter.FromKiloBytes(kilobytes).Bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<TimeSpan> GetUptime()
|
||||||
|
{
|
||||||
|
var process = Process.GetCurrentProcess();
|
||||||
|
var uptime = DateTime.Now - process.StartTime;
|
||||||
|
return Task.FromResult(uptime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<int> GetCpuUsage()
|
||||||
|
{
|
||||||
|
var process = Process.GetCurrentProcess();
|
||||||
|
var cpuTime = process.TotalProcessorTime;
|
||||||
|
var wallClockTime = DateTime.UtcNow - process.StartTime.ToUniversalTime();
|
||||||
|
|
||||||
|
var cpuUsage = (int)(100.0 * cpuTime.TotalMilliseconds / wallClockTime.TotalMilliseconds / Environment.ProcessorCount);
|
||||||
|
|
||||||
|
return Task.FromResult(cpuUsage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Shutdown()
|
||||||
|
{
|
||||||
|
Logger.LogCritical("Restart of api server has been requested");
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||||
|
await Host.StopAsync(CancellationToken.None);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Moonlight.ApiServer/Services/DiagnoseService.cs
Normal file
96
Moonlight.ApiServer/Services/DiagnoseService.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using Moonlight.Shared.Http.Responses.Admin.Sys;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
[Scoped]
|
||||||
|
public class DiagnoseService
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IDiagnoseProvider> DiagnoseProviders;
|
||||||
|
private readonly ILogger<DiagnoseService> Logger;
|
||||||
|
|
||||||
|
public DiagnoseService(
|
||||||
|
IEnumerable<IDiagnoseProvider> diagnoseProviders,
|
||||||
|
ILogger<DiagnoseService> logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DiagnoseProviders = diagnoseProviders;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<DiagnoseProvideResponse[]> GetProviders()
|
||||||
|
{
|
||||||
|
var availableProviders = new List<DiagnoseProvideResponse>();
|
||||||
|
|
||||||
|
foreach (var diagnoseProvider in DiagnoseProviders)
|
||||||
|
{
|
||||||
|
var name = diagnoseProvider.GetType().Name;
|
||||||
|
|
||||||
|
var type = diagnoseProvider.GetType().FullName;
|
||||||
|
|
||||||
|
// The type name is null if the type is a generic type, unlikely, but still could happen
|
||||||
|
if (type == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
availableProviders.Add(new DiagnoseProvideResponse()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Type = type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(
|
||||||
|
availableProviders.ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<MemoryStream> GenerateDiagnose(string[] requestedProviders)
|
||||||
|
{
|
||||||
|
IDiagnoseProvider[] providers;
|
||||||
|
|
||||||
|
if (requestedProviders.Length == 0)
|
||||||
|
providers = DiagnoseProviders.ToArray();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var foundProviders = new List<IDiagnoseProvider>();
|
||||||
|
|
||||||
|
foreach (var requestedProvider in requestedProviders)
|
||||||
|
{
|
||||||
|
var provider = DiagnoseProviders.FirstOrDefault(x => x.GetType().FullName == requestedProvider);
|
||||||
|
|
||||||
|
if (provider == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foundProviders.Add(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
providers = foundProviders.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var outputStream = new MemoryStream();
|
||||||
|
var zipArchive = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: true);
|
||||||
|
|
||||||
|
foreach (var provider in providers)
|
||||||
|
{
|
||||||
|
await provider.ModifyZipArchive(zipArchive);
|
||||||
|
}
|
||||||
|
|
||||||
|
zipArchive.Dispose();
|
||||||
|
|
||||||
|
outputStream.Position = 0;
|
||||||
|
return outputStream;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError("An unhandled error occured while generated the diagnose file: {e}", e);
|
||||||
|
|
||||||
|
throw new HttpApiException("An unhandled error occured while generating the diagnose file", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
168
Moonlight.ApiServer/Services/FrontendService.cs
Normal file
168
Moonlight.ApiServer/Services/FrontendService.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using MoonCore.Attributes;
|
||||||
|
using MoonCore.Exceptions;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Http.Controllers.Frontend;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
using Moonlight.Shared.Misc;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
[Scoped]
|
||||||
|
public class FrontendService
|
||||||
|
{
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
private readonly IWebHostEnvironment WebHostEnvironment;
|
||||||
|
private readonly IEnumerable<FrontendConfigurationOption> ConfigurationOptions;
|
||||||
|
private readonly IServiceProvider ServiceProvider;
|
||||||
|
private readonly DatabaseRepository<Theme> ThemeRepository;
|
||||||
|
|
||||||
|
public FrontendService(
|
||||||
|
AppConfiguration configuration,
|
||||||
|
IWebHostEnvironment webHostEnvironment,
|
||||||
|
IEnumerable<FrontendConfigurationOption> configurationOptions,
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
DatabaseRepository<Theme> themeRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
WebHostEnvironment = webHostEnvironment;
|
||||||
|
ConfigurationOptions = configurationOptions;
|
||||||
|
ServiceProvider = serviceProvider;
|
||||||
|
ThemeRepository = themeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<FrontendConfiguration> GetConfiguration()
|
||||||
|
{
|
||||||
|
var configuration = new FrontendConfiguration()
|
||||||
|
{
|
||||||
|
ApiUrl = Configuration.PublicUrl,
|
||||||
|
HostEnvironment = "ApiServer"
|
||||||
|
};
|
||||||
|
|
||||||
|
return Task.FromResult(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GenerateIndexHtml() // TODO: Cache
|
||||||
|
{
|
||||||
|
// Load requested theme
|
||||||
|
var theme = await ThemeRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefaultAsync(x => x.IsEnabled);
|
||||||
|
|
||||||
|
// Load configured javascript files
|
||||||
|
var scripts = ConfigurationOptions
|
||||||
|
.SelectMany(x => x.Scripts)
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
// Load configured css files
|
||||||
|
var styles = ConfigurationOptions
|
||||||
|
.SelectMany(x => x.Styles)
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return await ComponentHelper.RenderComponent<FrontendPage>(
|
||||||
|
ServiceProvider,
|
||||||
|
parameters =>
|
||||||
|
{
|
||||||
|
parameters["Theme"] = theme!;
|
||||||
|
parameters["Styles"] = styles;
|
||||||
|
parameters["Scripts"] = scripts;
|
||||||
|
parameters["Title"] = "Moonlight"; // TODO: Config
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Stream> GenerateZip() // TODO: Rework to be able to extract everything successfully
|
||||||
|
{
|
||||||
|
// We only allow the access to this function when we are actually hosting the frontend
|
||||||
|
if (!Configuration.Frontend.EnableHosting)
|
||||||
|
throw new HttpApiException("The hosting of the wasm client has been disabled", 400);
|
||||||
|
|
||||||
|
// Load and check wasm path
|
||||||
|
var wasmMainFile = WebHostEnvironment.WebRootFileProvider.GetFileInfo("index.html");
|
||||||
|
|
||||||
|
if (wasmMainFile is NotFoundFileInfo || string.IsNullOrEmpty(wasmMainFile.PhysicalPath))
|
||||||
|
throw new HttpApiException("Unable to find wasm location", 500);
|
||||||
|
|
||||||
|
var wasmPath = Path.GetDirectoryName(wasmMainFile.PhysicalPath)! + "/";
|
||||||
|
|
||||||
|
// Load and check the blazor framework files
|
||||||
|
var blazorFile = WebHostEnvironment.WebRootFileProvider.GetFileInfo("_framework/blazor.webassembly.js");
|
||||||
|
|
||||||
|
if (blazorFile is NotFoundFileInfo || string.IsNullOrEmpty(blazorFile.PhysicalPath))
|
||||||
|
throw new HttpApiException("Unable to find blazor location", 500);
|
||||||
|
|
||||||
|
var blazorPath = Path.GetDirectoryName(blazorFile.PhysicalPath)! + "/";
|
||||||
|
|
||||||
|
// Create zip
|
||||||
|
var memoryStream = new MemoryStream();
|
||||||
|
var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true);
|
||||||
|
|
||||||
|
// Add wasm application
|
||||||
|
await ArchiveFsItem(zipArchive, wasmPath, wasmPath);
|
||||||
|
|
||||||
|
// Add blazor files
|
||||||
|
await ArchiveFsItem(zipArchive, blazorPath, blazorPath, "_framework/");
|
||||||
|
|
||||||
|
// Add frontend.json
|
||||||
|
var frontendConfig = await GetConfiguration();
|
||||||
|
frontendConfig.HostEnvironment = "Static";
|
||||||
|
var frontendJson = JsonSerializer.Serialize(frontendConfig);
|
||||||
|
await ArchiveText(zipArchive, "frontend.json", frontendJson);
|
||||||
|
|
||||||
|
// Finish zip archive and reset stream so the code calling this function can process it
|
||||||
|
zipArchive.Dispose();
|
||||||
|
await memoryStream.FlushAsync();
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
return memoryStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ArchiveFsItem(ZipArchive archive, string path, string prefixToRemove, string prefixToAdd = "")
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var entryName = prefixToAdd + Formatter.ReplaceStart(path, prefixToRemove, "");
|
||||||
|
|
||||||
|
var entry = archive.CreateEntry(entryName);
|
||||||
|
|
||||||
|
await using var entryStream = entry.Open();
|
||||||
|
await using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
|
|
||||||
|
await fileStream.CopyToAsync(entryStream);
|
||||||
|
await entryStream.FlushAsync();
|
||||||
|
|
||||||
|
entryStream.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var directoryItem in Directory.EnumerateFileSystemEntries(path))
|
||||||
|
await ArchiveFsItem(archive, directoryItem, prefixToRemove, prefixToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ArchiveText(ZipArchive archive, string path, string content)
|
||||||
|
{
|
||||||
|
var data = Encoding.UTF8.GetBytes(content);
|
||||||
|
await ArchiveBytes(archive, path, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ArchiveBytes(ZipArchive archive, string path, byte[] bytes)
|
||||||
|
{
|
||||||
|
var entry = archive.CreateEntry(path);
|
||||||
|
await using var dataStream = entry.Open();
|
||||||
|
|
||||||
|
await dataStream.WriteAsync(bytes);
|
||||||
|
await dataStream.FlushAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Moonlight.ApiServer/Services/MetricsBackgroundService.cs
Normal file
81
Moonlight.ApiServer/Services/MetricsBackgroundService.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System.Diagnostics.Metrics;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
public class MetricsBackgroundService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<MetricsBackgroundService> Logger;
|
||||||
|
private readonly IServiceProvider ServiceProvider;
|
||||||
|
private readonly AppConfiguration Configuration;
|
||||||
|
|
||||||
|
private readonly IMetric[] Metrics;
|
||||||
|
private readonly Meter Meter;
|
||||||
|
|
||||||
|
public MetricsBackgroundService(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
IMeterFactory meterFactory,
|
||||||
|
IEnumerable<IMetric> metrics,
|
||||||
|
ILogger<MetricsBackgroundService> logger,
|
||||||
|
AppConfiguration configuration
|
||||||
|
)
|
||||||
|
{
|
||||||
|
ServiceProvider = serviceProvider;
|
||||||
|
Logger = logger;
|
||||||
|
Configuration = configuration;
|
||||||
|
|
||||||
|
Meter = meterFactory.Create("moonlight");
|
||||||
|
|
||||||
|
Metrics = metrics.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Initialize()
|
||||||
|
{
|
||||||
|
Logger.LogDebug(
|
||||||
|
"Initializing metrics: {names}",
|
||||||
|
string.Join(", ", Metrics.Select(x => x.GetType().FullName))
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var metric in Metrics)
|
||||||
|
await metric.Initialize(Meter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await Initialize();
|
||||||
|
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
using var scope = ServiceProvider.CreateScope();
|
||||||
|
|
||||||
|
foreach (var metric in Metrics)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await metric.Run(scope.ServiceProvider, stoppingToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(
|
||||||
|
"An unhandled error occured while collecting metric {name}: {e}",
|
||||||
|
metric.GetType().FullName,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(
|
||||||
|
TimeSpan.FromSeconds(Configuration.Metrics.Interval),
|
||||||
|
stoppingToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Moonlight.ApiServer/Services/UserService.cs
Normal file
42
Moonlight.ApiServer/Services/UserService.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using Moonlight.ApiServer.Database.Entities;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.ApiServer.Models;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
public class UserDeletionService
|
||||||
|
{
|
||||||
|
private readonly IUserDeleteHandler[] Handlers;
|
||||||
|
private readonly DatabaseRepository<User> UserRepository;
|
||||||
|
|
||||||
|
public UserDeletionService(
|
||||||
|
IEnumerable<IUserDeleteHandler> handlers,
|
||||||
|
DatabaseRepository<User> userRepository
|
||||||
|
)
|
||||||
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
Handlers = handlers.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserDeleteValidationResult> Validate(User user)
|
||||||
|
{
|
||||||
|
foreach (var handler in Handlers)
|
||||||
|
{
|
||||||
|
var result = await handler.Validate(user);
|
||||||
|
|
||||||
|
if (!result.IsAllowed)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return UserDeleteValidationResult.Allow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(User user, bool force)
|
||||||
|
{
|
||||||
|
foreach (var handler in Handlers)
|
||||||
|
await Delete(user, force);
|
||||||
|
|
||||||
|
await UserRepository.Remove(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Moonlight.ApiServer/Startup/Startup.Auth.cs
Normal file
64
Moonlight.ApiServer/Startup/Startup.Auth.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using MoonCore.Extended.JwtInvalidation;
|
||||||
|
using MoonCore.Permissions;
|
||||||
|
using Moonlight.ApiServer.Implementations;
|
||||||
|
using Moonlight.ApiServer.Interfaces;
|
||||||
|
using Moonlight.ApiServer.Services;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task RegisterAuth()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services
|
||||||
|
.AddAuthentication("coreAuthentication")
|
||||||
|
.AddJwtBearer("coreAuthentication", options =>
|
||||||
|
{
|
||||||
|
options.TokenValidationParameters = new()
|
||||||
|
{
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
|
||||||
|
Configuration.Authentication.Secret
|
||||||
|
)),
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ClockSkew = TimeSpan.Zero,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidAudience = Configuration.PublicUrl,
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidIssuer = Configuration.PublicUrl
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddJwtBearerInvalidation("coreAuthentication");
|
||||||
|
WebApplicationBuilder.Services.AddScoped<IJwtInvalidateHandler, UserAuthInvalidation>();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddAuthorizationPermissions(options =>
|
||||||
|
{
|
||||||
|
options.ClaimName = "permissions";
|
||||||
|
options.Prefix = "permissions:";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add local oauth2 provider if enabled
|
||||||
|
if (Configuration.Authentication.EnableLocalOAuth2)
|
||||||
|
WebApplicationBuilder.Services.AddScoped<IOAuth2Provider, LocalOAuth2Provider>();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddScoped<UserDeletionService>();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UseAuth()
|
||||||
|
{
|
||||||
|
WebApplication.UseAuthentication();
|
||||||
|
|
||||||
|
WebApplication.UseAuthorization();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Moonlight.ApiServer/Startup/Startup.Base.cs
Normal file
63
Moonlight.ApiServer/Startup/Startup.Base.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MoonCore.Extended.Extensions;
|
||||||
|
using MoonCore.Extensions;
|
||||||
|
using MoonCore.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task RegisterBase()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services.AutoAddServices<Startup>();
|
||||||
|
WebApplicationBuilder.Services.AddHttpClient();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddApiExceptionHandler();
|
||||||
|
|
||||||
|
// Add pre-existing services
|
||||||
|
WebApplicationBuilder.Services.AddSingleton(Configuration);
|
||||||
|
|
||||||
|
// Configure controllers
|
||||||
|
var mvcBuilder = WebApplicationBuilder.Services.AddControllers();
|
||||||
|
|
||||||
|
// Add plugin assemblies as application parts
|
||||||
|
foreach (var pluginStartup in PluginStartups.Select(x => x.GetType().Assembly).Distinct())
|
||||||
|
mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UseBase()
|
||||||
|
{
|
||||||
|
WebApplication.UseRouting();
|
||||||
|
WebApplication.UseExceptionHandler();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task MapBase()
|
||||||
|
{
|
||||||
|
WebApplication.MapControllers();
|
||||||
|
|
||||||
|
if (Configuration.Frontend.EnableHosting)
|
||||||
|
WebApplication.MapFallbackToController("Index", "Frontend");
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task ConfigureKestrel()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.WebHost.ConfigureKestrel(kestrelOptions =>
|
||||||
|
{
|
||||||
|
var maxUploadInBytes = ByteConverter
|
||||||
|
.FromMegaBytes(Configuration.Kestrel.UploadLimit)
|
||||||
|
.Bytes;
|
||||||
|
|
||||||
|
kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Moonlight.ApiServer/Startup/Startup.Config.cs
Normal file
35
Moonlight.ApiServer/Startup/Startup.Config.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MoonCore.EnvConfiguration;
|
||||||
|
using MoonCore.Yaml;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private async Task SetupAppConfiguration()
|
||||||
|
{
|
||||||
|
var configPath = Path.Combine("storage", "config.yml");
|
||||||
|
|
||||||
|
await YamlDefaultGenerator.Generate<AppConfiguration>(configPath);
|
||||||
|
|
||||||
|
// Configure configuration (wow)
|
||||||
|
var configurationBuilder = new ConfigurationBuilder();
|
||||||
|
|
||||||
|
configurationBuilder.AddYamlFile(configPath);
|
||||||
|
configurationBuilder.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_");
|
||||||
|
|
||||||
|
var configurationRoot = configurationBuilder.Build();
|
||||||
|
|
||||||
|
// Retrieve configuration
|
||||||
|
Configuration = AppConfiguration.CreateEmpty();
|
||||||
|
configurationRoot.Bind(Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task RegisterAppConfiguration()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services.AddSingleton(Configuration);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Moonlight.ApiServer/Startup/Startup.Database.cs
Normal file
25
Moonlight.ApiServer/Startup/Startup.Database.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using MoonCore.Extended.Abstractions;
|
||||||
|
using MoonCore.Extended.Extensions;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task RegisterDatabase()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services.AddDatabaseMappings();
|
||||||
|
WebApplicationBuilder.Services.AddServiceCollectionAccessor();
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddScoped(typeof(DatabaseRepository<>));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PrepareDatabase()
|
||||||
|
{
|
||||||
|
await WebApplication.Services.EnsureDatabaseMigrated();
|
||||||
|
|
||||||
|
WebApplication.Services.GenerateDatabaseMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Moonlight.ApiServer/Startup/Startup.Logging.cs
Normal file
63
Moonlight.ApiServer/Startup/Startup.Logging.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Logging;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task SetupLogging()
|
||||||
|
{
|
||||||
|
var loggerFactory = new LoggerFactory();
|
||||||
|
loggerFactory.AddAnsiConsole();
|
||||||
|
|
||||||
|
Logger = loggerFactory.CreateLogger<Startup>();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RegisterLogging()
|
||||||
|
{
|
||||||
|
// Configure application logging
|
||||||
|
WebApplicationBuilder.Logging.ClearProviders();
|
||||||
|
WebApplicationBuilder.Logging.AddAnsiConsole();
|
||||||
|
WebApplicationBuilder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log"));
|
||||||
|
|
||||||
|
// Logging levels
|
||||||
|
var logConfigPath = Path.Combine("storage", "logConfig.json");
|
||||||
|
|
||||||
|
// Ensure logging config, add a default one is missing
|
||||||
|
if (!File.Exists(logConfigPath))
|
||||||
|
{
|
||||||
|
var defaultLogLevels = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "Default", "Information" },
|
||||||
|
{ "Microsoft.AspNetCore", "Warning" },
|
||||||
|
{ "System.Net.Http.HttpClient", "Warning" }
|
||||||
|
};
|
||||||
|
|
||||||
|
var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels);
|
||||||
|
await File.WriteAllTextAsync(logConfigPath, logLevelsJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add logging configuration
|
||||||
|
var logLevels = JsonSerializer.Deserialize<Dictionary<string, string>>(
|
||||||
|
await File.ReadAllTextAsync(logConfigPath)
|
||||||
|
)!;
|
||||||
|
|
||||||
|
foreach (var level in logLevels)
|
||||||
|
WebApplicationBuilder.Logging.AddFilter(level.Key, Enum.Parse<LogLevel>(level.Value));
|
||||||
|
|
||||||
|
// Mute exception handler middleware
|
||||||
|
// https://github.com/dotnet/aspnetcore/issues/19740
|
||||||
|
WebApplicationBuilder.Logging.AddFilter(
|
||||||
|
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware",
|
||||||
|
LogLevel.Critical
|
||||||
|
);
|
||||||
|
|
||||||
|
WebApplicationBuilder.Logging.AddFilter(
|
||||||
|
"Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware",
|
||||||
|
LogLevel.Critical
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
Moonlight.ApiServer/Startup/Startup.Misc.cs
Normal file
73
Moonlight.ApiServer/Startup/Startup.Misc.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task PrintVersion()
|
||||||
|
{
|
||||||
|
// Fancy start console output... yes very fancy :>
|
||||||
|
var rainbow = new Crayon.Rainbow(0.5);
|
||||||
|
foreach (var c in "Moonlight")
|
||||||
|
{
|
||||||
|
Console.Write(
|
||||||
|
rainbow
|
||||||
|
.Next()
|
||||||
|
.Bold()
|
||||||
|
.Text(c.ToString())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task CreateStorage()
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("storage");
|
||||||
|
Directory.CreateDirectory(Path.Combine("storage", "logs"));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task RegisterCors()
|
||||||
|
{
|
||||||
|
var allowedOrigins = Configuration.Kestrel.AllowedOrigins;
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddCors(options =>
|
||||||
|
{
|
||||||
|
var cors = new CorsPolicyBuilder();
|
||||||
|
|
||||||
|
if (allowedOrigins.Contains("*"))
|
||||||
|
{
|
||||||
|
cors.SetIsOriginAllowed(_ => true)
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowCredentials();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cors.WithOrigins(allowedOrigins)
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
options.AddDefaultPolicy(
|
||||||
|
cors.Build()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UseCors()
|
||||||
|
{
|
||||||
|
WebApplication.UseCors();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
Moonlight.ApiServer/Startup/Startup.Plugins.cs
Normal file
87
Moonlight.ApiServer/Startup/Startup.Plugins.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MoonCore.Logging;
|
||||||
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private IServiceProvider PluginLoadServiceProvider;
|
||||||
|
private IPluginStartup[] PluginStartups;
|
||||||
|
|
||||||
|
private Task InitializePlugins()
|
||||||
|
{
|
||||||
|
// Create service provider for starting up
|
||||||
|
var serviceCollection = new ServiceCollection();
|
||||||
|
|
||||||
|
serviceCollection.AddSingleton(Configuration);
|
||||||
|
|
||||||
|
serviceCollection.AddLogging(builder =>
|
||||||
|
{
|
||||||
|
builder.ClearProviders();
|
||||||
|
builder.AddAnsiConsole();
|
||||||
|
});
|
||||||
|
|
||||||
|
PluginLoadServiceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HookPluginBuild()
|
||||||
|
{
|
||||||
|
foreach (var pluginAppStartup in PluginStartups)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await pluginAppStartup.BuildApplication(PluginLoadServiceProvider, WebApplicationBuilder);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(
|
||||||
|
"An error occured while processing 'BuildApp' for '{name}': {e}",
|
||||||
|
pluginAppStartup.GetType().FullName,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HookPluginConfigure()
|
||||||
|
{
|
||||||
|
foreach (var pluginAppStartup in PluginStartups)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await pluginAppStartup.ConfigureApplication(PluginLoadServiceProvider, WebApplication);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(
|
||||||
|
"An error occured while processing 'ConfigureApp' for '{name}': {e}",
|
||||||
|
pluginAppStartup.GetType().FullName,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HookPluginEndpoints()
|
||||||
|
{
|
||||||
|
foreach (var pluginEndpointStartup in PluginStartups)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await pluginEndpointStartup.ConfigureEndpoints(PluginLoadServiceProvider, WebApplication);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.LogError(
|
||||||
|
"An error occured while processing 'ConfigureEndpoints' for '{name}': {e}",
|
||||||
|
pluginEndpointStartup.GetType().FullName,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Moonlight.ApiServer/Startup/Startup.TickerQ.cs
Normal file
34
Moonlight.ApiServer/Startup/Startup.TickerQ.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Moonlight.ApiServer.Database;
|
||||||
|
using Moonlight.ApiServer.Implementations;
|
||||||
|
using TickerQ.DependencyInjection;
|
||||||
|
using TickerQ.EntityFrameworkCore.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private Task RegisterTickerQ()
|
||||||
|
{
|
||||||
|
WebApplicationBuilder.Services.AddTickerQ(builder =>
|
||||||
|
{
|
||||||
|
builder.SetExceptionHandler<TickerExceptionHandler>();
|
||||||
|
|
||||||
|
builder.AddOperationalStore<TickerDataContext>(optionBuilder =>
|
||||||
|
{
|
||||||
|
optionBuilder.CancelMissedTickersOnApplicationRestart();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
WebApplicationBuilder.Services.AddDbContext<TickerDataContext>();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task UseTickerQ()
|
||||||
|
{
|
||||||
|
WebApplication.UseTickerQ();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Moonlight.ApiServer/Startup/Startup.cs
Normal file
67
Moonlight.ApiServer/Startup/Startup.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Moonlight.ApiServer.Configuration;
|
||||||
|
using Moonlight.ApiServer.Plugins;
|
||||||
|
|
||||||
|
namespace Moonlight.ApiServer.Startup;
|
||||||
|
|
||||||
|
public partial class Startup
|
||||||
|
{
|
||||||
|
private string[] Args;
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
public ILogger<Startup> Logger { get; private set; }
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
public AppConfiguration Configuration { get; private set; }
|
||||||
|
|
||||||
|
// WebApplication Stuff
|
||||||
|
public WebApplication WebApplication { get; private set; }
|
||||||
|
public WebApplicationBuilder WebApplicationBuilder { get; private set; }
|
||||||
|
|
||||||
|
public Task Initialize(string[] args, IPluginStartup[]? plugins = null)
|
||||||
|
{
|
||||||
|
Args = args;
|
||||||
|
PluginStartups = plugins ?? [];
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddMoonlight(WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
WebApplicationBuilder = builder;
|
||||||
|
|
||||||
|
await PrintVersion();
|
||||||
|
|
||||||
|
await CreateStorage();
|
||||||
|
await SetupAppConfiguration();
|
||||||
|
await SetupLogging();
|
||||||
|
await InitializePlugins();
|
||||||
|
|
||||||
|
await ConfigureKestrel();
|
||||||
|
await RegisterAppConfiguration();
|
||||||
|
await RegisterLogging();
|
||||||
|
await RegisterBase();
|
||||||
|
await RegisterDatabase();
|
||||||
|
await RegisterAuth();
|
||||||
|
await RegisterCors();
|
||||||
|
await RegisterTickerQ();
|
||||||
|
await HookPluginBuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddMoonlight(WebApplication application)
|
||||||
|
{
|
||||||
|
WebApplication = application;
|
||||||
|
|
||||||
|
await PrepareDatabase();
|
||||||
|
|
||||||
|
await UseCors();
|
||||||
|
await UseBase();
|
||||||
|
await UseAuth();
|
||||||
|
await UseTickerQ();
|
||||||
|
await HookPluginConfigure();
|
||||||
|
|
||||||
|
await MapBase();
|
||||||
|
await HookPluginEndpoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj
Normal file
24
Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Moonlight.Client\Moonlight.Client.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.8" />
|
||||||
|
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.2" />
|
||||||
|
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.5"/>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.5" PrivateAssets="all"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="Plugins.props" />
|
||||||
|
|
||||||
|
</Project>
|
||||||
10
Moonlight.Client.Runtime/PluginLoader.cs
Normal file
10
Moonlight.Client.Runtime/PluginLoader.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using MoonCore.PluginFramework;
|
||||||
|
using Moonlight.Client.Plugins;
|
||||||
|
|
||||||
|
namespace Moonlight.Client.Runtime;
|
||||||
|
|
||||||
|
[PluginLoader]
|
||||||
|
public partial class PluginLoader : IPluginStartup
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
4
Moonlight.Client.Runtime/Plugins.props
Normal file
4
Moonlight.Client.Runtime/Plugins.props
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<Project>
|
||||||
|
<ItemGroup>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
20
Moonlight.Client.Runtime/Program.cs
Normal file
20
Moonlight.Client.Runtime/Program.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
using Moonlight.Client.Runtime;
|
||||||
|
using Moonlight.Client.Startup;
|
||||||
|
|
||||||
|
var pluginLoader = new PluginLoader();
|
||||||
|
pluginLoader.Initialize();
|
||||||
|
|
||||||
|
var startup = new Startup();
|
||||||
|
|
||||||
|
await startup.Initialize(pluginLoader.Instances);
|
||||||
|
|
||||||
|
var wasmHostBuilder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
|
||||||
|
await startup.AddMoonlight(wasmHostBuilder);
|
||||||
|
|
||||||
|
var wasmApp = wasmHostBuilder.Build();
|
||||||
|
|
||||||
|
await startup.AddMoonlight(wasmApp);
|
||||||
|
|
||||||
|
await wasmApp.RunAsync();
|
||||||
@@ -4,7 +4,8 @@
|
|||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://0.0.0.0:5230",
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
|
"applicationUrl": "http://localhost:5165",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
30
Moonlight.Client.Runtime/Styles/extract-classes.js
Normal file
30
Moonlight.Client.Runtime/Styles/extract-classes.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// extract-classes.js
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = (opts = {}) => {
|
||||||
|
const classSet = new Set();
|
||||||
|
|
||||||
|
return {
|
||||||
|
postcssPlugin: 'extract-tailwind-classes',
|
||||||
|
Rule(rule) {
|
||||||
|
const selectorParser = require('postcss-selector-parser');
|
||||||
|
selectorParser(selectors => {
|
||||||
|
selectors.walkClasses(node => {
|
||||||
|
classSet.add(node.value);
|
||||||
|
});
|
||||||
|
}).processSync(rule.selector);
|
||||||
|
},
|
||||||
|
OnceExit() {
|
||||||
|
const classArray = Array.from(classSet).sort();
|
||||||
|
|
||||||
|
if (!fs.existsSync("../../Moonlight.Client/Styles/mappings")){
|
||||||
|
fs.mkdirSync("../../Moonlight.Client/Styles/mappings");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync('../../Moonlight.Client/Styles/mappings/classes.map', classArray.join('\n'));
|
||||||
|
console.log(`✅ Extracted ${classArray.length} Tailwind classes to tailwind-classes.txt`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.postcss = true;
|
||||||
1347
Moonlight.Client.Runtime/Styles/package-lock.json
generated
Normal file
1347
Moonlight.Client.Runtime/Styles/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
Moonlight.Client.Runtime/Styles/package.json
Normal file
23
Moonlight.Client.Runtime/Styles/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "styles",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"tailwind": "npx postcss styles.css -o ../wwwroot/css/style.min.css --watch",
|
||||||
|
"tailwind-build": "npx postcss styles.css -o ../wwwroot/css/style.min.css",
|
||||||
|
"mappings": "EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/css/style.min.css "
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
|
"flyonui": "^2.2.0",
|
||||||
|
"tailwindcss": "^4.1.11",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"postcss-cli": "^11.0.1",
|
||||||
|
"postcss-selector-parser": "^7.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
}
|
||||||
|
}
|
||||||
11
Moonlight.Client.Runtime/Styles/postcss.config.js
Normal file
11
Moonlight.Client.Runtime/Styles/postcss.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const tailwindcss = require('@tailwindcss/postcss');
|
||||||
|
const extractClasses = require('./extract-classes');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if(process.env.EXTRACT_CLASSES === "true")
|
||||||
|
module.exports.plugins.push(extractClasses);
|
||||||
117
Moonlight.Client.Runtime/Styles/styles.css
Normal file
117
Moonlight.Client.Runtime/Styles/styles.css
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback') layer(base);
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap') layer(base);
|
||||||
|
@import url("https://cdn.jsdelivr.net/npm/lucide-static/font/lucide.css") layer(base);
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
|
@import "./node_modules/flyonui/variants.css";
|
||||||
|
@import "./theme.css";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-inter: "Inter", var(--font-sans);
|
||||||
|
--font-scp: "Source Code Pro", var(--font-mono);
|
||||||
|
|
||||||
|
--color-background: var(--mooncore-color-background);
|
||||||
|
--color-base-150: var(--mooncore-color-base-150);
|
||||||
|
--color-base-250: var(--mooncore-color-base-250);
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "flyonui" {
|
||||||
|
themes: mooncore --default;
|
||||||
|
}
|
||||||
|
|
||||||
|
@source "./node_modules/flyonui/dist/index.js";
|
||||||
|
|
||||||
|
@source "../**/*.razor";
|
||||||
|
@source "../**/*.cs";
|
||||||
|
@source "../**/*.html";
|
||||||
|
@source "**/*.map";
|
||||||
|
|
||||||
|
@source "../../Moonlight.Client/**/*.cs";
|
||||||
|
@source "../../Moonlight.Client/**/*.html";
|
||||||
|
@source "../../Moonlight.Client/**/*.razor";
|
||||||
|
@source "../../Moonlight.Client/Styles/**/*.map";
|
||||||
|
|
||||||
|
@source "../../Moonlight.ApiServer/**/*.razor";
|
||||||
|
|
||||||
|
#blazor-error-ui {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-loader-label:after {
|
||||||
|
content: var(--blazor-load-percentage-text, "Loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-loader-progress {
|
||||||
|
width: var(--blazor-load-percentage, 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@plugin "flyonui/theme" {
|
||||||
|
name: "mooncore";
|
||||||
|
default: true;
|
||||||
|
prefersdark: true;
|
||||||
|
color-scheme: "dark";
|
||||||
|
--color-base-100: var(--mooncore-color-base-100);
|
||||||
|
--color-base-200: var(--mooncore-color-base-200);
|
||||||
|
--color-base-300: var(--mooncore-color-base-300);
|
||||||
|
--color-base-content: var(--mooncore-color-base-content);
|
||||||
|
--color-primary: var(--mooncore-color-primary);
|
||||||
|
--color-primary-content: var(--mooncore-color-primary-content);
|
||||||
|
--color-secondary: var(--mooncore-color-secondary);
|
||||||
|
--color-secondary-content: var(--mooncore-color-secondary-content);
|
||||||
|
--color-accent: var(--mooncore-color-accent);
|
||||||
|
--color-accent-content: var(--mooncore-color-accent-content);
|
||||||
|
--color-neutral: var(--mooncore-color-neutral);
|
||||||
|
--color-neutral-content: var(--mooncore-color-neutral-content);
|
||||||
|
--color-info: var(--mooncore-color-info);
|
||||||
|
--color-info-content: var(--mooncore-color-info-content);
|
||||||
|
--color-success: var(--mooncore-color-success);
|
||||||
|
--color-success-content: var(--mooncore-color-success-content);
|
||||||
|
--color-warning: var(--mooncore-color-warning);
|
||||||
|
--color-warning-content: var(--mooncore-color-warning-content);
|
||||||
|
--color-error: var(--mooncore-color-error);
|
||||||
|
--color-error-content: var(--mooncore-color-error-content);
|
||||||
|
--radius-selector: var(--mooncore-radius-selector);
|
||||||
|
--radius-field: var(--mooncore-radius-field);
|
||||||
|
--radius-box: var(--mooncore-radius-box);
|
||||||
|
--size-selector: var(--mooncore-size-selector);
|
||||||
|
--size-field: var(--mooncore-size-field);
|
||||||
|
--border: var(--mooncore-border);
|
||||||
|
--depth: var(--mooncore-depth);
|
||||||
|
--noise: var(--mooncore-noise);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.btn {
|
||||||
|
@apply text-sm font-medium inline-flex items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
@apply border-base-content/30 bg-base-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
@apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.advance-select-toggle {
|
||||||
|
@apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select {
|
||||||
|
@apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
:where(th, td) {
|
||||||
|
@apply py-1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
@apply px-2.5 py-1.5 text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
@apply bg-base-150;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Moonlight.Client.Runtime/Styles/theme.css
Normal file
33
Moonlight.Client.Runtime/Styles/theme.css
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
@theme {
|
||||||
|
--mooncore-color-background: #0c0f18;
|
||||||
|
--mooncore-color-base-100: #1e2b47;
|
||||||
|
--mooncore-color-base-150: #1a2640;
|
||||||
|
--mooncore-color-base-200: #101a2e;
|
||||||
|
--mooncore-color-base-250: #0f1729;
|
||||||
|
--mooncore-color-base-300: #0c1221;
|
||||||
|
--mooncore-color-base-content: #dde5f5;
|
||||||
|
--mooncore-color-primary: oklch(.511 .262 276.966);
|
||||||
|
--mooncore-color-primary-content: #dde5f5;
|
||||||
|
--mooncore-color-secondary: oklch(37% 0.034 259.733);
|
||||||
|
--mooncore-color-secondary-content: #dde5f5;
|
||||||
|
--mooncore-color-accent: oklch(.627 .265 303.9);
|
||||||
|
--mooncore-color-accent-content: #dde5f5;
|
||||||
|
--mooncore-color-neutral: #dde5f5;
|
||||||
|
--mooncore-color-neutral-content: oklch(14% 0.005 285.823);
|
||||||
|
--mooncore-color-info: oklch(.546 .245 262.881);
|
||||||
|
--mooncore-color-info-content: #dde5f5;
|
||||||
|
--mooncore-color-success: oklch(.627 .194 149.214);
|
||||||
|
--mooncore-color-success-content: #dde5f5;
|
||||||
|
--mooncore-color-warning: oklch(.828 .189 84.429);
|
||||||
|
--mooncore-color-warning-content: #dde5f5;
|
||||||
|
--mooncore-color-error: oklch(.586 .253 17.585);
|
||||||
|
--mooncore-color-error-content: #dde5f5;
|
||||||
|
--mooncore-radius-selector: 0.25rem;
|
||||||
|
--mooncore-radius-field: 0.5rem;
|
||||||
|
--mooncore-radius-box: 0.5rem;
|
||||||
|
--mooncore-size-selector: 0.25rem;
|
||||||
|
--mooncore-size-field: 0.25rem;
|
||||||
|
--mooncore-border: 1px;
|
||||||
|
--mooncore-depth: 0;
|
||||||
|
--mooncore-noise: 0;
|
||||||
|
}
|
||||||
22
Moonlight.Client.Runtime/wwwroot/manifest.webmanifest
Normal file
22
Moonlight.Client.Runtime/wwwroot/manifest.webmanifest
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "Moonlight Client",
|
||||||
|
"short_name": "Moonlight.Client",
|
||||||
|
"id": "./",
|
||||||
|
"start_url": "./",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#030b1f",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "img/icon-512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "img/icon-192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user