Compare commits
18 Commits
v2.1_Refac
...
ecf0e01914
| Author | SHA1 | Date | |
|---|---|---|---|
| ecf0e01914 | |||
| 6d447a0ff9 | |||
| ba5e364c05 | |||
| 2a2ce28b5f | |||
| 91887ec047 | |||
| 2fc371c219 | |||
| 9470e06c0f | |||
| 2f8665f1d4 | |||
| 4d4f35e2be | |||
| 609ea3a443 | |||
| 3e19b29cde | |||
| 1475b89660 | |||
| 3bb9a08630 | |||
| 252c4103f3 | |||
| e7b1e77d0a | |||
| 7c5dc657dc | |||
| 2d1b48b0d4 | |||
| 52dbd13fb5 |
69
.gitea/workflows/publish-nuget.yml
Normal file
69
.gitea/workflows/publish-nuget.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
name: "Dev Publish: Nuget"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- v2.1
|
||||
paths:
|
||||
- 'MoonlightServers.*/*.csproj'
|
||||
|
||||
env:
|
||||
NUGET_SOURCE: https://git.battlestati.one/api/packages/Moonlight-Panel/nuget/index.json
|
||||
NUGET_PUBLIC: https://api.nuget.org/v3/index.json
|
||||
CONFIGURATION: Debug
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish ${{ matrix.project }}
|
||||
runs-on: linux_amd64
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
project:
|
||||
- MoonlightServers.Api
|
||||
- MoonlightServers.Shared
|
||||
- MoonlightServers.DaemonShared
|
||||
- MoonlightServers.Frontend
|
||||
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Restore NuGet packages
|
||||
run: >
|
||||
dotnet restore ${{ matrix.project }}
|
||||
--source ${{ env.NUGET_PUBLIC }}
|
||||
--source ${{ env.NUGET_SOURCE }}
|
||||
|
||||
# Frontend requires a host build + Tailwind compilation first
|
||||
- name: Build frontend host (Frontend only)
|
||||
if: matrix.project == 'MoonlightServers.Frontend'
|
||||
run: >
|
||||
dotnet build Hosts/MoonlightServers.Frontend.Host
|
||||
--configuration ${{ env.CONFIGURATION }}
|
||||
|
||||
- name: Build Tailwind styles (Frontend only)
|
||||
if: matrix.project == 'MoonlightServers.Frontend'
|
||||
working-directory: Hosts/MoonlightServers.Frontend.Host/Styles
|
||||
run: npm install && npm run build
|
||||
|
||||
- name: Build project
|
||||
run: >
|
||||
dotnet build ${{ matrix.project }}
|
||||
--configuration ${{ env.CONFIGURATION }}
|
||||
--no-restore
|
||||
|
||||
- name: Pack NuGet package
|
||||
run: >
|
||||
dotnet pack ${{ matrix.project }}
|
||||
--configuration ${{ env.CONFIGURATION }}
|
||||
--output ./artifacts
|
||||
--no-build
|
||||
|
||||
- name: Push NuGet package
|
||||
run: >
|
||||
dotnet nuget push ./artifacts/*.nupkg
|
||||
--skip-duplicate
|
||||
--source ${{ env.NUGET_SOURCE }}
|
||||
--api-key ${{ secrets.ACCESS_TOKEN }}
|
||||
51
.gitignore
vendored
51
.gitignore
vendored
@@ -301,7 +301,7 @@ node_modules/
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
@@ -395,44 +395,15 @@ FodyWeavers.xsd
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
**/.idea/**
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
# Style builds
|
||||
**/style.min.css
|
||||
**/package-lock.json
|
||||
**/bun.lock
|
||||
|
||||
# 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/
|
||||
.idea/**/.idea
|
||||
MoonlightServers.min.css
|
||||
core.min.css
|
||||
|
||||
# Build script for nuget packages
|
||||
finalPackages/
|
||||
nupkgs/
|
||||
|
||||
# Local daemon tests
|
||||
**/data/volumes/**
|
||||
|
||||
# Local plugin build
|
||||
tmp/
|
||||
style.min.css
|
||||
# Secrets
|
||||
**/.env
|
||||
**/appsettings.json
|
||||
**/appsettings.Development.json
|
||||
**/storage
|
||||
|
||||
6
Hosts/MoonlightServers.Api.Host/Api.props
Normal file
6
Hosts/MoonlightServers.Api.Host/Api.props
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- Put your plugin references here -->
|
||||
<!-- E.g. <PackageReference Include="MoonlightServers.Api" Version="2.1.0" /> -->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.5"/>
|
||||
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<PackageReference Include="SimplePlugin" Version="1.0.2"/>
|
||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MoonlightServers.Api\MoonlightServers.Api.csproj" />
|
||||
<ProjectReference Include="..\MoonlightServers.Frontend.Host\MoonlightServers.Frontend.Host.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="Api.props"/>
|
||||
</Project>
|
||||
9
Hosts/MoonlightServers.Api.Host/Program.cs
Normal file
9
Hosts/MoonlightServers.Api.Host/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Moonlight.Api;
|
||||
using SimplePlugin.Generated;
|
||||
|
||||
var plugins = PluginRegistry
|
||||
.Modules
|
||||
.OfType<MoonlightPlugin>()
|
||||
.ToArray();
|
||||
|
||||
await StartupHandler.RunAsync(args, plugins);
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5031",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Hosts/MoonlightServers.Api.Host/appsettings.Example.json
Normal file
23
Hosts/MoonlightServers.Api.Host/appsettings.Example.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Moonlight": {
|
||||
"Database": {
|
||||
"Host": "your-db.host",
|
||||
"Username": "change_me",
|
||||
"Password": "change_me",
|
||||
"Database": "change_me"
|
||||
},
|
||||
"Oidc": {
|
||||
"Authority": "http://localhost:8092",
|
||||
"RequireHttpsMetadata": false,
|
||||
"ClientId": "clientId",
|
||||
"ClientSecret": "clientSecret"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Hosts/MoonlightServers.Frontend.Host/Frontend.props
Normal file
6
Hosts/MoonlightServers.Frontend.Host/Frontend.props
Normal file
@@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<!-- Put your plugin references here -->
|
||||
<!-- E.g. <PackageReference Include="MoonlightServers.Frontend" Version="2.1.0" /> -->
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.5" PrivateAssets="all"/>
|
||||
<PackageReference Include="SimplePlugin" Version="1.0.2"/>
|
||||
<PackageReference Include="SimplePlugin.Abstractions" Version="1.0.2"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\MoonlightServers.Frontend\MoonlightServers.Frontend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="Frontend.props"/>
|
||||
</Project>
|
||||
9
Hosts/MoonlightServers.Frontend.Host/Program.cs
Normal file
9
Hosts/MoonlightServers.Frontend.Host/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Moonlight.Frontend;
|
||||
using SimplePlugin.Generated;
|
||||
|
||||
var plugins = PluginRegistry
|
||||
.Modules
|
||||
.OfType<MoonlightPlugin>()
|
||||
.ToArray();
|
||||
|
||||
await StartupHandler.RunAsync(args, plugins);
|
||||
@@ -0,0 +1,25 @@
|
||||
import fs from 'fs';
|
||||
import selectorParser from 'postcss-selector-parser';
|
||||
|
||||
export default function extractTailwindClasses(opts = {}) {
|
||||
const classSet = new Set();
|
||||
|
||||
return {
|
||||
postcssPlugin: 'extract-tailwind-classes',
|
||||
Rule(rule) {
|
||||
selectorParser(selectors => {
|
||||
selectors.walkClasses(node => {
|
||||
classSet.add(node.value);
|
||||
});
|
||||
}).processSync(rule.selector);
|
||||
},
|
||||
OnceExit() {
|
||||
const classArray = Array.from(classSet).sort();
|
||||
fs.mkdirSync('../../../MoonlightServers.Frontend/Styles', { recursive: true });
|
||||
fs.writeFileSync('../../../MoonlightServers.Frontend/Styles/MoonlightServers.Frontend.map', classArray.join('\n'));
|
||||
console.log(`Extracted classes ${classArray.length}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extractTailwindClasses.postcss = true;
|
||||
19
Hosts/MoonlightServers.Frontend.Host/Styles/package.json
Normal file
19
Hosts/MoonlightServers.Frontend.Host/Styles/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "npx postcss styles.css -o ../wwwroot/style.min.css --watch",
|
||||
"dev-build": "npx postcss styles.css -o ../wwwroot/style.min.css",
|
||||
"build": "cross-env EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/style.min.css"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"cssnano": "^7.1.2",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"postcss-selector-parser": "^7.1.1",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import tailwindcss from '@tailwindcss/postcss';
|
||||
import cssnano from 'cssnano';
|
||||
import extractTailwindClasses from './extract-classes.mjs';
|
||||
|
||||
const plugins = [
|
||||
tailwindcss,
|
||||
cssnano({
|
||||
preset: 'default'
|
||||
})
|
||||
];
|
||||
|
||||
if (process.env.EXTRACT_CLASSES === "true") {
|
||||
plugins.push(extractTailwindClasses());
|
||||
}
|
||||
|
||||
export default {
|
||||
plugins
|
||||
};
|
||||
31
Hosts/MoonlightServers.Frontend.Host/Styles/styles.css
Normal file
31
Hosts/MoonlightServers.Frontend.Host/Styles/styles.css
Normal file
@@ -0,0 +1,31 @@
|
||||
@import "tailwindcss";
|
||||
@import "tw-animate-css";
|
||||
|
||||
@import "../bin/ShadcnBlazor/scrollbar.css";
|
||||
@import "../bin/ShadcnBlazor/default-theme.css";
|
||||
@import "./theme.css";
|
||||
|
||||
@source "../bin/**/*.map";
|
||||
|
||||
@source "../../../MoonlightServers.Api/**/*.razor";
|
||||
@source "../../../MoonlightServers.Api/**/*.cs";
|
||||
@source "../../../MoonlightServers.Api/**/*.html";
|
||||
|
||||
@source "../../../MoonlightServers.Frontend/**/*.razor";
|
||||
@source "../../../MoonlightServers.Frontend/**/*.cs";
|
||||
@source "../../../MoonlightServers.Frontend/**/*.html";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
#blazor-error-ui {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
132
Hosts/MoonlightServers.Frontend.Host/Styles/theme.css
Normal file
132
Hosts/MoonlightServers.Frontend.Host/Styles/theme.css
Normal file
@@ -0,0 +1,132 @@
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
|
||||
--sidebar: var(--background);
|
||||
--sidebar-foreground: var(--foreground);
|
||||
--sidebar-primary: var(--primary);
|
||||
--sidebar-primary-foreground: var(--primary-foreground);
|
||||
--sidebar-accent: var(--accent);
|
||||
--sidebar-accent-foreground: var(--accent-foreground);
|
||||
--sidebar-border: var(--border);
|
||||
--sidebar-ring: var(--ring);
|
||||
|
||||
--font-sans: Inter, sans-serif;
|
||||
--font-serif: Georgia, serif;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Deep blue-slate background with purple undertones */
|
||||
--background: oklch(0.16 0.028 260);
|
||||
--foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Cards with slightly lighter blue-slate */
|
||||
--card: oklch(0.21 0.032 260);
|
||||
--card-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Popovers with medium depth */
|
||||
--popover: oklch(0.24 0.035 260);
|
||||
--popover-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Vibrant blue-purple primary */
|
||||
--primary: oklch(0.62 0.18 270);
|
||||
--primary-foreground: oklch(0.99 0.005 260);
|
||||
|
||||
/* Secondary with blue-slate tone */
|
||||
--secondary: oklch(0.27 0.038 260);
|
||||
--secondary-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Muted elements */
|
||||
--muted: oklch(0.25 0.035 260);
|
||||
--muted-foreground: oklch(0.66 0.025 260);
|
||||
|
||||
/* Accent with purple-blue blend */
|
||||
--accent: oklch(0.36 0.065 268);
|
||||
--accent-foreground: oklch(0.98 0.008 260);
|
||||
|
||||
/* Destructive red with good contrast */
|
||||
--destructive: oklch(0.62 0.22 25);
|
||||
--destructive-foreground: oklch(0.99 0.005 260);
|
||||
|
||||
/* Subtle borders and inputs */
|
||||
--border: oklch(0.32 0.025 260);
|
||||
--input: oklch(0.30 0.030 260);
|
||||
--ring: oklch(0.62 0.18 270);
|
||||
|
||||
/* Chart colors with blue-purple harmony */
|
||||
--chart-1: oklch(0.58 0.18 270);
|
||||
--chart-2: oklch(0.62 0.16 245);
|
||||
--chart-3: oklch(0.68 0.15 290);
|
||||
--chart-4: oklch(0.60 0.20 260);
|
||||
--chart-5: oklch(0.65 0.14 280);
|
||||
|
||||
/* Sidebar with slightly different depth */
|
||||
--sidebar: oklch(0.18 0.030 260);
|
||||
--sidebar-foreground: oklch(0.97 0.008 260);
|
||||
--sidebar-primary: oklch(0.60 0.17 270);
|
||||
--sidebar-primary-foreground: oklch(0.99 0.005 260);
|
||||
--sidebar-accent: oklch(0.26 0.038 260);
|
||||
--sidebar-accent-foreground: oklch(0.98 0.008 260);
|
||||
--sidebar-border: oklch(0.30 0.025 260);
|
||||
--sidebar-ring: oklch(0.58 0.17 270);
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
111
Hosts/MoonlightServers.Frontend.Host/wwwroot/index.html
Normal file
111
Hosts/MoonlightServers.Frontend.Host/wwwroot/index.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Moonlight</title>
|
||||
<base href="/" />
|
||||
<link rel="preload" id="webassembly" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback" />
|
||||
<link rel="stylesheet" href="style.min.css" />
|
||||
<script type="importmap"></script>
|
||||
|
||||
<script>
|
||||
window.frontendConfig = {
|
||||
STYLE_TAG_ID: 'theme-variables',
|
||||
configuration: {},
|
||||
|
||||
applyTheme: function(cssContent) {
|
||||
// Find or create the style tag
|
||||
let styleTag = document.getElementById(this.STYLE_TAG_ID);
|
||||
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.id = this.STYLE_TAG_ID;
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
// Update the style tag content
|
||||
styleTag.textContent = cssContent;
|
||||
},
|
||||
|
||||
reloadConfiguration: function (){
|
||||
try {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/api/frontend/config', false);
|
||||
xhr.send(null);
|
||||
|
||||
if (xhr.status === 200) {
|
||||
this.configuration = JSON.parse(xhr.responseText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load initial theme:', error);
|
||||
}
|
||||
},
|
||||
|
||||
getConfiguration: function (){
|
||||
return this.configuration;
|
||||
},
|
||||
|
||||
reload: function () {
|
||||
this.reloadConfiguration();
|
||||
|
||||
document.title = this.configuration.name;
|
||||
this.applyTheme(this.configuration.themeCss);
|
||||
}
|
||||
};
|
||||
|
||||
window.frontendConfig.reload();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-background text-foreground">
|
||||
<div id="app">
|
||||
<div class="h-screen w-full flex items-center justify-center">
|
||||
|
||||
<div class="flex min-w-0 flex-1 flex-col items-center justify-center gap-3 rounded-lg border-dashed p-6 text-center text-balance md:p-12">
|
||||
<div class="flex max-w-sm flex-col items-center gap-2 text-center">
|
||||
<div class="flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-muted text-foreground size-10 rounded-lg [&_svg:not([class*='size-'])]:size-6">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="lucide lucide-zap-icon lucide-zap size-6">
|
||||
<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-lg font-medium tracking-tight">
|
||||
Loading application
|
||||
</div>
|
||||
<div class="flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance">
|
||||
|
||||
<div class="bg-primary/20 w-full relative h-2 overflow-hidden rounded-full">
|
||||
<div class="bg-primary h-full w-[var(--blazor-load-percentage,0)] flex-1 transition-all">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
|
||||
<script src="/_content/ShadcnBlazor/interop.js" defer></script>
|
||||
<script src="/_content/ShadcnBlazor.Extras/interop.js" defer></script>
|
||||
<script src="/_content/ShadcnBlazor.Extras/codemirror-bundle.js" defer></script>
|
||||
|
||||
<script src="/_content/Moonlight.Frontend/chart.umd.js" defer></script>
|
||||
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
157
MoonlightServers.Api/Admin/Nodes/CrudController.cs
Normal file
157
MoonlightServers.Api/Admin/Nodes/CrudController.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Moonlight.Shared.Shared;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Nodes;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/nodes")]
|
||||
public class CrudController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Node> DatabaseRepository;
|
||||
private readonly HybridCache Cache;
|
||||
|
||||
public CrudController(
|
||||
DatabaseRepository<Node> databaseRepository,
|
||||
HybridCache cache
|
||||
)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Nodes.View)]
|
||||
public async Task<ActionResult<PagedData<NodeDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = DatabaseRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Node.Name) =>
|
||||
query.Where(role => EF.Functions.ILike(role.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<NodeDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Nodes.View)]
|
||||
public async Task<ActionResult<NodeDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
return NodeMapper.ToDto(node);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Nodes.Create)]
|
||||
public async Task<ActionResult<NodeDto>> CreateAsync([FromBody] CreateNodeDto request)
|
||||
{
|
||||
var node = NodeMapper.ToEntity(request);
|
||||
|
||||
node.TokenId = GenerateString(10);
|
||||
node.Token = GenerateString(64);
|
||||
|
||||
var finalRole = await DatabaseRepository.AddAsync(node);
|
||||
|
||||
return NodeMapper.ToDto(finalRole);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Nodes.Edit)]
|
||||
public async Task<ActionResult<NodeDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateNodeDto request)
|
||||
{
|
||||
var node = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
NodeMapper.Merge(node, request);
|
||||
|
||||
await DatabaseRepository.UpdateAsync(node);
|
||||
|
||||
return NodeMapper.ToDto(node);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Nodes.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
await DatabaseRepository.RemoveAsync(node);
|
||||
|
||||
// Remove cache for node token auth scheme
|
||||
await Cache.RemoveAsync(string.Format(NodeTokenSchemeHandler.CacheKeyFormat, node.TokenId));
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private static string GenerateString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var stringBuilder = new StringBuilder();
|
||||
var random = new Random();
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
stringBuilder.Append(chars[random.Next(chars.Length)]);
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
51
MoonlightServers.Api/Admin/Nodes/HealthController.cs
Normal file
51
MoonlightServers.Api/Admin/Nodes/HealthController.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Nodes;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Policy = Permissions.Nodes.View)]
|
||||
[Route("api/admin/servers/nodes/{id:int}/health")]
|
||||
public class HealthController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Node> DatabaseRepository;
|
||||
private readonly NodeService NodeService;
|
||||
private readonly ILogger<HealthController> Logger;
|
||||
|
||||
public HealthController(
|
||||
DatabaseRepository<Node> databaseRepository,
|
||||
NodeService nodeService,
|
||||
ILogger<HealthController> logger
|
||||
)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
NodeService = nodeService;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<NodeHealthDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
var health = await NodeService.GetHealthAsync(node);
|
||||
|
||||
return new NodeHealthDto()
|
||||
{
|
||||
StatusCode = health.StatusCode,
|
||||
RemoteStatusCode = health.Dto?.RemoteStatusCode ?? 0,
|
||||
IsHealthy = health is { StatusCode: >= 200 and <= 299, Dto.RemoteStatusCode: >= 200 and <= 299 }
|
||||
};
|
||||
}
|
||||
}
|
||||
20
MoonlightServers.Api/Admin/Nodes/NodeMapper.cs
Normal file
20
MoonlightServers.Api/Admin/Nodes/NodeMapper.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.DaemonShared.Http.Daemon;
|
||||
using MoonlightServers.Shared.Admin.Nodes;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Nodes;
|
||||
|
||||
[Mapper]
|
||||
[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")]
|
||||
[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")]
|
||||
public static partial class NodeMapper
|
||||
{
|
||||
public static partial NodeDto ToDto(Node node);
|
||||
public static partial IQueryable<NodeDto> ProjectToDto(this IQueryable<Node> nodes);
|
||||
public static partial Node ToEntity(CreateNodeDto dto);
|
||||
public static partial void Merge([MappingTarget] Node node, UpdateNodeDto dto);
|
||||
|
||||
public static partial NodeStatisticsDto ToDto(SystemStatisticsDto dto);
|
||||
}
|
||||
145
MoonlightServers.Api/Admin/Nodes/NodeService.cs
Normal file
145
MoonlightServers.Api/Admin/Nodes/NodeService.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.DaemonShared.Http;
|
||||
using MoonlightServers.DaemonShared.Http.Daemon;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Nodes;
|
||||
|
||||
public class NodeService
|
||||
{
|
||||
private readonly IHttpClientFactory ClientFactory;
|
||||
private readonly ILogger<NodeService> Logger;
|
||||
public NodeService(IHttpClientFactory clientFactory, ILogger<NodeService> logger)
|
||||
{
|
||||
ClientFactory = clientFactory;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
public async Task<NodeHealthStatus> GetHealthAsync(Node node)
|
||||
{
|
||||
var client = ClientFactory.CreateClient();
|
||||
|
||||
var request = CreateBaseRequest(node, HttpMethod.Get, "api/health");
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return new NodeHealthStatus((int)response.StatusCode, null);
|
||||
|
||||
try
|
||||
{
|
||||
var health = await response
|
||||
.Content
|
||||
.ReadFromJsonAsync<HealthDto>(SerializationContext.Default.Options);
|
||||
|
||||
return new NodeHealthStatus((int)response.StatusCode, health);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogTrace(e, "An unhandled error occured while processing health response of node {id}", node.Id);
|
||||
|
||||
return new NodeHealthStatus((int)response.StatusCode, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogTrace(e, "An error occured while fetching health status of node {id}", node.Id);
|
||||
return new NodeHealthStatus(0, null);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SystemStatisticsDto> GetStatisticsAsync(Node node)
|
||||
{
|
||||
var client = ClientFactory.CreateClient();
|
||||
|
||||
var request = CreateBaseRequest(node, HttpMethod.Get, "api/system/statistics");
|
||||
|
||||
var response = await client.SendAsync(request);
|
||||
|
||||
await EnsureSuccessAsync(response);
|
||||
|
||||
return (await response.Content.ReadFromJsonAsync<SystemStatisticsDto>(SerializationContext.Default.Options))!;
|
||||
}
|
||||
|
||||
private static HttpRequestMessage CreateBaseRequest(
|
||||
Node node,
|
||||
[StringSyntax(StringSyntaxAttribute.Uri)]
|
||||
HttpMethod method,
|
||||
string endpoint
|
||||
)
|
||||
{
|
||||
var request = new HttpRequestMessage();
|
||||
|
||||
request.Headers.Add(HeaderNames.Authorization, node.Token);
|
||||
request.RequestUri = new Uri(new Uri(node.HttpEndpointUrl), endpoint);
|
||||
request.Method = method;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private async Task EnsureSuccessAsync(HttpResponseMessage message)
|
||||
{
|
||||
if (message.IsSuccessStatusCode)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var problemDetails = await message.Content.ReadFromJsonAsync<ProblemDetails>(
|
||||
SerializationContext.Default.Options
|
||||
);
|
||||
|
||||
if (problemDetails == null)
|
||||
{
|
||||
// If we cant handle problem details, we handle it natively
|
||||
message.EnsureSuccessStatusCode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse into exception
|
||||
throw new NodeException(
|
||||
problemDetails.Type,
|
||||
problemDetails.Title,
|
||||
problemDetails.Status,
|
||||
problemDetails.Detail,
|
||||
problemDetails.Errors
|
||||
);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// If we cant handle problem details, we handle it natively
|
||||
message.EnsureSuccessStatusCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record NodeHealthStatus(int StatusCode, HealthDto? Dto);
|
||||
|
||||
public class NodeException : Exception
|
||||
{
|
||||
public string Type { get; }
|
||||
public string Title { get; }
|
||||
public int Status { get; }
|
||||
public string? Detail { get; }
|
||||
public Dictionary<string, string[]>? Errors { get; }
|
||||
|
||||
public NodeException(
|
||||
string type,
|
||||
string title,
|
||||
int status,
|
||||
string? detail = null,
|
||||
Dictionary<string, string[]>? errors = null)
|
||||
: base(detail ?? title)
|
||||
{
|
||||
Type = type;
|
||||
Title = title;
|
||||
Status = status;
|
||||
Detail = detail;
|
||||
Errors = errors;
|
||||
}
|
||||
}
|
||||
40
MoonlightServers.Api/Admin/Nodes/StatisticsController.cs
Normal file
40
MoonlightServers.Api/Admin/Nodes/StatisticsController.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Nodes;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Policy = Permissions.Nodes.View)]
|
||||
[Route("api/admin/servers/nodes/{id:int}/statistics")]
|
||||
public class StatisticsController : Controller
|
||||
{
|
||||
private readonly NodeService NodeService;
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
|
||||
public StatisticsController(NodeService nodeService, DatabaseRepository<Node> nodeRepository)
|
||||
{
|
||||
NodeService = nodeService;
|
||||
NodeRepository = nodeRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<NodeStatisticsDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
var statistics = await NodeService.GetStatisticsAsync(node);
|
||||
var dto = NodeMapper.ToDto(statistics);
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
172
MoonlightServers.Api/Admin/Templates/CrudController.cs
Normal file
172
MoonlightServers.Api/Admin/Templates/CrudController.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Shared;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates")]
|
||||
public class CrudController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Template> DatabaseRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public CrudController(
|
||||
DatabaseRepository<Template> databaseRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<TemplateDto>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length,
|
||||
[FromQuery] FilterOptions? filterOptions
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
// Query building
|
||||
|
||||
var query = DatabaseRepository
|
||||
.Query();
|
||||
|
||||
// Filters
|
||||
if (filterOptions != null)
|
||||
{
|
||||
foreach (var filterOption in filterOptions.Filters)
|
||||
{
|
||||
query = filterOption.Key switch
|
||||
{
|
||||
nameof(Template.Name) =>
|
||||
query.Where(role => EF.Functions.ILike(role.Name, $"%{filterOption.Value}%")),
|
||||
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<TemplateDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<DetailedTemplateDto>> GetAsync([FromRoute] int id)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.Include(x => x.DefaultDockerImage)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
return TemplateMapper.ToDetailedDto(template);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<TemplateDto>> CreateAsync([FromBody] CreateTemplateDto request)
|
||||
{
|
||||
var template = TemplateMapper.ToEntity(request);
|
||||
|
||||
// Fill in default values
|
||||
template.LifecycleConfig = new()
|
||||
{
|
||||
StartupCommands = [
|
||||
new StartupCommand
|
||||
{
|
||||
DisplayName = "Default Startup",
|
||||
Command = "bash startup.sh"
|
||||
}
|
||||
],
|
||||
StopCommand = "^C",
|
||||
OnlineLogPatterns = ["I am online"]
|
||||
};
|
||||
|
||||
template.InstallationConfig = new()
|
||||
{
|
||||
DockerImage = "debian",
|
||||
Script = "#!/bin/bash\necho Installing",
|
||||
Shell = "/bin/bash"
|
||||
};
|
||||
|
||||
template.FilesConfig = new()
|
||||
{
|
||||
ConfigurationFiles = []
|
||||
};
|
||||
|
||||
template.MiscellaneousConfig = new()
|
||||
{
|
||||
UseLegacyStartup = true
|
||||
};
|
||||
|
||||
var finalRole = await DatabaseRepository.AddAsync(template);
|
||||
|
||||
return TemplateMapper.ToDto(finalRole);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<DetailedTemplateDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateTemplateDto request)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
TemplateMapper.Merge(template, request);
|
||||
|
||||
template.DefaultDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == id)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.DefaultDockerImageId);
|
||||
|
||||
await DatabaseRepository.UpdateAsync(template);
|
||||
|
||||
return TemplateMapper.ToDetailedDto(template);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var template = await DatabaseRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with this id found", statusCode: 404);
|
||||
|
||||
await DatabaseRepository.RemoveAsync(template);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
144
MoonlightServers.Api/Admin/Templates/DockerImagesController.cs
Normal file
144
MoonlightServers.Api/Admin/Templates/DockerImagesController.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Shared;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates/{templateId:int}/dockerImages")]
|
||||
public class DockerImagesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
|
||||
public DockerImagesController(
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository,
|
||||
DatabaseRepository<Template> templateRepository
|
||||
)
|
||||
{
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
TemplateRepository = templateRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<DockerImageDto>>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
if (!await TemplateRepository.Query().AnyAsync(x => x.Id == templateId))
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
// Query building
|
||||
|
||||
var query = DockerImageRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == templateId);
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<DockerImageDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<DockerImageDto>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
return TemplateMapper.ToDto(templateDockerImage);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<DockerImageDto>> CreateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromBody] CreateDockerImageDto dto
|
||||
)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == templateId);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var dockerImage = TemplateMapper.ToEntity(dto);
|
||||
|
||||
dockerImage.Template = template;
|
||||
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(dockerImage);
|
||||
|
||||
return TemplateMapper.ToDto(finalDockerImage);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<DockerImageDto>> UpdateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateDockerImageDto dto
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
TemplateMapper.Merge(templateDockerImage, dto);
|
||||
|
||||
await DockerImageRepository.UpdateAsync(templateDockerImage);
|
||||
|
||||
return TemplateMapper.ToDto(templateDockerImage);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateDockerImage = await DockerImageRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateDockerImage == null)
|
||||
return Problem("No template or template dockerImage found with that id");
|
||||
|
||||
await DockerImageRepository.RemoveAsync(templateDockerImage);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
207
MoonlightServers.Api/Admin/Templates/PelicanEggImportService.cs
Normal file
207
MoonlightServers.Api/Admin/Templates/PelicanEggImportService.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using System.Text;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
using VYaml.Annotations;
|
||||
using VYaml.Serialization;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class PelicanEggImportService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public PelicanEggImportService(
|
||||
DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(string content)
|
||||
{
|
||||
var egg = YamlSerializer.Deserialize<Egg>(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
var template = new Template()
|
||||
{
|
||||
AllowUserDockerImageChange = true,
|
||||
Author = egg.Author,
|
||||
Description = egg.Description,
|
||||
DonateUrl = null,
|
||||
Name = egg.Name,
|
||||
UpdateUrl = egg.Meta.UpdateUrl,
|
||||
Version = "1.0.0",
|
||||
FilesConfig = new FilesConfig()
|
||||
{
|
||||
ConfigurationFiles = egg.Config.Files.Select(file => new ConfigurationFile()
|
||||
{
|
||||
Path = file.Key,
|
||||
Parser = file.Value.Parser,
|
||||
Mappings = file.Value.Find.Select(pair => new ConfigurationFileMapping()
|
||||
{
|
||||
Key = pair.Key,
|
||||
Value = pair.Value
|
||||
}).ToList()
|
||||
}).ToList()
|
||||
},
|
||||
InstallationConfig = new InstallationConfig()
|
||||
{
|
||||
DockerImage = egg.Scripts.Installation.Container,
|
||||
Script = egg.Scripts.Installation.Script,
|
||||
Shell = egg.Scripts.Installation.Entrypoint
|
||||
},
|
||||
LifecycleConfig = new LifecycleConfig()
|
||||
{
|
||||
OnlineLogPatterns = egg.Config.Startup.Values.ToList(),
|
||||
StopCommand = egg.Config.Stop,
|
||||
StartupCommands = egg.StartupCommands.Select(x => new StartupCommand()
|
||||
{
|
||||
DisplayName = x.Key,
|
||||
Command = x.Value
|
||||
}).ToList()
|
||||
},
|
||||
MiscellaneousConfig = new MiscellaneousConfig()
|
||||
{
|
||||
UseLegacyStartup = true
|
||||
},
|
||||
Variables = egg.Variables.Select(variable => new TemplateVariable()
|
||||
{
|
||||
Description = variable.Description,
|
||||
DisplayName = variable.Name,
|
||||
DefaultValue = variable.DefaultValue,
|
||||
EnvName = variable.EnvVariable
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
var isFirst = true;
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
foreach (var dockerImage in egg.DockerImages)
|
||||
{
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(new TemplateDockerImage()
|
||||
{
|
||||
DisplayName = dockerImage.Key,
|
||||
ImageName = dockerImage.Value,
|
||||
SkipPulling = false,
|
||||
Template = finalTemplate
|
||||
});
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
defaultDockerImage = finalDockerImage;
|
||||
}
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class Egg
|
||||
{
|
||||
[YamlMember("_comment")] public string? Comment { get; set; }
|
||||
|
||||
[YamlMember("meta")] public EggMeta Meta { get; set; } = new();
|
||||
|
||||
[YamlMember("exported_at")] public string? ExportedAt { get; set; }
|
||||
|
||||
[YamlMember("name")] public string Name { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("author")] public string Author { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("uuid")] public string Uuid { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("description")] public string Description { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("image")] public string? Image { get; set; }
|
||||
|
||||
[YamlMember("tags")] public List<string> Tags { get; set; } = new();
|
||||
|
||||
[YamlMember("features")] public List<string> Features { get; set; } = new();
|
||||
|
||||
[YamlMember("docker_images")] public Dictionary<string, string> DockerImages { get; set; } = new();
|
||||
|
||||
[YamlMember("file_denylist")] public Dictionary<string, string> FileDenylist { get; set; } = new();
|
||||
|
||||
[YamlMember("startup_commands")] public Dictionary<string, string> StartupCommands { get; set; } = new();
|
||||
|
||||
[YamlMember("config")] public EggConfig Config { get; set; } = new();
|
||||
|
||||
[YamlMember("scripts")] public EggScripts Scripts { get; set; } = new();
|
||||
|
||||
[YamlMember("variables")] public List<EggVariable> Variables { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggMeta
|
||||
{
|
||||
[YamlMember("version")] public string Version { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("update_url")] public string? UpdateUrl { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggConfig
|
||||
{
|
||||
[YamlMember("files")] public Dictionary<string, EggConfigFile> Files { get; set; } = new();
|
||||
|
||||
[YamlMember("startup")] public Dictionary<string, string> Startup { get; set; } = new();
|
||||
|
||||
[YamlMember("logs")] public Dictionary<string, string> Logs { get; set; } = new();
|
||||
|
||||
[YamlMember("stop")] public string Stop { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggConfigFile
|
||||
{
|
||||
[YamlMember("parser")] public string Parser { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("find")] public Dictionary<string, string> Find { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggScripts
|
||||
{
|
||||
[YamlMember("installation")] public EggInstallationScript Installation { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggInstallationScript
|
||||
{
|
||||
[YamlMember("script")] public string Script { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("container")] public string Container { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("entrypoint")] public string Entrypoint { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class EggVariable
|
||||
{
|
||||
[YamlMember("name")] public string Name { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("description")] public string Description { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("env_variable")] public string EnvVariable { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("default_value")] public string DefaultValue { get; set; } = string.Empty;
|
||||
|
||||
[YamlMember("user_viewable")] public bool UserViewable { get; set; }
|
||||
|
||||
[YamlMember("user_editable")] public bool UserEditable { get; set; }
|
||||
|
||||
[YamlMember("rules")] public List<string> Rules { get; set; } = new();
|
||||
|
||||
[YamlMember("sort")] public int Sort { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
using System.Text.Json;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class PterodactylEggImportService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public PterodactylEggImportService(
|
||||
DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(string json)
|
||||
{
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
var root = doc.RootElement;
|
||||
|
||||
var template = new Template
|
||||
{
|
||||
Name = Truncate(root.GetStringOrDefault("name") ?? "Unknown", 30),
|
||||
Description = Truncate(root.GetStringOrDefault("description") ?? "", 255),
|
||||
Author = Truncate(root.GetStringOrDefault("author") ?? "", 30),
|
||||
Version = "1.0.0",
|
||||
UpdateUrl = root.TryGetProperty("meta", out var meta)
|
||||
? meta.GetStringOrDefault("update_url")
|
||||
: null,
|
||||
DonateUrl = null,
|
||||
|
||||
FilesConfig = ParseFilesConfig(root),
|
||||
LifecycleConfig = ParseLifecycleConfig(root),
|
||||
InstallationConfig = ParseInstallationConfig(root),
|
||||
MiscellaneousConfig = new MiscellaneousConfig { UseLegacyStartup = true },
|
||||
|
||||
AllowUserDockerImageChange = true,
|
||||
Variables = ParseVariables(root)
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
var dockerImages = ParseDockerImageModels(root);
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
var isFirst = true;
|
||||
|
||||
foreach (var (displayName, imageName) in dockerImages)
|
||||
{
|
||||
var entity = new TemplateDockerImage
|
||||
{
|
||||
DisplayName = displayName,
|
||||
ImageName = imageName,
|
||||
SkipPulling = false,
|
||||
Template = finalTemplate
|
||||
};
|
||||
|
||||
var finalEntity = await DockerImageRepository.AddAsync(entity);
|
||||
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
defaultDockerImage = finalEntity;
|
||||
}
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
|
||||
private static FilesConfig ParseFilesConfig(JsonElement root)
|
||||
{
|
||||
var configFiles = new List<ConfigurationFile>();
|
||||
|
||||
if (!root.TryGetProperty("config", out var config))
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
if (!config.TryGetProperty("files", out var filesElement))
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
var filesJson = filesElement.ValueKind == JsonValueKind.String
|
||||
? filesElement.GetString()
|
||||
: filesElement.GetRawText();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filesJson) || filesJson == "{}" || filesJson == "[]")
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
|
||||
try
|
||||
{
|
||||
using var filesDoc = JsonDocument.Parse(filesJson);
|
||||
|
||||
foreach (var fileProperty in filesDoc.RootElement.EnumerateObject())
|
||||
{
|
||||
var parser = fileProperty.Value.GetStringOrDefault("parser") ?? "json";
|
||||
var mappings = new List<ConfigurationFileMapping>();
|
||||
|
||||
if (fileProperty.Value.TryGetProperty("find", out var find))
|
||||
{
|
||||
foreach (var mapping in find.EnumerateObject())
|
||||
{
|
||||
mappings.Add(new ConfigurationFileMapping
|
||||
{
|
||||
Key = mapping.Name,
|
||||
Value = mapping.Value.ValueKind == JsonValueKind.String
|
||||
? mapping.Value.GetString() ?? ""
|
||||
: mapping.Value.GetRawText()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
configFiles.Add(new ConfigurationFile
|
||||
{
|
||||
Path = fileProperty.Name,
|
||||
Parser = parser,
|
||||
Mappings = mappings
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
|
||||
return new FilesConfig { ConfigurationFiles = configFiles };
|
||||
}
|
||||
|
||||
private static LifecycleConfig ParseLifecycleConfig(JsonElement root)
|
||||
{
|
||||
var stopCommand = "";
|
||||
var onlinePatterns = new List<string>();
|
||||
|
||||
if (root.TryGetProperty("config", out var config))
|
||||
{
|
||||
stopCommand = config.GetStringOrDefault("stop") ?? "";
|
||||
|
||||
if (config.TryGetProperty("startup", out var startupElement))
|
||||
{
|
||||
var startupJson = startupElement.ValueKind == JsonValueKind.String
|
||||
? startupElement.GetString()
|
||||
: startupElement.GetRawText();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(startupJson))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var startupDoc = JsonDocument.Parse(startupJson);
|
||||
|
||||
if (startupDoc.RootElement.TryGetProperty("done", out var done))
|
||||
{
|
||||
var doneValue = done.ValueKind == JsonValueKind.String
|
||||
? done.GetString()
|
||||
: done.GetRawText();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(doneValue))
|
||||
onlinePatterns.Add(doneValue);
|
||||
}
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new LifecycleConfig
|
||||
{
|
||||
StartupCommands =
|
||||
[
|
||||
new StartupCommand
|
||||
{
|
||||
DisplayName = "Startup",
|
||||
Command = root.GetStringOrDefault("startup") ?? ""
|
||||
}
|
||||
],
|
||||
StopCommand = stopCommand,
|
||||
OnlineLogPatterns = onlinePatterns
|
||||
};
|
||||
}
|
||||
|
||||
private static InstallationConfig ParseInstallationConfig(JsonElement root)
|
||||
{
|
||||
if (!root.TryGetProperty("scripts", out var scripts))
|
||||
return new InstallationConfig();
|
||||
|
||||
if (!scripts.TryGetProperty("installation", out var installation))
|
||||
return new InstallationConfig();
|
||||
|
||||
return new InstallationConfig
|
||||
{
|
||||
DockerImage = installation.GetStringOrDefault("container") ?? "",
|
||||
Shell = installation.GetStringOrDefault("entrypoint") ?? "bash",
|
||||
Script = installation.GetStringOrDefault("script") ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
// Returns (DisplayName, ImageName, IsFirst) tuples to avoid a temporary model
|
||||
private static List<(string DisplayName, string ImageName)> ParseDockerImageModels(JsonElement root)
|
||||
{
|
||||
var result = new List<(string, string)>();
|
||||
|
||||
if (!root.TryGetProperty("docker_images", out var dockerImages))
|
||||
return result;
|
||||
|
||||
foreach (var img in dockerImages.EnumerateObject())
|
||||
{
|
||||
result.Add((
|
||||
Truncate(img.Name, 30),
|
||||
Truncate(img.Value.GetString() ?? img.Name, 255)
|
||||
));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<TemplateVariable> ParseVariables(JsonElement root)
|
||||
{
|
||||
var variables = new List<TemplateVariable>();
|
||||
|
||||
if (!root.TryGetProperty("variables", out var vars))
|
||||
return variables;
|
||||
|
||||
foreach (var v in vars.EnumerateArray())
|
||||
{
|
||||
variables.Add(new TemplateVariable
|
||||
{
|
||||
DisplayName = Truncate(v.GetStringOrDefault("name") ?? "Variable", 30),
|
||||
Description = Truncate(v.GetStringOrDefault("description") ?? "", 255),
|
||||
EnvName = Truncate(v.GetStringOrDefault("env_variable") ?? "", 60),
|
||||
DefaultValue = Truncate(v.GetStringOrDefault("default_value") ?? "", 1024)
|
||||
});
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
private static string Truncate(string value, int maxLength) =>
|
||||
value.Length <= maxLength ? value : value[..maxLength];
|
||||
}
|
||||
|
||||
internal static class JsonElementExtensions
|
||||
{
|
||||
public static string? GetStringOrDefault(this JsonElement element, string propertyName)
|
||||
{
|
||||
return element.TryGetProperty(propertyName, out var prop) && prop.ValueKind == JsonValueKind.String
|
||||
? prop.GetString()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
28
MoonlightServers.Api/Admin/Templates/TemplateMapper.cs
Normal file
28
MoonlightServers.Api/Admin/Templates/TemplateMapper.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[Mapper]
|
||||
[SuppressMessage("Mapper", "RMG020:No members are mapped in an object mapping")]
|
||||
[SuppressMessage("Mapper", "RMG012:No members are mapped in an object mapping")]
|
||||
public static partial class TemplateMapper
|
||||
{
|
||||
public static partial TemplateDto ToDto(Template template);
|
||||
public static partial DetailedTemplateDto ToDetailedDto(Template template);
|
||||
public static partial IQueryable<TemplateDto> ProjectToDto(this IQueryable<Template> templates);
|
||||
public static partial Template ToEntity(CreateTemplateDto dto);
|
||||
public static partial void Merge([MappingTarget] Template template, UpdateTemplateDto dto);
|
||||
|
||||
public static partial IQueryable<VariableDto> ProjectToDto(this IQueryable<TemplateVariable> variables);
|
||||
public static partial VariableDto ToDto(TemplateVariable variable);
|
||||
public static partial TemplateVariable ToEntity(CreateVariableDto dto);
|
||||
public static partial void Merge([MappingTarget] TemplateVariable variable, UpdateVariableDto dto);
|
||||
|
||||
public static partial IQueryable<DockerImageDto> ProjectToDto(this IQueryable<TemplateDockerImage> dockerImages);
|
||||
public static partial DockerImageDto ToDto(TemplateDockerImage dockerImage);
|
||||
public static partial TemplateDockerImage ToEntity(CreateDockerImageDto dto);
|
||||
public static partial void Merge([MappingTarget] TemplateDockerImage dockerImage, UpdateDockerImageDto dto);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using VYaml.Annotations;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateTransferModel
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public string Author { get; set; } = "";
|
||||
public string Version { get; set; } = "";
|
||||
public string? UpdateUrl { get; set; }
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
public FilesConfigTransferModel Files { get; set; } = new();
|
||||
public LifecycleConfigTransferModel Lifecycle { get; set; } = new();
|
||||
public InstallationConfigTransferModel Installation { get; set; } = new();
|
||||
public MiscellaneousConfigTransferModel Miscellaneous { get; set; } = new();
|
||||
|
||||
public bool AllowUserDockerImageChange { get; set; }
|
||||
public List<TemplateDockerImageTransferModel> DockerImages { get; set; } = new();
|
||||
|
||||
public List<TemplateVariableTransferModel> Variables { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateDockerImageTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string ImageName { get; set; } = "";
|
||||
public bool SkipPulling { get; set; }
|
||||
public bool IsDefault { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class TemplateVariableTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string Description { get; set; } = "";
|
||||
public string EnvName { get; set; } = "";
|
||||
public string? DefaultValue { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class FilesConfigTransferModel
|
||||
{
|
||||
public List<ConfigurationFileTransferModel> ConfigurationFiles { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class ConfigurationFileTransferModel
|
||||
{
|
||||
public string Path { get; set; } = "";
|
||||
public string Parser { get; set; } = "";
|
||||
public List<ConfigurationFileMappingTransferModel> Mappings { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class ConfigurationFileMappingTransferModel
|
||||
{
|
||||
public string Key { get; set; } = "";
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class LifecycleConfigTransferModel
|
||||
{
|
||||
public List<StartupCommandTransferModel> StartupCommands { get; set; } = new();
|
||||
public string StopCommand { get; set; } = "";
|
||||
public List<string> OnlineLogPatterns { get; set; } = new();
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class StartupCommandTransferModel
|
||||
{
|
||||
public string DisplayName { get; set; } = "";
|
||||
public string Command { get; set; } = "";
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class InstallationConfigTransferModel
|
||||
{
|
||||
public string DockerImage { get; set; } = "";
|
||||
public string Shell { get; set; } = "";
|
||||
public string Script { get; set; } = "";
|
||||
}
|
||||
|
||||
[YamlObject]
|
||||
public partial class MiscellaneousConfigTransferModel
|
||||
{
|
||||
public bool UseLegacyStartup { get; set; }
|
||||
}
|
||||
182
MoonlightServers.Api/Admin/Templates/TemplateTransferService.cs
Normal file
182
MoonlightServers.Api/Admin/Templates/TemplateTransferService.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
public class TemplateTransferService
|
||||
{
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
private readonly DatabaseRepository<TemplateDockerImage> DockerImageRepository;
|
||||
|
||||
public TemplateTransferService(DatabaseRepository<Template> templateRepository,
|
||||
DatabaseRepository<TemplateDockerImage> dockerImageRepository)
|
||||
{
|
||||
TemplateRepository = templateRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
public async Task<TemplateTransferModel?> ExportAsync(int id)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.Include(x => x.DefaultDockerImage)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (template == null)
|
||||
return null;
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = template.Name,
|
||||
Description = template.Description,
|
||||
Author = template.Author,
|
||||
Version = template.Version,
|
||||
UpdateUrl = template.UpdateUrl,
|
||||
DonateUrl = template.DonateUrl,
|
||||
|
||||
Files = new FilesConfigTransferModel
|
||||
{
|
||||
ConfigurationFiles = template.FilesConfig.ConfigurationFiles
|
||||
.Select(cf => new ConfigurationFileTransferModel
|
||||
{
|
||||
Path = cf.Path,
|
||||
Parser = cf.Parser,
|
||||
Mappings = cf.Mappings
|
||||
.Select(m => new ConfigurationFileMappingTransferModel { Key = m.Key, Value = m.Value })
|
||||
.ToList()
|
||||
})
|
||||
.ToList()
|
||||
},
|
||||
|
||||
Lifecycle = new LifecycleConfigTransferModel
|
||||
{
|
||||
StartupCommands = template.LifecycleConfig.StartupCommands
|
||||
.Select(sc => new StartupCommandTransferModel
|
||||
{ DisplayName = sc.DisplayName, Command = sc.Command })
|
||||
.ToList(),
|
||||
StopCommand = template.LifecycleConfig.StopCommand,
|
||||
OnlineLogPatterns = template.LifecycleConfig.OnlineLogPatterns.ToList()
|
||||
},
|
||||
|
||||
Installation = new InstallationConfigTransferModel
|
||||
{
|
||||
DockerImage = template.InstallationConfig.DockerImage,
|
||||
Shell = template.InstallationConfig.Shell,
|
||||
Script = template.InstallationConfig.Script
|
||||
},
|
||||
|
||||
Miscellaneous = new MiscellaneousConfigTransferModel
|
||||
{
|
||||
UseLegacyStartup = template.MiscellaneousConfig.UseLegacyStartup
|
||||
},
|
||||
|
||||
AllowUserDockerImageChange = template.AllowUserDockerImageChange,
|
||||
DockerImages = template.DockerImages
|
||||
.Select(img => new TemplateDockerImageTransferModel
|
||||
{
|
||||
DisplayName = img.DisplayName,
|
||||
ImageName = img.ImageName,
|
||||
SkipPulling = img.SkipPulling,
|
||||
IsDefault = template.DefaultDockerImage != null && img.Id == template.DefaultDockerImage.Id
|
||||
})
|
||||
.ToList(),
|
||||
|
||||
Variables = template.Variables
|
||||
.Select(v => new TemplateVariableTransferModel
|
||||
{
|
||||
DisplayName = v.DisplayName,
|
||||
Description = v.Description,
|
||||
EnvName = v.EnvName,
|
||||
DefaultValue = v.DefaultValue
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<Template> ImportAsync(TemplateTransferModel m)
|
||||
{
|
||||
var template = new Template
|
||||
{
|
||||
Name = m.Name,
|
||||
Description = m.Description,
|
||||
Author = m.Author,
|
||||
Version = m.Version,
|
||||
UpdateUrl = m.UpdateUrl,
|
||||
DonateUrl = m.DonateUrl,
|
||||
|
||||
FilesConfig = new FilesConfig
|
||||
{
|
||||
ConfigurationFiles = m.Files.ConfigurationFiles
|
||||
.Select(cf => new ConfigurationFile
|
||||
{
|
||||
Path = cf.Path,
|
||||
Parser = cf.Parser,
|
||||
Mappings = cf.Mappings
|
||||
.Select(mp => new ConfigurationFileMapping { Key = mp.Key, Value = mp.Value })
|
||||
.ToList()
|
||||
})
|
||||
.ToList()
|
||||
},
|
||||
|
||||
LifecycleConfig = new LifecycleConfig
|
||||
{
|
||||
StartupCommands = m.Lifecycle.StartupCommands
|
||||
.Select(sc => new StartupCommand { DisplayName = sc.DisplayName, Command = sc.Command })
|
||||
.ToList(),
|
||||
StopCommand = m.Lifecycle.StopCommand,
|
||||
OnlineLogPatterns = m.Lifecycle.OnlineLogPatterns.ToList()
|
||||
},
|
||||
|
||||
InstallationConfig = new InstallationConfig
|
||||
{
|
||||
DockerImage = m.Installation.DockerImage,
|
||||
Shell = m.Installation.Shell,
|
||||
Script = m.Installation.Script
|
||||
},
|
||||
|
||||
MiscellaneousConfig = new MiscellaneousConfig { UseLegacyStartup = m.Miscellaneous.UseLegacyStartup },
|
||||
|
||||
AllowUserDockerImageChange = m.AllowUserDockerImageChange,
|
||||
|
||||
Variables = m.Variables
|
||||
.Select(v => new TemplateVariable
|
||||
{
|
||||
DisplayName = v.DisplayName,
|
||||
Description = v.Description,
|
||||
EnvName = v.EnvName,
|
||||
DefaultValue = v.DefaultValue
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var finalTemplate = await TemplateRepository.AddAsync(template);
|
||||
|
||||
TemplateDockerImage? defaultDockerImage = null;
|
||||
|
||||
foreach (var img in m.DockerImages)
|
||||
{
|
||||
var entity = new TemplateDockerImage
|
||||
{
|
||||
DisplayName = img.DisplayName,
|
||||
ImageName = img.ImageName,
|
||||
SkipPulling = img.SkipPulling,
|
||||
Template = template
|
||||
};
|
||||
|
||||
var finalEntity = await DockerImageRepository.AddAsync(entity);
|
||||
|
||||
if (img.IsDefault)
|
||||
defaultDockerImage = finalEntity;
|
||||
}
|
||||
|
||||
finalTemplate.DefaultDockerImage = defaultDockerImage;
|
||||
|
||||
await TemplateRepository.UpdateAsync(finalTemplate);
|
||||
|
||||
return finalTemplate;
|
||||
}
|
||||
}
|
||||
88
MoonlightServers.Api/Admin/Templates/TransferController.cs
Normal file
88
MoonlightServers.Api/Admin/Templates/TransferController.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
using VYaml.Serialization;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates")]
|
||||
public class TransferController : Controller
|
||||
{
|
||||
private readonly TemplateTransferService TransferService;
|
||||
|
||||
public TransferController(TemplateTransferService transferService)
|
||||
{
|
||||
TransferService = transferService;
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/export")]
|
||||
public async Task<ActionResult> ExportAsync([FromRoute] int id)
|
||||
{
|
||||
var transferModel = await TransferService.ExportAsync(id);
|
||||
|
||||
if (transferModel == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var yml = YamlSerializer.Serialize(transferModel, new YamlSerializerOptions
|
||||
{
|
||||
Resolver = CompositeResolver.Create([
|
||||
GeneratedResolver.Instance,
|
||||
StandardResolver.Instance
|
||||
])
|
||||
});
|
||||
|
||||
return File(yml.ToArray(), "text/yaml", $"{transferModel.Name}.yml");
|
||||
}
|
||||
|
||||
[HttpPost("import")]
|
||||
public async Task<ActionResult<TemplateDto>> ImportAsync()
|
||||
{
|
||||
string content;
|
||||
|
||||
await using (Stream receiveStream = Request.Body)
|
||||
|
||||
using (StreamReader readStream = new StreamReader(receiveStream))
|
||||
content = await readStream.ReadToEndAsync();
|
||||
|
||||
if(content.Contains("version: PLCN_v3"))
|
||||
{
|
||||
var importService = HttpContext.RequestServices.GetRequiredService<PelicanEggImportService>();
|
||||
|
||||
var template = await importService.ImportAsync(content);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
if (
|
||||
content.Contains("PTDL_v2", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v1", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v2", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Contains("PLCN_v3", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
{
|
||||
var importService = HttpContext.RequestServices.GetRequiredService<PterodactylEggImportService>();
|
||||
|
||||
var template = await importService.ImportAsync(content);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
else
|
||||
{
|
||||
var transferModel = YamlSerializer.Deserialize<TemplateTransferModel>(
|
||||
Encoding.UTF8.GetBytes(content),
|
||||
new YamlSerializerOptions
|
||||
{
|
||||
Resolver = CompositeResolver.Create([
|
||||
GeneratedResolver.Instance,
|
||||
StandardResolver.Instance
|
||||
])
|
||||
}
|
||||
);
|
||||
|
||||
var template = await TransferService.ImportAsync(transferModel);
|
||||
|
||||
return TemplateMapper.ToDto(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
MoonlightServers.Api/Admin/Templates/VariablesController.cs
Normal file
144
MoonlightServers.Api/Admin/Templates/VariablesController.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.Shared.Shared;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
using MoonlightServers.Shared;
|
||||
using MoonlightServers.Shared.Admin.Templates;
|
||||
|
||||
namespace MoonlightServers.Api.Admin.Templates;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/templates/{templateId:int}/variables")]
|
||||
public class VariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<TemplateVariable> VariableRepository;
|
||||
private readonly DatabaseRepository<Template> TemplateRepository;
|
||||
|
||||
public VariablesController(
|
||||
DatabaseRepository<TemplateVariable> variableRepository,
|
||||
DatabaseRepository<Template> templateRepository
|
||||
)
|
||||
{
|
||||
VariableRepository = variableRepository;
|
||||
TemplateRepository = templateRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<PagedData<VariableDto>>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int length
|
||||
)
|
||||
{
|
||||
// Validation
|
||||
if (startIndex < 0)
|
||||
return Problem("Invalid start index specified", statusCode: 400);
|
||||
|
||||
if (length is < 1 or > 100)
|
||||
return Problem("Invalid length specified");
|
||||
|
||||
if (!await TemplateRepository.Query().AnyAsync(x => x.Id == templateId))
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
// Query building
|
||||
|
||||
var query = VariableRepository
|
||||
.Query()
|
||||
.Where(x => x.Template.Id == templateId);
|
||||
|
||||
// Pagination
|
||||
var data = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.ProjectToDto()
|
||||
.Skip(startIndex)
|
||||
.Take(length)
|
||||
.ToArrayAsync();
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
return new PagedData<VariableDto>(data, total);
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.View)]
|
||||
public async Task<ActionResult<VariableDto>> GetAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
return TemplateMapper.ToDto(templateVariable);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = Permissions.Templates.Create)]
|
||||
public async Task<ActionResult<VariableDto>> CreateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromBody] CreateVariableDto dto
|
||||
)
|
||||
{
|
||||
var template = await TemplateRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == templateId);
|
||||
|
||||
if (template == null)
|
||||
return Problem("No template with that id found", statusCode: 404);
|
||||
|
||||
var variable = TemplateMapper.ToEntity(dto);
|
||||
|
||||
variable.Template = template;
|
||||
|
||||
var finalVariable = await VariableRepository.AddAsync(variable);
|
||||
|
||||
return TemplateMapper.ToDto(finalVariable);
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Edit)]
|
||||
public async Task<ActionResult<VariableDto>> UpdateAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateVariableDto dto
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
TemplateMapper.Merge(templateVariable, dto);
|
||||
|
||||
await VariableRepository.UpdateAsync(templateVariable);
|
||||
|
||||
return TemplateMapper.ToDto(templateVariable);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = Permissions.Templates.Delete)]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int templateId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var templateVariable = await VariableRepository
|
||||
.Query()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Template.Id == templateId);
|
||||
|
||||
if (templateVariable == null)
|
||||
return Problem("No template or template variable found with that id");
|
||||
|
||||
await VariableRepository.RemoveAsync(templateVariable);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Configuration;
|
||||
|
||||
public class NodeTokenOptions
|
||||
{
|
||||
public TimeSpan LookupCacheL1Expiry { get; set; } = TimeSpan.FromSeconds(30);
|
||||
public TimeSpan LookupCacheL2Expiry { get; set; } = TimeSpan.FromMinutes(3);
|
||||
}
|
||||
66
MoonlightServers.Api/Infrastructure/Database/DataContext.cs
Normal file
66
MoonlightServers.Api/Infrastructure/Database/DataContext.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moonlight.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database;
|
||||
|
||||
public class DataContext : DbContext
|
||||
{
|
||||
public DbSet<Node> Nodes { get; set; }
|
||||
public DbSet<Template> Templates { get; set; }
|
||||
public DbSet<TemplateDockerImage> TemplateDockerImages { get; set; }
|
||||
public DbSet<TemplateVariable> TemplateVariablesVariables { get; set; }
|
||||
|
||||
private readonly IOptions<DatabaseOptions> Options;
|
||||
public DataContext(IOptions<DatabaseOptions> options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (optionsBuilder.IsConfigured)
|
||||
return;
|
||||
|
||||
optionsBuilder.UseNpgsql(
|
||||
$"Host={Options.Value.Host};" +
|
||||
$"Port={Options.Value.Port};" +
|
||||
$"Username={Options.Value.Username};" +
|
||||
$"Password={Options.Value.Password};" +
|
||||
$"Database={Options.Value.Database}",
|
||||
builder =>
|
||||
{
|
||||
builder.MigrationsAssembly(typeof(DataContext).Assembly);
|
||||
builder.MigrationsHistoryTable("MigrationsHistory", "servers");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema("servers");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<Template>()
|
||||
.ComplexProperty(x => x.FilesConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.LifecycleConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.InstallationConfig, builder => builder.ToJson())
|
||||
.ComplexProperty(x => x.MiscellaneousConfig, builder => builder.ToJson());
|
||||
|
||||
// One-to-many: Template => DockerImages
|
||||
modelBuilder.Entity<Template>()
|
||||
.HasMany(t => t.DockerImages)
|
||||
.WithOne(d => d.Template)
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
// One-to-one: Template => DefaultDockerImage
|
||||
modelBuilder.Entity<Template>()
|
||||
.HasOne(t => t.DefaultDockerImage)
|
||||
.WithOne()
|
||||
.HasForeignKey<Template>("DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Interfaces;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database;
|
||||
|
||||
public class DatabaseRepository<T> where T : class
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
private readonly DbSet<T> Set;
|
||||
|
||||
public DatabaseRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
Set = DataContext.Set<T>();
|
||||
}
|
||||
|
||||
public IQueryable<T> Query() => Set;
|
||||
|
||||
public async Task<T> AddAsync(T entity)
|
||||
{
|
||||
if (entity is IActionTimestamps actionTimestamps)
|
||||
{
|
||||
actionTimestamps.CreatedAt = DateTimeOffset.UtcNow;
|
||||
actionTimestamps.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
var final = Set.Add(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
return final.Entity;
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(T entity)
|
||||
{
|
||||
if (entity is IActionTimestamps actionTimestamps)
|
||||
actionTimestamps.UpdatedAt = DateTimeOffset.UtcNow;
|
||||
|
||||
Set.Update(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task RemoveAsync(T entity)
|
||||
{
|
||||
Set.Remove(entity);
|
||||
await DataContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database;
|
||||
|
||||
public class DbMigrationService : IHostedLifecycleService
|
||||
{
|
||||
private readonly ILogger<DbMigrationService> Logger;
|
||||
private readonly IServiceProvider ServiceProvider;
|
||||
|
||||
public DbMigrationService(ILogger<DbMigrationService> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
Logger = logger;
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task StartingAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Checking for pending migrations");
|
||||
|
||||
await using var scope = ServiceProvider.CreateAsyncScope();
|
||||
var context = scope.ServiceProvider.GetRequiredService<DataContext>();
|
||||
|
||||
var availableMigrations = context.Database.GetMigrations();
|
||||
Logger.LogTrace("Available migrations: {names}", string.Join(", ", availableMigrations));
|
||||
|
||||
var pendingMigrations = await context.Database.GetPendingMigrationsAsync(cancellationToken);
|
||||
var migrationNames = pendingMigrations.ToArray();
|
||||
|
||||
if (migrationNames.Length == 0)
|
||||
{
|
||||
Logger.LogTrace("No pending migrations found");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogInformation("Pending migrations: {names}", string.Join(", ", migrationNames));
|
||||
Logger.LogInformation("Migration started");
|
||||
|
||||
await context.Database.MigrateAsync(cancellationToken);
|
||||
|
||||
Logger.LogInformation("Migration complete");
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task StartedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task StoppedAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public Task StoppingAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Interfaces;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class Node : IActionTimestamps
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(50)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(100)]
|
||||
public string HttpEndpointUrl { get; set; }
|
||||
|
||||
[MaxLength(10)]
|
||||
public string TokenId { get; set; }
|
||||
|
||||
[MaxLength(64)]
|
||||
public string Token { get; set; }
|
||||
|
||||
// Action timestamps
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class Template
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Meta
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Name { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Author { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string Version { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
public string? UpdateUrl { get; set; }
|
||||
|
||||
[MaxLength(2048)]
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
// JSON Options
|
||||
public FilesConfig FilesConfig { get; set; }
|
||||
public LifecycleConfig LifecycleConfig { get; set; }
|
||||
public InstallationConfig InstallationConfig { get; set; }
|
||||
public MiscellaneousConfig MiscellaneousConfig { get; set; }
|
||||
|
||||
// Docker Images
|
||||
public bool AllowUserDockerImageChange { get; set; }
|
||||
public TemplateDockerImage? DefaultDockerImage { get; set; }
|
||||
public List<TemplateDockerImage> DockerImages { get; set; } = new();
|
||||
|
||||
// Variables
|
||||
public List<TemplateVariable> Variables { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class TemplateDockerImage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string ImageName { get; set; }
|
||||
|
||||
public bool SkipPulling { get; set; }
|
||||
|
||||
// Relations
|
||||
public Template Template { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
public class TemplateVariable
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
[MaxLength(30)]
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
[MaxLength(255)]
|
||||
public string Description { get; set; }
|
||||
|
||||
[MaxLength(60)]
|
||||
public string EnvName { get; set; }
|
||||
|
||||
[MaxLength(1024)]
|
||||
public string? DefaultValue { get; set; }
|
||||
|
||||
// Relations
|
||||
public Template Template { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Interfaces;
|
||||
|
||||
internal interface IActionTimestamps
|
||||
{
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public DateTimeOffset UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class FilesConfig
|
||||
{
|
||||
public List<ConfigurationFile> ConfigurationFiles { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ConfigurationFile
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string Parser { get; set; } = string.Empty;
|
||||
public List<ConfigurationFileMapping> Mappings { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ConfigurationFileMapping
|
||||
{
|
||||
public string Key { get; set; } = string.Empty;
|
||||
public string? Value { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class InstallationConfig
|
||||
{
|
||||
public string DockerImage { get; set; } = string.Empty;
|
||||
public string Shell { get; set; } = string.Empty;
|
||||
public string Script { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class LifecycleConfig
|
||||
{
|
||||
public List<StartupCommand> StartupCommands { get; set; } = [];
|
||||
public string StopCommand { get; set; } = string.Empty;
|
||||
public List<string> OnlineLogPatterns { get; set; } = [];
|
||||
}
|
||||
|
||||
public class StartupCommand
|
||||
{
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string Command { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Json;
|
||||
|
||||
public class MiscellaneousConfig
|
||||
{
|
||||
public bool UseLegacyStartup { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260305104238_AddedBasicNodeEntity")]
|
||||
partial class AddedBasicNodeEntity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Node", 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>("HttpEndpointUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedBasicNodeEntity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "servers");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Nodes",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
HttpEndpointUrl = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
TokenId = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||
Token = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Nodes", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Nodes",
|
||||
schema: "servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
315
MoonlightServers.Api/Infrastructure/Database/Migrations/20260312075719_AddedTemplateEntities.Designer.cs
generated
Normal file
315
MoonlightServers.Api/Infrastructure/Database/Migrations/20260312075719_AddedTemplateEntities.Designer.cs
generated
Normal file
@@ -0,0 +1,315 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260312075719_AddedTemplateEntities")]
|
||||
partial class AddedTemplateEntities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Node", 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>("HttpEndpointUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowUserDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int?>("DefaultDockerImageId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "FilesConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "ConfigurationFiles", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Parser")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("Path")
|
||||
.IsRequired();
|
||||
|
||||
b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Mappings", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile.Mappings#ConfigurationFileMapping", b3 =>
|
||||
{
|
||||
b3.IsRequired();
|
||||
|
||||
b3.Property<string>("Key")
|
||||
.IsRequired();
|
||||
|
||||
b3.Property<string>("Value")
|
||||
.IsRequired();
|
||||
});
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("FilesConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "InstallationConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.InstallationConfig#InstallationConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<string>("DockerImage")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Script")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Shell")
|
||||
.IsRequired();
|
||||
|
||||
b1
|
||||
.ToJson("InstallationConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "LifecycleConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.PrimitiveCollection<string>("OnlineLogPatterns")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("StopCommand")
|
||||
.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "StartupCommands", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig.StartupCommands#StartupCommand", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Command")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("DisplayName")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("LifecycleConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "MiscellaneousConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.MiscellaneousConfig#MiscellaneousConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<bool>("UseLegacyStartup");
|
||||
|
||||
b1
|
||||
.ToJson("MiscellaneousConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DefaultDockerImageId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Templates", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("ImageName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("SkipPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("EnvName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateVariablesVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", "DefaultDockerImage")
|
||||
.WithOne()
|
||||
.HasForeignKey("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DefaultDockerImage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedTemplateEntities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TemplateDockerImages",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DisplayName = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
ImageName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
SkipPulling = table.Column<bool>(type: "boolean", nullable: false),
|
||||
TemplateId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TemplateDockerImages", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Templates",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Author = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Version = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
UpdateUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
DonateUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
AllowUserDockerImageChange = table.Column<bool>(type: "boolean", nullable: false),
|
||||
DefaultDockerImageId = table.Column<int>(type: "integer", nullable: true),
|
||||
FilesConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
InstallationConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
LifecycleConfig = table.Column<string>(type: "jsonb", nullable: false),
|
||||
MiscellaneousConfig = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Templates", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Templates_TemplateDockerImages_DefaultDockerImageId",
|
||||
column: x => x.DefaultDockerImageId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "TemplateDockerImages",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TemplateVariablesVariables",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DisplayName = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
EnvName = table.Column<string>(type: "character varying(60)", maxLength: 60, nullable: false),
|
||||
DefaultValue = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
TemplateId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TemplateVariablesVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TemplateVariablesVariables_Templates_TemplateId",
|
||||
column: x => x.TemplateId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Templates",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateDockerImages_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages",
|
||||
column: "TemplateId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Templates_DefaultDockerImageId",
|
||||
schema: "servers",
|
||||
table: "Templates",
|
||||
column: "DefaultDockerImageId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateVariablesVariables_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateVariablesVariables",
|
||||
column: "TemplateId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_TemplateDockerImages_Templates_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages",
|
||||
column: "TemplateId",
|
||||
principalSchema: "servers",
|
||||
principalTable: "Templates",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_TemplateDockerImages_Templates_TemplateId",
|
||||
schema: "servers",
|
||||
table: "TemplateDockerImages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TemplateVariablesVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Templates",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TemplateDockerImages",
|
||||
schema: "servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20260312153948_AddedNullabilityForTemplateVariableDefaultValue")]
|
||||
partial class AddedNullabilityForTemplateVariableDefaultValue
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Node", 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>("HttpEndpointUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowUserDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int?>("DefaultDockerImageId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "FilesConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "ConfigurationFiles", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Parser")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("Path")
|
||||
.IsRequired();
|
||||
|
||||
b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Mappings", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile.Mappings#ConfigurationFileMapping", b3 =>
|
||||
{
|
||||
b3.IsRequired();
|
||||
|
||||
b3.Property<string>("Key")
|
||||
.IsRequired();
|
||||
|
||||
b3.Property<string>("Value");
|
||||
});
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("FilesConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "InstallationConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.InstallationConfig#InstallationConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<string>("DockerImage")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Script")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Shell")
|
||||
.IsRequired();
|
||||
|
||||
b1
|
||||
.ToJson("InstallationConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "LifecycleConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.PrimitiveCollection<string>("OnlineLogPatterns")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("StopCommand")
|
||||
.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "StartupCommands", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig.StartupCommands#StartupCommand", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Command")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("DisplayName")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("LifecycleConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "MiscellaneousConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.MiscellaneousConfig#MiscellaneousConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<bool>("UseLegacyStartup");
|
||||
|
||||
b1
|
||||
.ToJson("MiscellaneousConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DefaultDockerImageId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Templates", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("ImageName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("SkipPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("EnvName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateVariablesVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", "DefaultDockerImage")
|
||||
.WithOne()
|
||||
.HasForeignKey("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DefaultDockerImage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddedNullabilityForTemplateVariableDefaultValue : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DefaultValue",
|
||||
schema: "servers",
|
||||
table: "TemplateVariablesVariables",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1024)",
|
||||
oldMaxLength: 1024);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "DefaultValue",
|
||||
schema: "servers",
|
||||
table: "TemplateVariablesVariables",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1024)",
|
||||
oldMaxLength: 1024,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
partial class DataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "10.0.3")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Node", 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>("HttpEndpointUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("character varying(10)");
|
||||
|
||||
b.Property<DateTimeOffset>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowUserDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<int?>("DefaultDockerImageId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "FilesConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "ConfigurationFiles", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Parser")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("Path")
|
||||
.IsRequired();
|
||||
|
||||
b2.ComplexCollection(typeof(List<Dictionary<string, object>>), "Mappings", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.FilesConfig#FilesConfig.ConfigurationFiles#ConfigurationFile.Mappings#ConfigurationFileMapping", b3 =>
|
||||
{
|
||||
b3.IsRequired();
|
||||
|
||||
b3.Property<string>("Key")
|
||||
.IsRequired();
|
||||
|
||||
b3.Property<string>("Value");
|
||||
});
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("FilesConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "InstallationConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.InstallationConfig#InstallationConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<string>("DockerImage")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Script")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("Shell")
|
||||
.IsRequired();
|
||||
|
||||
b1
|
||||
.ToJson("InstallationConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "LifecycleConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.PrimitiveCollection<string>("OnlineLogPatterns")
|
||||
.IsRequired();
|
||||
|
||||
b1.Property<string>("StopCommand")
|
||||
.IsRequired();
|
||||
|
||||
b1.ComplexCollection(typeof(List<Dictionary<string, object>>), "StartupCommands", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.LifecycleConfig#LifecycleConfig.StartupCommands#StartupCommand", b2 =>
|
||||
{
|
||||
b2.IsRequired();
|
||||
|
||||
b2.Property<string>("Command")
|
||||
.IsRequired();
|
||||
|
||||
b2.Property<string>("DisplayName")
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
b1
|
||||
.ToJson("LifecycleConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.ComplexProperty(typeof(Dictionary<string, object>), "MiscellaneousConfig", "MoonlightServers.Api.Infrastructure.Database.Entities.Template.MiscellaneousConfig#MiscellaneousConfig", b1 =>
|
||||
{
|
||||
b1.IsRequired();
|
||||
|
||||
b1.Property<bool>("UseLegacyStartup");
|
||||
|
||||
b1
|
||||
.ToJson("MiscellaneousConfig")
|
||||
.HasColumnType("jsonb");
|
||||
});
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DefaultDockerImageId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Templates", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("ImageName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("SkipPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("character varying(30)");
|
||||
|
||||
b.Property<string>("EnvName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(60)
|
||||
.HasColumnType("character varying(60)");
|
||||
|
||||
b.Property<int>("TemplateId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TemplateId");
|
||||
|
||||
b.ToTable("TemplateVariablesVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", "DefaultDockerImage")
|
||||
.WithOne()
|
||||
.HasForeignKey("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "DefaultDockerImageId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DefaultDockerImage");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.TemplateVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.Api.Infrastructure.Database.Entities.Template", "Template")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("TemplateId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Template");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.Api.Infrastructure.Database.Entities.Template", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
|
||||
public class NodeTokenSchemeHandler : AuthenticationHandler<NodeTokenSchemeOptions>
|
||||
{
|
||||
public const string SchemeName = "MoonlightServers.NodeToken";
|
||||
public const string CacheKeyFormat = $"MoonlightServers.{nameof(NodeTokenSchemeHandler)}.{{0}}";
|
||||
|
||||
private readonly DatabaseRepository<Node> DatabaseRepository;
|
||||
private readonly HybridCache Cache;
|
||||
|
||||
public NodeTokenSchemeHandler(
|
||||
IOptionsMonitor<NodeTokenSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
DatabaseRepository<Node> databaseRepository,
|
||||
HybridCache cache
|
||||
) : base(options, logger, encoder)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// Basic format validation
|
||||
|
||||
if (!Context.Request.Headers.TryGetValue(HeaderNames.Authorization, out var authHeaderValues))
|
||||
return AuthenticateResult.Fail("No authorization header present");
|
||||
|
||||
if (authHeaderValues.Count != 1)
|
||||
return AuthenticateResult.Fail("No authorization value present");
|
||||
|
||||
var authHeaderValue = authHeaderValues[0];
|
||||
|
||||
if (string.IsNullOrEmpty(authHeaderValue))
|
||||
return AuthenticateResult.Fail("No authorization value present");
|
||||
|
||||
var authHeaderParts = authHeaderValue.Split(
|
||||
' ',
|
||||
StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries
|
||||
);
|
||||
|
||||
// Validate parts
|
||||
if (authHeaderParts.Length < 2)
|
||||
return AuthenticateResult.Fail("Malformed authorization header");
|
||||
|
||||
var tokenId = authHeaderParts[0];
|
||||
var token = authHeaderParts[1];
|
||||
|
||||
if (tokenId.Length != 10 && token.Length != 64)
|
||||
return AuthenticateResult.Fail("Malformed authorization header");
|
||||
|
||||
// Real validation
|
||||
|
||||
var cacheKey = string.Format(CacheKeyFormat, tokenId);
|
||||
|
||||
var session = await Cache.GetOrCreateAsync<NodeTokenSession?>(cacheKey, async cancellationToken =>
|
||||
{
|
||||
return await DatabaseRepository
|
||||
.Query()
|
||||
.Where(x => x.TokenId == tokenId)
|
||||
.Select(x => new NodeTokenSession(x.Id, x.Token))
|
||||
.FirstOrDefaultAsync(cancellationToken: cancellationToken);
|
||||
},
|
||||
new HybridCacheEntryOptions()
|
||||
{
|
||||
LocalCacheExpiration = Options.LookupCacheL1Expiry,
|
||||
Expiration = Options.LookupCacheL2Expiry
|
||||
}
|
||||
);
|
||||
|
||||
if(session == null || token != session.Token)
|
||||
return AuthenticateResult.Fail("Invalid authorization header");
|
||||
|
||||
// All checks have passed, create auth ticket
|
||||
return AuthenticateResult.Success(new AuthenticationTicket(
|
||||
new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
[
|
||||
new Claim("NodeId", session.Id.ToString())
|
||||
],
|
||||
SchemeName
|
||||
)
|
||||
),
|
||||
SchemeName
|
||||
));
|
||||
}
|
||||
|
||||
private record NodeTokenSession(int Id, string Token);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
|
||||
public class NodeTokenSchemeOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
public TimeSpan LookupCacheL1Expiry { get; set; } = TimeSpan.FromSeconds(30);
|
||||
public TimeSpan LookupCacheL2Expiry { get; set; } = TimeSpan.FromMinutes(3);
|
||||
}
|
||||
40
MoonlightServers.Api/MoonlightServers.Api.csproj
Normal file
40
MoonlightServers.Api/MoonlightServers.Api.csproj
Normal file
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget Package Settings">
|
||||
<Version>2.1.0</Version>
|
||||
<Title>MoonlightServers.Api</Title>
|
||||
<Authors>Moonlight Panel</Authors>
|
||||
<Description>Development package of MoonlightServers.Api</Description>
|
||||
<Copyright>Moonlight Panel</Copyright>
|
||||
<PackageProjectUrl>https://git.battlestati.one/Moonlight-Panel/Servers</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://git.battlestati.one/Moonlight-Panel/Servers/src/branch/v2.1/LICENSE</PackageLicenseUrl>
|
||||
<RepositoryUrl>https://git.battlestati.one/Moonlight-Panel/Servers</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moonlight.Api" Version="2.1.0">
|
||||
<ExcludeAssets>content;contentfiles</ExcludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Client\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MoonlightServers.DaemonShared\MoonlightServers.DaemonShared.csproj" />
|
||||
<ProjectReference Include="..\MoonlightServers.Shared\MoonlightServers.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
14
MoonlightServers.Api/Remote/Nodes/PingController.cs
Normal file
14
MoonlightServers.Api/Remote/Nodes/PingController.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
|
||||
namespace MoonlightServers.Api.Remote.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/remote/servers/nodes/ping")]
|
||||
[Authorize(AuthenticationSchemes = NodeTokenSchemeHandler.SchemeName)]
|
||||
public class PingController : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
public ActionResult Get() => NoContent();
|
||||
}
|
||||
55
MoonlightServers.Api/Startup.cs
Normal file
55
MoonlightServers.Api/Startup.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moonlight.Api;
|
||||
using MoonlightServers.Api.Admin.Nodes;
|
||||
using MoonlightServers.Api.Admin.Templates;
|
||||
using MoonlightServers.Api.Infrastructure.Configuration;
|
||||
using MoonlightServers.Api.Infrastructure.Database;
|
||||
using MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
|
||||
using MoonlightServers.Shared;
|
||||
using SimplePlugin.Abstractions;
|
||||
|
||||
namespace MoonlightServers.Api;
|
||||
|
||||
[PluginModule]
|
||||
public class Startup : MoonlightPlugin
|
||||
{
|
||||
public override void PreBuild(WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddControllers()
|
||||
.AddApplicationPart(typeof(Startup).Assembly)
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default);
|
||||
});
|
||||
|
||||
builder.Services.AddScoped(typeof(DatabaseRepository<>));
|
||||
|
||||
builder.Services.AddDbContext<DataContext>();
|
||||
builder.Services.AddHostedService<DbMigrationService>();
|
||||
|
||||
builder.Services.AddScoped<TemplateTransferService>();
|
||||
builder.Services.AddScoped<PterodactylEggImportService>();
|
||||
builder.Services.AddScoped<PelicanEggImportService>();
|
||||
|
||||
builder.Services.AddSingleton<NodeService>();
|
||||
|
||||
var nodeTokenOptions = new NodeTokenOptions();
|
||||
builder.Configuration.Bind("Moonlight:Servers:NodeToken", nodeTokenOptions);
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication()
|
||||
.AddScheme<NodeTokenSchemeOptions, NodeTokenSchemeHandler>(NodeTokenSchemeHandler.SchemeName, options =>
|
||||
{
|
||||
options.LookupCacheL1Expiry = nodeTokenOptions.LookupCacheL1Expiry;
|
||||
options.LookupCacheL2Expiry = nodeTokenOptions.LookupCacheL2Expiry;
|
||||
});
|
||||
|
||||
builder.Logging.AddFilter(
|
||||
"MoonlightServers.Api.Infrastructure.Implementations.NodeToken.NodeTokenSchemeHandler",
|
||||
LogLevel.Warning
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using MoonCore.PluginFramework;
|
||||
using Moonlight.ApiServer.Plugins;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Runtime;
|
||||
|
||||
[PluginLoader]
|
||||
public partial class DevPluginLoader : IPluginStartup
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoDefaultLaunchSettingsFile>True</NoDefaultLaunchSettingsFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MoonlightServers.ApiServer\MoonlightServers.ApiServer.csproj"/>
|
||||
<ProjectReference Include="..\MoonlightServers.Frontend.Runtime\MoonlightServers.Frontend.Runtime.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.9" />
|
||||
<PackageReference Include="MoonCore.PluginFramework" Version="1.0.9"/>
|
||||
<PackageReference Include="MoonCore.PluginFramework.Generator" Version="1.0.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Properties\launchSettings.json">
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,34 +0,0 @@
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using Moonlight.ApiServer.Startup;
|
||||
using MoonlightServers.ApiServer.Runtime;
|
||||
|
||||
var pluginLoader = new DevPluginLoader();
|
||||
pluginLoader.Initialize();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddMoonlight(pluginLoader.Instances);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMoonlight(pluginLoader.Instances);
|
||||
|
||||
// Add frontend
|
||||
var configuration = AppConfiguration.CreateEmpty();
|
||||
builder.Configuration.Bind(configuration);
|
||||
|
||||
// Handle setup of wasm app hosting in the runtime
|
||||
// so the Moonlight.ApiServer doesn't need the wasm package
|
||||
if (configuration.Frontend.EnableHosting)
|
||||
{
|
||||
if (app.Environment.IsDevelopment())
|
||||
app.UseWebAssemblyDebugging();
|
||||
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
}
|
||||
|
||||
app.MapMoonlight(pluginLoader.Instances);
|
||||
|
||||
|
||||
await app.RunAsync();
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Dev Server": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5269",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"MOONLIGHT_PUBLICURL": "http://localhost:5269",
|
||||
"HTTP_PROXY": "",
|
||||
"HTTPS_PROXY": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class Allocation
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Relations
|
||||
public Node Node { get; set; }
|
||||
public Server? Server { get; set; }
|
||||
|
||||
public string IpAddress { get; set; }
|
||||
public int Port { get; set; }
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class Node
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Relations
|
||||
public List<Server> Servers { get; set; } = new();
|
||||
public List<Allocation> Allocations { get; set; } = new();
|
||||
|
||||
// Meta
|
||||
public string Name { get; set; }
|
||||
|
||||
// Connection details
|
||||
public string Fqdn { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string TokenId { get; set; }
|
||||
public int HttpPort { get; set; }
|
||||
public int FtpPort { get; set; }
|
||||
public bool UseSsl { get; set; }
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class Server
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Relations
|
||||
public Star Star { get; set; }
|
||||
public Node Node { get; set; }
|
||||
public List<Allocation> Allocations { get; set; } = new();
|
||||
public List<ServerVariable> Variables { get; set; } = new();
|
||||
public List<ServerBackup> Backups { get; set; } = new();
|
||||
public List<ServerShare> Shares { get; set; } = new();
|
||||
|
||||
// Meta
|
||||
public string Name { get; set; }
|
||||
public int OwnerId { get; set; }
|
||||
|
||||
// Star specific stuff
|
||||
public string? StartupOverride { get; set; }
|
||||
public int DockerImageIndex { get; set; }
|
||||
|
||||
// Resources and limits
|
||||
public int Cpu { get; set; }
|
||||
public int Memory { get; set; }
|
||||
public int Disk { get; set; }
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class ServerBackup
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime CompletedAt { get; set; }
|
||||
|
||||
public long Size { get; set; }
|
||||
public bool Successful { get; set; }
|
||||
public bool Completed { get; set; }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class ServerShare
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int UserId { get; set; }
|
||||
public Server Server { get; set; }
|
||||
|
||||
public ServerShareContent Content { get; set; } = new();
|
||||
|
||||
[Column(TypeName="timestamp with time zone")]
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
[Column(TypeName="timestamp with time zone")]
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class ServerVariable
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// Relations
|
||||
public Server Server { get; set; }
|
||||
|
||||
public string Key { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class Star
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
// References
|
||||
public List<StarVariable> Variables { get; set; } = new();
|
||||
public List<StarDockerImage> DockerImages { get; set; } = new();
|
||||
|
||||
// Meta
|
||||
public string Name { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string? UpdateUrl { get; set; }
|
||||
public string? DonateUrl { get; set; }
|
||||
|
||||
// Start and stop
|
||||
public string StartupCommand { get; set; }
|
||||
public string StopCommand { get; set; }
|
||||
public string OnlineDetection { get; set; }
|
||||
|
||||
// Install
|
||||
public string InstallShell { get; set; }
|
||||
public string InstallDockerImage { get; set; }
|
||||
public string InstallScript { get; set; }
|
||||
|
||||
// Misc
|
||||
public int RequiredAllocations { get; set; }
|
||||
public bool AllowDockerImageChange { get; set; }
|
||||
public int DefaultDockerImage { get; set; }
|
||||
public string ParseConfiguration { get; set; }
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class StarDockerImage
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Star Star { get; set; }
|
||||
|
||||
public string DisplayName { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public bool AutoPulling { get; set; }
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
public class StarVariable
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Star Star { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public string Key { get; set; }
|
||||
public string DefaultValue { get; set; }
|
||||
|
||||
public bool AllowViewing { get; set; }
|
||||
public bool AllowEditing { get; set; }
|
||||
|
||||
public StarVariableType Type { get; set; }
|
||||
public string? Filter { get; set; }
|
||||
}
|
||||
@@ -1,529 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.ApiServer.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(ServersDataContext))]
|
||||
[Migration("20250922091731_RecreatedModelsInNewSchema")]
|
||||
partial class RecreatedModelsInNewSchema
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("Allocations", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Fqdn")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("FtpPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("HttpPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("UseSsl")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("Cpu")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Disk")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("DockerImageIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Memory")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartupOverride")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("Servers", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("Completed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime>("CompletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("Successful")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerBackups", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", 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<int>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerShares", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("DefaultDockerImage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallDockerImage")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallScript")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallShell")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OnlineDetection")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ParseConfiguration")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("RequiredAllocations")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartupCommand")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StopCommand")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stars", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AutoPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("StarDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowEditing")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowViewing")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Filter")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("StarVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.Navigation("Node");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
||||
.WithMany("Servers")
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany()
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Node");
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
|
||||
.WithMany("Backups")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Shares")
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.HasKey("ServerShareId");
|
||||
|
||||
b1.ToTable("ServerShares", "servers");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerShareId");
|
||||
|
||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||
{
|
||||
b2.Property<int>("ServerShareContentServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b2.Property<int>("Level")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||
|
||||
b2.ToTable("ServerShares", "servers");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("ServerShareContentServerShareId");
|
||||
});
|
||||
|
||||
b1.Navigation("Permissions");
|
||||
});
|
||||
|
||||
b.Navigation("Content")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Backups");
|
||||
|
||||
b.Navigation("Shares");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RecreatedModelsInNewSchema : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "servers");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Nodes",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Fqdn = table.Column<string>(type: "text", nullable: false),
|
||||
Token = table.Column<string>(type: "text", nullable: false),
|
||||
TokenId = table.Column<string>(type: "text", nullable: false),
|
||||
HttpPort = table.Column<int>(type: "integer", nullable: false),
|
||||
FtpPort = table.Column<int>(type: "integer", nullable: false),
|
||||
UseSsl = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Nodes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Stars",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Version = table.Column<string>(type: "text", nullable: false),
|
||||
Author = table.Column<string>(type: "text", nullable: false),
|
||||
UpdateUrl = table.Column<string>(type: "text", nullable: true),
|
||||
DonateUrl = table.Column<string>(type: "text", nullable: true),
|
||||
StartupCommand = table.Column<string>(type: "text", nullable: false),
|
||||
StopCommand = table.Column<string>(type: "text", nullable: false),
|
||||
OnlineDetection = table.Column<string>(type: "text", nullable: false),
|
||||
InstallShell = table.Column<string>(type: "text", nullable: false),
|
||||
InstallDockerImage = table.Column<string>(type: "text", nullable: false),
|
||||
InstallScript = table.Column<string>(type: "text", nullable: false),
|
||||
RequiredAllocations = table.Column<int>(type: "integer", nullable: false),
|
||||
AllowDockerImageChange = table.Column<bool>(type: "boolean", nullable: false),
|
||||
DefaultDockerImage = table.Column<int>(type: "integer", nullable: false),
|
||||
ParseConfiguration = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Stars", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Servers",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
StarId = table.Column<int>(type: "integer", nullable: false),
|
||||
NodeId = table.Column<int>(type: "integer", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
OwnerId = table.Column<int>(type: "integer", nullable: false),
|
||||
StartupOverride = table.Column<string>(type: "text", nullable: true),
|
||||
DockerImageIndex = table.Column<int>(type: "integer", nullable: false),
|
||||
Cpu = table.Column<int>(type: "integer", nullable: false),
|
||||
Memory = table.Column<int>(type: "integer", nullable: false),
|
||||
Disk = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Servers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Servers_Nodes_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Nodes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Servers_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StarDockerImages",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
StarId = table.Column<int>(type: "integer", nullable: false),
|
||||
DisplayName = table.Column<string>(type: "text", nullable: false),
|
||||
Identifier = table.Column<string>(type: "text", nullable: false),
|
||||
AutoPulling = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StarDockerImages", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StarDockerImages_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "StarVariables",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
StarId = table.Column<int>(type: "integer", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Description = table.Column<string>(type: "text", nullable: false),
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
DefaultValue = table.Column<string>(type: "text", nullable: false),
|
||||
AllowViewing = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AllowEditing = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Type = table.Column<int>(type: "integer", nullable: false),
|
||||
Filter = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_StarVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_StarVariables_Stars_StarId",
|
||||
column: x => x.StarId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Stars",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Allocations",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
NodeId = table.Column<int>(type: "integer", nullable: false),
|
||||
ServerId = table.Column<int>(type: "integer", nullable: true),
|
||||
IpAddress = table.Column<string>(type: "text", nullable: false),
|
||||
Port = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Allocations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Allocations_Nodes_NodeId",
|
||||
column: x => x.NodeId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Nodes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Allocations_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerBackups",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
CompletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
Size = table.Column<long>(type: "bigint", nullable: false),
|
||||
Successful = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Completed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ServerId = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ServerBackups", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerBackups_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerShares",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<int>(type: "integer", nullable: false),
|
||||
ServerId = table.Column<int>(type: "integer", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
Content = table.Column<string>(type: "jsonb", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ServerShares", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerShares_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ServerVariables",
|
||||
schema: "servers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ServerId = table.Column<int>(type: "integer", nullable: false),
|
||||
Key = table.Column<string>(type: "text", nullable: false),
|
||||
Value = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ServerVariables", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ServerVariables_Servers_ServerId",
|
||||
column: x => x.ServerId,
|
||||
principalSchema: "servers",
|
||||
principalTable: "Servers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Allocations_NodeId",
|
||||
schema: "servers",
|
||||
table: "Allocations",
|
||||
column: "NodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Allocations_ServerId",
|
||||
schema: "servers",
|
||||
table: "Allocations",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerBackups_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerBackups",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Servers_NodeId",
|
||||
schema: "servers",
|
||||
table: "Servers",
|
||||
column: "NodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Servers_StarId",
|
||||
schema: "servers",
|
||||
table: "Servers",
|
||||
column: "StarId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerShares_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerShares",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ServerVariables_ServerId",
|
||||
schema: "servers",
|
||||
table: "ServerVariables",
|
||||
column: "ServerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StarDockerImages_StarId",
|
||||
schema: "servers",
|
||||
table: "StarDockerImages",
|
||||
column: "StarId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StarVariables_StarId",
|
||||
schema: "servers",
|
||||
table: "StarVariables",
|
||||
column: "StarId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Allocations",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerBackups",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerShares",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ServerVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StarDockerImages",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "StarVariables",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Servers",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Nodes",
|
||||
schema: "servers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Stars",
|
||||
schema: "servers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,526 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using MoonlightServers.ApiServer.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(ServersDataContext))]
|
||||
partial class ServersDataContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("servers")
|
||||
.HasAnnotation("ProductVersion", "9.0.9")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("Allocations", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Fqdn")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("FtpPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("HttpPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("UseSsl")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Nodes", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("Cpu")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Disk")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("DockerImageIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Memory")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("NodeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("OwnerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartupOverride")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NodeId");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("Servers", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("Completed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime>("CompletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("Successful")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerBackups", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", 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<int>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerShares", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("ServerId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ServerId");
|
||||
|
||||
b.ToTable("ServerVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowDockerImageChange")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("DefaultDockerImage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("DonateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallDockerImage")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallScript")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("InstallShell")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OnlineDetection")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ParseConfiguration")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("RequiredAllocations")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StartupCommand")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StopCommand")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UpdateUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Stars", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AutoPulling")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("StarDockerImages", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<bool>("AllowEditing")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowViewing")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("DefaultValue")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Filter")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("StarId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StarId");
|
||||
|
||||
b.ToTable("StarVariables", "servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Allocation", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Allocations")
|
||||
.HasForeignKey("ServerId");
|
||||
|
||||
b.Navigation("Node");
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Node", "Node")
|
||||
.WithMany("Servers")
|
||||
.HasForeignKey("NodeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany()
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Node");
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerBackup", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", null)
|
||||
.WithMany("Backups")
|
||||
.HasForeignKey("ServerId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerShare", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Shares")
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("MoonlightServers.ApiServer.Models.ServerShareContent", "Content", b1 =>
|
||||
{
|
||||
b1.Property<int>("ServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.HasKey("ServerShareId");
|
||||
|
||||
b1.ToTable("ServerShares", "servers");
|
||||
|
||||
b1.ToJson("Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ServerShareId");
|
||||
|
||||
b1.OwnsMany("MoonlightServers.ApiServer.Models.ServerShareContent+SharePermission", "Permissions", b2 =>
|
||||
{
|
||||
b2.Property<int>("ServerShareContentServerShareId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<int>("__synthesizedOrdinal")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.Property<string>("Identifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b2.Property<int>("Level")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b2.HasKey("ServerShareContentServerShareId", "__synthesizedOrdinal");
|
||||
|
||||
b2.ToTable("ServerShares", "servers");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("ServerShareContentServerShareId");
|
||||
});
|
||||
|
||||
b1.Navigation("Permissions");
|
||||
});
|
||||
|
||||
b.Navigation("Content")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.ServerVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Server", "Server")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("ServerId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Server");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarDockerImage", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany("DockerImages")
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.StarVariable", b =>
|
||||
{
|
||||
b.HasOne("MoonlightServers.ApiServer.Database.Entities.Star", "Star")
|
||||
.WithMany("Variables")
|
||||
.HasForeignKey("StarId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Star");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Node", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Servers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Server", b =>
|
||||
{
|
||||
b.Navigation("Allocations");
|
||||
|
||||
b.Navigation("Backups");
|
||||
|
||||
b.Navigation("Shares");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("MoonlightServers.ApiServer.Database.Entities.Star", b =>
|
||||
{
|
||||
b.Navigation("DockerImages");
|
||||
|
||||
b.Navigation("Variables");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.ApiServer.Configuration;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Database;
|
||||
|
||||
public class ServersDataContext : DbContext
|
||||
{
|
||||
public DbSet<Allocation> Allocations { get; set; }
|
||||
public DbSet<Node> Nodes { get; set; }
|
||||
public DbSet<Server> Servers { get; set; }
|
||||
public DbSet<ServerBackup> ServerBackups { get; set; }
|
||||
public DbSet<ServerShare> ServerShares { get; set; }
|
||||
public DbSet<ServerVariable> ServerVariables { get; set; }
|
||||
public DbSet<Star> Stars { get; set; }
|
||||
public DbSet<StarDockerImage> StarDockerImages { get; set; }
|
||||
public DbSet<StarVariable> StarVariables { get; set; }
|
||||
|
||||
private readonly AppConfiguration Configuration;
|
||||
private readonly string Schema = "servers";
|
||||
|
||||
public ServersDataContext(AppConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if(optionsBuilder.IsConfigured)
|
||||
return;
|
||||
|
||||
var database = Configuration.Database;
|
||||
|
||||
var connectionString = $"Host={database.Host};" +
|
||||
$"Port={database.Port};" +
|
||||
$"Database={database.Database};" +
|
||||
$"Username={database.Username};" +
|
||||
$"Password={database.Password}";
|
||||
|
||||
optionsBuilder.UseNpgsql(connectionString, builder =>
|
||||
{
|
||||
builder.MigrationsHistoryTable("MigrationsHistory", Schema);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Model.SetDefaultSchema(Schema);
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
#region Shares
|
||||
|
||||
modelBuilder.Ignore<ServerShareContent>();
|
||||
modelBuilder.Ignore<ServerShareContent.SharePermission>();
|
||||
|
||||
modelBuilder.Entity<ServerShare>(builder =>
|
||||
{
|
||||
builder.OwnsOne(x => x.Content, navigationBuilder =>
|
||||
{
|
||||
navigationBuilder.ToJson();
|
||||
|
||||
navigationBuilder.OwnsMany(x => x.Permissions);
|
||||
});
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using ServerState = MoonlightServers.Shared.Enums.ServerState;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Extensions;
|
||||
|
||||
public static class ServerStateExtensions
|
||||
{
|
||||
public static ServerState ToServerPowerState(this DaemonShared.Enums.ServerState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
DaemonShared.Enums.ServerState.Installing => ServerState.Installing,
|
||||
DaemonShared.Enums.ServerState.Stopping => ServerState.Stopping,
|
||||
DaemonShared.Enums.ServerState.Online => ServerState.Online,
|
||||
DaemonShared.Enums.ServerState.Starting => ServerState.Starting,
|
||||
DaemonShared.Enums.ServerState.Offline => ServerState.Offline,
|
||||
_ => ServerState.Offline
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
public class NodeAuthOptions : AuthenticationSchemeOptions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Helpers;
|
||||
|
||||
public class NodeAuthScheme : AuthenticationHandler<NodeAuthOptions>
|
||||
{
|
||||
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder,
|
||||
ISystemClock clock) : base(options, logger, encoder, clock)
|
||||
{
|
||||
}
|
||||
|
||||
public NodeAuthScheme(IOptionsMonitor<NodeAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(
|
||||
options, logger, encoder)
|
||||
{
|
||||
}
|
||||
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Request.Headers.ContainsKey("Authorization"))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var authHeaderValue = Request.Headers["Authorization"].FirstOrDefault();
|
||||
|
||||
if (string.IsNullOrEmpty(authHeaderValue))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
if (!authHeaderValue.Contains("Bearer "))
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var tokenParts = authHeaderValue
|
||||
.Replace("Bearer ", "")
|
||||
.Trim()
|
||||
.Split('.');
|
||||
|
||||
if (tokenParts.Length != 2)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var tokenId = tokenParts[0];
|
||||
var token = tokenParts[1];
|
||||
|
||||
if (tokenId.Length != 6)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
var nodeRepo = Context.RequestServices.GetRequiredService<DatabaseRepository<Node>>();
|
||||
|
||||
var node = await nodeRepo
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.TokenId == tokenId);
|
||||
|
||||
if (node == null)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
if (node.Token != token)
|
||||
return AuthenticateResult.NoResult();
|
||||
|
||||
return AuthenticateResult.Success(
|
||||
new AuthenticationTicket(
|
||||
new ClaimsPrincipal(
|
||||
new ClaimsIdentity(
|
||||
[
|
||||
new Claim("nodeId", node.Id.ToString())
|
||||
],
|
||||
"nodeAuthentication"
|
||||
)
|
||||
),
|
||||
"nodeAuthentication"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/nodes/{nodeId:int}/allocations")]
|
||||
public class NodeAllocationsController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
private readonly DatabaseRepository<Allocation> AllocationRepository;
|
||||
|
||||
public NodeAllocationsController(
|
||||
DatabaseRepository<Node> nodeRepository,
|
||||
DatabaseRepository<Allocation> allocationRepository
|
||||
)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
AllocationRepository = allocationRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await AllocationRepository
|
||||
.Get()
|
||||
.CountAsync(x => x.Node.Id == nodeId);
|
||||
|
||||
var allocations = await AllocationRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeAllocationResponse>()
|
||||
{
|
||||
Items = allocations,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> GetSingleAsync([FromRoute] int nodeId, [FromRoute] int id)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (allocation == null)
|
||||
return Problem("No allocation with that id found", statusCode: 400);
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> CreateAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromBody] CreateNodeAllocationRequest request
|
||||
)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with that id found", statusCode: 404);
|
||||
|
||||
var allocation = AllocationMapper.ToAllocation(request);
|
||||
|
||||
var finalAllocation = await AllocationRepository.AddAsync(allocation);
|
||||
|
||||
return AllocationMapper.ToNodeAllocation(finalAllocation);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
public async Task<ActionResult<NodeAllocationResponse>> UpdateAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateNodeAllocationRequest request
|
||||
)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (allocation == null)
|
||||
return Problem("No allocation with that id found", statusCode: 404);
|
||||
|
||||
AllocationMapper.Merge(request, allocation);
|
||||
await AllocationRepository.UpdateAsync(allocation);
|
||||
|
||||
return AllocationMapper.ToNodeAllocation(allocation);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int nodeId, [FromRoute] int id)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (allocation == null)
|
||||
return Problem("No allocation with that id found", statusCode: 404);
|
||||
|
||||
await AllocationRepository.RemoveAsync(allocation);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("range")]
|
||||
public async Task<ActionResult> CreateRangeAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromBody] CreateNodeAllocationRangeRequest request
|
||||
)
|
||||
{
|
||||
if (request.Start > request.End)
|
||||
return Problem("Invalid start and end specified", statusCode: 400);
|
||||
|
||||
if (request.End - request.Start == 0)
|
||||
return Problem("Empty range specified", statusCode: 400);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with that id found", statusCode: 404);
|
||||
|
||||
var existingAllocations = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Port >= request.Start && x.Port <= request.End &&
|
||||
x.IpAddress == request.IpAddress)
|
||||
.AsNoTracking()
|
||||
.ToArrayAsync();
|
||||
|
||||
var ports = new List<int>();
|
||||
|
||||
for (var i = request.Start; i < request.End; i++)
|
||||
{
|
||||
// Skip existing allocations
|
||||
if (existingAllocations.Any(x => x.Port == i))
|
||||
continue;
|
||||
|
||||
ports.Add(i);
|
||||
}
|
||||
|
||||
var allocations = ports
|
||||
.Select(port => new Allocation()
|
||||
{
|
||||
IpAddress = request.IpAddress,
|
||||
Port = port,
|
||||
Node = node
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
await AllocationRepository.RunTransactionAsync(async set => { await set.AddRangeAsync(allocations); });
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("all")]
|
||||
public async Task<ActionResult> DeleteAllAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var allocations = AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == nodeId)
|
||||
.ToArray();
|
||||
|
||||
await AllocationRepository.RunTransactionAsync(set => { set.RemoveRange(allocations); });
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("free")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeAllocationResponse>>> GetFreeAsync(
|
||||
[FromRoute] int nodeId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count,
|
||||
[FromQuery] int serverId = -1
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var node = NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == nodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
var freeAllocationsQuery = AllocationRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Where(x => x.Server == null || x.Server.Id == serverId);
|
||||
|
||||
var totalCount = await freeAllocationsQuery.CountAsync();
|
||||
|
||||
var allocations = await freeAllocationsQuery
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeAllocationResponse>()
|
||||
{
|
||||
Items = allocations,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Sys;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/nodes")]
|
||||
public class NodeStatusController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
private readonly NodeService NodeService;
|
||||
|
||||
public NodeStatusController(DatabaseRepository<Node> nodeRepository, NodeService nodeService)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
NodeService = nodeService;
|
||||
}
|
||||
|
||||
[HttpGet("{nodeId:int}/system/status")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.status")]
|
||||
public async Task<ActionResult<NodeSystemStatusResponse>> GetStatusAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
NodeSystemStatusResponse response;
|
||||
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
try
|
||||
{
|
||||
var statusResponse = await NodeService.GetSystemStatusAsync(node.Value);
|
||||
|
||||
sw.Stop();
|
||||
|
||||
response = new()
|
||||
{
|
||||
Version = statusResponse.Version,
|
||||
RoundtripError = statusResponse.TripError,
|
||||
RoundtripSuccess = statusResponse.TripSuccess,
|
||||
RoundtripTime = statusResponse.TripTime + sw.Elapsed,
|
||||
RoundtripRemoteFailure = !statusResponse.TripSuccess // When the remote trip failed, it's the remotes fault
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sw.Stop();
|
||||
|
||||
response = new()
|
||||
{
|
||||
Version = "Unknown",
|
||||
RoundtripError = e.Message,
|
||||
RoundtripSuccess = false,
|
||||
RoundtripTime = sw.Elapsed,
|
||||
RoundtripRemoteFailure = false
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||
{
|
||||
var result = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (result == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Helpers;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/nodes")]
|
||||
public class NodesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
|
||||
public NodesController(DatabaseRepository<Node> nodeRepository)
|
||||
{
|
||||
NodeRepository = nodeRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<CountedData<NodeResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await NodeRepository.Get().CountAsync();
|
||||
|
||||
var items = await NodeRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<NodeResponse>()
|
||||
{
|
||||
Items = items,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.get")]
|
||||
public async Task<ActionResult<NodeResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.create")]
|
||||
public async Task<ActionResult<NodeResponse>> CreateAsync([FromBody] CreateNodeRequest request)
|
||||
{
|
||||
var node = NodeMapper.ToNode(request);
|
||||
|
||||
node.TokenId = Formatter.GenerateString(6);
|
||||
node.Token = Formatter.GenerateString(32);
|
||||
|
||||
var finalNode = await NodeRepository.AddAsync(node);
|
||||
|
||||
return NodeMapper.ToAdminNodeResponse(finalNode);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.update")]
|
||||
public async Task<ActionResult<NodeResponse>> UpdateAsync([FromRoute] int id, [FromBody] UpdateNodeRequest request)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
NodeMapper.Merge(request, node);
|
||||
await NodeRepository.UpdateAsync(node);
|
||||
|
||||
return NodeMapper.ToAdminNodeResponse(node);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 404);
|
||||
|
||||
await NodeRepository.RemoveAsync(node);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes.Statistics;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/nodes/{nodeId:int}/statistics")]
|
||||
[Authorize(Policy = "permissions:admin.servers.nodes.statistics")]
|
||||
public class StatisticsController : Controller
|
||||
{
|
||||
private readonly NodeService NodeService;
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
|
||||
public StatisticsController(NodeService nodeService, DatabaseRepository<Node> nodeRepository)
|
||||
{
|
||||
NodeService = nodeService;
|
||||
NodeRepository = nodeRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action", MessageId = "time: 1142ms",
|
||||
Justification = "The daemon has an artificial delay of one second to calculate accurate cpu usage values")]
|
||||
public async Task<ActionResult<StatisticsResponse>> GetAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
var statistics = await NodeService.GetStatisticsAsync(node.Value);
|
||||
|
||||
return new StatisticsResponse()
|
||||
{
|
||||
Cpu = new()
|
||||
{
|
||||
Model = statistics.Cpu.Model,
|
||||
Usage = statistics.Cpu.Usage,
|
||||
UsagePerCore = statistics.Cpu.UsagePerCore
|
||||
},
|
||||
Memory = new()
|
||||
{
|
||||
Available = statistics.Memory.Available,
|
||||
Total = statistics.Memory.Total,
|
||||
Cached = statistics.Memory.Cached,
|
||||
Free = statistics.Memory.Free,
|
||||
SwapFree = statistics.Memory.SwapFree,
|
||||
SwapTotal = statistics.Memory.SwapTotal
|
||||
},
|
||||
Disks = statistics.Disks.Select(x => new StatisticsResponse.DiskData()
|
||||
{
|
||||
Device = x.Device,
|
||||
DiskFree = x.DiskFree,
|
||||
DiskTotal = x.DiskTotal,
|
||||
InodesFree = x.InodesFree,
|
||||
InodesTotal = x.InodesTotal,
|
||||
MountPath = x.MountPath
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("docker")]
|
||||
public async Task<ActionResult<DockerStatisticsResponse>> GetDockerAsync([FromRoute] int nodeId)
|
||||
{
|
||||
var node = await GetNodeAsync(nodeId);
|
||||
|
||||
if (node.Value == null)
|
||||
return node.Result ?? Problem("Unable to retrieve node");
|
||||
|
||||
var statistics = await NodeService.GetDockerStatisticsAsync(node.Value);
|
||||
|
||||
return new DockerStatisticsResponse()
|
||||
{
|
||||
BuildCacheReclaimable = statistics.BuildCacheReclaimable,
|
||||
BuildCacheUsed = statistics.BuildCacheUsed,
|
||||
ContainersReclaimable = statistics.ContainersReclaimable,
|
||||
ContainersUsed = statistics.ContainersUsed,
|
||||
ImagesReclaimable = statistics.ImagesReclaimable,
|
||||
ImagesUsed = statistics.ImagesUsed,
|
||||
Version = statistics.Version
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Node>> GetNodeAsync(int nodeId)
|
||||
{
|
||||
var result = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == nodeId);
|
||||
|
||||
if (result == null)
|
||||
return Problem("A node with this id could not be found", statusCode: 404);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.ServerVariables;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers")]
|
||||
public class ServerVariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<ServerVariable> VariableRepository;
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
|
||||
public ServerVariablesController(
|
||||
DatabaseRepository<ServerVariable> variableRepository,
|
||||
DatabaseRepository<Server> serverRepository
|
||||
)
|
||||
{
|
||||
VariableRepository = variableRepository;
|
||||
ServerRepository = serverRepository;
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/variables")]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<CountedData<ServerVariableResponse>>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var serverExists = await ServerRepository
|
||||
.Get()
|
||||
.AnyAsync(x => x.Id == serverId);
|
||||
|
||||
if (!serverExists)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var query = VariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == serverId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var variables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<ServerVariableResponse>()
|
||||
{
|
||||
Items = variables,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Servers;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Servers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers")]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
private readonly DatabaseRepository<Allocation> AllocationRepository;
|
||||
private readonly DatabaseRepository<ServerVariable> VariableRepository;
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
private readonly ILogger<ServersController> Logger;
|
||||
private readonly ServerService ServerService;
|
||||
|
||||
public ServersController(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<Node> nodeRepository,
|
||||
DatabaseRepository<Allocation> allocationRepository,
|
||||
DatabaseRepository<ServerVariable> variableRepository,
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
DatabaseRepository<User> userRepository,
|
||||
ILogger<ServersController> logger,
|
||||
ServerService serverService
|
||||
)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
AllocationRepository = allocationRepository;
|
||||
VariableRepository = variableRepository;
|
||||
ServerRepository = serverRepository;
|
||||
UserRepository = userRepository;
|
||||
ServerService = serverService;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<CountedData<ServerResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await ServerRepository.Get().CountAsync();
|
||||
|
||||
var servers = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<ServerResponse>()
|
||||
{
|
||||
Items = servers,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.read")]
|
||||
public async Task<ActionResult<ServerResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.AsNoTracking()
|
||||
.Where(x => x.Id == id)
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||
public async Task<ActionResult<ServerResponse>> CreateAsync([FromBody] CreateServerRequest request)
|
||||
{
|
||||
// Check if owner user exist
|
||||
if (UserRepository.Get().All(x => x.Id != request.OwnerId))
|
||||
return Problem("No user with this id found", statusCode: 400);
|
||||
|
||||
// Check if the star exists
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.DockerImages)
|
||||
.FirstOrDefaultAsync(x => x.Id == request.StarId);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with this id found", statusCode: 400);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == request.NodeId);
|
||||
|
||||
if (node == null)
|
||||
return Problem("No node with this id found", statusCode: 400);
|
||||
|
||||
var allocations = new List<Allocation>();
|
||||
|
||||
// Fetch specified allocations from the request
|
||||
foreach (var allocationId in request.AllocationIds)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Server == null)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.FirstOrDefaultAsync(x => x.Id == allocationId);
|
||||
|
||||
if (allocation == null)
|
||||
continue;
|
||||
|
||||
allocations.Add(allocation);
|
||||
}
|
||||
|
||||
// Check if the specified allocations are enough for the star
|
||||
if (allocations.Count < star.RequiredAllocations)
|
||||
{
|
||||
var amountRequiredToSatisfy = star.RequiredAllocations - allocations.Count;
|
||||
|
||||
var freeAllocations = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Server == null)
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Take(amountRequiredToSatisfy)
|
||||
.ToArrayAsync();
|
||||
|
||||
allocations.AddRange(freeAllocations);
|
||||
|
||||
if (allocations.Count < star.RequiredAllocations)
|
||||
{
|
||||
return Problem(
|
||||
$"Unable to find enough free allocations. Found: {allocations.Count}, Required: {star.RequiredAllocations}",
|
||||
statusCode: 400
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var server = ServerMapper.ToServer(request);
|
||||
|
||||
// Set allocations
|
||||
server.Allocations = allocations;
|
||||
|
||||
// Variables
|
||||
foreach (var variable in star.Variables)
|
||||
{
|
||||
var requestVar = request.Variables.FirstOrDefault(x => x.Key == variable.Key);
|
||||
|
||||
var serverVar = new ServerVariable()
|
||||
{
|
||||
Key = variable.Key,
|
||||
Value = requestVar != null
|
||||
? requestVar.Value
|
||||
: variable.DefaultValue
|
||||
};
|
||||
|
||||
server.Variables.Add(serverVar);
|
||||
}
|
||||
|
||||
// Set relations
|
||||
server.Node = node;
|
||||
server.Star = star;
|
||||
|
||||
var finalServer = await ServerRepository.AddAsync(server);
|
||||
|
||||
try
|
||||
{
|
||||
await ServerService.SyncAsync(finalServer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError("Unable to sync server to node the server is assigned to: {e}", e);
|
||||
|
||||
// We are deleting the server from the database after the creation has failed
|
||||
// to ensure we won't have a bugged server in the database which doesn't exist on the node
|
||||
await ServerRepository.RemoveAsync(finalServer);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return ServerMapper.ToAdminServerResponse(finalServer);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.write")]
|
||||
public async Task<ActionResult<ServerResponse>> UpdateAsync(
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateServerRequest request
|
||||
)
|
||||
{
|
||||
//TODO: Handle shrinking virtual disk
|
||||
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Star)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
ServerMapper.Merge(request, server);
|
||||
|
||||
var allocations = new List<Allocation>();
|
||||
|
||||
// Fetch specified allocations from the request
|
||||
foreach (var allocationId in request.AllocationIds)
|
||||
{
|
||||
var allocation = await AllocationRepository
|
||||
.Get()
|
||||
.Where(x => x.Server == null || x.Server.Id == server.Id)
|
||||
.Where(x => x.Node.Id == server.Node.Id)
|
||||
.FirstOrDefaultAsync(x => x.Id == allocationId);
|
||||
|
||||
// ^ This loads the allocations specified in the request.
|
||||
// Valid allocations are either free ones or ones which are already allocated to this server
|
||||
|
||||
if (allocation == null)
|
||||
continue;
|
||||
|
||||
allocations.Add(allocation);
|
||||
}
|
||||
|
||||
// Check if the specified allocations are enough for the star
|
||||
if (allocations.Count < server.Star.RequiredAllocations)
|
||||
{
|
||||
return Problem(
|
||||
$"You need to specify at least {server.Star.RequiredAllocations} allocation(s)",
|
||||
statusCode: 400
|
||||
);
|
||||
}
|
||||
|
||||
// Set allocations
|
||||
server.Allocations = allocations;
|
||||
|
||||
// Process variables
|
||||
foreach (var variable in request.Variables)
|
||||
{
|
||||
// Search server variable associated to the variable in the request
|
||||
var serverVar = server.Variables
|
||||
.FirstOrDefault(x => x.Key == variable.Key);
|
||||
|
||||
if (serverVar == null)
|
||||
continue;
|
||||
|
||||
// Update value
|
||||
serverVar.Value = variable.Value;
|
||||
}
|
||||
|
||||
await ServerRepository.UpdateAsync(server);
|
||||
|
||||
// Notify the node about the changes
|
||||
await ServerService.SyncAsync(server);
|
||||
|
||||
return ServerMapper.ToAdminServerResponse(server);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id, [FromQuery] bool force = false)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.Include(x => x.Star)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Backups)
|
||||
.Include(x => x.Allocations)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with that id found", statusCode: 404);
|
||||
|
||||
server.Variables.Clear();
|
||||
server.Backups.Clear();
|
||||
server.Allocations.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
// If the sync fails on the node and we aren't forcing the deletion,
|
||||
// we don't want to delete it from the database yet
|
||||
await ServerService.SyncDeleteAsync(server);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (force)
|
||||
{
|
||||
Logger.LogWarning(
|
||||
"An error occured while syncing deletion of a server to the node. Continuing anyways. Error: {e}",
|
||||
e
|
||||
);
|
||||
}
|
||||
else
|
||||
throw;
|
||||
}
|
||||
|
||||
await ServerRepository.RemoveAsync(server);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/stars/{starId:int}/dockerImages")]
|
||||
public class StarDockerImagesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
private readonly DatabaseRepository<StarDockerImage> DockerImageRepository;
|
||||
|
||||
public StarDockerImagesController(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<StarDockerImage> dockerImageRepository
|
||||
)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
DockerImageRepository = dockerImageRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<ActionResult<CountedData<StarDockerImageResponse>>> GetAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var query = DockerImageRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == starId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var dockerImages = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarDockerImageResponse>()
|
||||
{
|
||||
Items = dockerImages,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> GetSingleAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.Where(x => x.Id == id && x.Star.Id == starId)
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
return dockerImage;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> CreateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromBody] CreateStarDockerImageRequest request
|
||||
)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == starId);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = DockerImageMapper.ToDockerImage(request);
|
||||
dockerImage.Star = star;
|
||||
|
||||
var finalDockerImage = await DockerImageRepository.AddAsync(dockerImage);
|
||||
|
||||
return DockerImageMapper.ToAdminResponse(finalDockerImage);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult<StarDockerImageResponse>> UpdateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarDockerImageRequest request
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
DockerImageMapper.Merge(request, dockerImage);
|
||||
await DockerImageRepository.UpdateAsync(dockerImage);
|
||||
|
||||
return DockerImageMapper.ToAdminResponse(dockerImage);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.write")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var dockerImage = await DockerImageRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (dockerImage == null)
|
||||
return Problem("No star docker image with this id found", statusCode: 404);
|
||||
|
||||
await DockerImageRepository.RemoveAsync(dockerImage);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/stars")]
|
||||
public class StarImportExportController : Controller
|
||||
{
|
||||
private readonly StarImportExportService ImportExportService;
|
||||
|
||||
public StarImportExportController(StarImportExportService importExportService)
|
||||
{
|
||||
ImportExportService = importExportService;
|
||||
}
|
||||
|
||||
[HttpGet("{starId:int}/export")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<ActionResult> ExportAsync([FromRoute] int starId)
|
||||
{
|
||||
var exportedStar = await ImportExportService.ExportAsync(starId);
|
||||
return Content(exportedStar, "application/json");
|
||||
}
|
||||
|
||||
[HttpPost("import")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<StarResponse> ImportAsync()
|
||||
{
|
||||
if (Request.Form.Files.Count == 0)
|
||||
throw new HttpApiException("No file to import provided", 400);
|
||||
|
||||
if (Request.Form.Files.Count > 1)
|
||||
throw new HttpApiException("Only one file to import allowed", 400);
|
||||
|
||||
var file = Request.Form.Files[0];
|
||||
await using var stream = file.OpenReadStream();
|
||||
using var sr = new StreamReader(stream, Encoding.UTF8);
|
||||
var content = await sr.ReadToEndAsync();
|
||||
|
||||
var star = await ImportExportService.ImportAsync(content);
|
||||
|
||||
return StarMapper.ToAdminResponse(star);
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.StarVariables;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.StarVariables;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/stars/{starId:int}/variables")]
|
||||
public class StarVariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
private readonly DatabaseRepository<StarVariable> VariableRepository;
|
||||
|
||||
public StarVariablesController(
|
||||
DatabaseRepository<Star> starRepository,
|
||||
DatabaseRepository<StarVariable> variableRepository)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
VariableRepository = variableRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<ActionResult<CountedData<StarVariableResponse>>> GetAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
return Problem("No star with this id found", statusCode: 404);
|
||||
|
||||
var query = VariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == starId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var variables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarVariableResponse>()
|
||||
{
|
||||
Items = variables,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.get")]
|
||||
public async Task<StarVariableResponse> GetSingleAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<StarVariableResponse> CreateAsync([FromRoute] int starId,
|
||||
[FromBody] CreateStarVariableRequest request)
|
||||
{
|
||||
var star = StarRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Id == starId);
|
||||
|
||||
if (star == null)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = StarVariableMapper.ToStarVariable(request);
|
||||
starVariable.Star = star;
|
||||
|
||||
await VariableRepository.AddAsync(starVariable);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||
public async Task<StarVariableResponse> UpdateAsync(
|
||||
[FromRoute] int starId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarVariableRequest request
|
||||
)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
StarVariableMapper.Merge(request, starVariable);
|
||||
await VariableRepository.UpdateAsync(starVariable);
|
||||
|
||||
return StarVariableMapper.ToAdminResponse(starVariable);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||
public async Task DeleteAsync([FromRoute] int starId, [FromRoute] int id)
|
||||
{
|
||||
var starExists = StarRepository
|
||||
.Get()
|
||||
.Any(x => x.Id == starId);
|
||||
|
||||
if (!starExists)
|
||||
throw new HttpApiException("No star with this id found", 404);
|
||||
|
||||
var starVariable = await VariableRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id && x.Star.Id == starId);
|
||||
|
||||
if (starVariable == null)
|
||||
throw new HttpApiException("No variable with this id found", 404);
|
||||
|
||||
await VariableRepository.RemoveAsync(starVariable);
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Stars;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Stars;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Admin.Stars;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/servers/stars")]
|
||||
public class StarsController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Star> StarRepository;
|
||||
|
||||
public StarsController(DatabaseRepository<Star> starRepository)
|
||||
{
|
||||
StarRepository = starRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<CountedData<StarResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var totalCount = await StarRepository.Get().CountAsync();
|
||||
|
||||
var stars = await StarRepository
|
||||
.Get()
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.ToArrayAsync();
|
||||
|
||||
return new CountedData<StarResponse>()
|
||||
{
|
||||
Items = stars,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.read")]
|
||||
public async Task<ActionResult<StarResponse>> GetSingleAsync([FromRoute] int id)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.AsNoTracking()
|
||||
.ProjectToAdminResponse()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
return star;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.create")]
|
||||
public async Task<ActionResult<StarResponse>> CreateAsync([FromBody] CreateStarRequest request)
|
||||
{
|
||||
var star = StarMapper.ToStar(request);
|
||||
|
||||
// Default values
|
||||
star.DonateUrl = null;
|
||||
star.UpdateUrl = null;
|
||||
star.Version = "1.0.0";
|
||||
star.StartupCommand = "echo Starting up :)";
|
||||
star.StopCommand = "^C";
|
||||
star.OnlineDetection = "Online text";
|
||||
star.InstallShell = "/bin/bash";
|
||||
star.InstallDockerImage = "debian:latest";
|
||||
star.InstallScript = "echo Installing...";
|
||||
star.RequiredAllocations = 1;
|
||||
star.AllowDockerImageChange = false;
|
||||
star.DefaultDockerImage = -1;
|
||||
star.ParseConfiguration = "[]";
|
||||
|
||||
var finalStar = await StarRepository.AddAsync(star);
|
||||
|
||||
return StarMapper.ToAdminResponse(finalStar);
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.update")]
|
||||
public async Task<ActionResult<StarResponse>> UpdateAsync(
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateStarRequest request
|
||||
)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
StarMapper.Merge(request, star);
|
||||
await StarRepository.UpdateAsync(star);
|
||||
|
||||
return StarMapper.ToAdminResponse(star);
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "permissions:admin.servers.stars.delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
|
||||
{
|
||||
var star = await StarRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (star == null)
|
||||
return Problem("No star with that id found", statusCode: 404);
|
||||
|
||||
await StarRepository.RemoveAsync(star);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.DaemonShared.Enums;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Files;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Files;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/client/servers/{serverId:int}/files")]
|
||||
public class FilesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly ServerFileSystemService ServerFileSystemService;
|
||||
private readonly NodeService NodeService;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public FilesController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerFileSystemService serverFileSystemService,
|
||||
NodeService nodeService,
|
||||
ServerAuthorizeService authorizeService
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
ServerFileSystemService = serverFileSystemService;
|
||||
NodeService = nodeService;
|
||||
AuthorizeService = authorizeService;
|
||||
}
|
||||
|
||||
[HttpGet("list")]
|
||||
public async Task<ActionResult<ServerFilesEntryResponse[]>> ListAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var entries = await ServerFileSystemService.ListAsync(server.Value, path);
|
||||
|
||||
return entries.Select(x => new ServerFilesEntryResponse()
|
||||
{
|
||||
Name = x.Name,
|
||||
Size = x.Size,
|
||||
IsFolder = x.IsFolder,
|
||||
CreatedAt = x.CreatedAt,
|
||||
UpdatedAt = x.UpdatedAt
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
[HttpPost("move")]
|
||||
public async Task<ActionResult> MoveAsync([FromRoute] int serverId, [FromQuery] string oldPath, [FromQuery] string newPath)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MoveAsync(server.Value, oldPath, newPath);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("delete")]
|
||||
public async Task<ActionResult> DeleteAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.DeleteAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("mkdir")]
|
||||
public async Task<ActionResult> MkdirAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("touch")]
|
||||
public async Task<ActionResult> TouchAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerFileSystemService.MkdirAsync(server.Value, path);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("upload")]
|
||||
public async Task<ActionResult<ServerFilesUploadResponse>> UploadAsync([FromRoute] int serverId)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
var accessToken = NodeService.CreateAccessToken(
|
||||
server.Node,
|
||||
parameters =>
|
||||
{
|
||||
parameters.Add("type", "upload");
|
||||
parameters.Add("serverId", server.Id);
|
||||
},
|
||||
TimeSpan.FromMinutes(1)
|
||||
);
|
||||
|
||||
var url = "";
|
||||
|
||||
url += server.Node.UseSsl ? "https://" : "http://";
|
||||
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/";
|
||||
url += $"api/servers/upload?access_token={accessToken}";
|
||||
|
||||
return new ServerFilesUploadResponse()
|
||||
{
|
||||
UploadUrl = url
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("download")]
|
||||
public async Task<ActionResult<ServerFilesDownloadResponse>> DownloadAsync([FromRoute] int serverId, [FromQuery] string path)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
var accessToken = NodeService.CreateAccessToken(
|
||||
server.Node,
|
||||
parameters =>
|
||||
{
|
||||
parameters.Add("type", "download");
|
||||
parameters.Add("path", path);
|
||||
parameters.Add("serverId", server.Id);
|
||||
},
|
||||
TimeSpan.FromMinutes(1)
|
||||
);
|
||||
|
||||
var url = "";
|
||||
|
||||
url += server.Node.UseSsl ? "https://" : "http://";
|
||||
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/";
|
||||
url += $"api/servers/download?access_token={accessToken}";
|
||||
|
||||
return new ServerFilesDownloadResponse()
|
||||
{
|
||||
DownloadUrl = url
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("compress")]
|
||||
public async Task<ActionResult> CompressAsync([FromRoute] int serverId, [FromBody] ServerFilesCompressRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||
return Problem("Invalid compress type provided", statusCode: 400);
|
||||
|
||||
await ServerFileSystemService.CompressAsync(server.Value, type, request.Items, request.Destination);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost("decompress")]
|
||||
public async Task<ActionResult> DecompressAsync([FromRoute] int serverId, [FromBody] ServerFilesDecompressRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
if (!Enum.TryParse(request.Type, true, out CompressType type))
|
||||
return Problem("Invalid decompress type provided", statusCode: 400);
|
||||
|
||||
await ServerFileSystemService.DecompressAsync(server.Value, type, request.Path, request.Destination);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Files,
|
||||
level
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
[Route("api/client/servers/{serverId:int}")]
|
||||
public class PowerController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly ServerService ServerService;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public PowerController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerService serverService,
|
||||
ServerAuthorizeService authorizeService
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
ServerService = serverService;
|
||||
AuthorizeService = authorizeService;
|
||||
}
|
||||
|
||||
[HttpPost("start")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> StartAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.StartAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("stop")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> StopAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.StopAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("kill")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> KillAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.KillAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Power,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,380 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Extensions;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Allocations;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/client/servers")]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly ServerService ServerService;
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<ServerShare> ShareRepository;
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
private readonly NodeService NodeService;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public ServersController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
NodeService nodeService,
|
||||
ServerService serverService,
|
||||
ServerAuthorizeService authorizeService,
|
||||
DatabaseRepository<ServerShare> shareRepository,
|
||||
DatabaseRepository<User> userRepository
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
NodeService = nodeService;
|
||||
ServerService = serverService;
|
||||
AuthorizeService = authorizeService;
|
||||
ShareRepository = shareRepository;
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<CountedData<ServerDetailResponse>>> GetAllAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var userIdClaim = User.FindFirstValue("UserId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
return Problem("Only users are able to use this endpoint", statusCode: 400);
|
||||
|
||||
var userId = int.Parse(userIdClaim);
|
||||
|
||||
var query = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Star)
|
||||
.Include(x => x.Node)
|
||||
.Where(x => x.OwnerId == userId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.AsNoTracking()
|
||||
.ToArrayAsync();
|
||||
|
||||
var mappedItems = items.Select(x => new ServerDetailResponse()
|
||||
{
|
||||
Id = x.Id,
|
||||
Name = x.Name,
|
||||
NodeName = x.Node.Name,
|
||||
StarName = x.Star.Name,
|
||||
Cpu = x.Cpu,
|
||||
Memory = x.Memory,
|
||||
Disk = x.Disk,
|
||||
Allocations = x.Allocations.Select(y => new AllocationDetailResponse()
|
||||
{
|
||||
Id = y.Id,
|
||||
Port = y.Port,
|
||||
IpAddress = y.IpAddress
|
||||
}).ToArray()
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerDetailResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("shared")]
|
||||
public async Task<ActionResult<CountedData<ServerDetailResponse>>> GetAllSharedAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var userIdClaim = User.FindFirstValue("UserId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim))
|
||||
return Problem("Only users are able to use this endpoint", statusCode: 400);
|
||||
|
||||
var userId = int.Parse(userIdClaim);
|
||||
|
||||
var query = ShareRepository
|
||||
.Get()
|
||||
.Include(x => x.Server)
|
||||
.ThenInclude(x => x.Node)
|
||||
.Include(x => x.Server)
|
||||
.ThenInclude(x => x.Star)
|
||||
.Include(x => x.Server)
|
||||
.ThenInclude(x => x.Allocations)
|
||||
.Where(x => x.UserId == userId);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var ownerIds = items
|
||||
.Select(x => x.Server.OwnerId)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
var owners = await UserRepository
|
||||
.Get()
|
||||
.Where(x => ownerIds.Contains(x.Id))
|
||||
.ToArrayAsync();
|
||||
|
||||
var mappedItems = items.Select(x => new ServerDetailResponse()
|
||||
{
|
||||
Id = x.Server.Id,
|
||||
Name = x.Server.Name,
|
||||
NodeName = x.Server.Node.Name,
|
||||
StarName = x.Server.Star.Name,
|
||||
Cpu = x.Server.Cpu,
|
||||
Memory = x.Server.Memory,
|
||||
Disk = x.Server.Disk,
|
||||
Allocations = x.Server.Allocations.Select(y => new AllocationDetailResponse()
|
||||
{
|
||||
Id = y.Id,
|
||||
Port = y.Port,
|
||||
IpAddress = y.IpAddress
|
||||
}).ToArray(),
|
||||
Share = new()
|
||||
{
|
||||
SharedBy = owners.First(y => y.Id == x.Server.OwnerId).Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(x.Content)
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerDetailResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}")]
|
||||
public async Task<ActionResult<ServerDetailResponse>> GetAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Allocations)
|
||||
.Include(x => x.Star)
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizationResult = await AuthorizeService.AuthorizeAsync(
|
||||
User,
|
||||
server,
|
||||
String.Empty,
|
||||
ServerPermissionLevel.None
|
||||
);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizationResult.Message ?? "No server with this id found",
|
||||
statusCode: 404
|
||||
);
|
||||
}
|
||||
|
||||
// Create mapped response
|
||||
var response = new ServerDetailResponse()
|
||||
{
|
||||
Id = server.Id,
|
||||
Name = server.Name,
|
||||
NodeName = server.Node.Name,
|
||||
StarName = server.Star.Name,
|
||||
Cpu = server.Cpu,
|
||||
Memory = server.Memory,
|
||||
Disk = server.Disk,
|
||||
Allocations = server.Allocations.Select(y => new AllocationDetailResponse()
|
||||
{
|
||||
Id = y.Id,
|
||||
Port = y.Port,
|
||||
IpAddress = y.IpAddress
|
||||
}).ToArray()
|
||||
};
|
||||
|
||||
// Handle requests on shared servers
|
||||
if (authorizationResult.Share != null)
|
||||
{
|
||||
var owner = await UserRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == server.OwnerId);
|
||||
|
||||
response.Share = new()
|
||||
{
|
||||
SharedBy = owner.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(authorizationResult.Share.Content)
|
||||
};
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/status")]
|
||||
public async Task<ActionResult<ServerStatusResponse>> GetStatusAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.None
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var status = await ServerService.GetStatusAsync(server.Value);
|
||||
|
||||
return new ServerStatusResponse()
|
||||
{
|
||||
State = status.State.ToServerPowerState()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/ws")]
|
||||
public async Task<ActionResult<ServerWebSocketResponse>> GetWebSocketAsync([FromRoute] int serverId)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
// TODO: Handle transparent node proxy
|
||||
|
||||
var accessToken = NodeService.CreateAccessToken(server.Node, parameters =>
|
||||
{
|
||||
parameters.Add("type", "websocket");
|
||||
parameters.Add("serverId", server.Id);
|
||||
}, TimeSpan.FromMinutes(15)); // TODO: Configurable
|
||||
|
||||
var url = "";
|
||||
|
||||
url += server.Node.UseSsl ? "https://" : "http://";
|
||||
url += $"{server.Node.Fqdn}:{server.Node.HttpPort}/api/servers/ws";
|
||||
|
||||
return new ServerWebSocketResponse()
|
||||
{
|
||||
Target = url,
|
||||
AccessToken = accessToken
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/logs")]
|
||||
public async Task<ActionResult<ServerLogsResponse>> GetLogsAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var logs = await ServerService.GetLogsAsync(server.Value);
|
||||
|
||||
return new ServerLogsResponse()
|
||||
{
|
||||
Messages = logs.Messages
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{serverId:int}/stats")]
|
||||
public async Task<ActionResult<ServerStatsResponse>> GetStatsAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.Read
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var stats = await ServerService.GetStatsAsync(server.Value);
|
||||
|
||||
return new ServerStatsResponse()
|
||||
{
|
||||
CpuUsage = stats.CpuUsage,
|
||||
MemoryUsage = stats.MemoryUsage,
|
||||
NetworkRead = stats.NetworkRead,
|
||||
NetworkWrite = stats.NetworkWrite,
|
||||
IoRead = stats.IoRead,
|
||||
IoWrite = stats.IoWrite
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("{serverId:int}/command")]
|
||||
public async Task<ActionResult> CommandAsync([FromRoute] int serverId, [FromBody] ServerCommandRequest request)
|
||||
{
|
||||
var server = await GetServerByIdAsync(
|
||||
serverId,
|
||||
ServerPermissionConstants.Console,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.RunCommandAsync(server.Value, request.Command);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, string permissionId,
|
||||
ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(User, server, permissionId, level);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/client/servers")]
|
||||
public class SettingsController : Controller
|
||||
{
|
||||
private readonly ServerService ServerService;
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public SettingsController(
|
||||
ServerService serverService,
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerAuthorizeService authorizeService
|
||||
)
|
||||
{
|
||||
ServerService = serverService;
|
||||
ServerRepository = serverRepository;
|
||||
AuthorizeService = authorizeService;
|
||||
}
|
||||
|
||||
[HttpPost("{serverId:int}/install")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> InstallAsync([FromRoute] int serverId)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
await ServerService.InstallAsync(server.Value);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Node)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Settings,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using Moonlight.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Mappers;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Shares;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Shares;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/client/servers/{serverId:int}/shares")]
|
||||
public class SharesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<ServerShare> ShareRepository;
|
||||
private readonly DatabaseRepository<User> UserRepository;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public SharesController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
DatabaseRepository<ServerShare> shareRepository,
|
||||
DatabaseRepository<User> userRepository,
|
||||
ServerAuthorizeService authorizeService
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
ShareRepository = shareRepository;
|
||||
UserRepository = userRepository;
|
||||
AuthorizeService = authorizeService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<CountedData<ServerShareResponse>>> GetAllAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var query = ShareRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == server.Value.Id);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var items = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var userIds = items
|
||||
.Select(x => x.UserId)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
var users = await UserRepository
|
||||
.Get()
|
||||
.Where(x => userIds.Contains(x.Id))
|
||||
.ToArrayAsync();
|
||||
|
||||
var mappedItems = items.Select(x => new ServerShareResponse()
|
||||
{
|
||||
Id = x.Id,
|
||||
Username = users.First(y => y.Id == x.UserId).Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(x.Content)
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerShareResponse>()
|
||||
{
|
||||
Items = mappedItems,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<ActionResult<ServerShareResponse>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == share.UserId);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = share.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ServerShareResponse>> CreateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] CreateShareRequest request
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Username == request.Username);
|
||||
|
||||
if (user == null)
|
||||
return Problem("A user with that username could not be found", statusCode: 400);
|
||||
|
||||
var share = new ServerShare()
|
||||
{
|
||||
Server = server.Value,
|
||||
Content = ShareMapper.MapToServerShareContent(request.Permissions),
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
UserId = user.Id
|
||||
};
|
||||
|
||||
var finalShare = await ShareRepository.AddAsync(share);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = finalShare.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(finalShare.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpPatch("{id:int}")]
|
||||
public async Task<ActionResult<ServerShareResponse>> UpdateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id,
|
||||
[FromBody] UpdateShareRequest request
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
share.Content = ShareMapper.MapToServerShareContent(request.Permissions);
|
||||
|
||||
share.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
await ShareRepository.UpdateAsync(share);
|
||||
|
||||
var user = await UserRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == share.UserId);
|
||||
|
||||
if (user == null)
|
||||
return Problem("A user with that id could not be found", statusCode: 400);
|
||||
|
||||
var mappedItem = new ServerShareResponse()
|
||||
{
|
||||
Id = share.Id,
|
||||
Username = user.Username,
|
||||
Permissions = ShareMapper.MapToPermissionLevels(share.Content)
|
||||
};
|
||||
|
||||
return mappedItem;
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
public async Task<ActionResult> DeleteAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromRoute] int id
|
||||
)
|
||||
{
|
||||
var server = await GetServerByIdAsync(serverId);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Value.Id && x.Id == id);
|
||||
|
||||
if (share == null)
|
||||
return Problem("A share with that id cannot be found", statusCode: 404);
|
||||
|
||||
await ShareRepository.RemoveAsync(share);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Shares,
|
||||
ServerPermissionLevel.ReadWrite
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Services;
|
||||
using MoonlightServers.Shared.Constants;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
using MoonlightServers.Shared.Http.Requests.Client.Servers.Variables;
|
||||
using MoonlightServers.Shared.Http.Responses.Client.Servers.Variables;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Client;
|
||||
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/client/servers/{serverId:int}/variables")]
|
||||
public class VariablesController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<ServerVariable> ServerVariableRepository;
|
||||
private readonly DatabaseRepository<StarVariable> StarVariableRepository;
|
||||
private readonly ServerAuthorizeService AuthorizeService;
|
||||
|
||||
public VariablesController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
ServerAuthorizeService authorizeService,
|
||||
DatabaseRepository<ServerVariable> serverVariableRepository,
|
||||
DatabaseRepository<StarVariable> starVariableRepository
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
AuthorizeService = authorizeService;
|
||||
ServerVariableRepository = serverVariableRepository;
|
||||
StarVariableRepository = starVariableRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<CountedData<ServerVariableDetailResponse>>> GetAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
var server = await GetServerByIdAsync(serverId, ServerPermissionLevel.Read);
|
||||
|
||||
if (server.Value == null)
|
||||
return server.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var query = StarVariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Star.Id == server.Value.Star.Id);
|
||||
|
||||
var totalCount = await query.CountAsync();
|
||||
|
||||
var starVariables = await query
|
||||
.OrderBy(x => x.Id)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var starVariableKeys = starVariables
|
||||
.Select(x => x.Key)
|
||||
.ToArray();
|
||||
|
||||
var serverVariables = await ServerVariableRepository
|
||||
.Get()
|
||||
.Where(x => x.Server.Id == server.Value.Id && starVariableKeys.Contains(x.Key))
|
||||
.ToArrayAsync();
|
||||
|
||||
var responses = starVariables.Select(starVariable =>
|
||||
{
|
||||
var serverVariable = serverVariables.First(x => x.Key == starVariable.Key);
|
||||
|
||||
return new ServerVariableDetailResponse()
|
||||
{
|
||||
Key = starVariable.Key,
|
||||
Value = serverVariable.Value,
|
||||
Type = starVariable.Type,
|
||||
Name = starVariable.Name,
|
||||
Description = starVariable.Description,
|
||||
Filter = starVariable.Filter
|
||||
};
|
||||
}).ToArray();
|
||||
|
||||
return new CountedData<ServerVariableDetailResponse>()
|
||||
{
|
||||
Items = responses,
|
||||
TotalCount = totalCount
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
public async Task<ActionResult<ServerVariableDetailResponse>> UpdateSingleAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] UpdateServerVariableRequest request
|
||||
)
|
||||
{
|
||||
// TODO: Handle filter
|
||||
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == request.Key);
|
||||
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == request.Key);
|
||||
|
||||
if (serverVariable == null || starVariable == null)
|
||||
throw new HttpApiException($"No variable with the key found: {request.Key}", 400);
|
||||
|
||||
serverVariable.Value = request.Value;
|
||||
await ServerRepository.UpdateAsync(server);
|
||||
|
||||
return new ServerVariableDetailResponse()
|
||||
{
|
||||
Key = starVariable.Key,
|
||||
Value = serverVariable.Value,
|
||||
Type = starVariable.Type,
|
||||
Name = starVariable.Name,
|
||||
Description = starVariable.Description,
|
||||
Filter = starVariable.Filter
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
public async Task<ActionResult<ServerVariableDetailResponse[]>> UpdateAsync(
|
||||
[FromRoute] int serverId,
|
||||
[FromBody] UpdateServerVariableRangeRequest request
|
||||
)
|
||||
{
|
||||
var serverResult = await GetServerByIdAsync(serverId, ServerPermissionLevel.ReadWrite);
|
||||
|
||||
if (serverResult.Value == null)
|
||||
return serverResult.Result ?? Problem("Unable to retrieve server");
|
||||
|
||||
var server = serverResult.Value;
|
||||
|
||||
foreach (var variable in request.Variables)
|
||||
{
|
||||
// TODO: Handle filter
|
||||
|
||||
var serverVariable = server.Variables.FirstOrDefault(x => x.Key == variable.Key);
|
||||
var starVariable = server.Star.Variables.FirstOrDefault(x => x.Key == variable.Key);
|
||||
|
||||
if (serverVariable == null || starVariable == null)
|
||||
throw new HttpApiException($"No variable with the key found: {variable.Key}", 400);
|
||||
|
||||
serverVariable.Value = variable.Value;
|
||||
}
|
||||
|
||||
await ServerRepository.UpdateAsync(server);
|
||||
|
||||
return request.Variables.Select(requestVariable =>
|
||||
{
|
||||
var serverVariable = server.Variables.First(x => x.Key == requestVariable.Key);
|
||||
var starVariable = server.Star.Variables.First(x => x.Key == requestVariable.Key);
|
||||
|
||||
return new ServerVariableDetailResponse()
|
||||
{
|
||||
Key = starVariable.Key,
|
||||
Value = serverVariable.Value,
|
||||
Type = starVariable.Type,
|
||||
Name = starVariable.Name,
|
||||
Description = starVariable.Description,
|
||||
Filter = starVariable.Filter
|
||||
};
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private async Task<ActionResult<Server>> GetServerByIdAsync(int serverId, ServerPermissionLevel level)
|
||||
{
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Star)
|
||||
.ThenInclude(x => x.Variables)
|
||||
.Include(x => x.Variables)
|
||||
.FirstOrDefaultAsync(x => x.Id == serverId);
|
||||
|
||||
if (server == null)
|
||||
return Problem("No server with this id found", statusCode: 404);
|
||||
|
||||
var authorizeResult = await AuthorizeService.AuthorizeAsync(
|
||||
User, server,
|
||||
ServerPermissionConstants.Variables,
|
||||
level
|
||||
);
|
||||
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return Problem(
|
||||
authorizeResult.Message ?? "No permission for the requested resource",
|
||||
statusCode: 403
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Remote.Nodes;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/remote/server/node")]
|
||||
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
|
||||
public class NodeTripController : Controller
|
||||
{
|
||||
[HttpGet("trip")]
|
||||
public Task GetAsync() => Task.CompletedTask;
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MoonCore.Common;
|
||||
using MoonCore.Exceptions;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.DaemonShared.PanelSide.Http.Responses;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Http.Controllers.Remote;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/remote/servers")]
|
||||
[Authorize(AuthenticationSchemes = "nodeAuthentication")]
|
||||
public class ServersController : Controller
|
||||
{
|
||||
private readonly DatabaseRepository<Server> ServerRepository;
|
||||
private readonly DatabaseRepository<Node> NodeRepository;
|
||||
private readonly ILogger<ServersController> Logger;
|
||||
|
||||
public ServersController(
|
||||
DatabaseRepository<Server> serverRepository,
|
||||
DatabaseRepository<Node> nodeRepository,
|
||||
ILogger<ServersController> logger
|
||||
)
|
||||
{
|
||||
ServerRepository = serverRepository;
|
||||
NodeRepository = nodeRepository;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<CountedData<ServerDataResponse>>> GetAsync(
|
||||
[FromQuery] int startIndex,
|
||||
[FromQuery] int count
|
||||
)
|
||||
{
|
||||
if (count > 100)
|
||||
return Problem("Only 100 items can be fetched at a time", statusCode: 400);
|
||||
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
var total = await ServerRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.CountAsync();
|
||||
|
||||
var servers = await ServerRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Include(x => x.Star)
|
||||
.ThenInclude(x => x.DockerImages)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Allocations)
|
||||
.Skip(startIndex)
|
||||
.Take(count)
|
||||
.ToArrayAsync();
|
||||
|
||||
var serverData = new List<ServerDataResponse>();
|
||||
|
||||
foreach (var server in servers)
|
||||
{
|
||||
var convertedData = ConvertToServerData(server);
|
||||
|
||||
if (convertedData == null)
|
||||
continue;
|
||||
|
||||
serverData.Add(convertedData);
|
||||
}
|
||||
|
||||
return new CountedData<ServerDataResponse>()
|
||||
{
|
||||
Items = serverData.ToArray(),
|
||||
TotalCount = total
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
public async Task<ServerDataResponse> GetAsync([FromRoute] int id)
|
||||
{
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
// Load the server with the star data attached. We filter by the node to ensure the node can only access
|
||||
// servers linked to it
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Include(x => x.Star)
|
||||
.ThenInclude(x => x.DockerImages)
|
||||
.Include(x => x.Variables)
|
||||
.Include(x => x.Allocations)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
throw new HttpApiException("No server with this id found", 404);
|
||||
|
||||
var convertedData = ConvertToServerData(server);
|
||||
|
||||
if (convertedData == null)
|
||||
throw new HttpApiException("An error occured while creating the server data model", 500);
|
||||
|
||||
return convertedData;
|
||||
}
|
||||
|
||||
[HttpGet("{id:int}/install")]
|
||||
public async Task<ServerInstallDataResponse> GetInstallAsync([FromRoute] int id)
|
||||
{
|
||||
// Load the node via the id
|
||||
var nodeId = int.Parse(User.Claims.First(x => x.Type == "nodeId").Value);
|
||||
|
||||
var node = await NodeRepository
|
||||
.Get()
|
||||
.FirstAsync(x => x.Id == nodeId);
|
||||
|
||||
// Load the server with the star data attached. We filter by the node to ensure the node can only access
|
||||
// servers linked to it
|
||||
var server = await ServerRepository
|
||||
.Get()
|
||||
.Where(x => x.Node.Id == node.Id)
|
||||
.Include(x => x.Star)
|
||||
.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
if (server == null)
|
||||
throw new HttpApiException("No server with this id found", 404);
|
||||
|
||||
return new ServerInstallDataResponse()
|
||||
{
|
||||
Script = server.Star.InstallScript,
|
||||
DockerImage = server.Star.InstallDockerImage,
|
||||
Shell = server.Star.InstallShell
|
||||
};
|
||||
}
|
||||
|
||||
private ServerDataResponse? ConvertToServerData(Server server)
|
||||
{
|
||||
// Find the docker image to use for this server
|
||||
StarDockerImage? dockerImage = null;
|
||||
|
||||
// Handle server set image if specified
|
||||
if (server.DockerImageIndex != -1)
|
||||
{
|
||||
dockerImage = server.Star.DockerImages
|
||||
.Skip(server.DockerImageIndex)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
// Handle star default image if set
|
||||
if (dockerImage == null && server.Star.DefaultDockerImage != -1)
|
||||
{
|
||||
dockerImage = server.Star.DockerImages
|
||||
.Skip(server.Star.DefaultDockerImage)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (dockerImage == null)
|
||||
dockerImage = server.Star.DockerImages.LastOrDefault();
|
||||
|
||||
if (dockerImage == null)
|
||||
{
|
||||
Logger.LogWarning("Unable to map server data for server {id}: No docker image available", server.Id);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert model
|
||||
return new ServerDataResponse()
|
||||
{
|
||||
Id = server.Id,
|
||||
StartupCommand = server.StartupOverride ?? server.Star.StartupCommand,
|
||||
Allocations = server.Allocations.Select(x => new AllocationDataResponse()
|
||||
{
|
||||
IpAddress = x.IpAddress,
|
||||
Port = x.Port
|
||||
}).ToArray(),
|
||||
Variables = server.Variables.ToDictionary(x => x.Key, x => x.Value),
|
||||
Cpu = server.Cpu,
|
||||
Disk = server.Disk,
|
||||
Memory = server.Memory,
|
||||
OnlineDetection = server.Star.OnlineDetection,
|
||||
DockerImage = dockerImage.Identifier,
|
||||
PullDockerImage = dockerImage.AutoPulling,
|
||||
ParseConiguration = server.Star.ParseConfiguration,
|
||||
StopCommand = server.Star.StopCommand,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class AdminAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
private readonly IAuthorizationService AuthorizationService;
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public AdminAuthFilter(IAuthorizationService authorizationService)
|
||||
{
|
||||
AuthorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
var authResult = await AuthorizationService.AuthorizeAsync(
|
||||
user,
|
||||
"permissions:admin.servers.manage"
|
||||
);
|
||||
|
||||
return authResult.Succeeded ? ServerAuthorizationResult.Success() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class OwnerAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
public int Priority => 0;
|
||||
|
||||
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
var userIdValue = user.FindFirstValue("UserId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdValue)) // This is the case for api keys
|
||||
return Task.FromResult<ServerAuthorizationResult?>(null);
|
||||
|
||||
var userId = int.Parse(userIdValue);
|
||||
|
||||
if (server.OwnerId != userId)
|
||||
return Task.FromResult<ServerAuthorizationResult?>(null);
|
||||
|
||||
return Task.FromResult<ServerAuthorizationResult?>(
|
||||
ServerAuthorizationResult.Success()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MoonCore.Extended.Abstractions;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Interfaces;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Implementations.ServerAuthFilters;
|
||||
|
||||
public class ShareAuthFilter : IServerAuthorizationFilter
|
||||
{
|
||||
private readonly DatabaseRepository<ServerShare> ShareRepository;
|
||||
|
||||
public ShareAuthFilter(DatabaseRepository<ServerShare> shareRepository)
|
||||
{
|
||||
ShareRepository = shareRepository;
|
||||
}
|
||||
|
||||
public int Priority => 0;
|
||||
|
||||
public async Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
)
|
||||
{
|
||||
var userIdValue = user.FindFirstValue("userId");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdValue))
|
||||
return null;
|
||||
|
||||
var userId = int.Parse(userIdValue);
|
||||
|
||||
var share = await ShareRepository
|
||||
.Get()
|
||||
.FirstOrDefaultAsync(x => x.Server.Id == server.Id && x.UserId == userId);
|
||||
|
||||
if (share == null)
|
||||
return null;
|
||||
|
||||
if (string.IsNullOrEmpty(permissionId) || requiredLevel == ServerPermissionLevel.None)
|
||||
return ServerAuthorizationResult.Success(share);
|
||||
|
||||
var possiblePermShare = share.Content.Permissions.FirstOrDefault(x => x.Identifier == permissionId);
|
||||
|
||||
if (possiblePermShare == null)
|
||||
return null;
|
||||
|
||||
if (possiblePermShare.Level >= requiredLevel)
|
||||
return ServerAuthorizationResult.Success(share);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.ApiServer.Models;
|
||||
using MoonlightServers.Shared.Enums;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Interfaces;
|
||||
|
||||
public interface IServerAuthorizationFilter
|
||||
{
|
||||
// Return null => skip to next filter / handler
|
||||
// Return any value, instant complete
|
||||
|
||||
public int Priority { get; }
|
||||
|
||||
public Task<ServerAuthorizationResult?> ProcessAsync(
|
||||
ClaimsPrincipal user,
|
||||
Server server,
|
||||
string permissionId,
|
||||
ServerPermissionLevel requiredLevel
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.NodeAllocations;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.NodeAllocations;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class AllocationMapper
|
||||
{
|
||||
public static partial NodeAllocationResponse ToNodeAllocation(Allocation allocation);
|
||||
public static partial Allocation ToAllocation(CreateNodeAllocationRequest request);
|
||||
public static partial void Merge(UpdateNodeAllocationRequest request, Allocation allocation);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<NodeAllocationResponse> ProjectToAdminResponse(this IQueryable<Allocation> allocations);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.StarDockerImages;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.StarDockerImages;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class DockerImageMapper
|
||||
{
|
||||
public static partial StarDockerImageResponse ToAdminResponse(StarDockerImage dockerImage);
|
||||
public static partial StarDockerImage ToDockerImage(CreateStarDockerImageRequest request);
|
||||
public static partial void Merge(UpdateStarDockerImageRequest request, StarDockerImage variable);
|
||||
|
||||
// EF Migrations
|
||||
|
||||
public static partial IQueryable<StarDockerImageResponse> ProjectToAdminResponse(this IQueryable<StarDockerImage> dockerImages);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using MoonlightServers.ApiServer.Database.Entities;
|
||||
using MoonlightServers.Shared.Http.Requests.Admin.Nodes;
|
||||
using MoonlightServers.Shared.Http.Responses.Admin.Nodes;
|
||||
using Riok.Mapperly.Abstractions;
|
||||
|
||||
namespace MoonlightServers.ApiServer.Mappers;
|
||||
|
||||
[Mapper(AllowNullPropertyAssignment = false)]
|
||||
public static partial class NodeMapper
|
||||
{
|
||||
public static partial NodeResponse ToAdminNodeResponse(Node node);
|
||||
public static partial Node ToNode(CreateNodeRequest request);
|
||||
public static partial void Merge(UpdateNodeRequest request, Node node);
|
||||
|
||||
// EF Projections
|
||||
|
||||
public static partial IQueryable<NodeResponse> ProjectToAdminResponse(this IQueryable<Node> nodes);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user