diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..928e628f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +**/appsettings.json +**/appsettings.Development.json +**/appsettings* +**/compose.* +**/.env.example +LICENSE +README.md \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c80469a5 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +DATABASE_HOST=localhost +DATABASE_PORT=5432 +DATABASE_USERNAME=user +DATABASE_PASSWORD=user +DATABASE_DATABASE=user +OIDC_AUTHORITY=http://localhost:8092 +OIDC_AUTHORITY=http://localhost:8092 +OIDC_CLIENT_ID=client_id +OIDC_CLIENT_SECRET=client_secret +OIDC_REQUIRE_HTTPS_METADATA=false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe07704..00000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 997f071a..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -# These are supported funding model platforms -ko_fi: masuowo diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index 7334cb5d..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: Bug Report -description: Something isn't working quite right in the software. -labels: [not confirmed] -body: -- type: markdown - attributes: - value: | - Bug reports should only be used for reporting issues with how the software works. For assistance installing this software, as well as debugging issues with dependencies, please use our [Discord server](https://discord.gg/TJaspT7A8p). - -- type: textarea - attributes: - label: Current Behavior - description: Please provide a clear & concise description of the issue. - validations: - required: true - -- type: textarea - attributes: - label: Expected Behavior - description: Please describe what you expected to happen. - validations: - required: true - -- type: textarea - attributes: - label: Steps to Reproduce - description: Please be as detailed as possible when providing steps to reproduce, failure to provide steps will result in this issue being closed. - validations: - required: true - -- type: input - id: panel-version - attributes: - label: Panel Version - description: Version number of your Panel (latest is not a version) - placeholder: v2 EA - validations: - required: true - -- type: input - id: daemon-version - attributes: - label: Daemon Version - description: Version number of your Daemon (latest is not a version) - placeholder: v2 EA - validations: - required: true - -- type: input - id: image-details - attributes: - label: Games and/or Images Affected - description: Please include the specific game(s) or Image(s) you are running into this bug with. - placeholder: Minecraft (Paper), Minecraft (Forge) - -- type: input - id: docker-image - attributes: - label: Docker Image - description: The specific Docker image you are using for the game(s) above. - placeholder: ghcr.io/xxx/yolks:java_17 - -- type: textarea - id: panel-logs - attributes: - label: Error Logs - description: | - Run the following command to collect logs on your system. - - Panel: `docker logs moonlight` - Wings: `sudo wings diagnostics` - placeholder: logs here - render: bash - validations: - required: false - -- type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please [search here](https://github.com/Moonlight-Panel/Moonlight/issues) to see if an issue already exists for your problem. - options: - - label: I have searched the existing issues before opening this issue. - required: true - - label: I have provided all relevant details, including the specific game and Docker images I am using if this issue is related to running a server. - required: true - - label: I have checked in the Discord server and believe this is a bug with the software, and not a configuration issue with my specific system. - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 7917ea02..00000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Installation Help - url: https://discord.gg/TJaspT7A8p - about: Please visit our Discord for help with your installation. - - name: General Question - url: https://discord.gg/TJaspT7A8p - about: Please visit our Discord for general questions about Moonlight Panel. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 87c299e5..00000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Feature Request -description: Suggest a new feature or improvement for the software. -labels: [feature request] -body: -- type: checkboxes - attributes: - label: Is there an existing feature request for this? - description: Please [search here](https://github.com/Moonlight-Panel/Moonlight/issues?q=is%3Aissue) to see if someone else has already suggested this. - options: - - label: I have searched the existing issues before opening this feature request. - required: true - -- type: textarea - attributes: - label: Describe the feature you would like to see. - description: "A clear & concise description of the feature you'd like to have added, and what issues it would solve." - validations: - required: true - -- type: textarea - attributes: - label: Describe the solution you'd like. - description: "You must explain how you'd like to see this feature implemented. Technical implementation details are not necessary, rather an idea of how you'd like to see this feature used." - validations: - required: true - -- type: textarea - attributes: - label: Additional context to this request. - description: "Add any other context or screenshots about the feature request." - validations: - required: false \ No newline at end of file diff --git a/.github/workflows/publish-dev-packages.yml b/.github/workflows/publish-dev-packages.yml deleted file mode 100644 index f42e3983..00000000 --- a/.github/workflows/publish-dev-packages.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build and Publish NuGet Package - -on: - push: - branches: [ v2_ChangeArchitecture,v2.1 ] - paths: - - '**.csproj' - - workflow_dispatch: - -jobs: - publish: - runs-on: debian-12 - strategy: - matrix: - project: - - Moonlight.Client - - Moonlight.ApiServer - - Moonlight.Shared - - steps: - # Step 1: Clean environment - - name: Clean up Environment - run: | - rm -rf ./* - rm -rf ./.??* - - # Step 2: Checkout the code - - name: Checkout code - uses: actions/checkout@v3 - - # Step 3: Build project - - name: "Build project" - run: dotnet build --configuration Debug ${{ matrix.project }}/${{ matrix.project }}.csproj - - # Step 4: Pack project - - name: "Pack project" - run: dotnet pack --configuration Debug --no-build --output . ${{ matrix.project }}/${{ matrix.project }}.csproj - - # Step 5: Publish on package registry - - name: Publish on package registry" - run: dotnet nuget push "*.nupkg" --skip-duplicate --api-key ${{secrets.GH_PACKAGES_READWRITE}} --source https://nuget.pkg.github.com/Moonlight-Panel/index.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index 57505a98..2678919b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore @@ -395,40 +395,13 @@ FodyWeavers.xsd *.msp # JetBrains Rider -*.sln.iml - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Moonlight -storage/ **/.idea/** -style.min.css -# Build script for nuget packages -finalPackages/ -nupkgs/ +# Style builds +**/style.min.css +**/package-lock.json -# Scripts -**/bin/** -**/obj/** \ No newline at end of file +# Secrets +**/.env +**/appsettings.json +**/appsettings.Development.json \ No newline at end of file diff --git a/Hosts/Moonlight.Api.Host/AppStartupLoader.cs b/Hosts/Moonlight.Api.Host/AppStartupLoader.cs new file mode 100644 index 00000000..c1e7a9e8 --- /dev/null +++ b/Hosts/Moonlight.Api.Host/AppStartupLoader.cs @@ -0,0 +1,10 @@ +using MoonCore.PluginFramework; +using Moonlight.Api.Startup; + +namespace Moonlight.Api.Host; + +[PluginLoader] +public partial class AppStartupLoader : IAppStartup +{ + +} \ No newline at end of file diff --git a/Hosts/Moonlight.Api.Host/Dockerfile b/Hosts/Moonlight.Api.Host/Dockerfile new file mode 100644 index 00000000..36ee6a41 --- /dev/null +++ b/Hosts/Moonlight.Api.Host/Dockerfile @@ -0,0 +1,63 @@ +# Base image +FROM cgr.dev/chainguard/aspnet-runtime:latest AS base +WORKDIR /app + +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build + +# Build dependencies +RUN apt-get update; apt-get install nodejs npm -y; apt-get clean + +# Build options +ARG BUILD_CONFIGURATION=Release + +# Download npm packages +WORKDIR /src/Moonlight.Frontend/Styles + +COPY ["Moonlight.Frontend/Styles/package.json", "package.json"] + +RUN npm install + +# Restore nuget packages +WORKDIR /src + +COPY ["Moonlight.Api/Moonlight.Api.csproj", "Moonlight.Api/"] +COPY ["Moonlight.Frontend/Moonlight.Frontend.csproj", "Moonlight.Frontend/"] +COPY ["Moonlight.Shared/Moonlight.Shared.csproj", "Moonlight.Shared/"] + +RUN dotnet restore "Moonlight.Api/Moonlight.Api.csproj" +RUN dotnet restore "Moonlight.Frontend/Moonlight.Frontend.csproj" + +# Copy over the whole sources +COPY . . + +# Build styles +WORKDIR /src/Moonlight.Frontend/Styles +RUN npm run tailwind-build + +# Build projects +WORKDIR "/src/Moonlight.Api" +RUN dotnet build "./Moonlight.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build-api + +WORKDIR "/src/Moonlight.Frontend" +RUN dotnet build "./Moonlight.Frontend.csproj" -c $BUILD_CONFIGURATION -o /app/build-frontend + +FROM build AS publish + +# Publish options +ARG BUILD_CONFIGURATION=Release + +# Publish applications +WORKDIR "/src/Moonlight.Api" +RUN dotnet publish "./Moonlight.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish-api /p:UseAppHost=false + +WORKDIR "/src/Moonlight.Frontend" +RUN dotnet publish "./Moonlight.Frontend.csproj" -c $BUILD_CONFIGURATION -o /app/publish-frontend /p:UseAppHost=false + +FROM base AS final + +WORKDIR /app + +COPY --from=publish /app/publish-api . +COPY --from=publish /app/publish-frontend/wwwroot ./wwwroot + +ENTRYPOINT ["dotnet", "Moonlight.Api.dll"] \ No newline at end of file diff --git a/Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj b/Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj new file mode 100644 index 00000000..37e5935e --- /dev/null +++ b/Hosts/Moonlight.Api.Host/Moonlight.Api.Host.csproj @@ -0,0 +1,32 @@ + + + + net10.0 + enable + enable + + + + + + + + + + all + runtime; build; native; analyzers; buildtransitive + + + + + + + + + + + .dockerignore + + + + diff --git a/Hosts/Moonlight.Api.Host/Program.cs b/Hosts/Moonlight.Api.Host/Program.cs new file mode 100644 index 00000000..c4b7d8f9 --- /dev/null +++ b/Hosts/Moonlight.Api.Host/Program.cs @@ -0,0 +1,22 @@ +using Moonlight.Api.Host; + +var appLoader = new AppStartupLoader(); +appLoader.Initialize(); + +var builder = WebApplication.CreateBuilder(args); + +appLoader.PreBuild(builder); + +var app = builder.Build(); + +appLoader.PostBuild(app); + +appLoader.PostMiddleware(app); + +if (app.Environment.IsDevelopment()) + app.UseWebAssemblyDebugging(); + +app.UseBlazorFrameworkFiles(); +app.UseStaticFiles(); + +await app.RunAsync(); \ No newline at end of file diff --git a/Hosts/Moonlight.Api.Host/Properties/launchSettings.json b/Hosts/Moonlight.Api.Host/Properties/launchSettings.json new file mode 100644 index 00000000..cd3d7bcf --- /dev/null +++ b/Hosts/Moonlight.Api.Host/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5240", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs b/Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs new file mode 100644 index 00000000..6ef20cd4 --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/AppStartupLoader.cs @@ -0,0 +1,10 @@ +using MoonCore.PluginFramework; +using Moonlight.Frontend.Startup; + +namespace Moonlight.Frontend.Host; + +[PluginLoader] +public partial class AppStartupLoader : IAppStartup +{ + +} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj b/Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj similarity index 54% rename from Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj rename to Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj index 4c81345a..2ecce0ad 100644 --- a/Moonlight.Client.Runtime/Moonlight.Client.Runtime.csproj +++ b/Hosts/Moonlight.Frontend.Host/Moonlight.Frontend.Host.csproj @@ -1,28 +1,24 @@ - net9.0 + net10.0 enable enable - True + + True + true + true + true - - - - + + - - - - + - - - diff --git a/Hosts/Moonlight.Frontend.Host/Program.cs b/Hosts/Moonlight.Frontend.Host/Program.cs new file mode 100644 index 00000000..5b58db81 --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/Program.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Moonlight.Frontend.Host; + +var appLoader = new AppStartupLoader(); +appLoader.Initialize(); + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +appLoader.PreBuild(builder); + +var app = builder.Build(); + +appLoader.PostBuild(app); + +await app.RunAsync(); \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Properties/launchSettings.json b/Hosts/Moonlight.Frontend.Host/Properties/launchSettings.json similarity index 68% rename from Moonlight.Client.Runtime/Properties/launchSettings.json rename to Hosts/Moonlight.Frontend.Host/Properties/launchSettings.json index d9cbefe5..5c4e3431 100644 --- a/Moonlight.Client.Runtime/Properties/launchSettings.json +++ b/Hosts/Moonlight.Frontend.Host/Properties/launchSettings.json @@ -1,14 +1,15 @@ { + "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, - "launchBrowser": false, + "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5165", + "applicationUrl": "http://localhost:5250", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } -} \ No newline at end of file +} diff --git a/Hosts/Moonlight.Frontend.Host/Styles/package.json b/Hosts/Moonlight.Frontend.Host/Styles/package.json new file mode 100644 index 00000000..f6df15b7 --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/Styles/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "tailwind": "npx postcss styles.css -o ../wwwroot/style.min.css --watch", + "tailwind-build": "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", + "shadcnblazor": "^1.0.4", + "tailwindcss": "^4.1.18", + "tw-animate-css": "^1.4.0" + } +} diff --git a/Hosts/Moonlight.Frontend.Host/Styles/postcss.config.mjs b/Hosts/Moonlight.Frontend.Host/Styles/postcss.config.mjs new file mode 100644 index 00000000..8b9b8fba --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/Styles/postcss.config.mjs @@ -0,0 +1,8 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + "cssnano":{ + preset: 'default' + } + } +} \ No newline at end of file diff --git a/Hosts/Moonlight.Frontend.Host/Styles/styles.css b/Hosts/Moonlight.Frontend.Host/Styles/styles.css new file mode 100644 index 00000000..19377e05 --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/Styles/styles.css @@ -0,0 +1,30 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@import "./node_modules/shadcnblazor/scrollbar.css"; +@import "./node_modules/shadcnblazor/default-theme.css"; +@import "./theme.css"; +@source "./node_modules/shadcnblazor/classes.json"; + +@source "../../Moonlight.Api/**/*.razor"; +@source "../../Moonlight.Api/**/*.cs"; +@source "../../Moonlight.Api/**/*.html"; + +@source "../../Moonlight.Frontend/**/*.razor"; +@source "../../Moonlight.Frontend/**/*.cs"; +@source "../../Moonlight.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; + } +} \ No newline at end of file diff --git a/Hosts/Moonlight.Frontend.Host/Styles/theme.css b/Hosts/Moonlight.Frontend.Host/Styles/theme.css new file mode 100644 index 00000000..9e526acb --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/Styles/theme.css @@ -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); +} \ No newline at end of file diff --git a/Hosts/Moonlight.Frontend.Host/wwwroot/index.html b/Hosts/Moonlight.Frontend.Host/wwwroot/index.html new file mode 100644 index 00000000..83af45b2 --- /dev/null +++ b/Hosts/Moonlight.Frontend.Host/wwwroot/index.html @@ -0,0 +1,58 @@ + + + + + + + Moonlight + + + + + + + + +
+
+ +
+
+
+ + + + + +
+
+
+ Loading application +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0e259d42..00000000 --- a/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. diff --git a/Moonlight.Api/Configuration/DatabaseOptions.cs b/Moonlight.Api/Configuration/DatabaseOptions.cs new file mode 100644 index 00000000..4685d628 --- /dev/null +++ b/Moonlight.Api/Configuration/DatabaseOptions.cs @@ -0,0 +1,10 @@ +namespace Moonlight.Api.Configuration; + +public class DatabaseOptions +{ + public string Host { get; set; } + public int Port { get; set; } = 5432; + public string Username { get; set; } + public string Password { get; set; } + public string Database { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Api/Configuration/OidcOptions.cs b/Moonlight.Api/Configuration/OidcOptions.cs new file mode 100644 index 00000000..a99cda3d --- /dev/null +++ b/Moonlight.Api/Configuration/OidcOptions.cs @@ -0,0 +1,11 @@ +namespace Moonlight.Api.Configuration; + +public class OidcOptions +{ + public string Authority { get; set; } + public bool RequireHttpsMetadata { get; set; } = true; + public string ResponseType { get; set; } = "code"; + public string[]? Scopes { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/DataContext.cs b/Moonlight.Api/Database/DataContext.cs new file mode 100644 index 00000000..ddddf1cc --- /dev/null +++ b/Moonlight.Api/Database/DataContext.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using Moonlight.Api.Configuration; +using Moonlight.Api.Database.Entities; + +namespace Moonlight.Api.Database; + +public class DataContext : DbContext +{ + public DbSet Users { get; set; } + + private readonly IOptions Options; + + public DataContext(IOptions 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}" + ); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/DatabaseRepository.cs b/Moonlight.Api/Database/DatabaseRepository.cs new file mode 100644 index 00000000..ea153853 --- /dev/null +++ b/Moonlight.Api/Database/DatabaseRepository.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; + +namespace Moonlight.Api.Database; + +public class DatabaseRepository where T : class +{ + private readonly DataContext DataContext; + private readonly DbSet Set; + + public DatabaseRepository(DataContext dataContext) + { + DataContext = dataContext; + Set = DataContext.Set(); + } + + public IQueryable Query() => Set; + + public async Task AddAsync(T entity) + { + var final = Set.Add(entity); + await DataContext.SaveChangesAsync(); + return final.Entity; + } + + public async Task UpdateAsync(T entity) + { + Set.Update(entity); + await DataContext.SaveChangesAsync(); + } + + public async Task RemoveAsync(T entity) + { + Set.Remove(entity); + await DataContext.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/Entities/User.cs b/Moonlight.Api/Database/Entities/User.cs new file mode 100644 index 00000000..f5fcf8b4 --- /dev/null +++ b/Moonlight.Api/Database/Entities/User.cs @@ -0,0 +1,11 @@ +namespace Moonlight.Api.Database.Entities; + +public class User +{ + public int Id { get; set; } + + public string Username { get; set; } + public string Email { get; set; } + + public DateTimeOffset InvalidateTimestamp { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.Designer.cs b/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.Designer.cs new file mode 100644 index 00000000..8b45c33a --- /dev/null +++ b/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.Designer.cs @@ -0,0 +1,55 @@ +// + +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Moonlight.Api.Database; + +#nullable disable + +namespace Moonlight.Api.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20251216083232_AddedUsers")] + partial class AddedUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvalidateTimestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.cs b/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.cs new file mode 100644 index 00000000..9ecca052 --- /dev/null +++ b/Moonlight.Api/Database/Migrations/20251216083232_AddedUsers.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Moonlight.Api.Database.Migrations +{ + /// + public partial class AddedUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", + NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Username = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + InvalidateTimestamp = + table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => { table.PrimaryKey("PK_Users", x => x.Id); }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} \ No newline at end of file diff --git a/Moonlight.Api/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight.Api/Database/Migrations/DataContextModelSnapshot.cs new file mode 100644 index 00000000..fabcebfb --- /dev/null +++ b/Moonlight.Api/Database/Migrations/DataContextModelSnapshot.cs @@ -0,0 +1,52 @@ +// + +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Moonlight.Api.Database; + +#nullable disable + +namespace Moonlight.Api.Database.Migrations +{ + [DbContext(typeof(DataContext))] + partial class DataContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Moonlight.Api.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvalidateTimestamp") + .HasColumnType("timestamp with time zone"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); +#pragma warning restore 612, 618 + } + } +} \ No newline at end of file diff --git a/Moonlight.Api/Helpers/AppConsoleFormatter.cs b/Moonlight.Api/Helpers/AppConsoleFormatter.cs new file mode 100644 index 00000000..d5b7136a --- /dev/null +++ b/Moonlight.Api/Helpers/AppConsoleFormatter.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Logging.Console; + +namespace Moonlight.Api.Helpers; + +public class AppConsoleFormatter : ConsoleFormatter +{ + private const string TimestampColor = "\e[38;2;148;148;148m"; + private const string CategoryColor = "\e[38;2;198;198;198m"; + private const string MessageColor = "\e[38;2;255;255;255m"; + private const string Bold = "\e[1m"; + + // Pre-computed ANSI color codes for each log level + private const string CriticalColor = "\e[38;2;255;0;0m"; + private const string ErrorColor = "\e[38;2;255;0;0m"; + private const string WarningColor = "\e[38;2;215;215;0m"; + private const string InfoColor = "\e[38;2;135;215;255m"; + private const string DebugColor = "\e[38;2;198;198;198m"; + private const string TraceColor = "\e[38;2;68;68;68m"; + + public AppConsoleFormatter() : base(nameof(AppConsoleFormatter)) + { + } + + public override void Write( + in LogEntry logEntry, + IExternalScopeProvider? scopeProvider, + TextWriter textWriter) + { + var message = logEntry.Formatter(logEntry.State, logEntry.Exception); + + // Timestamp + textWriter.Write(TimestampColor); + textWriter.Write(DateTime.Now.ToString("dd.MM.yy HH:mm:ss")); + textWriter.Write(' '); + + // Log level with color and bold + var (levelText, levelColor) = GetLevelInfo(logEntry.LogLevel); + textWriter.Write(levelColor); + textWriter.Write(Bold); + textWriter.Write(levelText); + textWriter.Write(' '); + + // Category + textWriter.Write(CategoryColor); + textWriter.Write(logEntry.Category); + + // Message + textWriter.Write(MessageColor); + textWriter.Write(": "); + textWriter.Write(message); + + // Exception + if (logEntry.Exception != null) + { + textWriter.Write(MessageColor); + textWriter.WriteLine(logEntry.Exception.ToString()); + } + else + textWriter.WriteLine(); + } + + private static (string text, string color) GetLevelInfo(LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Critical => ("CRIT", CriticalColor), + LogLevel.Error => ("ERRO", ErrorColor), + LogLevel.Warning => ("WARN", WarningColor), + LogLevel.Information => ("INFO", InfoColor), + LogLevel.Debug => ("DEBG", DebugColor), + LogLevel.Trace => ("TRCE", TraceColor), + _ => ("NONE", "") + }; + } +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Controllers/AuthController.cs b/Moonlight.Api/Http/Controllers/AuthController.cs new file mode 100644 index 00000000..c7401736 --- /dev/null +++ b/Moonlight.Api/Http/Controllers/AuthController.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Moonlight.Shared.Http.Responses.Auth; + +namespace Moonlight.Api.Http.Controllers; + +[ApiController] +[Route("api/auth")] +public class AuthController : Controller +{ + private readonly IAuthenticationSchemeProvider SchemeProvider; + + public AuthController(IAuthenticationSchemeProvider schemeProvider) + { + SchemeProvider = schemeProvider; + } + + [HttpGet] + public async Task> GetSchemesAsync() + { + var schemes = await SchemeProvider.GetAllSchemesAsync(); + + return schemes + .Where(scheme => !string.IsNullOrWhiteSpace(scheme.DisplayName)) + .Select(scheme => new SchemeResponse(scheme.Name, scheme.DisplayName!)) + .ToArray(); + } + + [HttpGet("{schemeName:alpha}")] + public async Task StartAsync([FromRoute] string schemeName) + { + var scheme = await SchemeProvider.GetSchemeAsync(schemeName); + + if (scheme == null || string.IsNullOrWhiteSpace(scheme.DisplayName)) + return Problem("Invalid authentication scheme name", statusCode: 400); + + return Challenge(new AuthenticationProperties() + { + RedirectUri = "/" + }, scheme.Name); + } + + [Authorize] + [HttpGet("claims")] + public Task> GetClaimsAsync() + { + var result = User.Claims + .Select(claim => new ClaimResponse(claim.Type, claim.Value)) + .ToArray(); + + return Task.FromResult>(result); + } + + [HttpGet("logout")] + public Task LogoutAsync() + { + return Task.FromResult( + SignOut(new AuthenticationProperties() + { + RedirectUri = "/" + }) + ); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Http/Controllers/UsersController.cs b/Moonlight.Api/Http/Controllers/UsersController.cs new file mode 100644 index 00000000..6356ea69 --- /dev/null +++ b/Moonlight.Api/Http/Controllers/UsersController.cs @@ -0,0 +1,126 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Moonlight.Shared.Http.Requests; +using Moonlight.Shared.Http.Requests.Users; +using Moonlight.Shared.Http.Responses; +using Moonlight.Shared.Http.Responses.Users; +using Moonlight.Api.Database; +using Moonlight.Api.Database.Entities; +using Moonlight.Api.Mappers; + +namespace Moonlight.Api.Http.Controllers; + +[Authorize] +[ApiController] +[Route("api/users")] +public class UsersController : Controller +{ + private readonly DatabaseRepository UserRepository; + + public UsersController(DatabaseRepository userRepository) + { + UserRepository = userRepository; + } + + [HttpGet] + public async Task>> 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"); + + var query = UserRepository + .Query(); + + // Filters + if (filterOptions != null) + { + foreach (var filterOption in filterOptions.Filters) + { + query = filterOption.Key switch + { + nameof(Database.Entities.User.Email) => + query.Where(user => EF.Functions.ILike(user.Email, $"%{filterOption.Value}%")), + + nameof(Database.Entities.User.Username) => + query.Where(user => EF.Functions.ILike(user.Username, $"%{filterOption.Value}%")), + + _ => query + }; + } + } + + // Pagination + var data = await query + .ProjectToResponse() + .Skip(startIndex) + .Take(length) + .ToArrayAsync(); + + var total = await query.CountAsync(); + + return new PagedData(data, total); + } + + [HttpGet("{id:int}")] + public async Task> GetAsync([FromRoute] int id) + { + var user = await UserRepository + .Query() + .FirstOrDefaultAsync(x => x.Id == id); + + if (user == null) + return Problem("No user with this id found", statusCode: 404); + + return UserMapper.MapToResponse(user); + } + + [HttpPost] + public async Task> CreateAsync([FromBody] CreateUserRequest request) + { + var user = UserMapper.MapToUser(request); + user.InvalidateTimestamp = DateTimeOffset.UtcNow.AddMinutes(-1); + + var finalUser = await UserRepository.AddAsync(user); + + return UserMapper.MapToResponse(finalUser); + } + + [HttpPatch("{id:int}")] + public async Task> UpdateAsync([FromRoute] int id, [FromBody] UpdateUserRequest request) + { + var user = await UserRepository + .Query() + .FirstOrDefaultAsync(x => x.Id == id); + + if (user == null) + return Problem("No user with this id found", statusCode: 404); + + UserMapper.Merge(user, request); + await UserRepository.UpdateAsync(user); + + return UserMapper.MapToResponse(user); + } + + [HttpDelete("{id:int}")] + public async Task DeleteAsync([FromRoute] int id) + { + var user = await UserRepository + .Query() + .FirstOrDefaultAsync(user => user.Id == id); + + if (user == null) + return Problem("No user with this id found", statusCode: 404); + + await UserRepository.RemoveAsync(user); + return NoContent(); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Mappers/UserMapper.cs b/Moonlight.Api/Mappers/UserMapper.cs new file mode 100644 index 00000000..a70b8fc4 --- /dev/null +++ b/Moonlight.Api/Mappers/UserMapper.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Moonlight.Shared.Http.Requests.Users; +using Moonlight.Shared.Http.Responses.Users; +using Moonlight.Api.Database.Entities; + +namespace Moonlight.Api.Mappers; + +[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 UserMapper +{ + public static partial IQueryable ProjectToResponse(this IQueryable users); + + public static partial UserResponse MapToResponse(User user); + + public static partial void Merge([MappingTarget] User user, UpdateUserRequest request); + + public static partial User MapToUser(CreateUserRequest request); +} \ No newline at end of file diff --git a/Moonlight.Api/Moonlight.Api.csproj b/Moonlight.Api/Moonlight.Api.csproj new file mode 100644 index 00000000..b257bf03 --- /dev/null +++ b/Moonlight.Api/Moonlight.Api.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + enable + enable + Linux + + + + + + + + + + + + + + + + + + + + false + + + + \ No newline at end of file diff --git a/Moonlight.Api/Properties/launchSettings.json b/Moonlight.Api/Properties/launchSettings.json new file mode 100644 index 00000000..97f83af3 --- /dev/null +++ b/Moonlight.Api/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5185", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Moonlight.Api/Services/DbMigrationService.cs b/Moonlight.Api/Services/DbMigrationService.cs new file mode 100644 index 00000000..4d300a47 --- /dev/null +++ b/Moonlight.Api/Services/DbMigrationService.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Moonlight.Api.Database; + +namespace Moonlight.Api.Services; + +public class DbMigrationService : IHostedLifecycleService +{ + private readonly ILogger Logger; + private readonly IServiceProvider ServiceProvider; + + public DbMigrationService(ILogger 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(); + + var pendingMigrations = await context.Database.GetPendingMigrationsAsync(cancellationToken); + var migrationNames = pendingMigrations.ToArray(); + + if (migrationNames.Length == 0) + { + Logger.LogDebug("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; +} \ No newline at end of file diff --git a/Moonlight.Api/Services/UserAuthService.cs b/Moonlight.Api/Services/UserAuthService.cs new file mode 100644 index 00000000..2c6bb267 --- /dev/null +++ b/Moonlight.Api/Services/UserAuthService.cs @@ -0,0 +1,99 @@ +using System.Security.Claims; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Moonlight.Api.Database; +using Moonlight.Api.Database.Entities; + +namespace Moonlight.Api.Services; + +public class UserAuthService +{ + private readonly DatabaseRepository UserRepository; + private readonly ILogger Logger; + + private const string UserIdClaim = "UserId"; + private const string IssuedAtClaim = "IssuedAt"; + + public UserAuthService(DatabaseRepository userRepository, ILogger logger) + { + UserRepository = userRepository; + Logger = logger; + } + + public async Task SyncAsync(ClaimsPrincipal? principal) + { + if (principal is null) + return false; + + var username = principal.FindFirstValue(ClaimTypes.Name); + var email = principal.FindFirstValue(ClaimTypes.Email); + + if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(email)) + { + Logger.LogWarning("Unable to sync user to database as name and/or email claims are missing"); + return false; + } + + // We use email as the primary identifier here + var user = await UserRepository + .Query() + .AsNoTracking() + .FirstOrDefaultAsync(user => user.Email == email); + + if (user == null) // Sync user if not already existing in the database + { + user = await UserRepository.AddAsync(new User() + { + Username = username, + Email = email, + InvalidateTimestamp = DateTimeOffset.UtcNow.AddMinutes(-1) + }); + } + else // Update properties of existing user + { + user.Username = username; + + await UserRepository.UpdateAsync(user); + } + + principal.Identities.First().AddClaims([ + new Claim(UserIdClaim, user.Id.ToString()), + new Claim(IssuedAtClaim, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()) + ]); + + return true; + } + + public async Task ValidateAsync(ClaimsPrincipal? principal) + { + // Ignore malformed claims principal + if (principal is not { Identity.IsAuthenticated: true }) + return false; + + var userIdString = principal.FindFirstValue(UserIdClaim); + + if (!int.TryParse(userIdString, out var userId)) + return false; + + var user = await UserRepository + .Query() + .AsNoTracking() + .FirstOrDefaultAsync(user => user.Id == userId); + + if (user == null) + return false; + + var issuedAtString = principal.FindFirstValue(IssuedAtClaim); + + if (!long.TryParse(issuedAtString, out var issuedAtUnix)) + return false; + + var issuedAt = DateTimeOffset.FromUnixTimeSeconds(issuedAtUnix).ToUniversalTime(); + + // If the issued at timestamp is greater than the token validation timestamp + // everything is fine. If not it means that the token should be invalidated + // as it is too old + + return issuedAt > user.InvalidateTimestamp; + } +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/IAppStartup.cs b/Moonlight.Api/Startup/IAppStartup.cs new file mode 100644 index 00000000..7f49b7d7 --- /dev/null +++ b/Moonlight.Api/Startup/IAppStartup.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Builder; + +namespace Moonlight.Api.Startup; + +public interface IAppStartup +{ + public void PreBuild(WebApplicationBuilder builder); + public void PostBuild(WebApplication application); + public void PostMiddleware(WebApplication application); +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.Auth.cs b/Moonlight.Api/Startup/Startup.Auth.cs new file mode 100644 index 00000000..8f48f3f8 --- /dev/null +++ b/Moonlight.Api/Startup/Startup.Auth.cs @@ -0,0 +1,91 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Moonlight.Api.Configuration; +using Moonlight.Api.Services; + +namespace Moonlight.Api.Startup; + +public partial class Startup +{ + private static void AddAuth(WebApplicationBuilder builder) + { + var oidcOptions = new OidcOptions(); + builder.Configuration.GetSection("WebApp:Oidc").Bind(oidcOptions); + + builder.Services.AddScoped(); + + builder.Services.AddAuthentication("Session") + .AddCookie("Session", null, options => + { + options.Events.OnSigningIn += async context => + { + var authService = context + .HttpContext + .RequestServices + .GetRequiredService(); + + var result = await authService.SyncAsync(context.Principal); + + if (result) + context.Properties.IsPersistent = true; + else + context.Principal = new ClaimsPrincipal(); + }; + + options.Events.OnValidatePrincipal += async context => + { + var authService = context + .HttpContext + .RequestServices + .GetRequiredService(); + + var result = await authService.ValidateAsync(context.Principal); + + if (!result) + context.RejectPrincipal(); + }; + + options.Cookie = new CookieBuilder() + { + Name = "token", + Path = "/", + IsEssential = true, + SecurePolicy = CookieSecurePolicy.SameAsRequest + }; + }) + .AddOpenIdConnect("OIDC", "OpenID Connect", options => + { + options.Authority = oidcOptions.Authority; + options.RequireHttpsMetadata = oidcOptions.RequireHttpsMetadata; + + var scopes = oidcOptions.Scopes ?? ["openid", "email", "profile"]; + + options.Scope.Clear(); + + foreach (var scope in scopes) + options.Scope.Add(scope); + + options.ResponseType = oidcOptions.ResponseType; + options.ClientId = oidcOptions.ClientId; + options.ClientSecret = oidcOptions.ClientSecret; + + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); + options.ClaimActions.MapJsonKey(ClaimTypes.Name, "preferred_username"); + options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); + + options.GetClaimsFromUserInfoEndpoint = true; + }); + + builder.Services.AddAuthorization(); + } + + private static void UseAuth(WebApplication application) + { + application.UseAuthentication(); + application.UseAuthorization(); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.Base.cs b/Moonlight.Api/Startup/Startup.Base.cs new file mode 100644 index 00000000..1acba450 --- /dev/null +++ b/Moonlight.Api/Startup/Startup.Base.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; +using Moonlight.Shared.Http; +using Moonlight.Api.Helpers; + +namespace Moonlight.Api.Startup; + +public partial class Startup +{ + private static void AddBase(WebApplicationBuilder builder) + { + builder.Services.AddControllers().AddJsonOptions(options => + { + options.JsonSerializerOptions.TypeInfoResolverChain.Add(SerializationContext.Default); + }); + + builder.Logging.ClearProviders(); + builder.Logging.AddConsole(options => { options.FormatterName = nameof(AppConsoleFormatter); }); + builder.Logging.AddConsoleFormatter(); + } + + private static void UseBase(WebApplication application) + { + + application.UseRouting(); + } + + private static void MapBase(WebApplication application) + { + application.MapControllers(); + + application.MapFallbackToFile("index.html"); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.Database.cs b/Moonlight.Api/Startup/Startup.Database.cs new file mode 100644 index 00000000..d60678e6 --- /dev/null +++ b/Moonlight.Api/Startup/Startup.Database.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Moonlight.Api.Configuration; +using Moonlight.Api.Database; +using Moonlight.Api.Services; + +namespace Moonlight.Api.Startup; + +public partial class Startup +{ + private static void AddDatabase(WebApplicationBuilder builder) + { + builder.Services.AddOptions().BindConfiguration("WebApp:Database"); + + builder.Services.AddDbContext(); + builder.Services.AddScoped(typeof(DatabaseRepository<>)); + builder.Services.AddHostedService(); + } +} \ No newline at end of file diff --git a/Moonlight.Api/Startup/Startup.cs b/Moonlight.Api/Startup/Startup.cs new file mode 100644 index 00000000..e5066df1 --- /dev/null +++ b/Moonlight.Api/Startup/Startup.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Builder; + +namespace Moonlight.Api.Startup; + +public partial class Startup : IAppStartup +{ + public void PreBuild(WebApplicationBuilder builder) + { + AddBase(builder); + AddAuth(builder); + AddDatabase(builder); + } + + public void PostBuild(WebApplication application) + { + UseBase(application); + UseAuth(application); + } + + public void PostMiddleware(WebApplication application) + { + MapBase(application); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer.Runtime/Moonlight.ApiServer.Runtime.csproj b/Moonlight.ApiServer.Runtime/Moonlight.ApiServer.Runtime.csproj deleted file mode 100644 index 44073a4e..00000000 --- a/Moonlight.ApiServer.Runtime/Moonlight.ApiServer.Runtime.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - net9.0 - enable - enable - True - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - diff --git a/Moonlight.ApiServer.Runtime/PluginLoader.cs b/Moonlight.ApiServer.Runtime/PluginLoader.cs deleted file mode 100644 index 2d5fc003..00000000 --- a/Moonlight.ApiServer.Runtime/PluginLoader.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MoonCore.PluginFramework; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer.Runtime; - -[PluginLoader] -public partial class PluginLoader : IPluginStartup -{ - -} \ No newline at end of file diff --git a/Moonlight.ApiServer.Runtime/Plugins.props b/Moonlight.ApiServer.Runtime/Plugins.props deleted file mode 100644 index 3e0a6663..00000000 --- a/Moonlight.ApiServer.Runtime/Plugins.props +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Moonlight.ApiServer.Runtime/Program.cs b/Moonlight.ApiServer.Runtime/Program.cs deleted file mode 100644 index 5bb192b2..00000000 --- a/Moonlight.ApiServer.Runtime/Program.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Runtime; -using Moonlight.ApiServer.Startup; - -var pluginLoader = new PluginLoader(); -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(); \ No newline at end of file diff --git a/Moonlight.ApiServer.Runtime/Properties/launchSettings.json b/Moonlight.ApiServer.Runtime/Properties/launchSettings.json deleted file mode 100644 index 8c3160eb..00000000 --- a/Moonlight.ApiServer.Runtime/Properties/launchSettings.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5165", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "HTTP_PROXY": "", - "HTTPS_PROXY": "" - }, - "hotReloadEnabled": true - }, - "WASM Debug": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "applicationUrl": "http://localhost:5165", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "HTTP_PROXY": "", - "HTTPS_PROXY": "" - }, - "hotReloadEnabled": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}" - } - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Configuration/AppConfiguration.cs b/Moonlight.ApiServer/Configuration/AppConfiguration.cs deleted file mode 100644 index bdcc9b42..00000000 --- a/Moonlight.ApiServer/Configuration/AppConfiguration.cs +++ /dev/null @@ -1,154 +0,0 @@ -using MoonCore.Helpers; -using Moonlight.ApiServer.Implementations.LocalAuth; -using YamlDotNet.Serialization; - -namespace Moonlight.ApiServer.Configuration; - -public record AppConfiguration -{ - [YamlMember(Description = "Moonlight configuration\n\n\nThe public url your instance should be accessible through")] - public string PublicUrl { get; set; } = "http://localhost:5165"; - - [YamlMember(Description = "\nThe credentials of the postgres which moonlight should use")] - public DatabaseConfig Database { get; set; } = new(); - - [YamlMember(Description = "\nSettings regarding authentication")] - public AuthenticationConfig Authentication { get; set; } = new(); - - [YamlMember(Description = "\nThese options are only meant for development purposes")] - public DevelopmentConfig Development { get; set; } = new(); - - [YamlMember(Description = "\nSettings for hosting the frontend")] - public FrontendData Frontend { get; set; } = new(); - - [YamlMember(Description = "\nSettings for the internal web server moonlight is running in")] - public KestrelConfig Kestrel { get; set; } = new(); - - [YamlMember(Description = "\nSettings for the internal file manager for moonlights storage access")] - public FilesData Files { get; set; } = new(); - - [YamlMember(Description = "\nSettings for open telemetry")] - public OpenTelemetryData OpenTelemetry { get; set; } = new(); - - [YamlMember(Description = "\nConfiguration for the realtime communication solution SignalR")] - public SignalRData SignalR { get; set; } = new(); - - public static AppConfiguration CreateEmpty() - { - return new AppConfiguration() - { - // Set arrays as empty here - - Kestrel = new() - { - AllowedOrigins = [] - }, - Authentication = new() - { - EnabledSchemes = [] - } - }; - } - - public record SignalRData - { - [YamlMember(Description = - "\nWhether to use redis (or any other redis compatible solution) to scale out SignalR hubs. This is required when using multiple api server replicas")] - public bool UseRedis { get; set; } = false; - - public string RedisConnectionString { get; set; } = ""; - } - - public record FilesData - { - [YamlMember(Description = "The maximum file size limit a combine operation is allowed to process")] - public double CombineLimit { get; set; } = ByteConverter.FromGigaBytes(5).MegaBytes; - } - - public record FrontendData - { - [YamlMember(Description = "Enable the hosting of the frontend. Disable this if you only want to run the api server")] - public bool EnableHosting { get; set; } = true; - } - - public record DatabaseConfig - { - public string Host { get; set; } = "your-database-host.name"; - public int Port { get; set; } = 5432; - - public string Username { get; set; } = "db_user"; - public string Password { get; set; } = "db_password"; - - public string Database { get; set; } = "db_name"; - } - - public record AuthenticationConfig - { - [YamlMember(Description = "The secret token to use for creating jwts and encrypting things. This needs to be at least 32 characters long")] - public string Secret { get; set; } = Formatter.GenerateString(32); - - [YamlMember(Description = "Settings for the user sessions")] - public SessionsConfig Sessions { get; set; } = new(); - - [YamlMember(Description = "This specifies if the first registered/synced user will become an admin automatically")] - public bool FirstUserAdmin { get; set; } = true; - - [YamlMember(Description = "This specifies the authentication schemes the frontend should be able to challenge")] - public string[] EnabledSchemes { get; set; } = [LocalAuthConstants.AuthenticationScheme]; - } - - public record SessionsConfig - { - public string CookieName { get; set; } = "session"; - public int ExpiresIn { get; set; } = 10; - } - - public record DevelopmentConfig - { - [YamlMember(Description = "This toggles the availability of the api docs via /api/swagger")] - public bool EnableApiDocs { get; set; } = false; - } - - public record KestrelConfig - { - [YamlMember(Description = "The upload limit in megabytes for the api server")] - public int UploadLimit { get; set; } = 100; - - [YamlMember(Description = "The allowed origins for the api server. Use * to allow all origins (which is not advised)")] - public string[] AllowedOrigins { get; set; } = ["*"]; - } - - public record OpenTelemetryData - { - [YamlMember(Description = "This enables open telemetry for moonlight")] - public bool Enable { get; set; } = false; - - public OpenTelemetryMetricsData Metrics { get; set; } = new(); - public OpenTelemetryTracesData Traces { get; set; } = new(); - public OpenTelemetryLogsData Logs { get; set; } = new(); - } - - public record OpenTelemetryMetricsData - { - [YamlMember(Description = "This enables the exporting of metrics")] - public bool Enable { get; set; } = true; - - [YamlMember(Description = "Enables the /metrics exporter for prometheus")] - public bool EnablePrometheus { get; set; } = false; - - [YamlMember(Description = "The interval in which metrics are created, specified in seconds")] - public int Interval { get; set; } = 15; - } - - public record OpenTelemetryTracesData - { - [YamlMember(Description = "This enables the exporting of traces")] - public bool Enable { get; set; } = true; - } - - public record OpenTelemetryLogsData - { - [YamlMember(Description = "This enables the exporting of logs")] - public bool Enable { get; set; } = true; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/CoreDataContext.cs b/Moonlight.ApiServer/Database/CoreDataContext.cs deleted file mode 100644 index 5f6df282..00000000 --- a/Moonlight.ApiServer/Database/CoreDataContext.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Hangfire.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Models; - -namespace Moonlight.ApiServer.Database; - -public class CoreDataContext : DbContext -{ - private readonly AppConfiguration Configuration; - - public DbSet Users { get; set; } - public DbSet ApiKeys { get; set; } - public DbSet Themes { get; set; } - - public CoreDataContext(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", "core"); - }); - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Model.SetDefaultSchema("core"); - - base.OnModelCreating(modelBuilder); - modelBuilder.OnHangfireModelCreating(); - - modelBuilder.Ignore(); - modelBuilder.Entity() - .OwnsOne(x => x.Content, builder => - { - builder.ToJson(); - }); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/ApiKey.cs b/Moonlight.ApiServer/Database/Entities/ApiKey.cs deleted file mode 100644 index 9b75d2ec..00000000 --- a/Moonlight.ApiServer/Database/Entities/ApiKey.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Moonlight.ApiServer.Database.Entities; - -public class ApiKey -{ - public int Id { get; set; } - - public string Description { get; set; } - - public string[] Permissions { get; set; } = []; - - public DateTimeOffset ExpiresAt { get; set; } - - public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow; -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/Theme.cs b/Moonlight.ApiServer/Database/Entities/Theme.cs deleted file mode 100644 index 22f1ca04..00000000 --- a/Moonlight.ApiServer/Database/Entities/Theme.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Moonlight.ApiServer.Models; - -namespace Moonlight.ApiServer.Database.Entities; - -public class Theme -{ - public int Id { get; set; } - - public bool IsEnabled { get; set; } - - public string Name { get; set; } - public string Author { get; set; } - public string Version { get; set; } - - public string? UpdateUrl { get; set; } - public string? DonateUrl { get; set; } - - public ApplicationTheme Content { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Entities/User.cs b/Moonlight.ApiServer/Database/Entities/User.cs deleted file mode 100644 index 4a09c19d..00000000 --- a/Moonlight.ApiServer/Database/Entities/User.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Moonlight.ApiServer.Database.Entities; - -public class User -{ - public int Id { get; set; } - - public string Username { get; set; } - public string Email { get; set; } - public string Password { get; set; } - public DateTimeOffset TokenValidTimestamp { get; set; } = DateTimeOffset.MinValue; - public string[] Permissions { get; set; } = []; -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.Designer.cs b/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.Designer.cs deleted file mode 100644 index 9c9d9577..00000000 --- a/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.Designer.cs +++ /dev/null @@ -1,565 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Moonlight.ApiServer.Database; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Moonlight.ApiServer.Database.Migrations -{ - [DbContext(typeof(CoreDataContext))] - [Migration("20250919201409_RecreatedMigrationsForChangeOfSchema")] - partial class RecreatedMigrationsForChangeOfSchema - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("core") - .HasAnnotation("ProductVersion", "9.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Value") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("Key", "Value"); - - b.ToTable("HangfireCounter", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b => - { - b.Property("Key") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Field") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Key", "Field"); - - b.HasIndex("ExpireAt"); - - b.ToTable("HangfireHash", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("InvocationData") - .IsRequired() - .HasColumnType("text"); - - b.Property("StateId") - .HasColumnType("bigint"); - - b.Property("StateName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("StateId"); - - b.HasIndex("StateName"); - - b.ToTable("HangfireJob", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => - { - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("JobId", "Name"); - - b.ToTable("HangfireJobParameter", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b => - { - b.Property("Key") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Position") - .HasColumnType("integer"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Key", "Position"); - - b.HasIndex("ExpireAt"); - - b.ToTable("HangfireList", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b => - { - b.Property("Id") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("AcquiredAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("HangfireLock", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FetchedAt") - .IsConcurrencyToken() - .HasColumnType("timestamp with time zone"); - - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Queue") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("Queue", "FetchedAt"); - - b.ToTable("HangfireQueuedJob", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b => - { - b.Property("Id") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Heartbeat") - .HasColumnType("timestamp with time zone"); - - b.Property("Queues") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WorkerCount") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Heartbeat"); - - b.ToTable("HangfireServer", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b => - { - b.Property("Key") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Value") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Score") - .HasColumnType("double precision"); - - b.HasKey("Key", "Value"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("Key", "Score"); - - b.ToTable("HangfireSet", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasColumnType("text"); - - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Reason") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.ToTable("HangfireState", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.PrimitiveCollection("Permissions") - .IsRequired() - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.ToTable("ApiKeys", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Author") - .IsRequired() - .HasColumnType("text"); - - b.Property("DonateUrl") - .HasColumnType("text"); - - b.Property("IsEnabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("UpdateUrl") - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Themes", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("Password") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Permissions") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("TokenValidTimestamp") - .HasColumnType("timestamp with time zone"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State") - .WithMany() - .HasForeignKey("StateId"); - - b.Navigation("State"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("Parameters") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("QueuedJobs") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("States") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => - { - b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 => - { - b1.Property("ThemeId") - .HasColumnType("integer"); - - b1.Property("Border") - .HasColumnType("real"); - - b1.Property("ColorAccent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorAccentContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBackground") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase100") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase150") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase200") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase250") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase300") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBaseContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorError") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorErrorContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorInfo") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorInfoContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorNeutral") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorNeutralContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorPrimary") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorPrimaryContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSecondary") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSecondaryContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSuccess") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSuccessContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorWarning") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorWarningContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("Depth") - .HasColumnType("integer"); - - b1.Property("Noise") - .HasColumnType("integer"); - - b1.Property("RadiusBox") - .HasColumnType("real"); - - b1.Property("RadiusField") - .HasColumnType("real"); - - b1.Property("RadiusSelector") - .HasColumnType("real"); - - b1.Property("SizeField") - .HasColumnType("real"); - - b1.Property("SizeSelector") - .HasColumnType("real"); - - b1.HasKey("ThemeId"); - - b1.ToTable("Themes", "core"); - - b1.ToJson("Content"); - - b1.WithOwner() - .HasForeignKey("ThemeId"); - }); - - b.Navigation("Content") - .IsRequired(); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.Navigation("Parameters"); - - b.Navigation("QueuedJobs"); - - b.Navigation("States"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.cs b/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.cs deleted file mode 100644 index 5330cca4..00000000 --- a/Moonlight.ApiServer/Database/Migrations/20250919201409_RecreatedMigrationsForChangeOfSchema.cs +++ /dev/null @@ -1,399 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Moonlight.ApiServer.Database.Migrations -{ - /// - public partial class RecreatedMigrationsForChangeOfSchema : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.EnsureSchema( - name: "core"); - - migrationBuilder.CreateTable( - name: "ApiKeys", - schema: "core", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Description = table.Column(type: "text", nullable: false), - Permissions = table.Column(type: "text[]", nullable: false), - ExpiresAt = table.Column(type: "timestamp with time zone", nullable: false), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_ApiKeys", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireCounter", - schema: "core", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Key = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Value = table.Column(type: "bigint", nullable: false), - ExpireAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireCounter", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireHash", - schema: "core", - columns: table => new - { - Key = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Field = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Value = table.Column(type: "text", nullable: true), - ExpireAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireHash", x => new { x.Key, x.Field }); - }); - - migrationBuilder.CreateTable( - name: "HangfireList", - schema: "core", - columns: table => new - { - Key = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Position = table.Column(type: "integer", nullable: false), - Value = table.Column(type: "text", nullable: true), - ExpireAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireList", x => new { x.Key, x.Position }); - }); - - migrationBuilder.CreateTable( - name: "HangfireLock", - schema: "core", - columns: table => new - { - Id = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - AcquiredAt = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireLock", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireServer", - schema: "core", - columns: table => new - { - Id = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - StartedAt = table.Column(type: "timestamp with time zone", nullable: false), - Heartbeat = table.Column(type: "timestamp with time zone", nullable: false), - WorkerCount = table.Column(type: "integer", nullable: false), - Queues = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireServer", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireSet", - schema: "core", - columns: table => new - { - Key = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), - Value = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Score = table.Column(type: "double precision", nullable: false), - ExpireAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireSet", x => new { x.Key, x.Value }); - }); - - migrationBuilder.CreateTable( - name: "Themes", - schema: "core", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - IsEnabled = table.Column(type: "boolean", nullable: false), - Name = table.Column(type: "text", nullable: false), - Author = table.Column(type: "text", nullable: false), - Version = table.Column(type: "text", nullable: false), - UpdateUrl = table.Column(type: "text", nullable: true), - DonateUrl = table.Column(type: "text", nullable: true), - Content = table.Column(type: "jsonb", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Themes", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Users", - schema: "core", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Username = table.Column(type: "text", nullable: false), - Email = table.Column(type: "text", nullable: false), - Password = table.Column(type: "text", nullable: false), - TokenValidTimestamp = table.Column(type: "timestamp with time zone", nullable: false), - Permissions = table.Column(type: "text[]", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireJob", - schema: "core", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - StateId = table.Column(type: "bigint", nullable: true), - StateName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - ExpireAt = table.Column(type: "timestamp with time zone", nullable: true), - InvocationData = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireJob", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "HangfireJobParameter", - schema: "core", - columns: table => new - { - JobId = table.Column(type: "bigint", nullable: false), - Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Value = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireJobParameter", x => new { x.JobId, x.Name }); - table.ForeignKey( - name: "FK_HangfireJobParameter_HangfireJob_JobId", - column: x => x.JobId, - principalSchema: "core", - principalTable: "HangfireJob", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "HangfireQueuedJob", - schema: "core", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - JobId = table.Column(type: "bigint", nullable: false), - Queue = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - FetchedAt = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireQueuedJob", x => x.Id); - table.ForeignKey( - name: "FK_HangfireQueuedJob_HangfireJob_JobId", - column: x => x.JobId, - principalSchema: "core", - principalTable: "HangfireJob", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "HangfireState", - schema: "core", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - JobId = table.Column(type: "bigint", nullable: false), - Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - Reason = table.Column(type: "text", nullable: true), - CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), - Data = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_HangfireState", x => x.Id); - table.ForeignKey( - name: "FK_HangfireState_HangfireJob_JobId", - column: x => x.JobId, - principalSchema: "core", - principalTable: "HangfireJob", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_HangfireCounter_ExpireAt", - schema: "core", - table: "HangfireCounter", - column: "ExpireAt"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireCounter_Key_Value", - schema: "core", - table: "HangfireCounter", - columns: new[] { "Key", "Value" }); - - migrationBuilder.CreateIndex( - name: "IX_HangfireHash_ExpireAt", - schema: "core", - table: "HangfireHash", - column: "ExpireAt"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireJob_ExpireAt", - schema: "core", - table: "HangfireJob", - column: "ExpireAt"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireJob_StateId", - schema: "core", - table: "HangfireJob", - column: "StateId"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireJob_StateName", - schema: "core", - table: "HangfireJob", - column: "StateName"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireList_ExpireAt", - schema: "core", - table: "HangfireList", - column: "ExpireAt"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireQueuedJob_JobId", - schema: "core", - table: "HangfireQueuedJob", - column: "JobId"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireQueuedJob_Queue_FetchedAt", - schema: "core", - table: "HangfireQueuedJob", - columns: new[] { "Queue", "FetchedAt" }); - - migrationBuilder.CreateIndex( - name: "IX_HangfireServer_Heartbeat", - schema: "core", - table: "HangfireServer", - column: "Heartbeat"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireSet_ExpireAt", - schema: "core", - table: "HangfireSet", - column: "ExpireAt"); - - migrationBuilder.CreateIndex( - name: "IX_HangfireSet_Key_Score", - schema: "core", - table: "HangfireSet", - columns: new[] { "Key", "Score" }); - - migrationBuilder.CreateIndex( - name: "IX_HangfireState_JobId", - schema: "core", - table: "HangfireState", - column: "JobId"); - - migrationBuilder.AddForeignKey( - name: "FK_HangfireJob_HangfireState_StateId", - schema: "core", - table: "HangfireJob", - column: "StateId", - principalSchema: "core", - principalTable: "HangfireState", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_HangfireJob_HangfireState_StateId", - schema: "core", - table: "HangfireJob"); - - migrationBuilder.DropTable( - name: "ApiKeys", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireCounter", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireHash", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireJobParameter", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireList", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireLock", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireQueuedJob", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireServer", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireSet", - schema: "core"); - - migrationBuilder.DropTable( - name: "Themes", - schema: "core"); - - migrationBuilder.DropTable( - name: "Users", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireState", - schema: "core"); - - migrationBuilder.DropTable( - name: "HangfireJob", - schema: "core"); - } - } -} diff --git a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs b/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs deleted file mode 100644 index 540e66c1..00000000 --- a/Moonlight.ApiServer/Database/Migrations/CoreDataContextModelSnapshot.cs +++ /dev/null @@ -1,562 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Moonlight.ApiServer.Database; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Moonlight.ApiServer.Database.Migrations -{ - [DbContext(typeof(CoreDataContext))] - partial class CoreDataContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasDefaultSchema("core") - .HasAnnotation("ProductVersion", "9.0.8") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireCounter", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Key") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Value") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("Key", "Value"); - - b.ToTable("HangfireCounter", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireHash", b => - { - b.Property("Key") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Field") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Key", "Field"); - - b.HasIndex("ExpireAt"); - - b.ToTable("HangfireHash", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("InvocationData") - .IsRequired() - .HasColumnType("text"); - - b.Property("StateId") - .HasColumnType("bigint"); - - b.Property("StateName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("StateId"); - - b.HasIndex("StateName"); - - b.ToTable("HangfireJob", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => - { - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("JobId", "Name"); - - b.ToTable("HangfireJobParameter", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireList", b => - { - b.Property("Key") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Position") - .HasColumnType("integer"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Key", "Position"); - - b.HasIndex("ExpireAt"); - - b.ToTable("HangfireList", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireLock", b => - { - b.Property("Id") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("AcquiredAt") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("HangfireLock", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FetchedAt") - .IsConcurrencyToken() - .HasColumnType("timestamp with time zone"); - - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Queue") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.HasIndex("Queue", "FetchedAt"); - - b.ToTable("HangfireQueuedJob", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireServer", b => - { - b.Property("Id") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Heartbeat") - .HasColumnType("timestamp with time zone"); - - b.Property("Queues") - .IsRequired() - .HasColumnType("text"); - - b.Property("StartedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("WorkerCount") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("Heartbeat"); - - b.ToTable("HangfireServer", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireSet", b => - { - b.Property("Key") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("Value") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("ExpireAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Score") - .HasColumnType("double precision"); - - b.HasKey("Key", "Value"); - - b.HasIndex("ExpireAt"); - - b.HasIndex("Key", "Score"); - - b.ToTable("HangfireSet", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .IsRequired() - .HasColumnType("text"); - - b.Property("JobId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("Reason") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("JobId"); - - b.ToTable("HangfireState", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.ApiKey", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExpiresAt") - .HasColumnType("timestamp with time zone"); - - b.PrimitiveCollection("Permissions") - .IsRequired() - .HasColumnType("text[]"); - - b.HasKey("Id"); - - b.ToTable("ApiKeys", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Author") - .IsRequired() - .HasColumnType("text"); - - b.Property("DonateUrl") - .HasColumnType("text"); - - b.Property("IsEnabled") - .HasColumnType("boolean"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("UpdateUrl") - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Themes", "core"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.User", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Email") - .IsRequired() - .HasColumnType("text"); - - b.Property("Password") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Permissions") - .IsRequired() - .HasColumnType("text[]"); - - b.Property("TokenValidTimestamp") - .HasColumnType("timestamp with time zone"); - - b.Property("Username") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Users", "core"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireState", "State") - .WithMany() - .HasForeignKey("StateId"); - - b.Navigation("State"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJobParameter", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("Parameters") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireQueuedJob", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("QueuedJobs") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireState", b => - { - b.HasOne("Hangfire.EntityFrameworkCore.HangfireJob", "Job") - .WithMany("States") - .HasForeignKey("JobId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Job"); - }); - - modelBuilder.Entity("Moonlight.ApiServer.Database.Entities.Theme", b => - { - b.OwnsOne("Moonlight.ApiServer.Models.ApplicationTheme", "Content", b1 => - { - b1.Property("ThemeId") - .HasColumnType("integer"); - - b1.Property("Border") - .HasColumnType("real"); - - b1.Property("ColorAccent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorAccentContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBackground") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase100") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase150") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase200") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase250") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBase300") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorBaseContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorError") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorErrorContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorInfo") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorInfoContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorNeutral") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorNeutralContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorPrimary") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorPrimaryContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSecondary") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSecondaryContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSuccess") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorSuccessContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorWarning") - .IsRequired() - .HasColumnType("text"); - - b1.Property("ColorWarningContent") - .IsRequired() - .HasColumnType("text"); - - b1.Property("Depth") - .HasColumnType("integer"); - - b1.Property("Noise") - .HasColumnType("integer"); - - b1.Property("RadiusBox") - .HasColumnType("real"); - - b1.Property("RadiusField") - .HasColumnType("real"); - - b1.Property("RadiusSelector") - .HasColumnType("real"); - - b1.Property("SizeField") - .HasColumnType("real"); - - b1.Property("SizeSelector") - .HasColumnType("real"); - - b1.HasKey("ThemeId"); - - b1.ToTable("Themes", "core"); - - b1.ToJson("Content"); - - b1.WithOwner() - .HasForeignKey("ThemeId"); - }); - - b.Navigation("Content") - .IsRequired(); - }); - - modelBuilder.Entity("Hangfire.EntityFrameworkCore.HangfireJob", b => - { - b.Navigation("Parameters"); - - b.Navigation("QueuedJobs"); - - b.Navigation("States"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs b/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs deleted file mode 100644 index 11105ff9..00000000 --- a/Moonlight.ApiServer/Extensions/ZipArchiveExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.IO.Compression; -using System.Text; - -namespace Moonlight.ApiServer.Extensions; - -public static class ZipArchiveExtensions -{ - public static async Task AddBinaryAsync(this ZipArchive archive, string name, byte[] bytes) - { - var entry = archive.CreateEntry(name); - await using var dataStream = entry.Open(); - - await dataStream.WriteAsync(bytes); - await dataStream.FlushAsync(); - } - - public static async Task AddTextAsync(this ZipArchive archive, string name, string content) - { - var data = Encoding.UTF8.GetBytes(content); - await archive.AddBinaryAsync(name, data); - } - - public static async Task AddFileAsync(this ZipArchive archive, string name, string path) - { - var fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - var entry = archive.CreateEntry(name); - await using var dataStream = entry.Open(); - - await fs.CopyToAsync(dataStream); - await dataStream.FlushAsync(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Helpers/FilePathHelper.cs b/Moonlight.ApiServer/Helpers/FilePathHelper.cs deleted file mode 100644 index 49937bb5..00000000 --- a/Moonlight.ApiServer/Helpers/FilePathHelper.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Moonlight.ApiServer.Helpers; - -public class FilePathHelper -{ - public static string SanitizePath(string path) - { - if (string.IsNullOrWhiteSpace(path)) - return string.Empty; - - // Normalize separators - path = path.Replace('\\', '/'); - - // Remove ".." and "." - var parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries) - .Where(part => part != ".." && part != "."); - - var sanitized = string.Join("/", parts); - - // Ensure it does not start with a slash - if (sanitized.StartsWith('/')) - sanitized = sanitized.TrimStart('/'); - - return sanitized; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs deleted file mode 100644 index f79fc823..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs +++ /dev/null @@ -1,150 +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 Moonlight.ApiServer.Mappers; -using Moonlight.ApiServer.Services; -using Moonlight.Shared.Http.Requests.Admin.ApiKeys; -using Moonlight.Shared.Http.Responses.Admin.ApiKeys; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.ApiKeys; - -[ApiController] -[Route("api/admin/apikeys")] -public class ApiKeysController : Controller -{ - private readonly DatabaseRepository ApiKeyRepository; - private readonly ApiKeyService ApiKeyService; - - public ApiKeysController(DatabaseRepository apiKeyRepository, ApiKeyService apiKeyService) - { - ApiKeyRepository = apiKeyRepository; - ApiKeyService = apiKeyService; - } - - [HttpGet] - [Authorize(Policy = "permissions:admin.apikeys.get")] - public async Task>> GetAsync( - [FromQuery] int startIndex, - [FromQuery] int count, - [FromQuery] string? orderBy, - [FromQuery] string? filter, - [FromQuery] string orderByDir = "asc" - ) - { - if (count > 100) - return Problem("You cannot fetch more items than 100 at a time", statusCode: 400); - - IQueryable query = ApiKeyRepository.Get(); - - query = orderBy switch - { - nameof(ApiKey.Id) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Id) - : query.OrderBy(x => x.Id), - - nameof(ApiKey.ExpiresAt) => orderByDir == "desc" - ? query.OrderByDescending(x => x.ExpiresAt) - : query.OrderBy(x => x.ExpiresAt), - - nameof(ApiKey.CreatedAt) => orderByDir == "desc" - ? query.OrderByDescending(x => x.CreatedAt) - : query.OrderBy(x => x.CreatedAt), - - _ => query.OrderBy(x => x.Id) - }; - - if (!string.IsNullOrEmpty(filter)) - { - query = query.Where(x => - EF.Functions.ILike(x.Description, $"%{filter}%") - ); - } - - var totalCount = await query.CountAsync(); - - var items = await query - .Skip(startIndex) - .Take(count) - .AsNoTracking() - .ProjectToResponse() - .ToArrayAsync(); - - return new CountedData() - { - Items = items, - TotalCount = totalCount - }; - } - - [HttpGet("{id:int}")] - [Authorize(Policy = "permissions:admin.apikeys.get")] - public async Task> GetSingleAsync(int id) - { - var apiKey = await ApiKeyRepository - .Get() - .AsNoTracking() - .ProjectToResponse() - .FirstOrDefaultAsync(x => x.Id == id); - - if (apiKey == null) - return Problem("No api key with that id found", statusCode: 404); - - return apiKey; - } - - [HttpPost] - [Authorize(Policy = "permissions:admin.apikeys.create")] - public async Task CreateAsync([FromBody] CreateApiKeyRequest request) - { - var apiKey = ApiKeyMapper.ToApiKey(request); - - var finalApiKey = await ApiKeyRepository.AddAsync(apiKey); - - var response = new CreateApiKeyResponse - { - Id = finalApiKey.Id, - Permissions = finalApiKey.Permissions, - Description = finalApiKey.Description, - ExpiresAt = finalApiKey.ExpiresAt, - Secret = ApiKeyService.GenerateJwt(finalApiKey) - }; - - return response; - } - - [HttpPatch("{id:int}")] - [Authorize(Policy = "permissions:admin.apikeys.update")] - public async Task> UpdateAsync([FromRoute] int id, [FromBody] UpdateApiKeyRequest request) - { - var apiKey = await ApiKeyRepository - .Get() - .FirstOrDefaultAsync(x => x.Id == id); - - if (apiKey == null) - return Problem("No api key with that id found", statusCode: 404); - - ApiKeyMapper.Merge(apiKey, request); - - await ApiKeyRepository.UpdateAsync(apiKey); - - return ApiKeyMapper.ToResponse(apiKey); - } - - [HttpDelete("{id:int}")] - [Authorize(Policy = "permissions:admin.apikeys.delete")] - public async Task DeleteAsync([FromRoute] int id) - { - var apiKey = await ApiKeyRepository - .Get() - .FirstOrDefaultAsync(x => x.Id == id); - - if (apiKey == null) - return Problem("No api key with that id found", statusCode: 404); - - await ApiKeyRepository.RemoveAsync(apiKey); - return NoContent(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/AdvancedController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/AdvancedController.cs deleted file mode 100644 index 68204362..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/AdvancedController.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Services; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; - -[Authorize] -[ApiController] -[Route("api/admin/system/advanced")] -public class AdvancedController : Controller -{ - private readonly FrontendService FrontendService; - - public AdvancedController(FrontendService frontendService) - { - FrontendService = frontendService; - } - - [HttpGet("frontend")] - [Authorize(Policy = "permissions:admin.system.advanced.frontend")] - public async Task FrontendAsync() - { - var stream = await FrontendService.GenerateZipAsync(); - await Results.File(stream, fileDownloadName: "frontend.zip").ExecuteAsync(HttpContext); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Customisation/ThemesController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Customisation/ThemesController.cs deleted file mode 100644 index 670fcbe6..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Customisation/ThemesController.cs +++ /dev/null @@ -1,153 +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 Moonlight.ApiServer.Mappers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; -using Moonlight.Shared.Http.Responses.Admin; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Customisation; - -[ApiController] -[Route("api/admin/system/customisation/themes")] -public class ThemesController : Controller -{ - private readonly DatabaseRepository ThemeRepository; - - public ThemesController(DatabaseRepository themeRepository) - { - ThemeRepository = themeRepository; - } - - [HttpGet] - [Authorize(Policy = "permissions:admin.system.customisation.themes.read")] - public async Task>> GetAsync( - [FromQuery] int startIndex, - [FromQuery] int count, - [FromQuery] string? orderBy, - [FromQuery] string? filter, - [FromQuery] string orderByDir = "asc" - ) - { - if (count > 100) - return Problem("You cannot fetch more items than 100 at a time", statusCode: 400); - - IQueryable query = ThemeRepository.Get(); - - query = orderBy switch - { - nameof(Theme.Id) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Id) - : query.OrderBy(x => x.Id), - - nameof(Theme.Name) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Name) - : query.OrderBy(x => x.Name), - - nameof(Theme.Version) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Version) - : query.OrderBy(x => x.Version), - - _ => query.OrderBy(x => x.Id) - }; - - if (!string.IsNullOrEmpty(filter)) - { - query = query.Where(x => - EF.Functions.ILike(x.Name, $"%{filter}%") - ); - } - - var totalCount = await query.CountAsync(); - - var items = await query - .Skip(startIndex) - .Take(count) - .AsNoTracking() - .ProjectToResponse() - .ToArrayAsync(); - - return new CountedData() - { - Items = items, - TotalCount = totalCount - }; - } - - [HttpGet("{id:int}")] - [Authorize(Policy = "permissions:admin.system.customisation.themes.read")] - public async Task> GetSingleAsync([FromRoute] int id) - { - var theme = await ThemeRepository - .Get() - .AsNoTracking() - .ProjectToResponse() - .FirstOrDefaultAsync(t => t.Id == id); - - if (theme == null) - return Problem("Theme with this id not found", statusCode: 404); - - return theme; - } - - [HttpPost] - [Authorize(Policy = "permissions:admin.system.customisation.themes.write")] - public async Task> CreateAsync([FromBody] CreateThemeRequest request) - { - var theme = ThemeMapper.ToTheme(request); - - var finalTheme = await ThemeRepository.AddAsync(theme); - - return ThemeMapper.ToResponse(finalTheme); - } - - [HttpPatch("{id:int}")] - [Authorize(Policy = "permissions:admin.system.customisation.themes.write")] - public async Task> UpdateAsync([FromRoute] int id, [FromBody] UpdateThemeRequest request) - { - var theme = await ThemeRepository - .Get() - .FirstOrDefaultAsync(t => t.Id == id); - - if (theme == null) - return Problem("Theme with this id not found", statusCode: 404); - - // Disable all other enabled themes if we are enabling the current theme. - // This ensures only one theme is enabled at the time - if (request.IsEnabled) - { - var otherThemes = await ThemeRepository - .Get() - .Where(x => x.IsEnabled && x.Id != id) - .ToArrayAsync(); - - foreach (var otherTheme in otherThemes) - otherTheme.IsEnabled = false; - - await ThemeRepository.RunTransactionAsync(set => { set.UpdateRange(otherThemes); }); - } - - ThemeMapper.Merge(theme, request); - - await ThemeRepository.UpdateAsync(theme); - - return ThemeMapper.ToResponse(theme); - } - - [HttpDelete("{id:int}")] - [Authorize(Policy = "permissions:admin.system.customisation.themes.write")] - public async Task DeleteAsync([FromRoute] int id) - { - var theme = await ThemeRepository - .Get() - .FirstOrDefaultAsync(x => x.Id == id); - - if (theme == null) - return Problem("Theme with this id not found", statusCode: 404); - - await ThemeRepository.RemoveAsync(theme); - return NoContent(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs deleted file mode 100644 index d07e7ee9..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/DiagnoseController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Services; -using Moonlight.Shared.Http.Requests.Admin.Sys; -using Moonlight.Shared.Http.Responses.Admin.Sys; - - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; - -[ApiController] -[Route("api/admin/system/diagnose")] -[Authorize(Policy = "permissions:admin.system.diagnose")] -public class DiagnoseController : Controller -{ - private readonly DiagnoseService DiagnoseService; - - public DiagnoseController(DiagnoseService diagnoseService) - { - DiagnoseService = diagnoseService; - } - - [HttpPost] - public async Task DiagnoseAsync([FromBody] GenerateDiagnoseRequest request) - { - var stream = await DiagnoseService.GenerateDiagnoseAsync(request.Providers); - - return File(stream, "application/zip", "diagnose.zip"); - } - - [HttpGet("providers")] - public async Task> GetProvidersAsync() - { - return await DiagnoseService.GetProvidersAsync(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CombineController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CombineController.cs deleted file mode 100644 index 1b5e3410..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CombineController.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Helpers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Files; - -[ApiController] -[Route("api/admin/system/files")] -[Authorize(Policy = "permissions:admin.system.files")] -public class CombineController : Controller -{ - private readonly AppConfiguration Configuration; - - private const string BaseDirectory = "storage"; - - public CombineController(AppConfiguration configuration) - { - Configuration = configuration; - } - - [HttpPost("combine")] - public async Task CombineAsync([FromBody] CombineRequest request) - { - // Validate file lenght - if (request.Files.Length < 2) - return Results.Problem("At least two files are required", statusCode: 400); - - // Resolve the physical paths - var destination = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(request.Destination)); - - var files = request.Files - .Select(path => Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(path))) - .ToArray(); - - // Validate max file size - long combinedSize = 0; - - foreach (var file in files) - { - var fi = new FileInfo(file); - combinedSize += fi.Length; - } - - if (ByteConverter.FromBytes(combinedSize).MegaBytes > Configuration.Files.CombineLimit) - { - return Results.Problem("The combine operation exceeds the maximum file size", statusCode: 400); - } - - // Combine files - - await using var destinationFs = System.IO.File.Open( - destination, - FileMode.Create, - FileAccess.ReadWrite, - FileShare.Read - ); - - foreach (var file in files) - { - await using var fs = System.IO.File.Open( - file, - FileMode.Open, - FileAccess.ReadWrite - ); - - await fs.CopyToAsync(destinationFs); - await destinationFs.FlushAsync(); - - fs.Close(); - } - - await destinationFs.FlushAsync(); - destinationFs.Close(); - - return Results.Ok(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CompressController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CompressController.cs deleted file mode 100644 index dedea4cf..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/CompressController.cs +++ /dev/null @@ -1,183 +0,0 @@ -using System.Text; -using ICSharpCode.SharpZipLib.GZip; -using ICSharpCode.SharpZipLib.Tar; -using ICSharpCode.SharpZipLib.Zip; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Helpers; -using Moonlight.ApiServer.Helpers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Files; - -[ApiController] -[Route("api/admin/system/files")] -[Authorize(Policy = "permissions:admin.system.files")] -public class CompressController : Controller -{ - private const string BaseDirectory = "storage"; - - [HttpPost("compress")] - public async Task CompressAsync([FromBody] CompressRequest request) - { - // Validate item length - if (request.Items.Length == 0) - { - return Results.Problem( - "At least one item is required", - statusCode: 400 - ); - } - - // Build paths - var destinationPath = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(request.Destination)); - var rootPath = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(request.Root)); - - // Resolve the relative to the root item paths to absolute paths - var itemsPaths = request.Items.Select(item => - Path.Combine( - BaseDirectory, - FilePathHelper.SanitizePath( - UnixPath.Combine(request.Root, item) - ) - ) - ); - - // Execute request - switch (request.Format) - { - case "tar.gz": - await CompressTarGzAsync(destinationPath, itemsPaths, rootPath); - break; - - case "zip": - await CompressZipAsync(destinationPath, itemsPaths, rootPath); - break; - - default: - return Results.Problem("Unsupported archive format specified", statusCode: 400); - } - - return Results.Ok(); - } - - - - #region Tar Gz - - private async Task CompressTarGzAsync(string destination, IEnumerable items, string root) - { - await using var outStream = System.IO.File.Create(destination); - await using var gzoStream = new GZipOutputStream(outStream); - await using var tarStream = new TarOutputStream(gzoStream, Encoding.UTF8); - - foreach (var item in items) - await CompressItemToTarGzAsync(tarStream, item, root); - - await tarStream.FlushAsync(); - await gzoStream.FlushAsync(); - await outStream.FlushAsync(); - - tarStream.Close(); - gzoStream.Close(); - outStream.Close(); - } - - private async Task CompressItemToTarGzAsync(TarOutputStream tarOutputStream, string item, string root) - { - if (System.IO.File.Exists(item)) - { - // Open file stream - var fs = System.IO.File.Open(item, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - // Meta - var entry = TarEntry.CreateTarEntry( - Formatter - .ReplaceStart(item, root, "") - .TrimStart('/') - ); - - // Set size - entry.Size = fs.Length; - - // Write entry - await tarOutputStream.PutNextEntryAsync(entry, CancellationToken.None); - - // Copy file content to tar stream - await fs.CopyToAsync(tarOutputStream); - fs.Close(); - - // Close the entry - tarOutputStream.CloseEntry(); - - return; - } - - if (Directory.Exists(item)) - { - foreach (var fsEntry in Directory.EnumerateFileSystemEntries(item)) - await CompressItemToTarGzAsync(tarOutputStream, fsEntry, root); - } - } - - #endregion - - #region ZIP - - private async Task CompressZipAsync(string destination, IEnumerable items, string root) - { - await using var outStream = System.IO.File.Create(destination); - await using var zipOutputStream = new ZipOutputStream(outStream); - - foreach (var item in items) - await AddItemToZipAsync(zipOutputStream, item, root); - - await zipOutputStream.FlushAsync(); - await outStream.FlushAsync(); - - zipOutputStream.Close(); - outStream.Close(); - } - - private async Task AddItemToZipAsync(ZipOutputStream outputStream, string item, string root) - { - if (System.IO.File.Exists(item)) - { - // Open file stream - var fs = System.IO.File.Open(item, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - // Meta - var entry = new ZipEntry( - Formatter - .ReplaceStart(item, root, "") - .TrimStart('/') - ); - - entry.Size = fs.Length; - - // Write entry - await outputStream.PutNextEntryAsync(entry, CancellationToken.None); - - // Copy file content to tar stream - await fs.CopyToAsync(outputStream); - fs.Close(); - - // Close the entry - outputStream.CloseEntry(); - - // Flush caches - await outputStream.FlushAsync(); - - return; - } - - if (Directory.Exists(item)) - { - foreach (var subItem in Directory.EnumerateFileSystemEntries(item)) - await AddItemToZipAsync(outputStream, subItem, root); - } - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DecompressController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DecompressController.cs deleted file mode 100644 index 8a44d59c..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DecompressController.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Text; -using ICSharpCode.SharpZipLib.GZip; -using ICSharpCode.SharpZipLib.Tar; -using ICSharpCode.SharpZipLib.Zip; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Helpers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Files; - -[ApiController] -[Route("api/admin/system/files")] -[Authorize(Policy = "permissions:admin.system.files")] -public class DecompressController : Controller -{ - private const string BaseDirectory = "storage"; - - [HttpPost("decompress")] - public async Task DecompressAsync([FromBody] DecompressRequest request) - { - var path = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(request.Path)); - var destination = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(request.Destination)); - - switch (request.Format) - { - case "tar.gz": - await DecompressTarGzAsync(path, destination); - break; - - case "zip": - await DecompressZipAsync(path, destination); - break; - } - } - - #region Tar Gz - - private async Task DecompressTarGzAsync(string path, string destination) - { - await using var fs = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - await using var gzipInputStream = new GZipInputStream(fs); - await using var tarInputStream = new TarInputStream(gzipInputStream, Encoding.UTF8); - - while (true) - { - var entry = await tarInputStream.GetNextEntryAsync(CancellationToken.None); - - if (entry == null) - break; - - var safeFilePath = FilePathHelper.SanitizePath(entry.Name); - var fileDestination = Path.Combine(destination, safeFilePath); - var parentFolder = Path.GetDirectoryName(fileDestination); - - // Ensure parent directory exists, if it's not the base directory - if (parentFolder != null && parentFolder != BaseDirectory) - Directory.CreateDirectory(parentFolder); - - await using var fileDestinationFs = - System.IO.File.Open(fileDestination, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); - await tarInputStream.CopyToAsync(fileDestinationFs, CancellationToken.None); - - await fileDestinationFs.FlushAsync(); - fileDestinationFs.Close(); - } - - tarInputStream.Close(); - gzipInputStream.Close(); - fs.Close(); - } - - #endregion - - #region Zip - - private async Task DecompressZipAsync(string path, string destination) - { - await using var fs = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - await using var zipInputStream = new ZipInputStream(fs); - - while (true) - { - var entry = zipInputStream.GetNextEntry(); - - if (entry == null) - break; - - if (entry.IsDirectory) - continue; - - var safeFilePath = FilePathHelper.SanitizePath(entry.Name); - var fileDestination = Path.Combine(destination, safeFilePath); - var parentFolder = Path.GetDirectoryName(fileDestination); - - // Ensure parent directory exists, if it's not the base directory - if (parentFolder != null && parentFolder != BaseDirectory) - Directory.CreateDirectory(parentFolder); - - await using var fileDestinationFs = - System.IO.File.Open(fileDestination, FileMode.Create, FileAccess.ReadWrite, FileShare.Read); - await zipInputStream.CopyToAsync(fileDestinationFs, CancellationToken.None); - - await fileDestinationFs.FlushAsync(); - fileDestinationFs.Close(); - } - - zipInputStream.Close(); - fs.Close(); - } - - #endregion -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DownloadUrlController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DownloadUrlController.cs deleted file mode 100644 index d813c1f0..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/DownloadUrlController.cs +++ /dev/null @@ -1,128 +0,0 @@ -using ICSharpCode.SharpZipLib.Zip; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Exceptions; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Helpers; -using Moonlight.Shared.Http.Responses.Admin.Sys; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Files; - -[ApiController] -[Route("api/admin/system/files/downloadUrl")] -[Authorize(Policy = "permissions:admin.system.files")] -public class DownloadUrlController : Controller -{ - private readonly AppConfiguration Configuration; - - private const string BaseDirectory = "storage"; - - public DownloadUrlController(AppConfiguration configuration) - { - Configuration = configuration; - } - - [HttpGet] - public async Task GetAsync([FromQuery] string path) - { - var physicalPath = Path.Combine(BaseDirectory, FilePathHelper.SanitizePath(path)); - var name = Path.GetFileName(physicalPath); - - if (System.IO.File.Exists(physicalPath)) - { - await using var fs = System.IO.File.Open(physicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - await Results.File(fs, fileDownloadName: name).ExecuteAsync(HttpContext); - } - else if(Directory.Exists(physicalPath)) - { - // Without the base directory we would have the full path to the target folder - // inside the zip - - var baseDirectory = Path.Combine( - BaseDirectory, - FilePathHelper.SanitizePath(Path.GetDirectoryName(path) ?? "") - ); - - Response.StatusCode = 200; - Response.ContentType = "application/zip"; - Response.Headers["Content-Disposition"] = $"attachment; filename=\"{name}.zip\""; - - try - { - await using var zipStream = new ZipOutputStream(Response.Body); - zipStream.IsStreamOwner = false; - - await StreamFolderAsZipAsync(zipStream, physicalPath, baseDirectory, HttpContext.RequestAborted); - } - catch (ZipException) - { - // Ignored - } - catch (TaskCanceledException) - { - // Ignored - } - } - } - - private async Task StreamFolderAsZipAsync( - ZipOutputStream zipStream, - string path, string rootPath, - CancellationToken cancellationToken - ) - { - foreach (var file in Directory.EnumerateFiles(path)) - { - if (HttpContext.RequestAborted.IsCancellationRequested) - return; - - var fi = new FileInfo(file); - - var filePath = Formatter.ReplaceStart(file, rootPath, ""); - - await zipStream.PutNextEntryAsync(new ZipEntry(filePath) - { - Size = fi.Length, - }, cancellationToken); - - await using var fs = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - await fs.CopyToAsync(zipStream, cancellationToken); - await fs.FlushAsync(cancellationToken); - - fs.Close(); - - await zipStream.FlushAsync(cancellationToken); - } - - foreach (var directory in Directory.EnumerateDirectories(path)) - { - if (HttpContext.RequestAborted.IsCancellationRequested) - return; - - await StreamFolderAsZipAsync(zipStream, directory, rootPath, cancellationToken); - } - } - - - // Yes I know we can just create that url on the client as the exist validation is done on both endpoints, - // but we leave it here for future modifications. E.g. using a distributed file provider or smth like that - [HttpPost] - public Task PostAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - if (System.IO.File.Exists(physicalPath) || Directory.Exists(physicalPath)) - { - return Task.FromResult(new DownloadUrlResponse() - { - Url = $"{Configuration.PublicUrl}/api/admin/system/files/downloadUrl?path={path}" - }); - } - - throw new HttpApiException("No such file or directory found", 404); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/FilesController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/FilesController.cs deleted file mode 100644 index ab14bb3d..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/Files/FilesController.cs +++ /dev/null @@ -1,192 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Exceptions; -using Moonlight.ApiServer.Helpers; -using Moonlight.Shared.Http.Responses.Admin.Sys; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys.Files; - -[ApiController] -[Route("api/admin/system/files")] -[Authorize(Policy = "permissions:admin.system.files")] -public class FilesController : Controller -{ - private const string BaseDirectory = "storage"; - - [HttpPost("touch")] - public async Task CreateFileAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - if (System.IO.File.Exists(physicalPath)) - throw new HttpApiException("A file already exists at that path", 400); - - if (Directory.Exists(path)) - throw new HttpApiException("A folder already exists at that path", 400); - - await using var fs = System.IO.File.Create(physicalPath); - fs.Close(); - } - - [HttpPost("mkdir")] - public Task CreateFolderAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - if (Directory.Exists(path)) - throw new HttpApiException("A folder already exists at that path", 400); - - if (System.IO.File.Exists(physicalPath)) - throw new HttpApiException("A file already exists at that path", 400); - - Directory.CreateDirectory(physicalPath); - return Task.CompletedTask; - } - - [HttpGet("list")] - public Task ListAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - var entries = new List(); - - var files = Directory.GetFiles(physicalPath); - - foreach (var file in files) - { - var fi = new FileInfo(file); - - entries.Add(new FileSystemEntryResponse() - { - Name = fi.Name, - Size = fi.Length, - CreatedAt = fi.CreationTimeUtc, - IsFolder = false, - UpdatedAt = fi.LastWriteTimeUtc - }); - } - - var directories = Directory.GetDirectories(physicalPath); - - foreach (var directory in directories) - { - var di = new DirectoryInfo(directory); - - entries.Add(new FileSystemEntryResponse() - { - Name = di.Name, - Size = 0, - CreatedAt = di.CreationTimeUtc, - UpdatedAt = di.LastWriteTimeUtc, - IsFolder = true - }); - } - - return Task.FromResult( - entries.ToArray() - ); - } - - [HttpPost("move")] - public Task MoveAsync([FromQuery] string oldPath, [FromQuery] string newPath) - { - var oldSafePath = FilePathHelper.SanitizePath(oldPath); - var newSafePath = FilePathHelper.SanitizePath(newPath); - - var oldPhysicalDirPath = Path.Combine(BaseDirectory, oldSafePath); - - if (Directory.Exists(oldPhysicalDirPath)) - { - var newPhysicalDirPath = Path.Combine(BaseDirectory, newSafePath); - - Directory.Move( - oldPhysicalDirPath, - newPhysicalDirPath - ); - } - else - { - var oldPhysicalFilePath = Path.Combine(BaseDirectory, oldSafePath); - var newPhysicalFilePath = Path.Combine(BaseDirectory, newSafePath); - - System.IO.File.Move( - oldPhysicalFilePath, - newPhysicalFilePath - ); - } - - return Task.CompletedTask; - } - - [HttpDelete("delete")] - public Task DeleteAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalDirPath = Path.Combine(BaseDirectory, safePath); - - if (Directory.Exists(physicalDirPath)) - Directory.Delete(physicalDirPath, true); - else - { - var physicalFilePath = Path.Combine(BaseDirectory, safePath); - - System.IO.File.Delete(physicalFilePath); - } - - return Task.CompletedTask; - } - - [HttpPost("upload")] - public async Task UploadAsync([FromQuery] string path) - { - if (Request.Form.Files.Count != 1) - return Results.Problem("Only one file is allowed in the request", statusCode: 400); - - var file = Request.Form.Files[0]; - - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - // Create directory which the new file should be put into - var baseDirectory = Path.GetDirectoryName(physicalPath); - - if(!string.IsNullOrEmpty(baseDirectory)) - Directory.CreateDirectory(baseDirectory); - - // Create file from provided form - await using var dataStream = file.OpenReadStream(); - - await using var targetStream = System.IO.File.Open( - physicalPath, - FileMode.Create, - FileAccess.ReadWrite, - FileShare.Read - ); - - // Copy the content to the newly created file - await dataStream.CopyToAsync(targetStream); - await targetStream.FlushAsync(); - - // Close both streams - targetStream.Close(); - dataStream.Close(); - - return Results.Ok(); - } - - [HttpGet("download")] - public async Task DownloadAsync([FromQuery] string path) - { - var safePath = FilePathHelper.SanitizePath(path); - var physicalPath = Path.Combine(BaseDirectory, safePath); - - await using var fs = System.IO.File.Open(physicalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - await fs.CopyToAsync(Response.Body); - - fs.Close(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/HangfireController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/HangfireController.cs deleted file mode 100644 index 1c426b69..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/HangfireController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Hangfire; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Moonlight.Shared.Http.Responses.Admin.Hangfire; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; - -[ApiController] -[Route("api/admin/system/hangfire")] -[Authorize(Policy = "permissions:admin.system.hangfire")] -public class HangfireController : Controller -{ - private readonly JobStorage JobStorage; - - public HangfireController(JobStorage jobStorage) - { - JobStorage = jobStorage; - } - - [HttpGet("stats")] - public Task GetStatsAsync() - { - var statistics = JobStorage.GetMonitoringApi().GetStatistics(); - - return Task.FromResult(new HangfireStatsResponse() - { - Awaiting = statistics.Awaiting, - Deleted = statistics.Deleted, - Enqueued = statistics.Enqueued, - Failed = statistics.Failed, - Processing = statistics.Processing, - Queues = statistics.Queues, - Recurring = statistics.Recurring, - Retries = statistics.Retries, - Scheduled = statistics.Scheduled, - Servers = statistics.Servers, - Succeeded = statistics.Succeeded - }); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs deleted file mode 100644 index 9778a4ef..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Sys/SystemController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Services; -using Moonlight.Shared.Http.Responses.Admin.Sys; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Sys; - -[ApiController] -[Route("api/admin/system")] -public class SystemController : Controller -{ - private readonly ApplicationService ApplicationService; - - public SystemController(ApplicationService applicationService) - { - ApplicationService = applicationService; - } - - [HttpGet] - [Authorize(Policy = "permissions:admin.system.overview")] - public async Task GetOverviewAsync() - { - return new() - { - Uptime = await ApplicationService.GetUptimeAsync(), - CpuUsage = await ApplicationService.GetCpuUsageAsync(), - MemoryUsage = await ApplicationService.GetMemoryUsageAsync(), - OperatingSystem = await ApplicationService.GetOsNameAsync() - }; - } - - [HttpPost("shutdown")] - [Authorize(Policy = "permissions:admin.system.shutdown")] - public async Task ShutdownAsync() - { - await ApplicationService.ShutdownAsync(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs deleted file mode 100644 index 9f11acfa..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs +++ /dev/null @@ -1,195 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Common; -using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Helpers; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Services; -using Moonlight.ApiServer.Mappers; -using Moonlight.Shared.Http.Requests.Admin.Users; -using Moonlight.Shared.Http.Responses.Admin.Users; - -namespace Moonlight.ApiServer.Http.Controllers.Admin.Users; - -[ApiController] -[Route("api/admin/users")] -public class UsersController : Controller -{ - private readonly DatabaseRepository UserRepository; - - public UsersController(DatabaseRepository userRepository) - { - UserRepository = userRepository; - } - - [HttpGet] - [Authorize(Policy = "permissions:admin.users.get")] - public async Task>> GetAsync( - [FromQuery] int startIndex, - [FromQuery] int count, - [FromQuery] string? orderBy, - [FromQuery] string? filter, - [FromQuery] string orderByDir = "asc" - ) - { - if (count > 100) - return Problem("You cannot fetch more items than 100 at a time", statusCode: 400); - - IQueryable query = UserRepository.Get(); - - query = orderBy switch - { - nameof(Database.Entities.User.Id) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Id) - : query.OrderBy(x => x.Id), - - nameof(Database.Entities.User.Username) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Username) - : query.OrderBy(x => x.Username), - - nameof(Database.Entities.User.Email) => orderByDir == "desc" - ? query.OrderByDescending(x => x.Email) - : query.OrderBy(x => x.Email), - - _ => query.OrderBy(x => x.Id) - }; - - if (!string.IsNullOrEmpty(filter)) - { - query = query.Where(x => - EF.Functions.ILike(x.Username, $"%{filter}%") || - EF.Functions.ILike(x.Email, $"%{filter}%") - ); - } - - var totalCount = await query.CountAsync(); - - var items = await query - .Skip(startIndex) - .Take(count) - .AsNoTracking() - .ProjectToResponse() - .ToArrayAsync(); - - return new CountedData() - { - Items = items, - TotalCount = totalCount - }; - } - - [HttpGet("{id}")] - [Authorize(Policy = "permissions:admin.users.get")] - public async Task> GetSingleAsync(int id) - { - var user = await UserRepository - .Get() - .ProjectToResponse() - .FirstOrDefaultAsync(x => x.Id == id); - - if (user == null) - return Problem("No user with that id found", statusCode: 404); - - return user; - } - - [HttpPost] - [Authorize(Policy = "permissions:admin.users.create")] - public async Task> CreateAsync([FromBody] CreateUserRequest request) - { - // Reformat values - request.Username = request.Username.ToLower().Trim(); - request.Email = request.Email.ToLower().Trim(); - - // Check for users with the same values - if (UserRepository.Get().Any(x => x.Username == request.Username)) - return Problem("A user with that username already exists", statusCode: 400); - - if (UserRepository.Get().Any(x => x.Email == request.Email)) - return Problem("A user with that email address already exists", statusCode: 400); - - var hashedPassword = HashHelper.Hash(request.Password); - - var user = new User() - { - Email = request.Email, - Username = request.Username, - Password = hashedPassword, - Permissions = request.Permissions - }; - - var finalUser = await UserRepository.AddAsync(user); - - return UserMapper.ToResponse(finalUser); - } - - [HttpPatch("{id}")] - [Authorize(Policy = "permissions:admin.users.update")] - public async Task> UpdateAsync([FromRoute] int id, [FromBody] UpdateUserRequest request) - { - var user = await UserRepository - .Get() - .FirstOrDefaultAsync(x => x.Id == id); - - if (user == null) - return Problem("No user with that id found", statusCode: 404); - - // Reformat values - request.Username = request.Username.ToLower().Trim(); - request.Email = request.Email.ToLower().Trim(); - - // Check for users with the same values - if (UserRepository.Get().Any(x => x.Username == request.Username && x.Id != user.Id)) - return Problem("Another user with that username already exists", statusCode: 400); - - if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id)) - return Problem("Another user with that email address already exists", statusCode: 400); - - // Perform hashing the password if required - if (!string.IsNullOrEmpty(request.Password)) - { - user.Password = HashHelper.Hash(request.Password); - user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after password change - } - - if (request.Permissions.Any(x => !user.Permissions.Contains(x))) - { - user.Permissions = request.Permissions; - user.TokenValidTimestamp = DateTime.UtcNow; // Log out user after permission change - } - - user.Email = request.Email; - user.Username = request.Username; - - await UserRepository.UpdateAsync(user); - - return UserMapper.ToResponse(user); - } - - [HttpDelete("{id}")] - [Authorize(Policy = "permissions:admin.users.delete")] - public async Task DeleteAsync([FromRoute] int id, [FromQuery] bool force = false) - { - var user = await UserRepository - .Get() - .FirstOrDefaultAsync(x => x.Id == id); - - if (user == null) - return Problem("No user with that id found", statusCode: 404); - - var deletionService = HttpContext.RequestServices.GetRequiredService(); - - if (!force) - { - var validationResult = await deletionService.ValidateAsync(user); - - if (!validationResult.IsAllowed) - return Problem("Unable to delete user", statusCode: 400, title: validationResult.Reason); - } - - await deletionService.DeleteAsync(user, force); - return NoContent(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs b/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs deleted file mode 100644 index 790b4177..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Auth/AuthController.cs +++ /dev/null @@ -1,128 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Interfaces; -using Moonlight.Shared.Http.Responses.Auth; - -namespace Moonlight.ApiServer.Http.Controllers.Auth; - -[ApiController] -[Route("api/auth")] -public class AuthController : Controller -{ - private readonly IAuthenticationSchemeProvider SchemeProvider; - private readonly IEnumerable Extensions; - private readonly AppConfiguration Configuration; - - public AuthController( - IAuthenticationSchemeProvider schemeProvider, - IEnumerable extensions, - AppConfiguration configuration - ) - { - SchemeProvider = schemeProvider; - Extensions = extensions; - Configuration = configuration; - } - - [HttpGet] - public async Task GetSchemesAsync() - { - var schemes = await SchemeProvider.GetAllSchemesAsync(); - - var allowedSchemes = Configuration.Authentication.EnabledSchemes; - - return schemes - .Where(x => allowedSchemes.Contains(x.Name)) - .Select(scheme => new AuthSchemeResponse() - { - DisplayName = scheme.DisplayName ?? scheme.Name, - Identifier = scheme.Name - }) - .ToArray(); - } - - [HttpGet("{identifier:alpha}")] - public async Task StartSchemeAsync([FromRoute] string identifier) - { - // Validate identifier against our enable list - var allowedSchemes = Configuration.Authentication.EnabledSchemes; - - if (!allowedSchemes.Contains(identifier)) - { - await Results - .Problem( - "Invalid scheme identifier provided", - statusCode: 404 - ) - .ExecuteAsync(HttpContext); - - return; - } - - // Now we can check if it even exists - var scheme = await SchemeProvider.GetSchemeAsync(identifier); - - if (scheme == null) - { - await Results - .Problem( - "Invalid scheme identifier provided", - statusCode: 404 - ) - .ExecuteAsync(HttpContext); - - return; - } - - // Everything fine, challenge the frontend - await HttpContext.ChallengeAsync( - scheme.Name, - new AuthenticationProperties() - { - RedirectUri = "/" - } - ); - } - - [Authorize] - [HttpGet("check")] - public async Task CheckAsync() - { - var username = User.FindFirstValue(ClaimTypes.Name)!; - var id = User.FindFirstValue(ClaimTypes.NameIdentifier)!; - var email = User.FindFirstValue(ClaimTypes.Email)!; - var userId = User.FindFirstValue("UserId")!; - var permissions = User.FindFirstValue("Permissions")!; - - // Create basic set of claims used by the frontend - var claims = new List() - { - new(ClaimTypes.Name, username), - new(ClaimTypes.NameIdentifier, id), - new(ClaimTypes.Email, email), - new("UserId", userId), - new("Permissions", permissions) - }; - - // Enrich the frontend claims by extensions (used by plugins) - foreach (var extension in Extensions) - { - claims.AddRange( - await extension.GetFrontendClaimsAsync(User) - ); - } - - return claims.ToArray(); - } - - [HttpGet("logout")] - public async Task LogoutAsync() - { - await HttpContext.SignOutAsync(); - await Results.Redirect("/").ExecuteAsync(HttpContext); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendController.cs b/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendController.cs deleted file mode 100644 index 7d9592a9..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Text; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Moonlight.ApiServer.Services; -using Moonlight.Shared.Misc; - -namespace Moonlight.ApiServer.Http.Controllers.Frontend; - -[ApiController] -[Route("/")] -public class FrontendController : Controller -{ - private readonly FrontendService FrontendService; - - public FrontendController(FrontendService frontendService) - { - FrontendService = frontendService; - } - - [HttpGet("frontend.json")] - public async Task GetConfigurationAsync() - => await FrontendService.GetConfigurationAsync(); - - [HttpGet] - public async Task IndexAsync() - { - var content = await FrontendService.GenerateIndexHtmlAsync(); - - return Results.Text(content, "text/html", Encoding.UTF8); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor b/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor deleted file mode 100644 index edea5c68..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Frontend/FrontendPage.razor +++ /dev/null @@ -1,102 +0,0 @@ -@using Moonlight.ApiServer.Database.Entities - - - - - - - - @Title - - - @foreach (var style in Styles) - { - - } - - - - - - @if (Theme != null) - { - - } - - - -
- -
-
-
-
-
-
-
-
-
-
- -
- -@foreach (var script in Scripts) -{ - -} - - - - - - - -@code -{ - [Parameter] public string Title { get; set; } - [Parameter] public string[] Scripts { get; set; } - [Parameter] public string[] Styles { get; set; } - [Parameter] public Theme? Theme { get; set; } -} diff --git a/Moonlight.ApiServer/Http/Controllers/LocalAuth/LocalAuthController.cs b/Moonlight.ApiServer/Http/Controllers/LocalAuth/LocalAuthController.cs deleted file mode 100644 index add14783..00000000 --- a/Moonlight.ApiServer/Http/Controllers/LocalAuth/LocalAuthController.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Helpers; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Implementations.LocalAuth; - -namespace Moonlight.ApiServer.Http.Controllers.LocalAuth; - -[ApiController] -[Route("api/localAuth")] -public class LocalAuthController : Controller -{ - private readonly DatabaseRepository UserRepository; - private readonly IServiceProvider ServiceProvider; - private readonly IAuthenticationService AuthenticationService; - private readonly IOptionsMonitor Options; - private readonly ILogger Logger; - private readonly AppConfiguration Configuration; - - public LocalAuthController( - DatabaseRepository userRepository, - IServiceProvider serviceProvider, - IAuthenticationService authenticationService, - IOptionsMonitor options, - ILogger logger, - AppConfiguration configuration - ) - { - UserRepository = userRepository; - ServiceProvider = serviceProvider; - AuthenticationService = authenticationService; - Options = options; - Logger = logger; - Configuration = configuration; - } - - [HttpGet] - [HttpGet("login")] - public async Task LoginAsync() - { - var html = await ComponentHelper.RenderToHtmlAsync(ServiceProvider); - - return Content(html, "text/html"); - } - - [HttpGet("register")] - public async Task RegisterAsync() - { - var html = await ComponentHelper.RenderToHtmlAsync(ServiceProvider); - - return Content(html, "text/html"); - } - - [HttpPost] - [HttpPost("login")] - public async Task LoginAsync([FromForm] string email, [FromForm] string password) - { - try - { - // Perform login - var user = await InternalLoginAsync(email, password); - - // Login user - var options = Options.Get(LocalAuthConstants.AuthenticationScheme); - - await AuthenticationService.SignInAsync(HttpContext, options.SignInScheme, new ClaimsPrincipal( - new ClaimsIdentity( - [ - new Claim(ClaimTypes.Email, user.Email), - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), - new Claim(ClaimTypes.Name, user.Username) - ], - LocalAuthConstants.AuthenticationScheme - ) - ), new AuthenticationProperties()); - - // Redirect back to wasm app - return Redirect("/"); - } - catch (Exception e) - { - string errorMessage; - - if (e is AggregateException aggregateException) - errorMessage = aggregateException.Message; - else - { - errorMessage = "An internal error occured"; - Logger.LogError(e, "An unhandled error occured while logging in user"); - } - - var html = await ComponentHelper.RenderToHtmlAsync(ServiceProvider, - parameters => { parameters["ErrorMessage"] = errorMessage; }); - - return Content(html, "text/html"); - } - } - - [HttpPost("register")] - public async Task RegisterAsync([FromForm] string email, [FromForm] string password, [FromForm] string username) - { - try - { - // Perform register - var user = await InternalRegisterAsync(username, email, password); - - // Login user - var options = Options.Get(LocalAuthConstants.AuthenticationScheme); - - await AuthenticationService.SignInAsync(HttpContext, options.SignInScheme, new ClaimsPrincipal( - new ClaimsIdentity( - [ - new Claim(ClaimTypes.Email, user.Email), - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), - new Claim(ClaimTypes.Name, user.Username) - ], - LocalAuthConstants.AuthenticationScheme - ) - ), new AuthenticationProperties()); - - // Redirect back to wasm app - return Redirect("/"); - } - catch (Exception e) - { - string errorMessage; - - if (e is AggregateException aggregateException) - errorMessage = aggregateException.Message; - else - { - errorMessage = "An internal error occured"; - Logger.LogError(e, "An unhandled error occured while logging in user"); - } - - var html = await ComponentHelper.RenderToHtmlAsync(ServiceProvider, - parameters => { parameters["ErrorMessage"] = errorMessage; }); - - return Content(html, "text/html"); - } - } - - private async Task InternalRegisterAsync(string username, string email, string password) - { - email = email.ToLower(); - username = username.ToLower(); - - if (await UserRepository.Get().AnyAsync(x => x.Username == username)) - throw new AggregateException("A account with that username already exists"); - - if (await UserRepository.Get().AnyAsync(x => x.Email == email)) - throw new AggregateException("A account with that email already exists"); - - string[] permissions = []; - - if (Configuration.Authentication.FirstUserAdmin) - { - var count = await UserRepository - .Get() - .CountAsync(); - - if (count == 0) - permissions = ["*"]; - } - - var user = new User() - { - Username = username, - Email = email, - Password = HashHelper.Hash(password), - Permissions = permissions - }; - - var finalUser = await UserRepository.AddAsync(user); - - return finalUser; - } - - private async Task InternalLoginAsync(string email, string password) - { - email = email.ToLower(); - - var user = await UserRepository - .Get() - .FirstOrDefaultAsync(x => x.Email == email); - - if (user == null) - throw new AggregateException("Invalid combination of email and password"); - - if (!HashHelper.Verify(password, user.Password)) - throw new AggregateException("Invalid combination of email and password"); - - return user; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/LocalAuth/Login.razor b/Moonlight.ApiServer/Http/Controllers/LocalAuth/Login.razor deleted file mode 100644 index 3d96ecc7..00000000 --- a/Moonlight.ApiServer/Http/Controllers/LocalAuth/Login.razor +++ /dev/null @@ -1,57 +0,0 @@ - - - Login into your account - - - - - - -
-
-
-
- brand-logo -
-
-

Login into your account

-

After logging in you will be able to manage your services

-
-
- - @if (!string.IsNullOrEmpty(ErrorMessage)) - { -
- @ErrorMessage -
- } - -
-
- - -
-
- - -
- -
-

- No account? - Create an account -

-
-
-
-
- - - - -@code -{ - [Parameter] public string? ErrorMessage { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/LocalAuth/Register.razor b/Moonlight.ApiServer/Http/Controllers/LocalAuth/Register.razor deleted file mode 100644 index 238353fc..00000000 --- a/Moonlight.ApiServer/Http/Controllers/LocalAuth/Register.razor +++ /dev/null @@ -1,61 +0,0 @@ - - - Register a new account - - - - - - -
-
-
-
- brand-logo -
-
-

Register a new account

-

After signing up you will be able to manage your services

-
-
- - @if (!string.IsNullOrEmpty(ErrorMessage)) - { -
- @ErrorMessage -
- } - -
-
- - -
-
- - -
-
- - -
- -
-

- Already registered? - Login into your account -

-
-
-
-
- - - -@code -{ - [Parameter] public string? ErrorMessage { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerController.cs b/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerController.cs deleted file mode 100644 index 8e293774..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerController.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Text.Json; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Models; - -namespace Moonlight.ApiServer.Http.Controllers.Swagger; - -[Route("api/swagger")] -public class SwaggerController : Controller -{ - private readonly AppConfiguration Configuration; - private readonly IServiceProvider ServiceProvider; - - public SwaggerController( - AppConfiguration configuration, - IServiceProvider serviceProvider - ) - { - Configuration = configuration; - ServiceProvider = serviceProvider; - } - - [HttpGet] - [Authorize] - public async Task GetAsync() - { - if (!Configuration.Development.EnableApiDocs) - return BadRequest("Api docs are disabled"); - - var options = new ApiDocsOptions(); - var optionsJson = JsonSerializer.Serialize(options); - - var html = await ComponentHelper.RenderToHtmlAsync( - ServiceProvider, - parameters => - { - parameters.Add("Options", optionsJson); - } - ); - - return Content(html, "text/html"); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerPage.razor b/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerPage.razor deleted file mode 100644 index 637daccc..00000000 --- a/Moonlight.ApiServer/Http/Controllers/Swagger/SwaggerPage.razor +++ /dev/null @@ -1,92 +0,0 @@ - - - - Moonlight Api Reference - - - - - - - - - - - -@code -{ - [Parameter] public string Options { get; set; } -} diff --git a/Moonlight.ApiServer/Http/Hubs/DiagnoseHub.cs b/Moonlight.ApiServer/Http/Hubs/DiagnoseHub.cs deleted file mode 100644 index 3207e3b2..00000000 --- a/Moonlight.ApiServer/Http/Hubs/DiagnoseHub.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.SignalR; - -namespace Moonlight.ApiServer.Http.Hubs; - -[Authorize(Policy = "permissions:admin.system.diagnose")] -public class DiagnoseHub : Hub -{ - [HubMethodName("Ping")] - public async Task PingAsync() - { - await Clients.All.SendAsync("Pong"); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/IAssemblyMarker.cs b/Moonlight.ApiServer/IAssemblyMarker.cs deleted file mode 100644 index fdbc64c3..00000000 --- a/Moonlight.ApiServer/IAssemblyMarker.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Moonlight.ApiServer; - -public interface IAssemblyMarker; \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs b/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs deleted file mode 100644 index 8a3986c6..00000000 --- a/Moonlight.ApiServer/Implementations/Diagnose/CoreConfigDiagnoseProvider.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.Diagnostics; -using System.IO.Compression; -using MoonCore.Yaml; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Extensions; -using Moonlight.ApiServer.Interfaces; - -namespace Moonlight.ApiServer.Implementations.Diagnose; - -public class CoreConfigDiagnoseProvider : IDiagnoseProvider -{ - private readonly AppConfiguration Configuration; - - public CoreConfigDiagnoseProvider(AppConfiguration configuration) - { - Configuration = configuration; - } - - private string CheckForNullOrEmpty(string? content) - { - return string.IsNullOrEmpty(content) - ? "ISEMPTY" - : "ISNOTEMPTY"; - } - - public async Task ModifyZipArchiveAsync(ZipArchive archive) - { - try - { - var configString = YamlSerializer.Serialize(Configuration); - var configuration = YamlSerializer.Deserialize(configString); - - configuration.Database.Password = CheckForNullOrEmpty(configuration.Database.Password); - configuration.Authentication.Secret = CheckForNullOrEmpty(configuration.Authentication.Secret); - configuration.SignalR.RedisConnectionString = CheckForNullOrEmpty(configuration.SignalR.RedisConnectionString); - - await archive.AddTextAsync( - "core/config.txt", - YamlSerializer.Serialize(configuration) - ); - } - catch (Exception e) - { - await archive.AddTextAsync("core/config.txt", $"Unable to load config: {e.ToStringDemystified()}"); - } - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs b/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs deleted file mode 100644 index b522f902..00000000 --- a/Moonlight.ApiServer/Implementations/Diagnose/LogsDiagnoseProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.IO.Compression; -using Moonlight.ApiServer.Extensions; -using Moonlight.ApiServer.Interfaces; - -namespace Moonlight.ApiServer.Implementations.Diagnose; - -public class LogsDiagnoseProvider : IDiagnoseProvider -{ - public async Task ModifyZipArchiveAsync(ZipArchive archive) - { - var path = Path.Combine("storage", "logs", "moonlight.log"); - - if (File.Exists(path)) - { - var logsContent = await File.ReadAllTextAsync(path); - await archive.AddTextAsync("logs.txt", logsContent); - } - else - await archive.AddTextAsync("logs.txt", "Logs file moonlight.log has not been found"); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthConstants.cs b/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthConstants.cs deleted file mode 100644 index 1d35cdc6..00000000 --- a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthConstants.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.ApiServer.Implementations.LocalAuth; - -public static class LocalAuthConstants -{ - public const string AuthenticationScheme = "LocalAuth"; -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthHandler.cs b/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthHandler.cs deleted file mode 100644 index 8109bac6..00000000 --- a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthHandler.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Moonlight.ApiServer.Implementations.LocalAuth; - -public class LocalAuthHandler : AuthenticationHandler -{ - public LocalAuthHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder - ) : base(options, logger, encoder) - { - } - - protected override Task HandleAuthenticateAsync() - { - return Task.FromResult( - AuthenticateResult.Fail("Local authentication does not directly support AuthenticateAsync") - ); - } - - protected override async Task HandleChallengeAsync(AuthenticationProperties properties) - { - await Results - .Redirect("/api/localAuth") - .ExecuteAsync(Context); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthOptions.cs b/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthOptions.cs deleted file mode 100644 index 8292ef44..00000000 --- a/Moonlight.ApiServer/Implementations/LocalAuth/LocalAuthOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Authentication; - -namespace Moonlight.ApiServer.Implementations.LocalAuth; - -public class LocalAuthOptions : AuthenticationSchemeOptions -{ - public string? SignInScheme { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Metrics/ApplicationMetric.cs b/Moonlight.ApiServer/Implementations/Metrics/ApplicationMetric.cs deleted file mode 100644 index 1ca9b24a..00000000 --- a/Moonlight.ApiServer/Implementations/Metrics/ApplicationMetric.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Diagnostics.Metrics; -using Microsoft.Extensions.DependencyInjection; -using Moonlight.ApiServer.Interfaces; -using Moonlight.ApiServer.Services; - -namespace Moonlight.ApiServer.Implementations.Metrics; - -public class ApplicationMetric : IMetric -{ - private Gauge MemoryUsage; - private Gauge CpuUsage; - private Gauge Uptime; - - public Task InitializeAsync(Meter meter) - { - MemoryUsage = meter.CreateGauge("moonlight_memory_usage"); - CpuUsage = meter.CreateGauge("moonlight_cpu_usage"); - Uptime = meter.CreateGauge("moonlight_uptime"); - - return Task.CompletedTask; - } - - public async Task RunAsync(IServiceProvider provider, CancellationToken cancellationToken) - { - var applicationService = provider.GetRequiredService(); - - var memory = await applicationService.GetMemoryUsageAsync(); - MemoryUsage.Record(memory); - - var uptime = await applicationService.GetUptimeAsync(); - Uptime.Record(uptime.TotalSeconds); - - var cpu = await applicationService.GetCpuUsageAsync(); - CpuUsage.Record(cpu); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Metrics/UsersMetric.cs b/Moonlight.ApiServer/Implementations/Metrics/UsersMetric.cs deleted file mode 100644 index 006f55b1..00000000 --- a/Moonlight.ApiServer/Implementations/Metrics/UsersMetric.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Diagnostics.Metrics; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Extended.Abstractions; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Interfaces; - -namespace Moonlight.ApiServer.Implementations.Metrics; - -public class UsersMetric : IMetric -{ - private Gauge Users; - - public Task InitializeAsync(Meter meter) - { - Users = meter.CreateGauge("moonlight_users"); - - return Task.CompletedTask; - } - - public async Task RunAsync(IServiceProvider provider, CancellationToken cancellationToken) - { - var usersRepo = provider.GetRequiredService>(); - var count = await usersRepo.Get().CountAsync(cancellationToken: cancellationToken); - - Users.Record(count); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs b/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs deleted file mode 100644 index 1fb2f010..00000000 --- a/Moonlight.ApiServer/Implementations/Startup/CoreStartup.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.OpenApi.Models; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database; -using Moonlight.ApiServer.Implementations.Diagnose; -using Moonlight.ApiServer.Implementations.Metrics; -using Moonlight.ApiServer.Interfaces; -using Moonlight.ApiServer.Models; -using Moonlight.ApiServer.Plugins; -using Moonlight.ApiServer.Services; -using OpenTelemetry.Logs; -using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; -using OpenTelemetry.Trace; - -namespace Moonlight.ApiServer.Implementations.Startup; - -public class CoreStartup : IPluginStartup -{ - public void AddPlugin(WebApplicationBuilder builder) - { - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - #region Api Docs - - if (configuration.Development.EnableApiDocs) - { - builder.Services.AddEndpointsApiExplorer(); - - // Configure swagger api specification generator and set the document title for the api docs to use - builder.Services.AddSwaggerGen(options => - { - options.SwaggerDoc("main", new OpenApiInfo() - { - Title = "Moonlight API" - }); - - options.CustomSchemaIds(x => x.FullName); - - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - Name = "Authorization", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = "Bearer" - }); - }); - } - - #endregion - - #region Database - - builder.Services.AddDbContext(); - - #endregion - - #region Diagnose - - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - #endregion - - #region Prometheus - - if (configuration.OpenTelemetry.Enable) - { - var openTel = builder.Services.AddOpenTelemetry(); - var openTelConfig = configuration.OpenTelemetry; - - var resourceBuilder = ResourceBuilder.CreateDefault(); - resourceBuilder.AddService(serviceName: "moonlight"); - - openTel.ConfigureResource(x => x.AddService(serviceName: "moonlight")); - - if (openTelConfig.Metrics.Enable) - { - builder.Services.AddSingleton(); - builder.Services.AddHostedService(sp => sp.GetRequiredService()); - - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - - openTel.WithMetrics(providerBuilder => - { - providerBuilder.AddAspNetCoreInstrumentation(); - providerBuilder.AddOtlpExporter(); - - if (openTelConfig.Metrics.EnablePrometheus) - providerBuilder.AddPrometheusExporter(); - - providerBuilder.AddMeter("moonlight"); - }); - } - - if (openTelConfig.Logs.Enable) - { - openTel.WithLogging(); - - builder.Logging.AddOpenTelemetry(options => - { - options.SetResourceBuilder(resourceBuilder); - options.AddOtlpExporter(); - }); - } - - if (openTelConfig.Traces.Enable) - { - openTel.WithTracing(providerBuilder => - { - providerBuilder.AddAspNetCoreInstrumentation(); - - providerBuilder.AddOtlpExporter(); - }); - } - } - - #endregion - - #region Client / Frontend - - if (configuration.Frontend.EnableHosting) - { - builder.Services.AddSingleton(new FrontendConfigurationOption() - { - Scripts = - [ - "/_content/Moonlight.Client/js/moonlight.js", "/_content/MoonCore.Blazor.FlyonUi/moonCore.js", - "/_content/MoonCore.Blazor.FlyonUi/ace/ace.js" - ], - Styles = ["/css/style.min.css"] - }); - } - - #endregion - } - - public void UsePlugin(WebApplication app) - { - var configuration = AppConfiguration.CreateEmpty(); - app.Configuration.Bind(configuration); - - #region Prometheus - - if (configuration.OpenTelemetry is { Enable: true, Metrics.EnablePrometheus: true }) - app.UseOpenTelemetryPrometheusScrapingEndpoint(); - - #endregion - } - - public void MapPlugin(WebApplication app) - { - var configuration = AppConfiguration.CreateEmpty(); - app.Configuration.Bind(configuration); - - if (configuration.Development.EnableApiDocs) - app.MapSwagger("/api/swagger/{documentName}"); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IAuthCheckExtension.cs b/Moonlight.ApiServer/Interfaces/IAuthCheckExtension.cs deleted file mode 100644 index 41a2bce2..00000000 --- a/Moonlight.ApiServer/Interfaces/IAuthCheckExtension.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Security.Claims; -using Moonlight.Shared.Http.Responses.Auth; - -namespace Moonlight.ApiServer.Interfaces; - -public interface IAuthCheckExtension -{ - /// - /// This function will be called by the frontend reaching out to the api server for claim information. - /// You can use this function to give your frontend plugins access to user specific data which is - /// static for the session. E.g. the avatar url of a user - /// - /// The principal of the current signed-in user - /// An array of claim responses which gets added to the list of claims to send to the frontend - public Task GetFrontendClaimsAsync(ClaimsPrincipal principal); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs b/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs deleted file mode 100644 index f7b19273..00000000 --- a/Moonlight.ApiServer/Interfaces/IDiagnoseProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.IO.Compression; - -namespace Moonlight.ApiServer.Interfaces; - -public interface IDiagnoseProvider -{ - public Task ModifyZipArchiveAsync(ZipArchive archive); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IMetric.cs b/Moonlight.ApiServer/Interfaces/IMetric.cs deleted file mode 100644 index 77507005..00000000 --- a/Moonlight.ApiServer/Interfaces/IMetric.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Diagnostics.Metrics; - -namespace Moonlight.ApiServer.Interfaces; - -public interface IMetric -{ - public Task InitializeAsync(Meter meter); - public Task RunAsync(IServiceProvider provider, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IUserAuthExtension.cs b/Moonlight.ApiServer/Interfaces/IUserAuthExtension.cs deleted file mode 100644 index eca9963d..00000000 --- a/Moonlight.ApiServer/Interfaces/IUserAuthExtension.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Security.Claims; -using Moonlight.ApiServer.Database.Entities; - -namespace Moonlight.ApiServer.Interfaces; - -public interface IUserAuthExtension -{ - /// - /// This function is called on every sign-in. It should be used to synchronize additional user data from the principal - /// or extend the claims saved in the user session - /// - /// The current user this method is called for - /// The principal after being processed by moonlight itself - /// The result of the synchronisation. Returning false will immediately invalidate the sign-in and no other extensions will be called - public Task SyncAsync(User user, ClaimsPrincipal principal); - - /// - /// IMPORTANT: Please note that heavy operations should not occur in this method as it will be called for every request - /// of every user - /// - /// The current user this method is called for - /// The principal after being processed by moonlight itself - /// The result of the validation. Returning false will immediately invalidate the users session and no other extensions will be called - public Task ValidateAsync(User user, ClaimsPrincipal principal); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs b/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs deleted file mode 100644 index 614c5031..00000000 --- a/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Models; - -namespace Moonlight.ApiServer.Interfaces; - -public interface IUserDeleteHandler -{ - public Task ValidateAsync(User user); - public Task DeleteAsync(User user, bool force); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Mappers/ApiKeyMapper.cs b/Moonlight.ApiServer/Mappers/ApiKeyMapper.cs deleted file mode 100644 index 2425cda2..00000000 --- a/Moonlight.ApiServer/Mappers/ApiKeyMapper.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Moonlight.ApiServer.Database.Entities; -using Moonlight.Shared.Http.Requests.Admin.ApiKeys; -using Moonlight.Shared.Http.Responses.Admin.ApiKeys; -using Riok.Mapperly.Abstractions; - -namespace Moonlight.ApiServer.Mappers; - -[Mapper] -public static partial class ApiKeyMapper -{ - // Mappers - public static partial ApiKeyResponse ToResponse(ApiKey apiKey); - public static partial ApiKey ToApiKey(CreateApiKeyRequest request); - public static partial void Merge([MappingTarget] ApiKey apiKey, UpdateApiKeyRequest request); - - // EF Relations - - public static partial IQueryable ProjectToResponse(this IQueryable apiKeys); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Mappers/ThemeMapper.cs b/Moonlight.ApiServer/Mappers/ThemeMapper.cs deleted file mode 100644 index 4b0e7659..00000000 --- a/Moonlight.ApiServer/Mappers/ThemeMapper.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Moonlight.ApiServer.Database.Entities; -using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; -using Moonlight.Shared.Http.Responses.Admin; -using Riok.Mapperly.Abstractions; - -namespace Moonlight.ApiServer.Mappers; - -[Mapper] -public static partial class ThemeMapper -{ - // Mappers - public static partial ThemeResponse ToResponse(Theme theme); - public static partial Theme ToTheme(CreateThemeRequest request); - public static partial void Merge([MappingTarget] Theme theme, UpdateThemeRequest request); - - // EF Relations - - public static partial IQueryable ProjectToResponse(this IQueryable themes); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Mappers/UserMapper.cs b/Moonlight.ApiServer/Mappers/UserMapper.cs deleted file mode 100644 index 35069d31..00000000 --- a/Moonlight.ApiServer/Mappers/UserMapper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Moonlight.ApiServer.Database.Entities; -using Moonlight.Shared.Http.Responses.Admin.Users; -using Riok.Mapperly.Abstractions; - -namespace Moonlight.ApiServer.Mappers; - -[Mapper] -public static partial class UserMapper -{ - // Mappers - public static partial UserResponse ToResponse(User user); - - // EF Relations - public static partial IQueryable ProjectToResponse(this IQueryable users); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/ApiDocsOptions.cs b/Moonlight.ApiServer/Models/ApiDocsOptions.cs deleted file mode 100644 index 60e601a1..00000000 --- a/Moonlight.ApiServer/Models/ApiDocsOptions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Moonlight.ApiServer.Models; - -// From https://github.com/scalar/scalar/blob/main/packages/scalar.aspnetcore/ScalarOptions.cs - -public class ApiDocsOptions -{ - public string Theme { get; set; } = "purple"; - - public bool? DarkMode { get; set; } - public bool? HideDownloadButton { get; set; } - public bool? ShowSideBar { get; set; } - - public bool? WithDefaultFonts { get; set; } - - public string? Layout { get; set; } - - public string? CustomCss { get; set; } - - public string? SearchHotkey { get; set; } - - public Dictionary? Metadata { get; set; } - - public ScalarAuthenticationOptions? Authentication { get; set; } -} - -public class ScalarAuthenticationOptions -{ - public string? PreferredSecurityScheme { get; set; } - - public ScalarAuthenticationApiKey? ApiKey { get; set; } -} - -public class ScalarAuthenticationoAuth2 -{ - public string? ClientId { get; set; } - - public List? Scopes { get; set; } -} - -public class ScalarAuthenticationApiKey -{ - public string? Token { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/ApplicationTheme.cs b/Moonlight.ApiServer/Models/ApplicationTheme.cs deleted file mode 100644 index 35b27433..00000000 --- a/Moonlight.ApiServer/Models/ApplicationTheme.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace Moonlight.ApiServer.Models; - -public class ApplicationTheme -{ - public string ColorBackground { get; set; } - - public string ColorBase100 { get; set; } - public string ColorBase150 { get; set; } - public string ColorBase200 { get; set; } - public string ColorBase250 { get; set; } - public string ColorBase300 { get; set; } - - public string ColorBaseContent { get; set; } - - public string ColorPrimary { get; set; } - public string ColorPrimaryContent { get; set; } - - public string ColorSecondary { get; set; } - public string ColorSecondaryContent { get; set; } - - public string ColorAccent { get; set; } - public string ColorAccentContent { get; set; } - - public string ColorNeutral { get; set; } - public string ColorNeutralContent { get; set; } - - public string ColorInfo { get; set; } - public string ColorInfoContent { get; set; } - - public string ColorSuccess { get; set; } - public string ColorSuccessContent { get; set; } - - public string ColorWarning { get; set; } - public string ColorWarningContent { get; set; } - - public string ColorError { get; set; } - public string ColorErrorContent { get; set; } - - public float RadiusSelector { get; set; } - public float RadiusField { get; set; } - public float RadiusBox { get; set; } - - public float SizeSelector { get; set; } - public float SizeField { get; set; } - - public float Border { get; set; } - public int Depth { get; set; } - public int Noise { get; set; } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/FrontendConfigurationOption.cs b/Moonlight.ApiServer/Models/FrontendConfigurationOption.cs deleted file mode 100644 index 343a98d2..00000000 --- a/Moonlight.ApiServer/Models/FrontendConfigurationOption.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.ApiServer.Models; - -public class FrontendConfigurationOption -{ - public string[] Scripts { get; set; } = []; - public string[] Styles { get; set; } = []; -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs b/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs deleted file mode 100644 index e4dcc1dd..00000000 --- a/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Moonlight.ApiServer.Models; - -public class UserDeleteValidationResult -{ - public bool IsAllowed { get; set; } - public string Reason { get; set; } - - public static UserDeleteValidationResult Allow() - { - return new UserDeleteValidationResult() - { - IsAllowed = true - }; - } - - public static UserDeleteValidationResult Deny() - => Deny("No reason provided"); - - public static UserDeleteValidationResult Deny(string reason) - { - return new UserDeleteValidationResult() - { - IsAllowed = false, - Reason = reason - }; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj deleted file mode 100644 index 017ebaa5..00000000 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ /dev/null @@ -1,43 +0,0 @@ - - - - net9.0 - enable - enable - - - - - - - - - Moonlight.ApiServer - 2.1.15 - Moonlight Panel - A build of the api server for moonlight development - https://github.com/Moonlight-Panel/Moonlight - true - apiserver - true - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Moonlight.ApiServer/Plugins/IPluginStartup.cs b/Moonlight.ApiServer/Plugins/IPluginStartup.cs deleted file mode 100644 index 29c56df6..00000000 --- a/Moonlight.ApiServer/Plugins/IPluginStartup.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace Moonlight.ApiServer.Plugins; - -public interface IPluginStartup -{ - public void AddPlugin(WebApplicationBuilder builder); - public void UsePlugin(WebApplication app); - public void MapPlugin(WebApplication app); -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/ApiKeyAuthService.cs b/Moonlight.ApiServer/Services/ApiKeyAuthService.cs deleted file mode 100644 index ff6346a0..00000000 --- a/Moonlight.ApiServer/Services/ApiKeyAuthService.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Security.Claims; -using Microsoft.EntityFrameworkCore; -using MoonCore.Extended.Abstractions; -using Moonlight.ApiServer.Database.Entities; - -namespace Moonlight.ApiServer.Services; - -public class ApiKeyAuthService -{ - private readonly DatabaseRepository ApiKeyRepository; - - public ApiKeyAuthService(DatabaseRepository apiKeyRepository) - { - ApiKeyRepository = apiKeyRepository; - } - - public async Task ValidateAsync(ClaimsPrincipal? principal) - { - // Ignore malformed claims principal - if (principal is not { Identity.IsAuthenticated: true }) - return false; - - var apiKeyIdStr = principal.FindFirstValue("ApiKeyId"); - - if (!int.TryParse(apiKeyIdStr, out var apiKeyId)) - return false; - - return await ApiKeyRepository - .Get() - .AnyAsync(x => x.Id == apiKeyId); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/ApiKeyService.cs b/Moonlight.ApiServer/Services/ApiKeyService.cs deleted file mode 100644 index 177bd013..00000000 --- a/Moonlight.ApiServer/Services/ApiKeyService.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Text; -using Microsoft.IdentityModel.Tokens; -using MoonCore.Attributes; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database.Entities; - -namespace Moonlight.ApiServer.Services; - -[Singleton] -public class ApiKeyService -{ - private readonly AppConfiguration Configuration; - - public ApiKeyService(AppConfiguration configuration) - { - Configuration = configuration; - } - - public string GenerateJwt(ApiKey apiKey) - { - var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); - - var descriptor = new SecurityTokenDescriptor() - { - Expires = apiKey.ExpiresAt.UtcDateTime, - IssuedAt = DateTime.Now, - NotBefore = DateTime.Now.AddMinutes(-1), - Claims = new Dictionary() - { - { - "ApiKeyId", - apiKey.Id - }, - { - "Permissions", - string.Join(";", apiKey.Permissions) - } - }, - SigningCredentials = new SigningCredentials( - new SymmetricSecurityKey( - Encoding.UTF8.GetBytes(Configuration.Authentication.Secret) - ), - SecurityAlgorithms.HmacSha256 - ), - Issuer = Configuration.PublicUrl, - Audience = Configuration.PublicUrl - }; - - var securityToken = jwtSecurityTokenHandler.CreateJwtSecurityToken(descriptor); - return jwtSecurityTokenHandler.WriteToken(securityToken); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/ApplicationService.cs b/Moonlight.ApiServer/Services/ApplicationService.cs deleted file mode 100644 index db9c9197..00000000 --- a/Moonlight.ApiServer/Services/ApplicationService.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using MoonCore.Attributes; -using MoonCore.Helpers; - -namespace Moonlight.ApiServer.Services; - -[Singleton] -public class ApplicationService -{ - private ILogger Logger; - private readonly IHost Host; - - public ApplicationService(ILogger logger, IHost host) - { - Logger = logger; - Host = host; - } - - public Task GetOsNameAsync() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Windows platform detected - var osVersion = Environment.OSVersion.Version; - return Task.FromResult($"Windows {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}"); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - var releaseRaw = File - .ReadAllLines("/etc/os-release") - .FirstOrDefault(x => x.StartsWith("PRETTY_NAME=")); - - if (string.IsNullOrEmpty(releaseRaw)) - return Task.FromResult("Linux (unknown release)"); - - var release = releaseRaw - .Replace("PRETTY_NAME=", "") - .Replace("\"", ""); - - if(string.IsNullOrEmpty(release)) - return Task.FromResult("Linux (unknown release)"); - - return Task.FromResult(release); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - // macOS platform detected - var osVersion = Environment.OSVersion.Version; - return Task.FromResult($"macOS {osVersion.Major}.{osVersion.Minor}.{osVersion.Build}"); - } - - // Unknown platform - return Task.FromResult("N/A"); - } - - public async Task GetMemoryUsageAsync() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var process = Process.GetCurrentProcess(); - return process.PrivateMemorySize64; - } - else - { - var lines = await File.ReadAllLinesAsync("/proc/self/smaps"); - var kilobytes = 0; - - foreach (var line in lines) - { - if(!line.StartsWith("pss:", StringComparison.InvariantCultureIgnoreCase)) - continue; - - var valueString = line - .Replace("pss:", "", StringComparison.InvariantCultureIgnoreCase) - .Replace("kb", "", StringComparison.InvariantCultureIgnoreCase) - .Trim(); - - kilobytes += int.Parse(valueString); - } - - return ByteConverter.FromKiloBytes(kilobytes).Bytes; - } - } - - public Task GetUptimeAsync() - { - var process = Process.GetCurrentProcess(); - var uptime = DateTime.Now - process.StartTime; - return Task.FromResult(uptime); - } - - public Task GetCpuUsageAsync() - { - var process = Process.GetCurrentProcess(); - var cpuTime = process.TotalProcessorTime; - var wallClockTime = DateTime.UtcNow - process.StartTime.ToUniversalTime(); - - var cpuUsage = (int)(100.0 * cpuTime.TotalMilliseconds / wallClockTime.TotalMilliseconds / Environment.ProcessorCount); - - return Task.FromResult(cpuUsage); - } - - public Task ShutdownAsync() - { - Logger.LogCritical("Restart of api server has been requested"); - - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromSeconds(1)); - await Host.StopAsync(CancellationToken.None); - }); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/DiagnoseService.cs b/Moonlight.ApiServer/Services/DiagnoseService.cs deleted file mode 100644 index ad47ae5a..00000000 --- a/Moonlight.ApiServer/Services/DiagnoseService.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Moonlight.ApiServer.Interfaces; -using System.IO.Compression; -using Microsoft.Extensions.Logging; -using MoonCore.Attributes; -using MoonCore.Exceptions; -using Moonlight.Shared.Http.Responses.Admin.Sys; - -namespace Moonlight.ApiServer.Services; - -[Scoped] -public class DiagnoseService -{ - private readonly IEnumerable DiagnoseProviders; - private readonly ILogger Logger; - - public DiagnoseService( - IEnumerable diagnoseProviders, - ILogger logger - ) - { - DiagnoseProviders = diagnoseProviders; - Logger = logger; - } - - public Task GetProvidersAsync() - { - var availableProviders = new List(); - - foreach (var diagnoseProvider in DiagnoseProviders) - { - var name = diagnoseProvider.GetType().Name; - - var type = diagnoseProvider.GetType().FullName; - - // The type name is null if the type is a generic type, unlikely, but still could happen - if (type == null) - continue; - - availableProviders.Add(new DiagnoseProvideResponse() - { - Name = name, - Type = type - }); - } - - return Task.FromResult( - availableProviders.ToArray() - ); - } - - public async Task GenerateDiagnoseAsync(string[] requestedProviders) - { - IDiagnoseProvider[] providers; - - if (requestedProviders.Length == 0) - providers = DiagnoseProviders.ToArray(); - else - { - var foundProviders = new List(); - - foreach (var requestedProvider in requestedProviders) - { - var provider = DiagnoseProviders.FirstOrDefault(x => x.GetType().FullName == requestedProvider); - - if (provider == null) - continue; - - foundProviders.Add(provider); - } - - providers = foundProviders.ToArray(); - } - - try - { - var outputStream = new MemoryStream(); - var zipArchive = new ZipArchive(outputStream, ZipArchiveMode.Create, leaveOpen: true); - - foreach (var provider in providers) - { - await provider.ModifyZipArchiveAsync(zipArchive); - } - - zipArchive.Dispose(); - - outputStream.Position = 0; - return outputStream; - } - catch (Exception e) - { - Logger.LogError("An unhandled error occured while generated the diagnose file: {e}", e); - - throw new HttpApiException("An unhandled error occured while generating the diagnose file", 500); - } - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/FrontendService.cs b/Moonlight.ApiServer/Services/FrontendService.cs deleted file mode 100644 index 18cfae33..00000000 --- a/Moonlight.ApiServer/Services/FrontendService.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.IO.Compression; -using System.Text; -using System.Text.Json; -using Microsoft.AspNetCore.Hosting; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.FileProviders; -using MoonCore.Attributes; -using MoonCore.Exceptions; -using MoonCore.Extended.Abstractions; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Http.Controllers.Frontend; -using Moonlight.ApiServer.Models; -using Moonlight.Shared.Misc; - -namespace Moonlight.ApiServer.Services; - -[Scoped] -public class FrontendService -{ - private readonly AppConfiguration Configuration; - private readonly IWebHostEnvironment WebHostEnvironment; - private readonly IEnumerable ConfigurationOptions; - private readonly IServiceProvider ServiceProvider; - private readonly DatabaseRepository ThemeRepository; - - public FrontendService( - AppConfiguration configuration, - IWebHostEnvironment webHostEnvironment, - IEnumerable configurationOptions, - IServiceProvider serviceProvider, - DatabaseRepository themeRepository - ) - { - Configuration = configuration; - WebHostEnvironment = webHostEnvironment; - ConfigurationOptions = configurationOptions; - ServiceProvider = serviceProvider; - ThemeRepository = themeRepository; - } - - public Task GetConfigurationAsync() - { - var configuration = new FrontendConfiguration() - { - ApiUrl = Configuration.PublicUrl, - HostEnvironment = "ApiServer" - }; - - return Task.FromResult(configuration); - } - - public async Task GenerateIndexHtmlAsync() // TODO: Cache - { - // Load requested theme - var theme = await ThemeRepository - .Get() - .FirstOrDefaultAsync(x => x.IsEnabled); - - // Load configured javascript files - var scripts = ConfigurationOptions - .SelectMany(x => x.Scripts) - .Distinct() - .ToArray(); - - // Load configured css files - var styles = ConfigurationOptions - .SelectMany(x => x.Styles) - .Distinct() - .ToArray(); - - return await ComponentHelper.RenderToHtmlAsync( - ServiceProvider, - parameters => - { - parameters["Theme"] = theme!; - parameters["Styles"] = styles; - parameters["Scripts"] = scripts; - parameters["Title"] = "Moonlight"; // TODO: Config - } - ); - } - - public async Task GenerateZipAsync() // TODO: Rework to be able to extract everything successfully - { - // We only allow the access to this function when we are actually hosting the frontend - if (!Configuration.Frontend.EnableHosting) - throw new HttpApiException("The hosting of the wasm client has been disabled", 400); - - // Load and check wasm path - var wasmMainFile = WebHostEnvironment.WebRootFileProvider.GetFileInfo("index.html"); - - if (wasmMainFile is NotFoundFileInfo || string.IsNullOrEmpty(wasmMainFile.PhysicalPath)) - throw new HttpApiException("Unable to find wasm location", 500); - - var wasmPath = Path.GetDirectoryName(wasmMainFile.PhysicalPath)! + "/"; - - // Load and check the blazor framework files - var blazorFile = WebHostEnvironment.WebRootFileProvider.GetFileInfo("_framework/blazor.webassembly.js"); - - if (blazorFile is NotFoundFileInfo || string.IsNullOrEmpty(blazorFile.PhysicalPath)) - throw new HttpApiException("Unable to find blazor location", 500); - - var blazorPath = Path.GetDirectoryName(blazorFile.PhysicalPath)! + "/"; - - // Create zip - var memoryStream = new MemoryStream(); - var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true); - - // Add wasm application - await ArchiveFsItemAsync(zipArchive, wasmPath, wasmPath); - - // Add blazor files - await ArchiveFsItemAsync(zipArchive, blazorPath, blazorPath, "_framework/"); - - // Add frontend.json - var frontendConfig = await GetConfigurationAsync(); - frontendConfig.HostEnvironment = "Static"; - var frontendJson = JsonSerializer.Serialize(frontendConfig); - await ArchiveTextAsync(zipArchive, "frontend.json", frontendJson); - - // Finish zip archive and reset stream so the code calling this function can process it - zipArchive.Dispose(); - await memoryStream.FlushAsync(); - memoryStream.Position = 0; - - return memoryStream; - } - - private async Task ArchiveFsItemAsync(ZipArchive archive, string path, string prefixToRemove, string prefixToAdd = "") - { - if (File.Exists(path)) - { - var entryName = prefixToAdd + Formatter.ReplaceStart(path, prefixToRemove, ""); - - var entry = archive.CreateEntry(entryName); - - await using var entryStream = entry.Open(); - await using var fileStream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - - await fileStream.CopyToAsync(entryStream); - await entryStream.FlushAsync(); - - entryStream.Close(); - } - else - { - foreach (var directoryItem in Directory.EnumerateFileSystemEntries(path)) - await ArchiveFsItemAsync(archive, directoryItem, prefixToRemove, prefixToAdd); - } - } - - private async Task ArchiveTextAsync(ZipArchive archive, string path, string content) - { - var data = Encoding.UTF8.GetBytes(content); - await ArchiveBytesAsync(archive, path, data); - } - - private async Task ArchiveBytesAsync(ZipArchive archive, string path, byte[] bytes) - { - var entry = archive.CreateEntry(path); - await using var dataStream = entry.Open(); - - await dataStream.WriteAsync(bytes); - await dataStream.FlushAsync(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/MetricsBackgroundService.cs b/Moonlight.ApiServer/Services/MetricsBackgroundService.cs deleted file mode 100644 index 0c25ec6d..00000000 --- a/Moonlight.ApiServer/Services/MetricsBackgroundService.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Diagnostics.Metrics; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Interfaces; - -namespace Moonlight.ApiServer.Services; - -public class MetricsBackgroundService : BackgroundService -{ - private readonly ILogger Logger; - private readonly IServiceProvider ServiceProvider; - private readonly AppConfiguration Configuration; - - private readonly IMetric[] Metrics; - private readonly Meter Meter; - - public MetricsBackgroundService( - IServiceProvider serviceProvider, - IMeterFactory meterFactory, - IEnumerable metrics, - ILogger logger, - AppConfiguration configuration - ) - { - ServiceProvider = serviceProvider; - Logger = logger; - Configuration = configuration; - - Meter = meterFactory.Create("moonlight"); - - Metrics = metrics.ToArray(); - } - - private async Task InitializeAsync() - { - Logger.LogDebug( - "Initializing metrics: {names}", - string.Join(", ", Metrics.Select(x => x.GetType().FullName)) - ); - - foreach (var metric in Metrics) - await metric.InitializeAsync(Meter); - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - await InitializeAsync(); - - while (!stoppingToken.IsCancellationRequested) - { - using var scope = ServiceProvider.CreateScope(); - - foreach (var metric in Metrics) - { - try - { - await metric.RunAsync(scope.ServiceProvider, stoppingToken); - } - catch (TaskCanceledException) - { - // Ignored - } - catch (Exception e) - { - Logger.LogError( - "An unhandled error occured while collecting metric {name}: {e}", - metric.GetType().FullName, - e - ); - } - } - - await Task.Delay( - TimeSpan.FromSeconds(Configuration.OpenTelemetry.Metrics.Interval), - stoppingToken - ); - } - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/UserAuthService.cs b/Moonlight.ApiServer/Services/UserAuthService.cs deleted file mode 100644 index d6624430..00000000 --- a/Moonlight.ApiServer/Services/UserAuthService.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.Security.Claims; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Helpers; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Interfaces; - -namespace Moonlight.ApiServer.Services; - -public class UserAuthService -{ - private readonly ILogger Logger; - private readonly DatabaseRepository UserRepository; - private readonly AppConfiguration Configuration; - private readonly IEnumerable Extensions; - - private const string UserIdClaim = "UserId"; - private const string IssuedAtClaim = "IssuedAt"; - - public UserAuthService( - ILogger logger, - DatabaseRepository userRepository, - AppConfiguration configuration, - IEnumerable extensions - ) - { - Logger = logger; - UserRepository = userRepository; - Configuration = configuration; - Extensions = extensions; - } - - public async Task SyncAsync(ClaimsPrincipal? principal) - { - // Ignore malformed claims principal - if (principal is not { Identity.IsAuthenticated: true }) - return false; - - // Search for email and username. We need both to create the user model if required. - // We do a ToLower here because external authentication provider might provide case-sensitive data - - var email = principal.FindFirstValue(ClaimTypes.Email)?.ToLower(); - var username = principal.FindFirstValue(ClaimTypes.Name)?.ToLower(); - - if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(username)) - { - Logger.LogWarning( - "The authentication scheme {scheme} did not provide claim types: email, name. These are required to sync to user to the database", - principal.Identity.AuthenticationType - ); - - return false; - } - - // If you plan to use multiple auth providers it can be a good idea - // to use an identifier in the user model which consists of the provider and the NameIdentifier - // instead of the email address. For simplicity, we just use the email as the identifier so multiple auth providers - // can lead to the same account when the email matches - var user = await UserRepository - .Get() - .FirstOrDefaultAsync(u => u.Email == email); - - if (user == null) - { - string[] permissions = []; - - // Yes I know we handle the first user admin thing in the LocalAuth too, - // but this only works fo the local auth. So if a user uses an external auth scheme - // like oauth2 discord, the first user admin toggle would do nothing - if (Configuration.Authentication.FirstUserAdmin) - { - var count = await UserRepository - .Get() - .CountAsync(); - - if (count == 0) - permissions = ["*"]; - } - - user = await UserRepository.AddAsync(new User() - { - Email = email, - TokenValidTimestamp = DateTimeOffset.UtcNow.AddMinutes(-1), - Username = username, - Password = HashHelper.Hash(Formatter.GenerateString(64)), - Permissions = permissions - }); - } - - // You can sync other properties here - if (user.Username != username) - { - user.Username = username; - await UserRepository.UpdateAsync(user); - } - - // Enrich claims with required metadata - principal.Identities.First().AddClaims([ - new Claim(UserIdClaim, user.Id.ToString()), - new Claim(IssuedAtClaim, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()), - new Claim("Permissions", string.Join(';', user.Permissions)) - ]); - - // Call extensions - foreach (var extension in Extensions) - { - var result = await extension.SyncAsync(user, principal); - - if (!result) // Exit immediately if result is false - return false; - } - - return true; - } - - public async Task ValidateAsync(ClaimsPrincipal? principal) - { - // Ignore malformed claims principal - if (principal is not { Identity.IsAuthenticated: true }) - return false; - - // Validate if the user still exists, and then we want to validate the token issue time - // against the invalidation time - - var userIdStr = principal.FindFirstValue(UserIdClaim); - - if (!int.TryParse(userIdStr, out var userId)) - return false; - - var user = await UserRepository - .Get() - .FirstOrDefaultAsync(u => u.Id == userId); - - if (user == null) - return false; - - // Token time validation - var issuedAtStr = principal.FindFirstValue(IssuedAtClaim); - - if (!long.TryParse(issuedAtStr, out var issuedAtUnix)) - return false; - - var issuedAt = DateTimeOffset - .FromUnixTimeSeconds(issuedAtUnix) - .ToUniversalTime(); - - // If the issued at timestamp is greater than the token validation timestamp - // everything is fine. If not it means that the token should be invalidated - // as it is too old - - if (issuedAt < user.TokenValidTimestamp) - return false; - - // Call extensions - foreach (var extension in Extensions) - { - var result = await extension.ValidateAsync(user, principal); - - if (!result) // Exit immediately if result is false - return false; - } - - return true; - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/UserDeletionService.cs b/Moonlight.ApiServer/Services/UserDeletionService.cs deleted file mode 100644 index 4e85b8fd..00000000 --- a/Moonlight.ApiServer/Services/UserDeletionService.cs +++ /dev/null @@ -1,42 +0,0 @@ -using MoonCore.Extended.Abstractions; -using Moonlight.ApiServer.Database.Entities; -using Moonlight.ApiServer.Interfaces; -using Moonlight.ApiServer.Models; - -namespace Moonlight.ApiServer.Services; - -public class UserDeletionService -{ - private readonly IUserDeleteHandler[] Handlers; - private readonly DatabaseRepository UserRepository; - - public UserDeletionService( - IEnumerable handlers, - DatabaseRepository userRepository - ) - { - UserRepository = userRepository; - Handlers = handlers.ToArray(); - } - - public async Task ValidateAsync(User user) - { - foreach (var handler in Handlers) - { - var result = await handler.ValidateAsync(user); - - if (!result.IsAllowed) - return result; - } - - return UserDeleteValidationResult.Allow(); - } - - public async Task DeleteAsync(User user, bool force) - { - foreach (var handler in Handlers) - await handler.DeleteAsync(user, force); - - await UserRepository.RemoveAsync(user); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Auth.cs b/Moonlight.ApiServer/Startup/Startup.Auth.cs deleted file mode 100644 index 6fc15509..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Auth.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System.Text; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using MoonCore.Permissions; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Implementations.LocalAuth; -using Moonlight.ApiServer.Services; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddAuth(this WebApplicationBuilder builder) - { - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - builder.Services - .AddAuthentication(options => { options.DefaultScheme = "MainScheme"; }) - .AddPolicyScheme("MainScheme", null, options => - { - // If an api key is specified via the bearer auth header - // we want to use the ApiKey scheme for authenticating the request - options.ForwardDefaultSelector = context => - { - var headers = context.Request.Headers; - - // For regular api calls - if (headers.ContainsKey("Authorization")) - return "ApiKey"; - - // For websocket requests which cannot use the Authorization header - if (headers.Upgrade == "websocket" && headers.Connection == "Upgrade" && context.Request.Query.ContainsKey("access_token")) - return "ApiKey"; - - // Regular user traffic/auth - return "Session"; - }; - }) - .AddJwtBearer("ApiKey", null, options => - { - options.TokenValidationParameters = new() - { - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( - configuration.Authentication.Secret - )), - ValidateIssuerSigningKey = true, - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero, - ValidateAudience = true, - ValidAudience = configuration.PublicUrl, - ValidateIssuer = true, - ValidIssuer = configuration.PublicUrl - }; - - options.Events = new JwtBearerEvents() - { - OnTokenValidated = async context => - { - var apiKeyAuthService = context - .HttpContext - .RequestServices - .GetRequiredService(); - - var result = await apiKeyAuthService.ValidateAsync(context.Principal); - - if (!result) - context.Fail("API key has been deleted"); - }, - - OnMessageReceived = context => - { - var accessToken = context.Request.Query["access_token"]; - - if (!string.IsNullOrEmpty(accessToken)) - context.Token = accessToken; - - return Task.CompletedTask; - } - }; - }) - .AddCookie("Session", null, options => - { - options.ExpireTimeSpan = TimeSpan.FromDays(configuration.Authentication.Sessions.ExpiresIn); - - options.Cookie = new CookieBuilder() - { - Name = configuration.Authentication.Sessions.CookieName, - Path = "/", - IsEssential = true, - SecurePolicy = CookieSecurePolicy.SameAsRequest - }; - - // As redirects won't work in our spa which uses API calls - // we need to customize the responses when certain actions happen - options.Events.OnRedirectToLogin = async context => - { - await Results.Problem( - title: "Unauthenticated", - detail: "You need to authenticate yourself to use this endpoint", - statusCode: 401 - ) - .ExecuteAsync(context.HttpContext); - }; - - options.Events.OnRedirectToAccessDenied = async context => - { - await Results.Problem( - title: "Permission denied", - detail: "You are missing the required permissions to access this endpoint", - statusCode: 403 - ) - .ExecuteAsync(context.HttpContext); - }; - - options.Events.OnSigningIn = async context => - { - var userSyncService = context - .HttpContext - .RequestServices - .GetRequiredService(); - - var result = await userSyncService.SyncAsync(context.Principal); - - if (!result) - context.Principal = new(); - else - context.Properties.IsPersistent = true; - }; - - options.Events.OnValidatePrincipal = async context => - { - var userSyncService = context - .HttpContext - .RequestServices - .GetRequiredService(); - - var result = await userSyncService.ValidateAsync(context.Principal); - - if (!result) - context.RejectPrincipal(); - }; - }) - .AddScheme(LocalAuthConstants.AuthenticationScheme, "Local Auth", options => - { - options.ForwardAuthenticate = "Session"; - options.ForwardSignIn = "Session"; - options.ForwardSignOut = "Session"; - - options.SignInScheme = "Session"; - }); - - builder.Services.AddAuthorization(); - - builder.Services.AddAuthorizationPermissions(options => - { - options.ClaimName = "Permissions"; - options.Prefix = "permissions:"; - }); - - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - // Setup data protection storage within storage folder - // so its persists in containers - var dpKeyPath = Path.Combine("storage", "dataProtectionKeys"); - - Directory.CreateDirectory(dpKeyPath); - - builder.Services - .AddDataProtection() - .PersistKeysToFileSystem( - new DirectoryInfo(dpKeyPath) - ); - - builder.Services.AddScoped(); - } - - private static void UseAuth(this WebApplication application) - { - application.UseAuthentication(); - application.UseAuthorization(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Base.cs b/Moonlight.ApiServer/Startup/Startup.Base.cs deleted file mode 100644 index c8ee6c85..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Base.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Extended.Extensions; -using MoonCore.Extensions; -using MoonCore.Helpers; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddBase(this WebApplicationBuilder builder, IPluginStartup[] startups) - { - builder.Services.AutoAddServices(); - builder.Services.AddHttpClient(); - - builder.Services.AddApiExceptionHandler(); - - // Configure controllers - var mvcBuilder = builder.Services.AddControllers(); - - // Add plugin assemblies as application parts - foreach (var pluginStartup in startups.Select(x => x.GetType().Assembly).Distinct()) - mvcBuilder.AddApplicationPart(pluginStartup.GetType().Assembly); - } - - private static void UseBase(this WebApplication application) - { - application.UseRouting(); - application.UseExceptionHandler(); - } - - private static void MapBase(this WebApplication application) - { - application.MapControllers(); - - // Frontend - var configuration = AppConfiguration.CreateEmpty(); - application.Configuration.Bind(configuration); - - if (configuration.Frontend.EnableHosting) - application.MapFallbackToController("Index", "Frontend"); - } - - private static void ConfigureKestrel(this WebApplicationBuilder builder) - { - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - builder.WebHost.ConfigureKestrel(kestrelOptions => - { - var maxUploadInBytes = ByteConverter - .FromMegaBytes(configuration.Kestrel.UploadLimit) - .Bytes; - - kestrelOptions.Limits.MaxRequestBodySize = maxUploadInBytes; - }); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Config.cs b/Moonlight.ApiServer/Startup/Startup.Config.cs deleted file mode 100644 index 645a0959..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Config.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.EnvConfiguration; -using MoonCore.Yaml; -using Moonlight.ApiServer.Configuration; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddConfiguration(this WebApplicationBuilder builder) - { - // Yaml - var yamlPath = Path.Combine("storage", "config.yml"); - - YamlDefaultGenerator.GenerateAsync(yamlPath).Wait(); - - builder.Configuration.AddYamlFile(yamlPath); - - // Env - builder.Configuration.AddEnvironmentVariables(prefix: "MOONLIGHT_", separator: "_"); - - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - builder.Services.AddSingleton(configuration); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Database.cs b/Moonlight.ApiServer/Startup/Startup.Database.cs deleted file mode 100644 index 109468fb..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Database.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Extensions; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddDatabase(this WebApplicationBuilder builder) - { - builder.Services.AddDbAutoMigrations(); - builder.Services.AddDatabaseMappings(); - - builder.Services.AddScoped(typeof(DatabaseRepository<>)); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Hangfire.cs b/Moonlight.ApiServer/Startup/Startup.Hangfire.cs deleted file mode 100644 index ed61016e..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Hangfire.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Hangfire; -using Hangfire.EntityFrameworkCore; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Moonlight.ApiServer.Database; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddMoonlightHangfire(this WebApplicationBuilder builder) - { - builder.Services.AddHangfire((provider, configuration) => - { - configuration.SetDataCompatibilityLevel(CompatibilityLevel.Version_180); - configuration.UseSimpleAssemblyNameTypeSerializer(); - configuration.UseRecommendedSerializerSettings(); - configuration.UseEFCoreStorage(() => - { - var scope = provider.CreateScope(); - return scope.ServiceProvider.GetRequiredService(); - }, new EFCoreStorageOptions()); - }); - - builder.Services.AddHangfireServer(); - - builder.Logging.AddFilter( - "Hangfire.Server.BackgroundServerProcess", - LogLevel.Warning - ); - - builder.Logging.AddFilter( - "Hangfire.BackgroundJobServer", - LogLevel.Warning - ); - } - - private static void UseMoonlightHangfire(this WebApplication application) - { - if (application.Environment.IsDevelopment()) - application.UseHangfireDashboard(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Logging.cs b/Moonlight.ApiServer/Startup/Startup.Logging.cs deleted file mode 100644 index b8071a53..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Logging.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Text.Json; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Logging; -using MoonCore.Logging; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddLogging(this WebApplicationBuilder builder) - { - // Logging providers - builder.Logging.ClearProviders(); - - builder.Logging.AddAnsiConsole(); - builder.Logging.AddFile(Path.Combine("storage", "logs", "moonlight.log")); - - // Logging levels - var logConfigPath = Path.Combine("storage", "logConfig.json"); - - // Ensure default log levels exist - if (!File.Exists(logConfigPath)) - { - var defaultLogLevels = new Dictionary - { - { "Default", "Information" }, - { "Microsoft.AspNetCore", "Warning" }, - { "System.Net.Http.HttpClient", "Warning" }, - { "Moonlight.ApiServer.Implementations.LocalAuth.LocalAuthHandler", "Warning" } - }; - - var logLevelsJson = JsonSerializer.Serialize(defaultLogLevels); - File.WriteAllText(logConfigPath, logLevelsJson); - } - - // Read log levels - var logLevels = JsonSerializer.Deserialize>( - File.ReadAllText(logConfigPath) - )!; - - // Apply configured log levels - foreach (var level in logLevels) - builder.Logging.AddFilter(level.Key, Enum.Parse(level.Value)); - - // Mute exception handler middleware - // https://github.com/dotnet/aspnetcore/issues/19740 - builder.Logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", - LogLevel.Critical - ); - - builder.Logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware", - LogLevel.Critical - ); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Misc.cs b/Moonlight.ApiServer/Startup/Startup.Misc.cs deleted file mode 100644 index 0b4de7bd..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Misc.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Moonlight.ApiServer.Configuration; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void PrintVersionAsync() - { - // Fancy start console output... yes very fancy :> - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - } - - private static void CreateStorageAsync() - { - Directory.CreateDirectory("storage"); - Directory.CreateDirectory(Path.Combine("storage", "logs")); - } - - private static void AddMoonlightCors(this WebApplicationBuilder builder) - { - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - var allowedOrigins = configuration.Kestrel.AllowedOrigins; - - builder.Services.AddCors(options => - { - var cors = new CorsPolicyBuilder(); - - if (allowedOrigins.Contains("*")) - { - cors.SetIsOriginAllowed(_ => true) - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials(); - } - else - { - cors.WithOrigins(allowedOrigins) - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials(); - } - - options.AddDefaultPolicy( - cors.Build() - ); - }); - } - - private static void UseMoonlightCors(this WebApplication application) - { - application.UseCors(); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Plugins.cs b/Moonlight.ApiServer/Startup/Startup.Plugins.cs deleted file mode 100644 index 4764d647..00000000 --- a/Moonlight.ApiServer/Startup/Startup.Plugins.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddPlugins(this WebApplicationBuilder builder, IPluginStartup[] startups) - { - foreach (var startup in startups) - startup.AddPlugin(builder); - } - - private static void UsePlugins(this WebApplication application, IPluginStartup[] startups) - { - foreach (var startup in startups) - startup.UsePlugin(application); - } - - private static void MapPlugins(this WebApplication application, IPluginStartup[] startups) - { - foreach (var startup in startups) - startup.MapPlugin(application); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.SignalR.cs b/Moonlight.ApiServer/Startup/Startup.SignalR.cs deleted file mode 100644 index 51bb233a..00000000 --- a/Moonlight.ApiServer/Startup/Startup.SignalR.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Moonlight.ApiServer.Configuration; -using Moonlight.ApiServer.Http.Hubs; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - private static void AddMoonlightSignalR(this WebApplicationBuilder builder) - { - var configuration = AppConfiguration.CreateEmpty(); - builder.Configuration.Bind(configuration); - - var signalRBuilder = builder.Services.AddSignalR(); - - if (configuration.SignalR.UseRedis) - signalRBuilder.AddStackExchangeRedis(configuration.SignalR.RedisConnectionString); - } - - private static void MapMoonlightSignalR(this WebApplication application) - { - application.MapHub("/api/admin/system/diagnose/ws"); - } -} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.cs b/Moonlight.ApiServer/Startup/Startup.cs deleted file mode 100644 index b21d72b9..00000000 --- a/Moonlight.ApiServer/Startup/Startup.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Moonlight.ApiServer.Plugins; - -namespace Moonlight.ApiServer.Startup; - -public static partial class Startup -{ - public static void AddMoonlight(this WebApplicationBuilder builder, IPluginStartup[] startups) - { - PrintVersionAsync(); - CreateStorageAsync(); - - builder.AddConfiguration(); - builder.AddLogging(); - - builder.ConfigureKestrel(); - builder.AddBase(startups); - builder.AddDatabase(); - builder.AddAuth(); - builder.AddMoonlightCors(); - builder.AddMoonlightHangfire(); - builder.AddMoonlightSignalR(); - - builder.AddPlugins(startups); - } - - public static void UseMoonlight(this WebApplication application, IPluginStartup[] startups) - { - application.UseBase(); - application.UseMoonlightCors(); - application.UseAuth(); - application.UseMoonlightHangfire(); - application.UsePlugins(startups); - } - - public static void MapMoonlight(this WebApplication application, IPluginStartup[] startups) - { - application.MapBase(); - application.MapMoonlightSignalR(); - application.MapPlugins(startups); - } -} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/PluginLoader.cs b/Moonlight.Client.Runtime/PluginLoader.cs deleted file mode 100644 index 78b278a9..00000000 --- a/Moonlight.Client.Runtime/PluginLoader.cs +++ /dev/null @@ -1,10 +0,0 @@ -using MoonCore.PluginFramework; -using Moonlight.Client.Plugins; - -namespace Moonlight.Client.Runtime; - -[PluginLoader] -public partial class PluginLoader : IPluginStartup -{ - -} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Plugins.props b/Moonlight.Client.Runtime/Plugins.props deleted file mode 100644 index 3e0a6663..00000000 --- a/Moonlight.Client.Runtime/Plugins.props +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Program.cs b/Moonlight.Client.Runtime/Program.cs deleted file mode 100644 index 6b4aea89..00000000 --- a/Moonlight.Client.Runtime/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Moonlight.Client.Runtime; -using Moonlight.Client.Startup; - -var pluginLoader = new PluginLoader(); -pluginLoader.Initialize(); - -var builder = WebAssemblyHostBuilder.CreateDefault(args); - -builder.AddMoonlight(pluginLoader.Instances); - -var app = builder.Build(); - -app.ConfigureMoonlight(pluginLoader.Instances); - -await app.RunAsync(); \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Styles/extract-classes.js b/Moonlight.Client.Runtime/Styles/extract-classes.js deleted file mode 100644 index eb9e9242..00000000 --- a/Moonlight.Client.Runtime/Styles/extract-classes.js +++ /dev/null @@ -1,30 +0,0 @@ -// extract-classes.js -const fs = require('fs'); - -module.exports = (opts = {}) => { - const classSet = new Set(); - - return { - postcssPlugin: 'extract-tailwind-classes', - Rule(rule) { - const selectorParser = require('postcss-selector-parser'); - selectorParser(selectors => { - selectors.walkClasses(node => { - classSet.add(node.value); - }); - }).processSync(rule.selector); - }, - OnceExit() { - const classArray = Array.from(classSet).sort(); - - if (!fs.existsSync("../../Moonlight.Client/Styles/mappings")){ - fs.mkdirSync("../../Moonlight.Client/Styles/mappings"); - } - - fs.writeFileSync('../../Moonlight.Client/Styles/mappings/classes.map', classArray.join('\n')); - console.log(`✅ Extracted ${classArray.length} Tailwind classes to tailwind-classes.txt`); - } - }; -}; - -module.exports.postcss = true; \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Styles/package-lock.json b/Moonlight.Client.Runtime/Styles/package-lock.json deleted file mode 100644 index 5fedb79c..00000000 --- a/Moonlight.Client.Runtime/Styles/package-lock.json +++ /dev/null @@ -1,1347 +0,0 @@ -{ - "name": "styles", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "styles", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@tailwindcss/postcss": "^4.1.11", - "flyonui": "^2.2.0", - "postcss": "^8.5.6", - "postcss-cli": "^11.0.1", - "postcss-selector-parser": "^7.1.0", - "tailwindcss": "^4.1.11" - }, - "devDependencies": {} - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz", - "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.2.tgz", - "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==", - "dependencies": { - "@floating-ui/core": "^1.7.2", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==" - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz", - "integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.30.1", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz", - "integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==", - "hasInstallScript": true, - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-arm64": "4.1.11", - "@tailwindcss/oxide-darwin-x64": "4.1.11", - "@tailwindcss/oxide-freebsd-x64": "4.1.11", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", - "@tailwindcss/oxide-linux-x64-musl": "4.1.11", - "@tailwindcss/oxide-wasm32-wasi": "4.1.11", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz", - "integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz", - "integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz", - "integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz", - "integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz", - "integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz", - "integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz", - "integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz", - "integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz", - "integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz", - "integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.11", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz", - "integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz", - "integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz", - "integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.11", - "@tailwindcss/oxide": "4.1.11", - "postcss": "^8.4.41", - "tailwindcss": "4.1.11" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "engines": { - "node": ">=18" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/dependency-graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", - "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", - "engines": { - "node": ">=4" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "engines": { - "node": ">=8" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", - "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flyonui": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/flyonui/-/flyonui-2.2.0.tgz", - "integrity": "sha512-Gncal89zwklAYpqV8IjSgN/1edsXnbTlxhfKRcYa2WgeY8jBSuoNBQWqdL1DSLuXarxYluimwIv34YaRKNuSzg==", - "dependencies": { - "@floating-ui/dom": "^1.6.13" - } - }, - "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-cli": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz", - "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==", - "dependencies": { - "chokidar": "^3.3.0", - "dependency-graph": "^1.0.0", - "fs-extra": "^11.0.0", - "picocolors": "^1.0.0", - "postcss-load-config": "^5.0.0", - "postcss-reporter": "^7.0.0", - "pretty-hrtime": "^1.0.3", - "read-cache": "^1.0.0", - "slash": "^5.0.0", - "tinyglobby": "^0.2.12", - "yargs": "^17.0.0" - }, - "bin": { - "postcss": "index.js" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-load-config": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", - "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "lilconfig": "^3.1.1", - "yaml": "^2.4.2" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - } - } - }, - "node_modules/postcss-reporter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", - "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "picocolors": "^1.0.0", - "thenby": "^1.3.4" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", - "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==" - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/thenby": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", - "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==" - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "engines": { - "node": ">=18" - } - }, - "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - } - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - } - } -} diff --git a/Moonlight.Client.Runtime/Styles/package.json b/Moonlight.Client.Runtime/Styles/package.json deleted file mode 100644 index d528236e..00000000 --- a/Moonlight.Client.Runtime/Styles/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "styles", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "tailwind": "npx postcss styles.css -o ../wwwroot/css/style.min.css --watch", - "tailwind-build": "npx postcss styles.css -o ../wwwroot/css/style.min.css", - "mappings": "EXTRACT_CLASSES=true npx postcss styles.css -o ../wwwroot/css/style.min.css " - }, - "author": "", - "license": "ISC", - "dependencies": { - "@tailwindcss/postcss": "^4.1.11", - "flyonui": "^2.2.0", - "tailwindcss": "^4.1.11", - "postcss": "^8.5.6", - "postcss-cli": "^11.0.1", - "postcss-selector-parser": "^7.1.0" - }, - "devDependencies": { - } -} diff --git a/Moonlight.Client.Runtime/Styles/postcss.config.js b/Moonlight.Client.Runtime/Styles/postcss.config.js deleted file mode 100644 index 82078a37..00000000 --- a/Moonlight.Client.Runtime/Styles/postcss.config.js +++ /dev/null @@ -1,11 +0,0 @@ -const tailwindcss = require('@tailwindcss/postcss'); -const extractClasses = require('./extract-classes'); - -module.exports = { - plugins: [ - tailwindcss - ], -}; - -if(process.env.EXTRACT_CLASSES === "true") - module.exports.plugins.push(extractClasses); \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Styles/styles.css b/Moonlight.Client.Runtime/Styles/styles.css deleted file mode 100644 index c4f849f3..00000000 --- a/Moonlight.Client.Runtime/Styles/styles.css +++ /dev/null @@ -1,90 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=fallback') layer(base); -@import url('https://fonts.googleapis.com/css2?family=Source+Code+Pro:ital,wght@0,200..900;1,200..900&display=swap') layer(base); -@import url("https://cdn.jsdelivr.net/npm/lucide-static/font/lucide.css") layer(base); - -@import "tailwindcss"; -@import "./node_modules/flyonui/variants.css"; -@import "./theme.css"; - -@theme { - --font-inter: "Inter", var(--font-sans); - --font-scp: "Source Code Pro", var(--font-mono); -} - -@plugin "flyonui" { - themes: moonlight --default; -} - -@source "./node_modules/flyonui/dist/index.js"; - -@source "../**/*.razor"; -@source "../**/*.cs"; -@source "../**/*.html"; -@source "**/*.map"; - -@source "../../Moonlight.Client/**/*.cs"; -@source "../../Moonlight.Client/**/*.html"; -@source "../../Moonlight.Client/**/*.razor"; -@source "../../Moonlight.Client/Styles/**/*.map"; - -@source "../../Moonlight.ApiServer/**/*.razor"; - -#blazor-error-ui { - display: none; -} - -#blazor-loader-label:after { - content: var(--blazor-load-percentage-text, "Loading"); -} - -#blazor-loader-progress { - width: var(--blazor-load-percentage, 0%); -} - -@layer utilities { - .btn { - @apply text-sm font-medium inline-flex items-center justify-center; - } - - .checkbox { - @apply border-base-content/30 bg-base-100; - } - - .input { - @apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-100/50; - } - - .select { - @apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-100/50; - } - - .table { - :where(th, td) { - @apply py-1.5; - } - } - - .dropdown-item { - @apply px-2.5 py-1.5 text-sm; - } - - .advance-select-menu { - @apply !border-base-content/20 border-2 ring-0! outline-0! bg-base-200/50 !pt-0 !px-0 !py-0; - } - - .advance-select-option { - @apply !rounded-none hover:!bg-primary; - } - - .advance-select-option.selected { - @apply !bg-primary !text-primary-content; - } - - .advance-select-toggle { - @apply !border-base-content/20 border-2 ring-0! outline-0! focus:border-primary! focus-within:border-primary! bg-base-200/50; - } - - .table thead { - @apply !normal-case; - } -} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/Styles/theme.css b/Moonlight.Client.Runtime/Styles/theme.css deleted file mode 100644 index 7c475499..00000000 --- a/Moonlight.Client.Runtime/Styles/theme.css +++ /dev/null @@ -1,35 +0,0 @@ -@plugin "flyonui/theme" { - name: "moonlight"; - prefersdark: true; - color-scheme: "dark"; - - --color-base-100: #192032; - --color-base-200: #101522; - --color-base-300: #070a14; - - --color-base-content: #dde5f5; - --color-primary: oklch(.511 .262 276.966); - --color-primary-content: #dde5f5; - --color-secondary: oklch(37% 0.034 259.733); - --color-secondary-content: #dde5f5; - --color-accent: oklch(49.1% 0.27 292.581); - --color-accent-content: #dde5f5; - --color-neutral: oklch(27% 0.041 260.031); - --color-neutral-content: oklch(92% 0.004 286.32); - --color-info: oklch(.546 .245 262.881); - --color-info-content: #dde5f5; - --color-success: oklch(.627 .194 149.214); - --color-success-content: #dde5f5; - --color-warning: oklch(.828 .189 84.429); - --color-warning-content: #dde5f5; - --color-error: oklch(.586 .253 17.585); - --color-error-content: #dde5f5; - --radius-selector: 0.25rem; - --radius-field: 0.5rem; - --radius-box: 0.5rem; - --size-selector: 0.25rem; - --size-field: 0.25rem; - --border: 1px; - --depth: 0; - --noise: 0; -} \ No newline at end of file diff --git a/Moonlight.Client.Runtime/wwwroot/manifest.webmanifest b/Moonlight.Client.Runtime/wwwroot/manifest.webmanifest deleted file mode 100644 index 78a86a9e..00000000 --- a/Moonlight.Client.Runtime/wwwroot/manifest.webmanifest +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "Moonlight Client", - "short_name": "Moonlight.Client", - "id": "./", - "start_url": "./", - "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#030b1f", - "prefer_related_applications": false, - "icons": [ - { - "src": "img/icon-512.png", - "type": "image/png", - "sizes": "512x512" - }, - { - "src": "img/icon-192.png", - "type": "image/png", - "sizes": "192x192" - } - ] -} diff --git a/Moonlight.Client.Runtime/wwwroot/service-worker.js b/Moonlight.Client.Runtime/wwwroot/service-worker.js deleted file mode 100644 index fe614dae..00000000 --- a/Moonlight.Client.Runtime/wwwroot/service-worker.js +++ /dev/null @@ -1,4 +0,0 @@ -// In development, always fetch from the network and do not enable offline support. -// This is because caching would make development more difficult (changes would not -// be reflected on the first load after each change). -self.addEventListener('fetch', () => { }); diff --git a/Moonlight.Client.Runtime/wwwroot/service-worker.published.js b/Moonlight.Client.Runtime/wwwroot/service-worker.published.js deleted file mode 100644 index 293651c9..00000000 --- a/Moonlight.Client.Runtime/wwwroot/service-worker.published.js +++ /dev/null @@ -1,66 +0,0 @@ -// Caution! Be sure you understand the caveats before publishing an application with -// offline support. See https://aka.ms/blazor-offline-considerations - -self.importScripts('./service-worker-assets.js'); -self.addEventListener('install', event => event.waitUntil(onInstall(event))); -self.addEventListener('activate', event => event.waitUntil(onActivate(event))); -self.addEventListener('fetch', event => event.respondWith(onFetch(event))); - -const cacheNamePrefix = 'offline-cache-'; -const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; -const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/]; -const offlineAssetsExclude = [/^service-worker\.js$/]; - -// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'. -const base = "/"; -const baseUrl = new URL(base, self.origin); -const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href); - -// Add assets which are only available on the server and should not be cached here -const serverOnlyResources = [ - "/api", - "/oauth2" -]; - -async function onInstall(event) { - console.info('Service worker: Install'); - - // Fetch and cache all matching items from the assets manifest - const assetsRequests = self.assetsManifest.assets - .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) - .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) - .map(asset => new Request(asset.url, {integrity: asset.hash, cache: 'no-cache'})); - await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); -} - -async function onActivate(event) { - console.info('Service worker: Activate'); - - // Delete unused caches - const cacheKeys = await caches.keys(); - await Promise.all(cacheKeys - .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) - .map(key => caches.delete(key))); -} - -async function onFetch(event) { - let cachedResponse = null; - - if (event.request.method === 'GET') { - // For all navigation requests, try to serve index.html from cache, - // unless that request is for an offline resource. - - const path = new URL(event.request.url).pathname; - - const shouldServeIndexHtml = - event.request.mode === 'navigate' && - !manifestUrlList.some(url => url === event.request.url) && - !serverOnlyResources.some(url => path.startsWith(url)); // Check for server side assets - - const request = shouldServeIndexHtml ? 'index.html' : event.request; - const cache = await caches.open(cacheName); - cachedResponse = await cache.match(request); - } - - return cachedResponse || fetch(event.request); -} diff --git a/Moonlight.Client/GlobalUsings.cs b/Moonlight.Client/GlobalUsings.cs deleted file mode 100644 index 0598b4b1..00000000 --- a/Moonlight.Client/GlobalUsings.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Global using directives - -global using Microsoft.AspNetCore.Components.Web; -global using Microsoft.JSInterop; -global using Microsoft.Extensions.Logging; -global using MoonCore.Blazor.FlyonUi.Components; \ No newline at end of file diff --git a/Moonlight.Client/IAssemblyMarker.cs b/Moonlight.Client/IAssemblyMarker.cs deleted file mode 100644 index bd73daa6..00000000 --- a/Moonlight.Client/IAssemblyMarker.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Moonlight.Client; - -public interface IAssemblyMarker; \ No newline at end of file diff --git a/Moonlight.Client/Implementations/CoreStartup.cs b/Moonlight.Client/Implementations/CoreStartup.cs deleted file mode 100644 index 3e009450..00000000 --- a/Moonlight.Client/Implementations/CoreStartup.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Moonlight.Client.Interfaces; -using Moonlight.Client.Plugins; - -namespace Moonlight.Client.Implementations; - -public class CoreStartup : IPluginStartup -{ - public void AddPlugin(WebAssemblyHostBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - } - - public void ConfigurePlugin(WebAssemblyHost app) - { - - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/DefaultAdminOverviewElementProvider.cs b/Moonlight.Client/Implementations/DefaultAdminOverviewElementProvider.cs deleted file mode 100644 index 5a81811f..00000000 --- a/Moonlight.Client/Implementations/DefaultAdminOverviewElementProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Moonlight.Client.Interfaces; - -namespace Moonlight.Client.Implementations; - -public class DefaultAdminOverviewElementProvider : IAdminOverviewElementProvider -{ - public void ModifyOverview(List overviewComponents) - { - - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/DefaultOverviewElementProvider.cs b/Moonlight.Client/Implementations/DefaultOverviewElementProvider.cs deleted file mode 100644 index ddc6bdd2..00000000 --- a/Moonlight.Client/Implementations/DefaultOverviewElementProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Moonlight.Client.Interfaces; -using Moonlight.Client.UI.Components; - -namespace Moonlight.Client.Implementations; - -public class DefaultOverviewElementProvider : IOverviewElementProvider -{ - public void ModifyOverview(List overviewComponents) - { - overviewComponents.Add(typeof(WelcomeOverviewElement)); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs b/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs deleted file mode 100644 index 5ef8aae0..00000000 --- a/Moonlight.Client/Implementations/DefaultSidebarItemProvider.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Moonlight.Client.Interfaces; -using Moonlight.Client.Models; - -namespace Moonlight.Client.Implementations; - -public class DefaultSidebarItemProvider : ISidebarItemProvider -{ - public void ModifySidebar(List items) - { - items.AddRange( - [ - // User - new SidebarItem() - { - Icon = "icon-chart-no-axes-gantt", - Name = "Overview", - Path = "/", - Priority = 0, - RequiresExactMatch = true - }, - - // Admin - new SidebarItem() - { - Icon = "icon-chart-no-axes-gantt", - Name = "Overview", - Group = "Admin", - Path = "/admin", - Priority = 0, - RequiresExactMatch = true, - Policy = "permissions:admin.overview" - }, - new SidebarItem() - { - Icon = "icon-users", - Name = "Users", - Group = "Admin", - Path = "/admin/users", - Priority = 1, - RequiresExactMatch = false, - Policy = "permissions:admin.users.get" - }, - new SidebarItem() - { - Icon = "icon-key-square", - Name = "API", - Group = "Admin", - Path = "/admin/api", - Priority = 2, - RequiresExactMatch = false, - Policy = "permissions:admin.api.get" - }, - new SidebarItem() - { - Icon = "icon-settings", - Name = "System", - Group = "Admin", - Path = "/admin/system", - Priority = 3, - RequiresExactMatch = false, - Policy = "permissions:admin.system.overview" - }, - ] - ); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/LogErrorFilter.cs b/Moonlight.Client/Implementations/LogErrorFilter.cs deleted file mode 100644 index d2c1bc5a..00000000 --- a/Moonlight.Client/Implementations/LogErrorFilter.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MoonCore.Blazor.FlyonUi.Exceptions; - -namespace Moonlight.Client.Implementations; - -public class LogErrorFilter : IGlobalErrorFilter -{ - private readonly ILogger Logger; - - public LogErrorFilter(ILogger logger) - { - Logger = logger; - } - - public Task HandleExceptionAsync(Exception ex) - { - Logger.LogError(ex, "Global error processed"); - return Task.FromResult(false); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/SystemFsAccess.cs b/Moonlight.Client/Implementations/SystemFsAccess.cs deleted file mode 100644 index 350fa21c..00000000 --- a/Moonlight.Client/Implementations/SystemFsAccess.cs +++ /dev/null @@ -1,154 +0,0 @@ -using MoonCore.Blazor.FlyonUi.Files; -using MoonCore.Blazor.FlyonUi.Files.Manager; -using MoonCore.Blazor.FlyonUi.Files.Manager.Abstractions; -using MoonCore.Helpers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Files; -using Moonlight.Shared.Http.Responses.Admin.Sys; - -namespace Moonlight.Client.Implementations; - -public class SystemFsAccess : IFsAccess, ICombineAccess, IArchiveAccess, IDownloadUrlAccess -{ - private readonly HttpApiClient ApiClient; - - private const string BaseApiUrl = "api/admin/system/files"; - - public SystemFsAccess(HttpApiClient apiClient) - { - ApiClient = apiClient; - } - - public async Task CreateFileAsync(string path) - { - await ApiClient.Post( - $"{BaseApiUrl}/touch?path={path}" - ); - } - - public async Task CreateDirectoryAsync(string path) - { - await ApiClient.Post( - $"{BaseApiUrl}/mkdir?path={path}" - ); - } - - public async Task ListAsync(string path) - { - var entries = await ApiClient.GetJson( - $"{BaseApiUrl}/list?path={path}" - ); - - return entries.Select(x => new FsEntry() - { - Name = x.Name, - CreatedAt = x.CreatedAt, - IsFolder = x.IsFolder, - Size = x.Size, - UpdatedAt = x.UpdatedAt - }).ToArray(); - } - - public async Task MoveAsync(string oldPath, string newPath) - { - await ApiClient.Post( - $"{BaseApiUrl}/move?oldPath={oldPath}&newPath={newPath}" - ); - } - - public async Task ReadAsync(string path, Func onHandleData) - { - await using var stream = await ApiClient.GetStream( - $"{BaseApiUrl}/download?path={path}" - ); - - await onHandleData.Invoke(stream); - - stream.Close(); - } - - public async Task WriteAsync(string path, Stream dataStream) - { - using var multiPartForm = new MultipartFormDataContent(); - - multiPartForm.Add(new StreamContent(dataStream), "file", "file"); - - await ApiClient.Post( - $"{BaseApiUrl}/upload?path={path}", - multiPartForm - ); - } - - public async Task DeleteAsync(string path) - { - await ApiClient.Delete( - $"{BaseApiUrl}/delete?path={path}" - ); - } - - public async Task CombineAsync(string destination, string[] files) - { - await ApiClient.Post( - $"{BaseApiUrl}/combine", - new CombineRequest() - { - Destination = destination, - Files = files - } - ); - } - - public ArchiveFormat[] ArchiveFormats { get; } = - [ - new("zip", ["zip"], "Zip Archive"), - new("tar.gz", ["tar.gz"], "Tar.gz Archive") - ]; - - public async Task ArchiveAsync( - string destination, - ArchiveFormat format, - string root, - FsEntry[] files, - Func? onProgress = null - ) - { - await ApiClient.Post($"{BaseApiUrl}/compress", new CompressRequest() - { - Destination = destination, - Items = files.Select(x => x.Name).ToArray(), - Root = root, - Format = format.Identifier - }); - } - - public async Task UnarchiveAsync( - string path, - ArchiveFormat format, - string destination, - Func? onProgress = null) - { - await ApiClient.Post( - $"{BaseApiUrl}/decompress", - new DecompressRequest() - { - Format = format.Identifier, - Destination = destination, - Path = path - } - ); - } - - public async Task GetFileUrlAsync(string path) - => await GetDownloadUrlAsync(path); - - public async Task GetFolderUrlAsync(string path) - => await GetDownloadUrlAsync(path); - - private async Task GetDownloadUrlAsync(string path) - { - var response = await ApiClient.PostJson( - $"{BaseApiUrl}/downloadUrl?path={path}" - ); - - return response.Url; - } -} \ No newline at end of file diff --git a/Moonlight.Client/Implementations/UnauthenticatedErrorFilter.cs b/Moonlight.Client/Implementations/UnauthenticatedErrorFilter.cs deleted file mode 100644 index 6796d589..00000000 --- a/Moonlight.Client/Implementations/UnauthenticatedErrorFilter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Components; -using MoonCore.Blazor.FlyonUi.Exceptions; -using MoonCore.Blazor.FlyonUi.Toasts; -using MoonCore.Exceptions; - -namespace Moonlight.Client.Implementations; - -public class UnauthenticatedErrorFilter : IGlobalErrorFilter -{ - private readonly NavigationManager Navigation; - private readonly ToastService ToastService; - - public UnauthenticatedErrorFilter( - NavigationManager navigation, - ToastService toastService - ) - { - Navigation = navigation; - ToastService = toastService; - } - - public async Task HandleExceptionAsync(Exception ex) - { - if (ex is not HttpApiException { Status: 401 }) - return false; - - await ToastService.InfoAsync("Session expired", "Your session has expired. Reloading.."); - - Navigation.NavigateTo("/api/auth/logout", true); - return true; - } -} \ No newline at end of file diff --git a/Moonlight.Client/Interfaces/IAdminOverviewElementProvider.cs b/Moonlight.Client/Interfaces/IAdminOverviewElementProvider.cs deleted file mode 100644 index 183866dc..00000000 --- a/Moonlight.Client/Interfaces/IAdminOverviewElementProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.Client.Interfaces; - -public interface IAdminOverviewElementProvider -{ - public void ModifyOverview(List overviewComponents); -} \ No newline at end of file diff --git a/Moonlight.Client/Interfaces/IOverviewElementProvider.cs b/Moonlight.Client/Interfaces/IOverviewElementProvider.cs deleted file mode 100644 index 05351aa8..00000000 --- a/Moonlight.Client/Interfaces/IOverviewElementProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.Client.Interfaces; - -public interface IOverviewElementProvider -{ - public void ModifyOverview(List overviewComponents); -} \ No newline at end of file diff --git a/Moonlight.Client/Interfaces/ISidebarItemProvider.cs b/Moonlight.Client/Interfaces/ISidebarItemProvider.cs deleted file mode 100644 index 4c5c7e0c..00000000 --- a/Moonlight.Client/Interfaces/ISidebarItemProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Moonlight.Client.Models; - -namespace Moonlight.Client.Interfaces; - -public interface ISidebarItemProvider -{ - public void ModifySidebar(List items); -} \ No newline at end of file diff --git a/Moonlight.Client/Models/SidebarItem.cs b/Moonlight.Client/Models/SidebarItem.cs deleted file mode 100644 index 2246bc51..00000000 --- a/Moonlight.Client/Models/SidebarItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Moonlight.Client.Models; - -public class SidebarItem -{ - public string Icon { get; set; } - public string Name { get; set; } - public string? Group { get; set; } - public string Path { get; set; } - public int Priority { get; set; } - public bool RequiresExactMatch { get; set; } = false; - public string? Policy { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Client/Models/ThemeTransferModel.cs b/Moonlight.Client/Models/ThemeTransferModel.cs deleted file mode 100644 index 164556f3..00000000 --- a/Moonlight.Client/Models/ThemeTransferModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Moonlight.Shared.Misc; - -namespace Moonlight.Client.Models; - -public class ThemeTransferModel -{ - public string Name { get; set; } - public string Author { get; set; } - public string Version { get; set; } - public string? UpdateUrl { get; set; } - public string? DonateUrl { get; set; } - - public ApplicationTheme Content { get; set; } = new(); -} \ No newline at end of file diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj deleted file mode 100644 index 9e3296d0..00000000 --- a/Moonlight.Client/Moonlight.Client.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - net9.0 - enable - enable - - **\bin\**;**\obj\**;**\node_modules\**;**\Styles\*.json - - True - - - frontend - Moonlight.Client - 2.1.15 - Moonlight Panel - A build of the client for moonlight development - https://github.com/Moonlight-Panel/Moonlight - true - true - false - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Moonlight.Client/Moonlight.Client.targets b/Moonlight.Client/Moonlight.Client.targets deleted file mode 100644 index 94260208..00000000 --- a/Moonlight.Client/Moonlight.Client.targets +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/Moonlight.Client/Plugins/IPluginStartup.cs b/Moonlight.Client/Plugins/IPluginStartup.cs deleted file mode 100644 index 2a7c3f8f..00000000 --- a/Moonlight.Client/Plugins/IPluginStartup.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; - -namespace Moonlight.Client.Plugins; - -public interface IPluginStartup -{ - public void AddPlugin(WebAssemblyHostBuilder builder); - public void ConfigurePlugin(WebAssemblyHost app); -} \ No newline at end of file diff --git a/Moonlight.Client/Services/ApplicationAssemblyService.cs b/Moonlight.Client/Services/ApplicationAssemblyService.cs deleted file mode 100644 index 2c0ec274..00000000 --- a/Moonlight.Client/Services/ApplicationAssemblyService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Reflection; - -namespace Moonlight.Client.Services; - -public class ApplicationAssemblyService -{ - public List Assemblies { get; set; } = new(); -} \ No newline at end of file diff --git a/Moonlight.Client/Services/RemoteAuthStateProvider.cs b/Moonlight.Client/Services/RemoteAuthStateProvider.cs deleted file mode 100644 index 74635da7..00000000 --- a/Moonlight.Client/Services/RemoteAuthStateProvider.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Components.Authorization; -using MoonCore.Exceptions; -using MoonCore.Helpers; -using Moonlight.Shared.Http.Responses.Auth; - -namespace Moonlight.Client.Services; - -public class RemoteAuthStateProvider : AuthenticationStateProvider -{ - private readonly HttpApiClient ApiClient; - - public RemoteAuthStateProvider(HttpApiClient apiClient) - { - ApiClient = apiClient; - } - - public override async Task GetAuthenticationStateAsync() - { - ClaimsPrincipal principal; - - try - { - var claims = await ApiClient.GetJson( - "api/auth/check" - ); - - principal = new ClaimsPrincipal( - new ClaimsIdentity( - claims.Select(x => new Claim(x.Type, x.Value)), - "RemoteAuthentication" - ) - ); - } - catch (HttpApiException e) - { - if (e.Status != 401 && e.Status != 403) - throw; - - principal = new ClaimsPrincipal(); - } - - return new AuthenticationState(principal); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Services/ThemeService.cs b/Moonlight.Client/Services/ThemeService.cs deleted file mode 100644 index 58774bff..00000000 --- a/Moonlight.Client/Services/ThemeService.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MoonCore.Attributes; -using MoonCore.Helpers; -using Moonlight.Shared.Http.Requests.Admin.Sys.Theme; -using Moonlight.Shared.Http.Responses.Admin; - -namespace Moonlight.Client.Services; - -[Scoped] -public class ThemeService -{ - private readonly HttpApiClient ApiClient; - - public ThemeService(HttpApiClient apiClient) - { - ApiClient = apiClient; - } - - public async Task GetAsync(int id) - { - return await ApiClient.GetJson( - $"api/admin/system/customisation/themes/{id}" - ); - } - - public async Task CreateAsync(CreateThemeRequest request) - { - return await ApiClient.PostJson( - "api/admin/system/customisation/themes", - request - ); - } - - public async Task UpdateAsync(int id, UpdateThemeRequest request) - { - return await ApiClient.PatchJson( - $"api/admin/system/customisation/themes/{id}", - request - ); - } - - public async Task DeleteAsync(int id) - { - await ApiClient.Delete( - $"api/admin/system/customisation/themes/{id}" - ); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Services/WindowService.cs b/Moonlight.Client/Services/WindowService.cs deleted file mode 100644 index 7e3cda67..00000000 --- a/Moonlight.Client/Services/WindowService.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Moonlight.Client.Services; - -public class WindowService -{ - private readonly IJSRuntime JsRuntime; - - public WindowService(IJSRuntime jsRuntime) - { - JsRuntime = jsRuntime; - } - - public async Task OpenAsync(string url, string title, int height, int width) - => await JsRuntime.InvokeVoidAsync("moonlight.window.open", url, title, height, width); - - public async Task CloseAsync() - => await JsRuntime.InvokeVoidAsync("moonlight.window.closeCurrent"); -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Auth.cs b/Moonlight.Client/Startup/Startup.Auth.cs deleted file mode 100644 index 2f164288..00000000 --- a/Moonlight.Client/Startup/Startup.Auth.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Blazor.FlyonUi.Exceptions; -using MoonCore.Permissions; -using Moonlight.Client.Implementations; -using Moonlight.Client.Services; - -namespace Moonlight.Client.Startup; - -public static partial class Startup -{ - private static void AddAuth(this WebAssemblyHostBuilder builder) - { - builder.Services.AddAuthorizationCore(); - builder.Services.AddCascadingAuthenticationState(); - - builder.Services.AddScoped(); - builder.Services.AddScoped(); - - builder.Services.AddAuthorizationPermissions(options => - { - options.ClaimName = "Permissions"; - options.Prefix = "permissions:"; - }); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Base.cs b/Moonlight.Client/Startup/Startup.Base.cs deleted file mode 100644 index 772af308..00000000 --- a/Moonlight.Client/Startup/Startup.Base.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using MoonCore.Blazor.FlyonUi; -using MoonCore.Blazor.FlyonUi.Exceptions; -using MoonCore.Extensions; -using MoonCore.Helpers; -using Moonlight.Client.Implementations; -using Moonlight.Client.Services; -using Moonlight.Client.UI; - -namespace Moonlight.Client.Startup; - -public static partial class Startup -{ - private static void AddBase(this WebAssemblyHostBuilder builder) - { - builder.RootComponents.Add("#app"); - builder.RootComponents.Add("head::after"); - - builder.Services.AddScoped(_ => - new HttpClient - { - BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) - } - ); - - builder.Services.AddScoped(sp => - { - var httpClient = sp.GetRequiredService(); - return new HttpApiClient(httpClient); - }); - - builder.Services.AddFileManagerOperations(); - builder.Services.AddFlyonUiServices(); - - builder.Services.AddScoped(); - - builder.Services.AutoAddServices(); - - builder.Services.AddScoped(); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Logging.cs b/Moonlight.Client/Startup/Startup.Logging.cs deleted file mode 100644 index 045eda2c..00000000 --- a/Moonlight.Client/Startup/Startup.Logging.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using MoonCore.Logging; - -namespace Moonlight.Client.Startup; - -public static partial class Startup -{ - private static void AddLogging(this WebAssemblyHostBuilder builder) - { - builder.Logging.ClearProviders(); - builder.Logging.AddAnsiConsole(); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Misc.cs b/Moonlight.Client/Startup/Startup.Misc.cs deleted file mode 100644 index 08009f57..00000000 --- a/Moonlight.Client/Startup/Startup.Misc.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Moonlight.Client.Startup; - -public static partial class Startup -{ - private static void PrintVersion() - { - // Fancy start console output... yes very fancy :> - Console.Write("Running "); - - var rainbow = new Crayon.Rainbow(0.5); - foreach (var c in "Moonlight") - { - Console.Write( - rainbow - .Next() - .Bold() - .Text(c.ToString()) - ); - } - - Console.WriteLine(); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.Plugins.cs b/Moonlight.Client/Startup/Startup.Plugins.cs deleted file mode 100644 index 63fcaf77..00000000 --- a/Moonlight.Client/Startup/Startup.Plugins.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Moonlight.Client.Plugins; -using Moonlight.Client.Services; - -namespace Moonlight.Client.Startup; - -public partial class Startup -{ - private static void AddPlugins(this WebAssemblyHostBuilder builder, IPluginStartup[] startups) - { - foreach (var startup in startups) - startup.AddPlugin(builder); - - // Get all assemblies and combine them into the application assembly service - // TODO: Consider rewriting this as it may not be that performant to do string checking to find distinct items - builder.Services.AddSingleton(new ApplicationAssemblyService() - { - Assemblies = startups - .Select(x => x.GetType().Assembly) - .DistinctBy(x => x.FullName) - .ToList() - }); - } - - private static void ConfigurePlugins(this WebAssemblyHost app, IPluginStartup[] startups) - { - foreach (var startup in startups) - startup.ConfigurePlugin(app); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Startup/Startup.cs b/Moonlight.Client/Startup/Startup.cs deleted file mode 100644 index ba0c5d95..00000000 --- a/Moonlight.Client/Startup/Startup.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Moonlight.Client.Plugins; - -namespace Moonlight.Client.Startup; - -public static partial class Startup -{ - public static void AddMoonlight(this WebAssemblyHostBuilder builder, IPluginStartup[] startups) - { - PrintVersion(); - - builder.AddLogging(); - builder.AddBase(); - builder.AddAuth(); - builder.AddPlugins(startups); - } - - public static void ConfigureMoonlight(this WebAssemblyHost app, IPluginStartup[] startups) - { - app.ConfigurePlugins(startups); - } -} \ No newline at end of file diff --git a/Moonlight.Client/Styles/MoonCore.Blazor.FlyonUi/mooncore.map b/Moonlight.Client/Styles/MoonCore.Blazor.FlyonUi/mooncore.map deleted file mode 100755 index 7d500b42..00000000 --- a/Moonlight.Client/Styles/MoonCore.Blazor.FlyonUi/mooncore.map +++ /dev/null @@ -1,584 +0,0 @@ -!bg-base-100 -!border-base-content/40 -!border-none -!flex -!font-medium -!font-semibold -!h-2.5 -!justify-between -!me-1.5 -!ms-auto -!px-2.5 -!py-0.5 -!rounded-full -!rounded-xs -!text-sm -!w-2.5 -*:first:rounded-tl-lg -*:last:rounded-tr-lg --left-4 --ml-4 --translate-x-full --translate-y-1/2 -[animation-duration:0.8s] -[animation-timing-function:ease] -absolute -accordion -accordion-bordered -accordion-content -accordion-toggle -active -active-tab:bg-primary -active-tab:text-primary-content -advance-select-menu -advance-select-option -advance-select-tag -advance-select-toggle -alert -alert-error -alert-outline -alert-soft -align-bottom -align-middle -animate-bounce -animate-spin -avatar -avatar-placeholder -badge -basis-full -bg-background -bg-background/60 -bg-base-100 -bg-base-100/50 -bg-base-200 -bg-base-200! -bg-base-200/50 -bg-base-200/60 -bg-base-300 -bg-base-300/15 -bg-base-300/60 -bg-error -bg-info -bg-primary -bg-primary/5 -bg-success -bg-transparent -bg-warning -block -blur -border -border-0 -border-1 -border-2 -border-3 -border-b -border-b-2 -border-b-base-content/20 -border-b-primary -border-base-content/20 -border-base-content/25 -border-base-content/40 -border-base-content/5 -border-dashed -border-dotted -border-error/30 -border-info/30 -border-l-transparent -border-r-transparent -border-solid -border-success/30 -border-t -border-t-transparent -border-transparent -border-warning/30 -bottom-0 -bottom-full -breadcrumbs -break-words -btn -btn-accent -btn-active -btn-circle -btn-disabled -btn-error -btn-info -btn-primary -btn-secondary -btn-sm -btn-soft -btn-square -btn-success -btn-text -btn-warning -card -card-alert -card-body -card-border -card-footer -card-header -card-title -carousel -carousel-body -carousel-next -carousel-prev -carousel-slide -chat -chat-avatar -chat-bubble -chat-footer -chat-header -chat-receiver -chat-sender -checkbox -checkbox-primary -checkbox-xs -col-span-1 -collapse -combo-box-selected:block -combo-box-selected:dropdown-active -complete -container -contents -cursor-default -cursor-not-allowed -cursor-pointer -cursor-progress -diff -disabled -divide-base-100/60 -divide-y -divider -drawer -drawer-body -drawer-bottom -drawer-end -drawer-footer -drawer-header -drawer-start -drawer-title -drawer-top -dropdown -dropdown-active -dropdown-disabled -dropdown-item -dropdown-menu -dropdown-open:opacity-100 -dropdown-open:rotate-180 -dropdown-toggle -duration-300 -duration-500 -ease-in-out -ease-linear -end-3 -error-message -file-upload-complete:progress-success -fill-base-content -fill-black -fill-gray-200 -filter -filter-reset -fixed -flex -flex-1 -flex-col -flex-grow -flex-nowrap -flex-row -flex-shrink-0 -flex-wrap -focus-visible:outline-none -focus-within:border-primary -focus:border-primary -focus:outline-1 -focus:outline-none -focus:outline-primary -focus:ring-0 -font-bold -font-inter -font-medium -font-normal -font-semibold -footer -footer-center -gap-0.5 -gap-1 -gap-1.5 -gap-2 -gap-3 -gap-4 -gap-5 -gap-6 -gap-x-1 -gap-x-2 -gap-x-3 -gap-y-1 -gap-y-2.5 -grid -grid-cols-1 -grid-flow-col -grow -grow-0 -h-10 -h-2 -h-3 -h-36 -h-64 -h-8 -h-auto -h-full -h-screen -helper-text -hidden -hover:bg-primary/20 -hover:bg-primary/5 -hover:cursor-pointer -hover:text-base-content -hover:text-base-content/60 -hover:text-primary -image-full -indicator -indicator-item -inline -inline-block -inline-flex -inline-grid -input -input-floating -input-floating-label -input-lg -input-md -input-sm -input-xl -inset-0 -inset-y-0 -inset-y-2 -invisible -is-invalid -is-valid -isolate -italic -items-center -items-end -items-start -join -join-item -justify-between -justify-center -justify-end -justify-start -label-text -leading-3.5 -leading-none -left-0 -lg:bg-base-100/20 -lg:flex -lg:hidden -lg:min-w-0 -lg:p-10 -lg:pb-5 -lg:pl-64 -lg:pr-3.5 -lg:pt-5 -lg:ring-1 -lg:ring-base-content/10 -lg:rounded-lg -lg:shadow-xs -lg:table-cell -link -link-animated -link-hover -link-primary -list-disc -list-inside -loading -loading-sm -loading-spinner -loading-xl -lowercase -m-10 -mask -max-h-52 -max-lg:flex-col -max-lg:hidden -max-md:flex-wrap -max-md:justify-center -max-md:w-full -max-sm:hidden -max-w-64 -max-w-7xl -max-w-80 -max-w-fit -max-w-full -max-w-lg -max-w-sm -max-w-xl -mb-0.5 -mb-1 -mb-1.5 -mb-2 -mb-2.5 -mb-25 -mb-3 -mb-4 -mb-5 -md:flex -md:gap-2 -md:hidden! -md:items-center -md:min-w-md -md:navbar-end -md:text-3xl -me-1 -me-1.5 -me-2 -me-2.5 -me-5 -menu -menu-active -menu-disabled -menu-dropdown -menu-dropdown-show -menu-horizontal -menu-sm -menu-title -min-h-0 -min-h-svh -min-w-0 -min-w-28 -min-w-48 -min-w-60 -min-w-[100px] -min-w-full -min-w-sm -ml-3 -ml-4 -modal -modal-content -modal-dialog -modal-middle -mr-4 -ms-0.5 -ms-1 -ms-1.5 -ms-2 -ms-3 -ms-auto -mt-1 -mt-1.5 -mt-10 -mt-12 -mt-2 -mt-2.5 -mt-20 -mt-3 -mt-3.5 -mt-4 -mt-5 -mt-8 -mx-1 -mx-auto -my-20 -my-3 -my-5 -my-auto -navbar -navbar-start -object-cover -opacity-0 -opacity-100 -opacity-75 -open -origin-top-left -outline -outline-0 -overflow-hidden -overflow-x-auto -overflow-y-auto -overlay-open:duration-50 -overlay-open:opacity-100 -overlay-open:translate-x-0 -overlay-open:translate-y-0 -overscroll-contain -p-0 -p-0.5 -p-1 -p-1.5 -p-12 -p-2 -p-2.5 -p-3 -p-4 -p-5 -p-6 -p-8 -pb-1 -pe-1 -pin-input -placeholder-base-content/60 -pointer-events-auto -pointer-events-none -progress -progress-bar -progress-indeterminate -progress-primary -pt-0.5 -pt-2 -pt-3 -px-1.5 -px-2 -px-2.5 -px-3 -px-4 -px-5 -py-0.5 -py-1.5 -py-2 -py-2.5 -py-6 -radial-progress -range -relative -resize -right-0 -ring -ring-0 -ring-1 -ring-white/10 -rounded-box -rounded-field -rounded-full -rounded-lg -rounded-md -rounded-t-lg -row-active -row-hover -rtl:!mr-0 -select -select-floating -select-floating-label -select-sm -selected -selected:select-active -shadow-base-300/20 -shadow-lg -shadow-md -shadow-none -shadow-xs -shrink-0 -size-10 -size-12 -size-4 -size-5 -size-8 -size-8.5 -sm:auto-cols-max -sm:flex -sm:inline -sm:items-center -sm:items-end -sm:justify-between -sm:justify-end -sm:max-w-2xl -sm:max-w-3xl -sm:max-w-4xl -sm:max-w-5xl -sm:max-w-6xl -sm:max-w-7xl -sm:max-w-lg -sm:max-w-md -sm:max-w-xl -sm:mb-0 -sm:mt-5 -sm:p-6 -sm:py-2 -sm:text-sm/5 -space-x-1 -space-x-2.5 -space-y-1 -space-y-4 -sr-only -static -status -sticky -success-message -switch -tab -tab! -tab-active -table -table-pin-cols -table-pin-rows -tabs -tabs-bordered -tabs-lg -tabs-lifted -tabs-md -tabs-sm -tabs-xl -tabs-xs -text-2xl -text-4xl -text-5xl -text-accent -text-base -text-base-content -text-base-content/40 -text-base-content/50 -text-base-content/60 -text-base-content/70 -text-base-content/80 -text-base/6 -text-center -text-error -text-error-content -text-gray-400 -text-info -text-info-content -text-left -text-lg -text-primary -text-primary-content -text-right -text-sm -text-sm/5 -text-success -text-success-content -text-warning -text-warning-content -text-xl -text-xs -text-xs/5 -textarea -textarea-floating -textarea-floating-label -theme-controller -tooltip -tooltip-content -top-0 -top-1/2 -top-3 -top-full -transform -transition -transition-all -transition-opacity -translate-x-0 -truncate -underline -uppercase -validate -w-0 -w-0.5 -w-13 -w-4 -w-56 -w-6 -w-64 -w-full -whitespace-nowrap -xl:flex -xl:grid-cols-2 -z-40 -z-50 -z-69 -z-70 \ No newline at end of file diff --git a/Moonlight.Client/Styles/mappings/classes.map b/Moonlight.Client/Styles/mappings/classes.map deleted file mode 100644 index 8a269259..00000000 --- a/Moonlight.Client/Styles/mappings/classes.map +++ /dev/null @@ -1,771 +0,0 @@ -!bg-base-100 -!border-base-content/40 -!border-none -!flex -!font-medium -!font-semibold -!h-2.5 -!justify-between -!me-1.5 -!ms-auto -!p-2.5 -!px-2.5 -!py-0.5 -!rounded-full -!rounded-xs -!text-sm -!w-2.5 -!w-8 -*:[grid-area:1/1] -*:first:rounded-tl-lg -*:last:rounded-tr-lg --left-4 --ml-4 --translate-x-full --translate-y-1/2 -[animation-duration:0.8s] -[animation-timing-function:ease] -absolute -accordion -accordion-bordered -accordion-toggle -active -active-tab:bg-primary -active-tab:hover:text-primary-content -active-tab:text-base-content -active-tab:text-primary-content -advance-select-menu -advance-select-option -advance-select-tag -advance-select-toggle -alert -alert-error -alert-outline -alert-primary -alert-soft -align-bottom -align-middle -animate-bounce -animate-ping -animate-spin -aria-[current='page']:text-bg-soft-primary -avatar -avatar-away-bottom -avatar-away-top -avatar-busy-bottom -avatar-busy-top -avatar-offline-bottom -avatar-offline-top -avatar-online-bottom -avatar-online-top -avatar-placeholder -badge -badge-error -badge-info -badge-outline -badge-primary -badge-soft -badge-success -basis-full -bg-base-100 -bg-base-100/50 -bg-base-200 -bg-base-200! -bg-base-200/50 -bg-base-200/60 -bg-base-300 -bg-base-300/15 -bg-base-300/45 -bg-base-300/50 -bg-base-300/60 -bg-clip-text -bg-error -bg-gradient-to-r -bg-gray-800 -bg-gray-900 -bg-indigo-600 -bg-info -bg-primary -bg-primary/5 -bg-red-500 -bg-success -bg-transparent -bg-warning -bg-white/5 -block -blur -border -border-0 -border-1 -border-2 -border-3 -border-accent -border-b -border-b-2 -border-b-base-content/20 -border-b-primary -border-base-content -border-base-content/20 -border-base-content/25 -border-base-content/40 -border-base-content/5 -border-base-content/60 -border-base-content/70 -border-dashed -border-dotted -border-e-2 -border-error/30 -border-info/30 -border-l-4 -border-l-transparent -border-primary -border-r-transparent -border-solid -border-success -border-success/30 -border-t -border-t-2 -border-t-transparent -border-transparent -border-warning/30 -bottom-0 -bottom-full -breadcrumbs -breadcrumbs-separator -break-words -btn -btn-accent -btn-active -btn-block -btn-circle -btn-disabled -btn-error -btn-gradient -btn-info -btn-lg -btn-outline -btn-primary -btn-secondary -btn-sm -btn-soft -btn-square -btn-success -btn-text -btn-warning -card -card-alert -card-body -card-border -card-footer -card-header -card-title -carousel -carousel-body -carousel-next -carousel-prev -carousel-slide -chat -chat-avatar -chat-bubble -chat-footer -chat-header -chat-receiver -chat-sender -checkbox -checkbox-primary -checkbox-xs -col-span-1 -col-span-12 -col-span-2 -collapse -combo-box-selected:block -combo-box-selected:dropdown-active -complete -container -contents -cursor-default -cursor-not-allowed -cursor-pointer -cursor-progress -custom-option -diff -disabled -divide-base-100/60 -divide-y -divider -drawer -drawer-body -drawer-bottom -drawer-end -drawer-footer -drawer-header -drawer-start -drawer-title -drawer-top -drop-shadow -dropdown -dropdown-active -dropdown-disabled -dropdown-item -dropdown-menu -dropdown-open:opacity-100 -dropdown-open:rotate-180 -dropdown-toggle -duration-300 -duration-500 -ease-in-out -ease-linear -end-3 -error-message -file-upload-complete:progress-success -fill-base-content -fill-black -fill-gray-200 -filter -filter-reset -fixed -flex -flex-1 -flex-col -flex-grow -flex-nowrap -flex-row -flex-shrink-0 -flex-wrap -focus-visible:outline -focus-visible:outline-2 -focus-visible:outline-indigo-600 -focus-visible:outline-none -focus-visible:outline-offset-2 -focus-within:border-primary -focus:border-primary -focus:outline-1 -focus:outline-none -focus:outline-primary -focus:ring-0 -focus:ring-2 -focus:ring-indigo-600 -focus:ring-inset -font-bold -font-inter -font-medium -font-normal -font-semibold -footer -footer-center -from-violet-400 -gap-0.5 -gap-1 -gap-1.5 -gap-2 -gap-3 -gap-4 -gap-5 -gap-6 -gap-x-1 -gap-x-2 -gap-x-3 -gap-x-6 -gap-y-1 -gap-y-1.5 -gap-y-2 -gap-y-2.5 -gap-y-3 -gap-y-8 -grid -grid-cols-1 -grid-cols-12 -grid-cols-2 -grid-cols-3 -grid-cols-4 -grid-flow-col -grow -grow-0 -h-0 -h-10 -h-12 -h-14 -h-2 -h-3 -h-32 -h-36 -h-6 -h-64 -h-8 -h-auto -h-full -h-screen -helper-text -hidden -hover:bg-indigo-500 -hover:bg-primary/20 -hover:bg-primary/5 -hover:bg-transparent -hover:cursor-pointer -hover:text-base-content -hover:text-base-content/60 -hover:text-indigo-500 -hover:text-primary -image-full -indicator -indicator-item -inline -inline-block -inline-flex -inline-grid -input -input-floating -input-floating-label -input-lg -input-md -input-sm -input-xl -input-xs -inset-0 -inset-y-0 -inset-y-2 -invisible -is-invalid -is-valid -isolate -italic -items-center -items-end -items-start -join -join-item -justify-between -justify-center -justify-end -justify-start -justify-stretch -label-text -leading-3 -leading-3.5 -leading-6 -leading-9 -leading-[1.1] -leading-none -left-0 -lg:bg-base-100/20 -lg:flex -lg:gap-y-0 -lg:grid-cols-2 -lg:hidden -lg:justify-end -lg:justify-start -lg:min-w-0 -lg:p-10 -lg:p-8 -lg:pb-5 -lg:pl-64 -lg:pr-3.5 -lg:pt-5 -lg:px-8 -lg:ring-1 -lg:ring-base-content/10 -lg:rounded-lg -lg:shadow-xs -lg:table-cell -link -link-animated -link-hover -link-primary -list-disc -list-inside -list-none -loading -loading-lg -loading-sm -loading-spinner -loading-xl -loading-xs -lowercase -m-10 -mask -max-h-52 -max-lg:flex-col -max-lg:hidden -max-md:flex-wrap -max-md:justify-center -max-md:w-full -max-sm:hidden -max-w-64 -max-w-7xl -max-w-8 -max-w-80 -max-w-fit -max-w-full -max-w-lg -max-w-md -max-w-sm -max-w-xl -mb-0.5 -mb-1 -mb-1.5 -mb-2 -mb-2.5 -mb-25 -mb-3 -mb-4 -mb-5 -mb-8 -md:col-span-1 -md:col-span-6 -md:flex -md:gap-2 -md:grid-cols-2 -md:hidden! -md:items-center -md:min-w-md -md:navbar-end -md:table-cell -md:text-3xl -me-1 -me-1.5 -me-2 -me-2.5 -me-5 -menu -menu-active -menu-disabled -menu-dropdown -menu-dropdown-show -menu-focus -menu-horizontal -menu-sm -menu-title -min-h-0 -min-h-full -min-h-screen -min-h-svh -min-w-0 -min-w-28 -min-w-48 -min-w-60 -min-w-[100px] -min-w-full -min-w-sm -mix-blend-exclusion -ml-3 -ml-4 -modal -modal-content -modal-dialog -modal-middle -modal-title -mr-2 -mr-4 -ms-0.5 -ms-1 -ms-1.5 -ms-2 -ms-2.5 -ms-3 -ms-auto -mt-1 -mt-1.5 -mt-10 -mt-12 -mt-2 -mt-2.5 -mt-20 -mt-3 -mt-3.5 -mt-4 -mt-5 -mt-6 -mt-8 -mx-1 -mx-auto -my-2.5 -my-20 -my-3 -my-5 -my-8 -my-auto -navbar -navbar-start -object-cover -opacity-0 -opacity-100 -opacity-75 -open -origin-top-left -outline -outline-0 -overflow-hidden -overflow-x-auto -overflow-x-hidden -overflow-y-auto -overlay-open:duration-50 -overlay-open:opacity-100 -overlay-open:translate-x-0 -overlay-open:translate-y-0 -overscroll-contain -p-0 -p-0.5 -p-1 -p-1.5 -p-12 -p-2 -p-2.5 -p-3 -p-4 -p-5 -p-6 -p-8 -pb-1 -pe-1 -pe-1.5 -pin-input -pin-input-underline -placeholder-base-content/60 -placeholder:text-gray-600 -pointer-events-auto -pointer-events-none -progress -progress-bar -progress-indeterminate -progress-primary -pt-0 -pt-0.5 -pt-1.5 -pt-2 -pt-3 -px-1.5 -px-2 -px-2.5 -px-3 -px-4 -px-5 -px-6 -py-0.5 -py-1.5 -py-10 -py-12 -py-2 -py-2.5 -py-6 -radial-progress -radio -range -relative -resize -right-0 -ring -ring-0 -ring-1 -ring-gray-700 -ring-inset -ring-white/10 -rounded-box -rounded-field -rounded-full -rounded-lg -rounded-md -rounded-t-lg -rounded-xl -row-active -row-hover -rtl:!mr-0 -select -select-disabled:opacity-40 -select-disabled:pointer-events-none -select-floating -select-floating-label -select-lg -select-md -select-sm -select-xl -select-xs -selected -selected:select-active -shadow -shadow-base-300/20 -shadow-lg -shadow-md -shadow-none -shadow-sm -shadow-xs -shrink-0 -size-10 -size-12 -size-4 -size-5 -size-7 -size-8 -size-8.5 -skeleton -skeleton-animated -sm:auto-cols-max -sm:col-span-2 -sm:flex -sm:flex-nowrap -sm:gap-y-0 -sm:grid-cols-3 -sm:grid-cols-6 -sm:inline -sm:items-center -sm:items-end -sm:justify-between -sm:justify-end -sm:leading-6 -sm:max-w-2xl -sm:max-w-3xl -sm:max-w-4xl -sm:max-w-5xl -sm:max-w-6xl -sm:max-w-7xl -sm:max-w-[480px] -sm:max-w-lg -sm:max-w-md -sm:max-w-xl -sm:mb-0 -sm:min-w-md -sm:mr-3 -sm:mt-5 -sm:mt-6 -sm:mx-auto -sm:p-6 -sm:px-12 -sm:px-6 -sm:py-2 -sm:rounded-lg -sm:text-sm -sm:text-sm/5 -sm:w-1/2 -sm:w-full -space-x-1 -space-x-2.5 -space-y-1 -space-y-4 -space-y-6 -sr-only -stack -stat -stat-actions -stat-desc -stat-figure -stat-title -stat-value -static -stats -stats-border -status -status-error -sticky -success-message -switch -tab -tab! -tab-active -tab-content -table -table-pin-cols -table-pin-rows -tabs -tabs-bordered -tabs-lg -tabs-lifted -tabs-md -tabs-sm -tabs-vertical -tabs-xl -tabs-xs -text-2xl -text-3xl -text-4xl -text-5xl -text-accent -text-base -text-base-content -text-base-content/40 -text-base-content/50 -text-base-content/60 -text-base-content/70 -text-base-content/80 -text-base-content/90 -text-base/6 -text-center -text-error -text-error-content -text-gray-100 -text-gray-400 -text-gray-500 -text-indigo-600 -text-info -text-info-content -text-left -text-lg -text-primary -text-primary-content -text-right -text-slate-100 -text-sm -text-sm/5 -text-success -text-success-content -text-transparent -text-warning -text-warning-content -text-white -text-xl -text-xl! -text-xs -text-xs/5 -textarea -textarea-floating -textarea-floating-label -textarea-lg -textarea-md -textarea-sm -textarea-xl -textarea-xs -theme-controller -to-purple-400 -tooltip -tooltip-content -top-0 -top-1/2 -top-3 -top-full -tracking-tight -tracking-wide -transform -transition -transition-all -transition-opacity -transition-transform -translate-x-0 -translate-x-full -truncate -underline -uppercase -validate -via-sky-400 -w-0 -w-0.5 -w-12 -w-13 -w-20 -w-32 -w-4 -w-56 -w-6 -w-64 -w-8 -w-auto -w-fit -w-full -whitespace-nowrap -xl:flex -xl:grid-cols-2 -xl:grid-cols-3 -xl:grid-cols-4 -z-1 -z-10 -z-40 -z-50 -z-69 -z-70 \ No newline at end of file diff --git a/Moonlight.Client/UI/App.razor b/Moonlight.Client/UI/App.razor deleted file mode 100644 index 8b318e0d..00000000 --- a/Moonlight.Client/UI/App.razor +++ /dev/null @@ -1,13 +0,0 @@ -@using Moonlight.Client.UI.Shell -@using Moonlight.Client.Services -@using Moonlight.Client.UI.Partials - -@inject ApplicationAssemblyService ApplicationAssemblyService - - - - - - \ No newline at end of file diff --git a/Moonlight.Client/UI/Components/ColorSelector.razor b/Moonlight.Client/UI/Components/ColorSelector.razor deleted file mode 100644 index f3ba0c60..00000000 --- a/Moonlight.Client/UI/Components/ColorSelector.razor +++ /dev/null @@ -1,45 +0,0 @@ - - - -@code -{ - - #region - - [Parameter] - public string? Value - { - get => _value; - set - { - if (_value?.Equals(value) ?? false) - return; - - _value = value; - ValueChanged.InvokeAsync(value); - } - } - - [Parameter] public EventCallback ValueChanged { get; set; } - - private string? _value; - - #endregion - - [Parameter] public string Icon { get; set; } = "icon-paintbrush"; - - private string Id; - - protected override void OnInitialized() - { - Id = $"color-selector-{GetHashCode()}"; - } - - private async Task UpdateAsync(ChangeEventArgs args) - { - Value = args.Value?.ToString() ?? "#FFFFFF"; - await InvokeAsync(StateHasChanged); - } -} diff --git a/Moonlight.Client/UI/Components/HelperMessage.razor b/Moonlight.Client/UI/Components/HelperMessage.razor deleted file mode 100644 index b8dfba13..00000000 --- a/Moonlight.Client/UI/Components/HelperMessage.razor +++ /dev/null @@ -1,15 +0,0 @@ - - -@code -{ - [Parameter] public RenderFragment ChildContent { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Components/SignalRDebug.razor b/Moonlight.Client/UI/Components/SignalRDebug.razor deleted file mode 100644 index 807e3b3f..00000000 --- a/Moonlight.Client/UI/Components/SignalRDebug.razor +++ /dev/null @@ -1,62 +0,0 @@ -@using Microsoft.AspNetCore.SignalR.Client -@using Microsoft.Extensions.DependencyInjection - -@inject NavigationManager Navigation -@inject ToastService ToastService - -@implements IAsyncDisposable - -
-
-

SignalR

-
-
-

- SignalR is used by Moonlight to provide realtime communication to itself and other modules. - You can test the SignalR communication by pressing the button below. For scaled instances you need to configure a redis compatible server to be used by all your replicas in order - for all SignalR Hubs to be synced. -

-
- - - Send broadcast - - -
-
-
- -@code -{ - private HubConnection? Connection; - - private async Task LoadAsync(LazyLoader lazyLoader) - { - await lazyLoader.UpdateTextAsync("Connecting to SignalR endpoint"); - - Connection = new HubConnectionBuilder() - .WithUrl(Navigation.ToAbsoluteUri("/api/admin/system/diagnose/ws")) - .AddJsonProtocol() - .Build(); - - Connection.On( - "Pong", - async () => await ToastService.SuccessAsync("Received broadcast") - ); - - await Connection.StartAsync(); - } - - private async Task OnClick(WButton _) - { - if (Connection == null) - return; - - await Connection.SendAsync("Ping"); - } - - public async ValueTask DisposeAsync() - { - if (Connection != null) await Connection.DisposeAsync(); - } -} diff --git a/Moonlight.Client/UI/Components/StatCard.razor b/Moonlight.Client/UI/Components/StatCard.razor deleted file mode 100644 index cd00edb0..00000000 --- a/Moonlight.Client/UI/Components/StatCard.razor +++ /dev/null @@ -1,24 +0,0 @@ -@*
-
-

- @Text -

- -
-

@Title

-
*@ - -
-
- -
-
@Title
-
@Text
-
- -@code -{ - [Parameter] public string Title { get; set; } - [Parameter] public string Text { get; set; } - [Parameter] public string Icon { get; set; } -} diff --git a/Moonlight.Client/UI/Components/ThemeEditor.razor b/Moonlight.Client/UI/Components/ThemeEditor.razor deleted file mode 100644 index 0827d61c..00000000 --- a/Moonlight.Client/UI/Components/ThemeEditor.razor +++ /dev/null @@ -1,316 +0,0 @@ -@using Moonlight.Shared.Misc - -
-
-
- - Base Colors -
- -
- - Base Content -
- -
- - Base 100 -
- -
- - Base 200 -
- -
- - Base 300 -
-
- -
- -
- - Colors -
- -
- -
- - Primary -
-
- - Primary Content -
- -
- - Secondary -
-
- - Secondary Content -
- -
- - Accent -
-
- - Accent Content -
- -
- - Neutral -
-
- - Neutral Content -
- -
- - Info -
-
- - Info Content -
- -
- - Success -
-
- - Success Content -
- -
- - Warning -
-
- - Warning Content -
- -
- - Error -
-
- - Error Content -
-
-
- -
- -
- - Radius -
- -
-
Boxes
-
- - @foreach (var possibleValue in RadiusValues) - { - - } -
-
- -
-
Fields
-
- - @foreach (var possibleValue in RadiusValues) - { - - } -
-
- -
-
Selectors
-
- - @foreach (var possibleValue in RadiusValues) - { - - } -
-
-
- -
- -
- - Effects -
- -
- - -
- -
- - -
-
- -
- -
- - Sizes -
- -
-
Fields
- - -
- 3 - 3.5 - 4 - 4.5 - 5 -
-
- -
-
Selector
- - -
- 3 - 3.5 - 4 - 4.5 - 5 -
-
- -
-
Border Width
- - -
- 0.5 - 1 - 1.5 - 2 -
-
- -
-
- - -@code -{ - [Parameter] public ApplicationTheme Theme { get; set; } - - private float[] RadiusValues = - [ - 0, - 0.25f, - 0.5f, - 1f, - 2f - ]; - - private bool InputDepth - { - get => Theme.Depth != 0; - set => Theme.Depth = value ? 1 : 0; - } - - private bool InputNoise - { - get => Theme.Noise != 0; - set => Theme.Noise = value ? 1 : 0; - } - - private async Task UpdateRadiusBoxAsync(float value) - { - Theme.RadiusBox = value; - await InvokeAsync(StateHasChanged); - } - - private async Task UpdateRadiusFieldAsync(float value) - { - Theme.RadiusField = value; - await InvokeAsync(StateHasChanged); - } - - private async Task UpdateRadiusSelectorAsync(float value) - { - Theme.RadiusSelector = value; - await InvokeAsync(StateHasChanged); - } -} diff --git a/Moonlight.Client/UI/Components/WelcomeOverviewElement.razor b/Moonlight.Client/UI/Components/WelcomeOverviewElement.razor deleted file mode 100644 index 4add9855..00000000 --- a/Moonlight.Client/UI/Components/WelcomeOverviewElement.razor +++ /dev/null @@ -1,24 +0,0 @@ -@using System.Security.Claims -@using Microsoft.AspNetCore.Components.Authorization - -
-
-
- Welcome, @(Username) -
-
What do you want to do today?
-
-
- -@code -{ - [CascadingParameter] public Task AuthState { get; set; } - - private string Username; - - protected override async Task OnInitializedAsync() - { - var identity = await AuthState; - Username = identity.User.FindFirst(ClaimTypes.Name)!.Value; - } -} diff --git a/Moonlight.Client/UI/Layouts/MainLayout.razor b/Moonlight.Client/UI/Layouts/MainLayout.razor deleted file mode 100644 index 943d8cf9..00000000 --- a/Moonlight.Client/UI/Layouts/MainLayout.razor +++ /dev/null @@ -1,52 +0,0 @@ -@using MoonCore.Blazor.FlyonUi.Drawers -@using Moonlight.Client.UI.Partials -@using MoonCore.Blazor.FlyonUi.Files.Drop - -@inherits LayoutComponentBase - -
- - - -
-
-
- - @Body - -
-
-
-
- - - - - - - -
-
-
- - An unhandled error has occurred. -
-
- Reload - -
-
-
- -@code -{ - // Mobile navigation - public event Func OnStateChanged; - public bool ShowMobileNavigation { get; private set; } = false; - - public async Task ToggleMobileNavigationAsync() - { - ShowMobileNavigation = !ShowMobileNavigation; - await OnStateChanged(); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Partials/AppHeader.razor b/Moonlight.Client/UI/Partials/AppHeader.razor deleted file mode 100644 index f58fcd0f..00000000 --- a/Moonlight.Client/UI/Partials/AppHeader.razor +++ /dev/null @@ -1,49 +0,0 @@ -@using Moonlight.Client.UI.Layouts - -@inject NavigationManager Navigation - -
-
- - - -
-
- -
-
- -@code -{ - [Parameter] public MainLayout Layout { get; set; } - - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) - return Task.CompletedTask; - - Layout.OnStateChanged += () => InvokeAsync(StateHasChanged); - - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Partials/AppSidebar.razor b/Moonlight.Client/UI/Partials/AppSidebar.razor deleted file mode 100644 index 781fd9f7..00000000 --- a/Moonlight.Client/UI/Partials/AppSidebar.razor +++ /dev/null @@ -1,266 +0,0 @@ -@using System.Security.Claims -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Components.Authorization -@using Moonlight.Client.Interfaces -@using Moonlight.Client.Models -@using Moonlight.Client.UI.Layouts - -@inject NavigationManager Navigation -@inject IEnumerable SidebarItemProviders -@inject IAuthorizationService AuthorizationService - -@{ - var url = new Uri(Navigation.Uri); -} - -
- -
- - - -@code -{ - [Parameter] public MainLayout Layout { get; set; } - [CascadingParameter] public Task AuthState { get; set; } - - private Dictionary Items = new(); - - private string Username; - private string Email; - private ClaimsPrincipal Identity; - - protected override async Task OnInitializedAsync() - { - var authState = await AuthState; - - Identity = authState.User; - Username = Identity.FindFirst(ClaimTypes.Name)!.Value; - Email = Identity.FindFirst(ClaimTypes.Email)!.Value; - - var sidebarItems = new List(); - - foreach (var provider in SidebarItemProviders) - provider.ModifySidebar(sidebarItems); - - var itemsToRemove = new List(); - - foreach (var sidebarItem in sidebarItems) - { - if(string.IsNullOrEmpty(sidebarItem.Policy)) - continue; - - var authResult = await AuthorizationService.AuthorizeAsync(Identity, sidebarItem.Policy); - - if(authResult.Succeeded) - continue; - - itemsToRemove.Add(sidebarItem); - } - - foreach (var sidebarItem in itemsToRemove) - sidebarItems.Remove(sidebarItem); - - Items = sidebarItems - .GroupBy(x => x.Group ?? "") - .OrderByDescending(x => string.IsNullOrEmpty(x.Key)) - .ToDictionary(x => x.Key, x => x.OrderBy(y => y.Priority).ToArray()); - } - - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender) - return Task.CompletedTask; - - Layout.OnStateChanged += () => InvokeAsync(StateHasChanged); - - Navigation.LocationChanged += async (_, _) => - { - if (!Layout.ShowMobileNavigation) - return; - - await Layout.ToggleMobileNavigationAsync(); - }; - - return Task.CompletedTask; - } - - private Task LogoutAsync() - { - Navigation.NavigateTo("/api/auth/logout", true); - return Task.CompletedTask; - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Partials/LoginSelector.razor b/Moonlight.Client/UI/Partials/LoginSelector.razor deleted file mode 100644 index 559a220a..00000000 --- a/Moonlight.Client/UI/Partials/LoginSelector.razor +++ /dev/null @@ -1,101 +0,0 @@ -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Responses.Auth - -@inject HttpApiClient ApiClient -@inject NavigationManager Navigation - -
-
-
- - @if (ShowSelection) - { -
- brand-logo -
-
-

Login into your account

-

Chose a login method to continue

-
-
- - @if (AuthSchemes.Length == 0) - { -
- No auth schemes enabled/available -
- } - -
- @foreach (var scheme in AuthSchemes) - { - var config = Configs.GetValueOrDefault(scheme.Identifier); - - if (config == null) // Ignore all schemes which have no ui configured - continue; - - - } -
-
- } - else - { -
- -
- } -
-
-
-
- -@code -{ - private AuthSchemeResponse[] AuthSchemes; - private Dictionary Configs = new(); - private bool ShowSelection = false; - - protected override void OnInitialized() - { - Configs["LocalAuth"] = new AuthSchemeConfig() - { - Color = "#7636e3", - IconUrl = "/placeholder.jpg" - }; - } - - private async Task LoadAsync(LazyLoader arg) - { - AuthSchemes = await ApiClient.GetJson( - "api/auth" - ); - - // If we only have one auth scheme available - // we want to auto redirect the user without - // showing the selection screen - - if (AuthSchemes.Length == 1) - await StartAsync(AuthSchemes[0]); - else - ShowSelection = true; - } - - private Task StartAsync(AuthSchemeResponse scheme) - { - Navigation.NavigateTo($"/api/auth/{scheme.Identifier}", true); - return Task.CompletedTask; - } - - record AuthSchemeConfig - { - public string Color { get; set; } - public string IconUrl { get; set; } - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Shell/AppHeader.razor b/Moonlight.Client/UI/Shell/AppHeader.razor deleted file mode 100644 index 535e7dae..00000000 --- a/Moonlight.Client/UI/Shell/AppHeader.razor +++ /dev/null @@ -1,30 +0,0 @@ -@using MoonCore.Blazor.FlyonUi.Drawers - -@inject DrawerService DrawerService - - - -@code -{ - private async Task LaunchSidebarAsync() - { - await DrawerService.LaunchAsync(unfocusHide: true); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Shell/MainLayout.razor b/Moonlight.Client/UI/Shell/MainLayout.razor deleted file mode 100644 index 6002dd57..00000000 --- a/Moonlight.Client/UI/Shell/MainLayout.razor +++ /dev/null @@ -1,22 +0,0 @@ -@using MoonCore.Blazor.FlyonUi.Drawers -@using MoonCore.Blazor.FlyonUi.Modals -@using MoonCore.Blazor.FlyonUi.Toasts - -@inherits LayoutComponentBase - -
- -
- -
- @Body - - - - -
-
-
\ No newline at end of file diff --git a/Moonlight.Client/UI/Shell/SidebarComponent.razor b/Moonlight.Client/UI/Shell/SidebarComponent.razor deleted file mode 100644 index 1b74d19c..00000000 --- a/Moonlight.Client/UI/Shell/SidebarComponent.razor +++ /dev/null @@ -1,151 +0,0 @@ -@using System.Security.Claims -@using Microsoft.AspNetCore.Authorization -@using Microsoft.AspNetCore.Components.Authorization -@using Moonlight.Client.Interfaces -@using Moonlight.Client.Models - -@inherits MoonCore.Blazor.FlyonUi.Drawers.DrawerBase - -@inject NavigationManager Navigation -@inject IEnumerable SidebarItemProviders -@inject IAuthorizationService AuthorizationService - -@implements IDisposable - -@{ - var url = new Uri(Navigation.Uri); -} - -
-
-
- Logo -
-

Moonlight v2.1

-
-
-
- -
- - -@code -{ - [CascadingParameter] public Task AuthState { get; set; } - - private Dictionary Items = new(); - - private string Username; - private string Email; - private ClaimsPrincipal Identity; - - protected override async Task OnInitializedAsync() - { - var authState = await AuthState; - - Identity = authState.User; - Username = Identity.FindFirst(ClaimTypes.Name)!.Value; - Email = Identity.FindFirst(ClaimTypes.Email)!.Value; - - var sidebarItems = new List(); - - foreach (var provider in SidebarItemProviders) - provider.ModifySidebar(sidebarItems); - - var itemsToRemove = new List(); - - foreach (var sidebarItem in sidebarItems) - { - if(string.IsNullOrEmpty(sidebarItem.Policy)) - continue; - - var authResult = await AuthorizationService.AuthorizeAsync(Identity, sidebarItem.Policy); - - if(authResult.Succeeded) - continue; - - itemsToRemove.Add(sidebarItem); - } - - foreach (var sidebarItem in itemsToRemove) - sidebarItems.Remove(sidebarItem); - - Items = sidebarItems - .GroupBy(x => x.Group ?? "") - .OrderByDescending(x => string.IsNullOrEmpty(x.Key)) - .ToDictionary(x => x.Key, x => x.OrderBy(y => y.Priority).ToArray()); - - Navigation.LocationChanged += OnNavigated; - } - - private async void OnNavigated(object? sender, LocationChangedEventArgs e) - { - // No async void without try catch to prevent hard app crashes when async task fails - - try - { - await InvokeAsync(StateHasChanged); - } - catch (Exception) - { - // ignored - } - } - - private Task LogoutAsync() - { - Navigation.NavigateTo("/api/auth/logout", true); - return Task.CompletedTask; - } - - public void Dispose() - { - Navigation.LocationChanged -= OnNavigated; - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Shell/SidebarDrawer.razor b/Moonlight.Client/UI/Shell/SidebarDrawer.razor deleted file mode 100644 index 2a2626a9..00000000 --- a/Moonlight.Client/UI/Shell/SidebarDrawer.razor +++ /dev/null @@ -1,31 +0,0 @@ -@inherits MoonCore.Blazor.FlyonUi.Drawers.DrawerBase - -@inject NavigationManager Navigation - -@implements IDisposable - - - -@code -{ - protected override void OnInitialized() - { - Navigation.LocationChanged += OnLocationChanged; - } - - private async void OnLocationChanged(object? sender, LocationChangedEventArgs e) - { - try - { - await CloseAsync(); - } - catch (Exception) - { - // Ignored - } - } - - public void Dispose() - { - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Api/Create.razor b/Moonlight.Client/UI/Views/Admin/Api/Create.razor deleted file mode 100644 index 5cdece8b..00000000 --- a/Moonlight.Client/UI/Views/Admin/Api/Create.razor +++ /dev/null @@ -1,82 +0,0 @@ -@page "/admin/api/create" -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Requests.Admin.ApiKeys -@using Moonlight.Shared.Http.Responses.Admin.ApiKeys -@using MoonCore.Blazor.FlyonUi.Forms -@using MoonCore.Blazor.FlyonUi.Helpers - -@inject HttpApiClient ApiClient -@inject NavigationManager Navigation -@inject ToastService ToastService -@inject AlertService AlertService -@inject DownloadService DownloadService - - - - - Back - - - - Create - - - -
- -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -@code -{ - private HandleForm Form; - private CreateApiKeyRequest Request; - - private List Permissions = []; - - protected override void OnInitialized() - { - Request = new(); - } - - private async Task OnSubmit() - { - Request.Permissions = Permissions.ToArray(); - Request.ExpiresAt = Request.ExpiresAt.ToUniversalTime(); - - var response = await ApiClient.PostJson("api/admin/apikeys", Request); - - await DownloadService.DownloadAsync( - $"moonlight-key-{response.Id}.txt", - response.Secret - ); - - await AlertService.SuccessAsync( - "API Key successfully created", - "The API Key has been downloaded. Dont lose it, you cant view it anymore" - ); - - await ToastService.SuccessAsync("Successfully created api key"); - Navigation.NavigateTo("/admin/api"); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Api/Index.razor b/Moonlight.Client/UI/Views/Admin/Api/Index.razor deleted file mode 100644 index fd07813a..00000000 --- a/Moonlight.Client/UI/Views/Admin/Api/Index.razor +++ /dev/null @@ -1,128 +0,0 @@ -@page "/admin/api" - -@using MoonCore.Blazor.FlyonUi.Common -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Responses.Admin.ApiKeys -@using MoonCore.Blazor.FlyonUi.Grid -@using MoonCore.Blazor.FlyonUi.Grid.Columns -@using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems -@using MoonCore.Common - -@inject HttpApiClient ApiClient -@inject AlertService AlertService -@inject ToastService ToastService - -
-
-

- API Documentation -

- -
Open
-
- -
-
-
- -
-

- Learn about the api usage -

- -
Open
-
- -
-
-
- -
-

- Open API Specification -

- -
Open
-
- -
-
-
-
- - - - - - - @Formatter.FormatDate(context.ExpiresAt.UtcDateTime) - - - - - @Formatter.FormatDate(context.CreatedAt.UtcDateTime) - - - - -
- - - - - - - -
- -
- - - - Create - - -
- -@code -{ - private DataGrid Grid; - - private ItemSource ItemSource => ItemSourceFactory.From(LoadItemsAsync); - - private async Task> LoadItemsAsync( - int startIndex, int count, string? filter, SortOption? sortOption - ) - { - var query = $"?startIndex={startIndex}&count={count}"; - - if (sortOption != null) - { - var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc"; - query += $"&orderBy={sortOption.Column}&orderByDir={dir}"; - } - - if (!string.IsNullOrEmpty(filter)) - query += $"&filter={filter}"; - - return await ApiClient.GetJson>($"api/admin/apikeys{query}"); - } - - private async Task DeleteAsync(ApiKeyResponse apiKeyResponse) - { - await AlertService.ConfirmDangerAsync( - "API Key Deletion", - $"Do you really want to delete the api key '{apiKeyResponse.Description}'", - async () => - { - await ApiClient.Delete($"api/admin/apikeys/{apiKeyResponse.Id}"); - await ToastService.SuccessAsync("Successfully deleted api key"); - - await Grid.RefreshAsync(); - } - ); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Api/Update.razor b/Moonlight.Client/UI/Views/Admin/Api/Update.razor deleted file mode 100644 index 86130647..00000000 --- a/Moonlight.Client/UI/Views/Admin/Api/Update.razor +++ /dev/null @@ -1,60 +0,0 @@ -@page "/admin/api/{Id:int}" -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Requests.Admin.ApiKeys -@using Moonlight.Shared.Http.Responses.Admin.ApiKeys - -@inject HttpApiClient ApiClient -@inject NavigationManager Navigation -@inject ToastService ToastService - - - - - - Back - - - - Update - - - -
- -
-
- -
- -
-
-
-
-
-
- -@code -{ - [Parameter] public int Id { get; set; } - - private HandleForm Form; - private UpdateApiKeyRequest Request; - - private async Task LoadAsync(LazyLoader _) - { - var detail = await ApiClient.GetJson($"api/admin/apikeys/{Id}"); - - Request = new() - { - Description = detail.Description - }; - } - - private async Task OnSubmit() - { - await ApiClient.Patch($"api/admin/apikeys/{Id}", Request); - - await ToastService.SuccessAsync("Successfully updated api key"); - Navigation.NavigateTo("/admin/api"); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Index.razor b/Moonlight.Client/UI/Views/Admin/Index.razor deleted file mode 100644 index 315a3811..00000000 --- a/Moonlight.Client/UI/Views/Admin/Index.razor +++ /dev/null @@ -1,217 +0,0 @@ -@page "/admin" - -@using MoonCore.Helpers -@using Moonlight.Client.Interfaces - -@inject IEnumerable ElementProviders - -
- @foreach (var render in Renders) - { - @render - } -
- -@code -{ - private RenderFragment[] Renders; - - protected override void OnInitialized() - { - var renders = new List(); - - var elementTypes = new List(); - - foreach (var elementProvider in ElementProviders) - elementProvider.ModifyOverview(elementTypes); - - foreach (var elementType in elementTypes) - renders.Add(ComponentHelper.FromType(elementType)); - - Renders = renders.ToArray(); - } -} - -@* -@using ApexCharts - -
- - - - -
- -@code { - private List Data { get; set; } = new(); - - private ApexChartOptions Options; - private ApexChart ApexChart; - - private int Counter = 0; - private int IndexCounter = 0; - - protected override void OnInitialized() - { - Options = new() - { - Stroke = new() - { - Curve = new CurveSelections() - { - Curve.Smooth - }, - Width = 4 - }, - Title = new() - { - Text = "" - }, - Chart = new() - { - Toolbar = new() - { - Show = false - }, - Animations = new() - { - Easing = Easing.Linear, - DynamicAnimation = new() - { - Speed = 950 - } - }, - Zoom = new Zoom - { - Enabled = false - }, - Height = "100%", - Sparkline = new() - { - Enabled = false - } - }, - Tooltip = new() - { - Enabled = false - }, - Xaxis = new() - { - Range = 20, - Tooltip = new() - { - Enabled = false - }, - Labels = new() - { - Show = false - }, - AxisTicks = new() - { - Show = false - }, - AxisBorder = new() - { - Show = false - } - }, - Yaxis = new(), - Series = new(), - Grid = new() - { - Show = false, - Padding = new Padding() - { - Bottom = 0, - Left = 0, - Right = 0, - Top = 0 - } - }, - }; - - Options.Yaxis.Add(new() - { - Labels = new() - { - Show = false, - Style = new() - { - CssClass = "fs-6", - Colors = new("#FFFFFF") - }, - Formatter = $"function (value) {{ return Math.round(value) + ' Users' }}", - }, - Show = false - }); - - for (int i = 0; i < 20; i++) - { - IndexCounter++; - Data.Add(new MyData { Index = IndexCounter, Value = 0 }); - } - - Task.Run(async () => - { - await Task.Delay(1000); - - var random = new Random(); - - while (true) - { - var x = random.Next(0, 100); - await HandleData(x); - - await Task.Delay(1000); - } - }); - } - - public async Task HandleData(int newValue) - { - if (Counter > 30) - { - await ApexChart.UpdateSeriesAsync(); - Counter = 0; - } - - IndexCounter++; - var item = new MyData() - { - Index = IndexCounter, - Value = newValue - }; - - Data.Add(item); - - if (Data.Count > 30) - Data.RemoveAt(0); - - try - { - await ApexChart.AppendDataAsync(new[] { item }); - } - catch (TaskCanceledException) - { - /* ignore task canceled exception */ - } - - Counter++; - } - - public class MyData - { - public int Index { get; set; } - public int Value { get; set; } - } - - -}*@ \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Advanced.razor b/Moonlight.Client/UI/Views/Admin/Sys/Advanced.razor deleted file mode 100644 index 77aa45ad..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Advanced.razor +++ /dev/null @@ -1,42 +0,0 @@ -@page "/admin/system/advanced" - -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Blazor.FlyonUi.Helpers -@using MoonCore.Helpers - -@attribute [Authorize(Policy = "permissions:admin.system.advanced")] - -@inject HttpApiClient ApiClient -@inject DownloadService DownloadService - -
- -
- -
-
-
- Frontend hosting files -
-
-

- If you want to host your moonlight frontend on a static web server instead of it being hosted by the api server - (useful for hosting on a cdn for high availability) you can use this helper. By pressing the button below moonlight - will generate a zip file containing everything you need to host the frontend. Next to the WASM app itself it automatically - includes your installed theme and plugins. For more information, have a look at our docs -

- - Generate frontend.zip -
-
-
- -@code -{ - private async Task GenerateFrontendAsync(WButton _) - { - var stream = await ApiClient.GetStream("api/admin/system/advanced/frontend"); - - await DownloadService.DownloadAsync("frontend.zip", stream); - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Index.razor b/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Index.razor deleted file mode 100644 index 81eec357..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Index.razor +++ /dev/null @@ -1,220 +0,0 @@ -@page "/admin/system/customisation" - -@using System.Text.Json -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Blazor.FlyonUi.Common -@using MoonCore.Blazor.FlyonUi.Grid -@using MoonCore.Blazor.FlyonUi.Grid.Columns -@using MoonCore.Blazor.FlyonUi.Grid.ToolbarItems -@using MoonCore.Blazor.FlyonUi.Helpers -@using MoonCore.Common -@using MoonCore.Helpers -@using Moonlight.Client.Models -@using Moonlight.Client.Services -@using Moonlight.Shared.Http.Requests.Admin.Sys.Theme -@using Moonlight.Shared.Http.Responses.Admin - -@attribute [Authorize(Policy = "permissions:admin.system.theme")] - -@inject ThemeService ThemeService -@inject AlertService AlertService -@inject ToastService ToastService -@inject HttpApiClient ApiClient -@inject DownloadService DownloadService -@inject ILogger Logger - - - - - Themes - - -
- - - - - -
- @context.Name - - @if (context.IsEnabled) - { - - } -
- -
- - - - - -
- @if (!string.IsNullOrEmpty(context.DonateUrl)) - { - - - Donate - - } - - @if (!string.IsNullOrEmpty(context.UpdateUrl)) - { - - - Update - - } - - - - Export - - - - - - - - - -
- -
- - - - -
-
- - - Images & Logos - - - -@code -{ - private DataGrid Grid; - private ItemSource ItemSource => ItemSourceFactory.From(LoadItemsAsync); - - private async Task> LoadItemsAsync( - int startIndex, int count, string? filter, SortOption? sortOption - ) - { - var query = $"?startIndex={startIndex}&count={count}"; - - if (sortOption != null) - { - var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc"; - query += $"&orderBy={sortOption.Column}&orderByDir={dir}"; - } - - if (!string.IsNullOrEmpty(filter)) - query += $"&filter={filter}"; - - return await ApiClient.GetJson>($"api/admin/system/customisation/themes{query}"); - } - - private async Task ImportAsync(InputFileChangeEventArgs eventArgs) - { - if (eventArgs.FileCount < 1) - return; - - var files = eventArgs.GetMultipleFiles(); - - var maxFileSize = ByteConverter.FromMegaBytes(1).Bytes; - - foreach (var file in files) - { - try - { - if (!file.Name.EndsWith(".json")) - { - await ToastService.ErrorAsync($"Unable to import {file.Name}", "Only .json files are supported"); - continue; - } - - if (file.Size > maxFileSize) - { - await ToastService.ErrorAsync($"Unable to import {file.Name}", "Exceeded the maximum file limit of 1MB"); - continue; - } - - await using var stream = file.OpenReadStream(maxFileSize); - var themeTransfer = await JsonSerializer.DeserializeAsync(stream); - - stream.Close(); - - if (themeTransfer == null) - { - await ToastService.ErrorAsync($"Unable to import {file.Name}", "Failed to deserialize the content"); - continue; - } - - var theme = await ThemeService.CreateAsync(new CreateThemeRequest() - { - Name = themeTransfer.Name, - Author = themeTransfer.Author, - Content = themeTransfer.Content, - DonateUrl = themeTransfer.DonateUrl, - UpdateUrl = themeTransfer.UpdateUrl, - Version = themeTransfer.Version - }); - - await ToastService.SuccessAsync("Successfully imported theme", theme.Name); - - await Grid.RefreshAsync(); - } - catch (Exception e) - { - Logger.LogError(e, "An unhandled error occured while importing file {file} as theme", file.Name); - } - } - } - - private async Task ExportAsync(ThemeResponse theme) - { - var transfer = new ThemeTransferModel() - { - Name = theme.Name, - Author = theme.Author, - Content = theme.Content, - DonateUrl = theme.DonateUrl, - UpdateUrl = theme.UpdateUrl, - Version = theme.Version - }; - - var json = JsonSerializer.Serialize(transfer, new JsonSerializerOptions() - { - WriteIndented = true - }); - - var fileName = $"{transfer.Name.Replace(" ", string.Empty).Trim()}.json"; - - await DownloadService.DownloadAsync(fileName, json); - } - - private async Task DeleteAsync(ThemeResponse response) - { - await AlertService.ConfirmDangerAsync( - "Theme deletion", - $"Do you really want to delete the theme: {response.Name}", - async () => - { - await ThemeService.DeleteAsync(response.Id); - - await ToastService.SuccessAsync("Successfully deleted theme"); - await Grid.RefreshAsync(); - } - ); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Create.razor b/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Create.razor deleted file mode 100644 index ef66f2ff..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Create.razor +++ /dev/null @@ -1,113 +0,0 @@ -@page "/admin/system/customisation/themes/create" - -@using Moonlight.Client.Services -@using Moonlight.Shared.Misc -@using Moonlight.Client.UI.Components -@using Moonlight.Shared.Http.Requests.Admin.Sys.Theme - -@inject ThemeService ThemeService -@inject ToastService ToastService -@inject NavigationManager NavigationManager - - - - - Back - - - - Create - - - - -
-
- -
- -
-
-
- -
- -
-
-
- -
- - This field will be used for update checks -
-
-
- - -
- -@code -{ - private HandleForm Form; - private CreateThemeRequest Request = new(); - - protected override void OnInitialized() - { - Request.Content = CreateDefault(); - } - - private async Task OnValidSubmit() - { - await ThemeService.CreateAsync(Request); - await ToastService.SuccessAsync("Successfully created theme"); - - NavigationManager.NavigateTo("/admin/system/customisation"); - } - - private ApplicationTheme CreateDefault() - { - return new ApplicationTheme() - { - ColorBase100 = "#1e2b47", - ColorBase200 = "#101a2e", - ColorBase300 = "#0c1221", - - ColorBaseContent = "#dde5f5", - - ColorPrimary = "#4f39f6", - ColorPrimaryContent = "#dde5f5", - - ColorSecondary = "#354052", - ColorSecondaryContent = "#dde5f5", - - ColorAccent = "#7008e7", - ColorAccentContent = "#dde5f5", - - ColorNeutral = "#1a273a", - ColorNeutralContent = "#dde5f5", - - ColorInfo = "#155dfc", - ColorInfoContent = "#dde5f5", - - ColorSuccess = "#00a63e", - ColorSuccessContent = "#dde5f5", - - ColorWarning = "#ffba00", - ColorWarningContent = "#dde5f5", - - ColorError = "#ec003f", - ColorErrorContent = "#dde5f5", - - RadiusSelector = 0.25f, - RadiusField = 0.5f, - RadiusBox = 0.5f, - - SizeSelector = 0.25f, - SizeField = 0.25f, - - Border = 1f, - Depth = 0, - Noise = 0 - }; - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Update.razor b/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Update.razor deleted file mode 100644 index 928646dd..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Customisation/Themes/Update.razor +++ /dev/null @@ -1,107 +0,0 @@ -@page "/admin/system/customisation/themes/{id:int}" - -@using Moonlight.Client.Services -@using Moonlight.Shared.Http.Requests.Admin.Sys.Theme -@using Moonlight.Shared.Http.Responses.Admin -@using Moonlight.Client.UI.Components - -@inject ThemeService ThemeService -@inject ToastService ToastService -@inject NavigationManager Navigation - - - - - - Back - - - - Update - - - - -
-
- -
-
- - Toggle on in order to enable the theme globally (a reload is required to apply) -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- - This field will be used for update checks -
-
-
- -
- - Optional: Specify a url here which people should get redirected for donating to the author -
-
-
- -
- - Optional: Specify a url here which returns the latest version of this theme as the raw exported file -
-
-
- - -
-
- -@code -{ - [Parameter] public int Id { get; set; } - - private ThemeResponse Response; - private UpdateThemeRequest Request; - - private HandleForm Form; - - private async Task LoadAsync(LazyLoader _) - { - Response = await ThemeService.GetAsync(Id); - - Request = new() - { - Content = Response.Content, - Author = Response.Author, - Name = Response.Name, - Version = Response.Version, - DonateUrl = Response.DonateUrl, - IsEnabled = Response.IsEnabled, - UpdateUrl = Response.UpdateUrl - }; - } - - private async Task OnValidSubmit() - { - await ThemeService.UpdateAsync(Id, Request); - - await ToastService.SuccessAsync("Successfully updated theme"); - Navigation.NavigateTo("/admin/system/customisation"); - } -} - diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor b/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor deleted file mode 100644 index 4f3a2dbc..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Diagnose.razor +++ /dev/null @@ -1,123 +0,0 @@ -@page "/admin/system/diagnose" - -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Blazor.FlyonUi.Helpers -@using MoonCore.Helpers -@using Moonlight.Client.UI.Components -@using Moonlight.Shared.Http.Requests.Admin.Sys -@using Moonlight.Shared.Http.Responses.Admin.Sys - -@attribute [Authorize(Policy = "permissions:admin.system.diagnose")] - -@inject HttpApiClient ApiClient -@inject DownloadService DownloadService - -
- -
- -
-
-
- Report -
-
-

- With the button below you can create a diagnose report containing all important information to troubleshoot your moonlight instance and its modules. - The diagnose file is a zip containing different logs and censored config files which can be shared with our support on discord. - If you only want to export specific parts of the diagnose report, click on "Advanced" and select the desired providers -

- - Generate report - -
- - Advanced - @if (DropdownOpen) - { - - } - else - { - - } - - -
- -
- - -
- - @foreach (var item in AvailableProviders) - { -
- - -
- } -
-
-
-
-
- -
- -
-
- -@code -{ - private bool DropdownOpen = false; - private Dictionary AvailableProviders; - - private bool SelectAll - { - get => AvailableProviders.Values.All(v => v); - set - { - foreach (var k in AvailableProviders.Keys) - AvailableProviders[k] = value; - } - } - - private async Task LoadAsync(LazyLoader arg) - { - var providers = await ApiClient.GetJson( - "api/admin/system/diagnose/providers" - ); - - AvailableProviders = providers - .ToDictionary(x => x, _ => true); - } - - private async Task GenerateDiagnoseAsync(WButton button) - { - var request = new GenerateDiagnoseRequest(); - - if (!SelectAll) - { - // Filter the providers which have been selected if not all providers have been selected - request.Providers = AvailableProviders - .Where(x => x.Value) - .Select(x => x.Key.Type) - .ToArray(); - } - - var stream = await ApiClient.PostStream("api/admin/system/diagnose", request); - - await DownloadService.DownloadAsync("diagnose.zip", stream); - } - - - private async Task ToggleDropDownAsync() - { - DropdownOpen = !DropdownOpen; - await InvokeAsync(StateHasChanged); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Files.razor b/Moonlight.Client/UI/Views/Admin/Sys/Files.razor deleted file mode 100644 index 6c437847..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Files.razor +++ /dev/null @@ -1,48 +0,0 @@ -@page "/admin/system/files" - -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Helpers -@using Moonlight.Client.Implementations -@using MoonCore.Blazor.FlyonUi.Files.Manager -@using MoonCore.Blazor.FlyonUi.Files.Manager.Operations - -@attribute [Authorize(Policy = "permissions:admin.system.overview")] - -@inject HttpApiClient ApiClient - -
- -
- - - -@code -{ - private IFsAccess FsAccess; - - protected override void OnInitialized() - { - FsAccess = new SystemFsAccess(ApiClient); - } - - private void OnConfigure(FileManagerOptions options) - { - options.AddMultiOperation(); - options.AddMultiOperation(); - options.AddMultiOperation(); - options.AddMultiOperation(); - - //options.AddSingleOperation(); - options.AddSingleOperation(); - - options.AddToolbarOperation(); - options.AddToolbarOperation(); - options.AddToolbarOperation(); - - options.AddOpenOperation(); - options.AddOpenOperation(); - options.AddOpenOperation(); - - options.WriteLimit = (int)ByteConverter.FromMegaBytes(20).Bytes; - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Hangfire.razor b/Moonlight.Client/UI/Views/Admin/Sys/Hangfire.razor deleted file mode 100644 index e05e3bac..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Hangfire.razor +++ /dev/null @@ -1,48 +0,0 @@ -@page "/admin/system/hangfire" - -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Responses.Admin.Hangfire -@using Moonlight.Client.UI.Components - -@attribute [Authorize(Policy = "permissions:admin.system.hangfire")] - -@inject HttpApiClient ApiClient - -
- -
- -
- - Hangfire is used to run scheduled and repeating tasks scalable via multiple instances. Here you can see a bunch of stats of the integrated hangfire instance - -
- - -
- - - - - - - - - - - -
-
- -@code -{ - private HangfireStatsResponse Stats; - - private async Task LoadAsync(LazyLoader _) - { - Stats = await ApiClient.GetJson( - "api/admin/system/hangfire/stats" - ); - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Sys/Index.razor b/Moonlight.Client/UI/Views/Admin/Sys/Index.razor deleted file mode 100644 index 23d82185..00000000 --- a/Moonlight.Client/UI/Views/Admin/Sys/Index.razor +++ /dev/null @@ -1,53 +0,0 @@ -@page "/admin/system" - -@using Microsoft.AspNetCore.Authorization -@using MoonCore.Helpers -@using Moonlight.Client.UI.Components -@using Moonlight.Shared.Http.Responses.Admin.Sys - -@attribute [Authorize(Policy = "permissions:admin.system.overview")] - -@inject HttpApiClient ApiClient - -
- -
- - -
- - - - -
- - - Actions - - -
-
-
- - - Restart/Shutdown - -
-
-
-
- -@code -{ - private SystemOverviewResponse OverviewData; - - private async Task LoadOverviewAsync(LazyLoader arg) - { - OverviewData = await ApiClient.GetJson("api/admin/system"); - } - - private async Task RestartAsync(WButton _) - { - await ApiClient.Post("api/admin/system/shutdown"); - } -} diff --git a/Moonlight.Client/UI/Views/Admin/Users/Create.razor b/Moonlight.Client/UI/Views/Admin/Users/Create.razor deleted file mode 100644 index 1377e908..00000000 --- a/Moonlight.Client/UI/Views/Admin/Users/Create.razor +++ /dev/null @@ -1,73 +0,0 @@ -@page "/admin/users/create" -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Requests.Admin.Users -@using MoonCore.Blazor.FlyonUi.Forms - -@inject HttpApiClient ApiClient -@inject NavigationManager Navigation -@inject ToastService ToastService - - - - - Back - - - - Create - - - -
- -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -@code -{ - private HandleForm Form; - private CreateUserRequest Request; - - private List Permissions = []; - - protected override void OnInitialized() - { - Request = new(); - } - - private async Task OnSubmit() - { - Request.Permissions = Permissions.ToArray(); - - await ApiClient.Post("api/admin/users", Request); - - await ToastService.SuccessAsync("Successfully created user"); - Navigation.NavigateTo("/admin/users"); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Users/Index.razor b/Moonlight.Client/UI/Views/Admin/Users/Index.razor deleted file mode 100644 index 0ee9460d..00000000 --- a/Moonlight.Client/UI/Views/Admin/Users/Index.razor +++ /dev/null @@ -1,80 +0,0 @@ -@page "/admin/users" - -@using MoonCore.Blazor.FlyonUi.Common -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Responses.Admin.Users -@using MoonCore.Blazor.FlyonUi.Grid -@using MoonCore.Blazor.FlyonUi.Grid.Columns -@using MoonCore.Common - -@inject HttpApiClient ApiClient -@inject AlertService AlertService -@inject ToastService ToastService - - - - - - - - - - -
- - - - - - - -
- -
-
- -@code -{ - private DataGrid Grid; - private ItemSource ItemSource => ItemSourceFactory.From(LoadItemsAsync); - - private async Task> LoadItemsAsync( - int startIndex, int count, string? filter, SortOption? sortOption - ) - { - var query = $"?startIndex={startIndex}&count={count}"; - - if (sortOption != null) - { - var dir = sortOption.Direction == SortDirection.Descending ? "desc" : "asc"; - query += $"&orderBy={sortOption.Column}&orderByDir={dir}"; - } - - if (!string.IsNullOrEmpty(filter)) - query += $"&filter={filter}"; - - return await ApiClient.GetJson>($"api/admin/users{query}"); - } - - private async Task DeleteAsync(UserResponse response) - { - await AlertService.ConfirmDangerAsync( - "User deletion", - $"Do you really want to delete the user '{response.Username}'", - async () => - { - await ApiClient.Delete($"api/admin/users/{response.Id}"); - await ToastService.SuccessAsync("Successfully deleted user"); - - await Grid.RefreshAsync(); - } - ); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Users/Update.razor b/Moonlight.Client/UI/Views/Admin/Users/Update.razor deleted file mode 100644 index 39b8db35..00000000 --- a/Moonlight.Client/UI/Views/Admin/Users/Update.razor +++ /dev/null @@ -1,88 +0,0 @@ -@page "/admin/users/{Id:int}" -@using MoonCore.Helpers -@using Moonlight.Shared.Http.Requests.Admin.Users -@using Moonlight.Shared.Http.Responses.Admin.Users -@using MoonCore.Blazor.FlyonUi.Forms - -@inject HttpApiClient ApiClient -@inject NavigationManager Navigation -@inject ToastService ToastService - - - - - - Back - - - - Update - - - -
- -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- - Leave empty if the password should not be changed -
-
-
-
-
-
- -@code -{ - [Parameter] public int Id { get; set; } - - private HandleForm Form; - private UpdateUserRequest Request; - - private List Permissions = []; - - private async Task LoadAsync(LazyLoader _) - { - var detail = await ApiClient.GetJson($"api/admin/users/{Id}"); - - Permissions = detail.Permissions.ToList(); - - Request = new() - { - Email = detail.Email, - Permissions = detail.Permissions, - Username = detail.Username - }; - } - - private async Task OnSubmit() - { - Request.Permissions = Permissions.ToArray(); - - await ApiClient.Patch($"api/admin/users/{Id}", Request); - - await ToastService.SuccessAsync("Successfully updated user"); - Navigation.NavigateTo("/admin/users"); - } -} \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Index.razor b/Moonlight.Client/UI/Views/Index.razor deleted file mode 100644 index 9c4506d4..00000000 --- a/Moonlight.Client/UI/Views/Index.razor +++ /dev/null @@ -1,32 +0,0 @@ -@page "/" -@using MoonCore.Helpers -@using Moonlight.Client.Interfaces - -@inject IEnumerable ElementProviders - -
- @foreach (var render in Renders) - { - @render - } -
- -@code -{ - private RenderFragment[] Renders; - - protected override void OnInitialized() - { - var renders = new List(); - - var elementTypes = new List(); - - foreach (var elementProvider in ElementProviders) - elementProvider.ModifyOverview(elementTypes); - - foreach (var elementType in elementTypes) - renders.Add(ComponentHelper.FromType(elementType)); - - Renders = renders.ToArray(); - } -} diff --git a/Moonlight.Client/UiConstants.cs b/Moonlight.Client/UiConstants.cs deleted file mode 100644 index f6c6432f..00000000 --- a/Moonlight.Client/UiConstants.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Moonlight.Client; - -public static class UiConstants -{ - public static readonly string[] AdminNavNames = - [ - "Overview", "Customisation", "Files", "Hangfire", "Advanced", "Diagnose" - ]; - - public static readonly string[] AdminNavLinks = - [ - "/admin/system", "/admin/system/customisation", "/admin/system/files", "/admin/system/hangfire", - "/admin/system/advanced", "/admin/system/diagnose" - ]; -} \ No newline at end of file diff --git a/Moonlight.Client/wwwroot/img/ghost.png b/Moonlight.Client/wwwroot/img/ghost.png deleted file mode 100644 index 67275baa..00000000 Binary files a/Moonlight.Client/wwwroot/img/ghost.png and /dev/null differ diff --git a/Moonlight.Client/wwwroot/img/icon-192.png b/Moonlight.Client/wwwroot/img/icon-192.png deleted file mode 100644 index 1b417d98..00000000 Binary files a/Moonlight.Client/wwwroot/img/icon-192.png and /dev/null differ diff --git a/Moonlight.Client/wwwroot/img/icon-512.png b/Moonlight.Client/wwwroot/img/icon-512.png deleted file mode 100644 index 437487cf..00000000 Binary files a/Moonlight.Client/wwwroot/img/icon-512.png and /dev/null differ diff --git a/Moonlight.Client/wwwroot/img/pfp_placeholder.png b/Moonlight.Client/wwwroot/img/pfp_placeholder.png deleted file mode 100644 index 1108ddbb..00000000 Binary files a/Moonlight.Client/wwwroot/img/pfp_placeholder.png and /dev/null differ diff --git a/Moonlight.Client/wwwroot/js/moonlight.js b/Moonlight.Client/wwwroot/js/moonlight.js deleted file mode 100644 index a57ec020..00000000 --- a/Moonlight.Client/wwwroot/js/moonlight.js +++ /dev/null @@ -1,48 +0,0 @@ -window.moonlight = { - window: { - open: function (url, title, h, w) { - const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX; - const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY; - - const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; - const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; - - const systemZoom = width / window.screen.availWidth; - const left = (width - w) / 2 / systemZoom + dualScreenLeft - const top = (height - h) / 2 / systemZoom + dualScreenTop - const newWindow = window.open(url, title, - ` - scrollbars=yes, - width=${w / systemZoom}, - height=${h / systemZoom}, - top=${top}, - left=${left} - ` - ) - - if (window.focus) newWindow.focus(); - }, - closeCurrent() { - window.close(); - } - }, - assets: { - loadJavascript: function (url) { - let scriptElement = document.createElement('script'); - - scriptElement.src = url; - scriptElement.type = 'text/javascript'; - - (document.head || document.documentElement).appendChild(scriptElement); - }, - loadStylesheet: function (url) { - let linkElement = document.createElement('link'); - - linkElement.href = url; - linkElement.type = 'text/css'; - linkElement.rel = 'stylesheet'; - - (document.head || document.documentElement).appendChild(linkElement); - } - } -}; \ No newline at end of file diff --git a/Moonlight.Frontend/Constants.cs b/Moonlight.Frontend/Constants.cs new file mode 100644 index 00000000..5c42627f --- /dev/null +++ b/Moonlight.Frontend/Constants.cs @@ -0,0 +1,28 @@ +using System.Text.Json; +using Moonlight.Shared.Http; + +namespace Moonlight.Frontend; + +public static class Constants +{ + public static JsonSerializerOptions SerializerOptions + { + get + { + if (InternalOptions != null) + return InternalOptions; + + InternalOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + // Add source generated options from shared project + InternalOptions.TypeInfoResolverChain.Add(SerializationContext.Default); + + return InternalOptions; + } + } + + private static JsonSerializerOptions? InternalOptions; +} \ No newline at end of file diff --git a/Moonlight.Frontend/Mappers/UserMapper.cs b/Moonlight.Frontend/Mappers/UserMapper.cs new file mode 100644 index 00000000..847cc5b2 --- /dev/null +++ b/Moonlight.Frontend/Mappers/UserMapper.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; +using Riok.Mapperly.Abstractions; +using Moonlight.Shared.Http.Requests.Users; +using Moonlight.Shared.Http.Responses.Users; + +namespace Moonlight.Frontend.Mappers; + +[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 UserMapper +{ + public static partial UpdateUserRequest MapToUpdate(UserResponse response); +} \ No newline at end of file diff --git a/Moonlight.Frontend/Moonlight.Frontend.csproj b/Moonlight.Frontend/Moonlight.Frontend.csproj new file mode 100644 index 00000000..c04edb9d --- /dev/null +++ b/Moonlight.Frontend/Moonlight.Frontend.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + + True + false + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Moonlight.Frontend/Services/RemoteAuthProvider.cs b/Moonlight.Frontend/Services/RemoteAuthProvider.cs new file mode 100644 index 00000000..0dfc107e --- /dev/null +++ b/Moonlight.Frontend/Services/RemoteAuthProvider.cs @@ -0,0 +1,47 @@ +using System.Net; +using System.Net.Http.Json; +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using Moonlight.Shared.Http.Responses.Auth; + +namespace Moonlight.Frontend.Services; + +public class RemoteAuthProvider : AuthenticationStateProvider +{ + private readonly ILogger Logger; + private readonly HttpClient HttpClient; + + public RemoteAuthProvider(ILogger logger, HttpClient httpClient) + { + Logger = logger; + HttpClient = httpClient; + } + + public override async Task GetAuthenticationStateAsync() + { + try + { + var claimResponses = await HttpClient.GetFromJsonAsync( + "api/auth/claims", Constants.SerializerOptions + ); + + var claims = claimResponses!.Select(claim => new Claim(claim.Type, claim.Value)); + + return new AuthenticationState( + new ClaimsPrincipal(new ClaimsIdentity(claims, "remote")) + ); + } + catch (HttpRequestException e) + { + if (e.StatusCode != HttpStatusCode.Unauthorized) + Logger.LogError(e, "An api error occured while requesting claims from api"); + } + catch (Exception e) + { + Logger.LogError(e, "An unhandled error occured while requesting claims from api"); + } + + return new AuthenticationState(new ClaimsPrincipal()); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/Startup/IAppStartup.cs b/Moonlight.Frontend/Startup/IAppStartup.cs new file mode 100644 index 00000000..0fbe5eff --- /dev/null +++ b/Moonlight.Frontend/Startup/IAppStartup.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +namespace Moonlight.Frontend.Startup; + +public interface IAppStartup +{ + public void PreBuild(WebAssemblyHostBuilder builder); + public void PostBuild(WebAssemblyHost application); +} \ No newline at end of file diff --git a/Moonlight.Frontend/Startup/Startup.Auth.cs b/Moonlight.Frontend/Startup/Startup.Auth.cs new file mode 100644 index 00000000..6a716b60 --- /dev/null +++ b/Moonlight.Frontend/Startup/Startup.Auth.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Moonlight.Frontend.Services; + +namespace Moonlight.Frontend.Startup; + +public partial class Startup +{ + public void AddAuth(WebAssemblyHostBuilder builder) + { + builder.Services.AddScoped(); + builder.Services.AddAuthorizationCore(); + builder.Services.AddCascadingAuthenticationState(); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/Startup/Startup.Base.cs b/Moonlight.Frontend/Startup/Startup.Base.cs new file mode 100644 index 00000000..4b765ac4 --- /dev/null +++ b/Moonlight.Frontend/Startup/Startup.Base.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Moonlight.Frontend.UI; +using ShadcnBlazor; +using ShadcnBlazor.Extras; + +namespace Moonlight.Frontend.Startup; + +public partial class Startup +{ + public void AddBase(WebAssemblyHostBuilder builder) + { + builder.RootComponents.Add("#app"); + builder.RootComponents.Add("head::after"); + + builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + builder.Services.AddShadcnBlazor(); + builder.Services.AddShadcnBlazorExtras(); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/Startup/Startup.cs b/Moonlight.Frontend/Startup/Startup.cs new file mode 100644 index 00000000..90631038 --- /dev/null +++ b/Moonlight.Frontend/Startup/Startup.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +namespace Moonlight.Frontend.Startup; + +public partial class Startup : IAppStartup +{ + public void PreBuild(WebAssemblyHostBuilder builder) + { + AddBase(builder); + AddAuth(builder); + } + + public void PostBuild(WebAssemblyHost application) + { + + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/App.razor b/Moonlight.Frontend/UI/App.razor new file mode 100644 index 00000000..a811d897 --- /dev/null +++ b/Moonlight.Frontend/UI/App.razor @@ -0,0 +1,54 @@ +@using LucideBlazor +@using Microsoft.AspNetCore.Components.Authorization +@using ShadcnBlazor.Emptys +@using Moonlight.Frontend.UI.Components.Auth +@using Moonlight.Frontend.UI.Partials +@using Moonlight.Frontend.UI.Views + + + + + + + + + + + + + + + + + + + + @if (context.User.Identity?.IsAuthenticated ?? false) + { + + } + else + { + + } + + + + +
+ + + + + + + Critical Application Error + + + @context.ToString() + + + +
+
+
\ No newline at end of file diff --git a/Moonlight.Frontend/UI/Components/Auth/AccessDenied.razor b/Moonlight.Frontend/UI/Components/Auth/AccessDenied.razor new file mode 100644 index 00000000..94998665 --- /dev/null +++ b/Moonlight.Frontend/UI/Components/Auth/AccessDenied.razor @@ -0,0 +1,18 @@ +@using LucideBlazor +@using ShadcnBlazor.Emptys + +
+ + + + + + + Permission denied + + + You are not allowed to access this resource + + + +
\ No newline at end of file diff --git a/Moonlight.Frontend/UI/Components/Auth/Authenticating.razor b/Moonlight.Frontend/UI/Components/Auth/Authenticating.razor new file mode 100644 index 00000000..4611a40f --- /dev/null +++ b/Moonlight.Frontend/UI/Components/Auth/Authenticating.razor @@ -0,0 +1,15 @@ +@using LucideBlazor +@using ShadcnBlazor.Emptys + +
+ + + + + + + Authenticating + + + +
\ No newline at end of file diff --git a/Moonlight.Frontend/UI/Components/Auth/Authentication.razor b/Moonlight.Frontend/UI/Components/Auth/Authentication.razor new file mode 100644 index 00000000..dac2fb49 --- /dev/null +++ b/Moonlight.Frontend/UI/Components/Auth/Authentication.razor @@ -0,0 +1,62 @@ +@using ShadcnBlazor.Cards +@using ShadcnBlazor.Spinners +@using ShadcnBlazor.Buttons +@using Moonlight.Shared.Http.Responses.Auth + +@inject HttpClient HttpClient +@inject NavigationManager Navigation + +
+ + @if (Schemes == null || Schemes.Length == 1) + { +
+ +
+ } + else + { + + Login to WebApp + Select a login provider to continue + + +
+ @foreach (var scheme in Schemes) + { + + } +
+
+ } +
+
+ +@code +{ + private SchemeResponse[]? Schemes; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + return; + + var schemes = await HttpClient.GetFromJsonAsync( + "api/auth", Constants.SerializerOptions + ); + + if (schemes == null) + return; + + Schemes = schemes; + + if (schemes.Length == 1) + Navigation.NavigateTo($"/api/auth/{schemes[0].Name}", true); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Partials/AppHeader.razor b/Moonlight.Frontend/UI/Partials/AppHeader.razor new file mode 100644 index 00000000..d038244e --- /dev/null +++ b/Moonlight.Frontend/UI/Partials/AppHeader.razor @@ -0,0 +1,9 @@ +@using ShadcnBlazor.Sidebars + +
+
+ +
+
\ No newline at end of file diff --git a/Moonlight.Frontend/UI/Partials/AppSidebar.razor b/Moonlight.Frontend/UI/Partials/AppSidebar.razor new file mode 100644 index 00000000..a1329bf6 --- /dev/null +++ b/Moonlight.Frontend/UI/Partials/AppSidebar.razor @@ -0,0 +1,124 @@ +@using System.Diagnostics.CodeAnalysis +@using LucideBlazor +@using ShadcnBlazor.Sidebars + +@inject NavigationManager Navigation + +@implements IDisposable + +@{ + var url = new Uri(Navigation.Uri); +} + + + + + + + + Logo + Moonlight + + + + + + + + @foreach (var group in Items.GroupBy(x => x.Group)) + { + + @if (!string.IsNullOrWhiteSpace(group.Key)) + { + + @group.Key + + } + + + + @foreach (var item in group) + { + var isActive = item.IsExactPath + ? item.Path == url.LocalPath + : url.LocalPath.StartsWith(item.Path, StringComparison.OrdinalIgnoreCase); + + + + + + + @item.Name + + + + + } + + + + } + + + + + + + +@code +{ + private readonly List Items = new(); + + protected override void OnInitialized() + { + Items.AddRange([ + new() + { + Name = "Overview", + IconType = typeof(LayoutDashboardIcon), + Path = "/", + IsExactPath = true + }, + new() + { + Name = "Users", + IconType = typeof(UsersRoundIcon), + Path = "/users", + IsExactPath = false, + Group = "Admin" + }, + ]); + + Navigation.LocationChanged += OnLocationChanged; + } + + private async void OnLocationChanged(object? sender, LocationChangedEventArgs e) + { + try + { + await InvokeAsync(StateHasChanged); + } + catch (Exception) + { + // Ignored + } + } + + private record SidebarItem + { + public string Name { get; set; } + public string Path { get; set; } + public bool IsExactPath { get; set; } + public string? Group { get; set; } + + // Used to prevent the IL-Trimming from removing this type as its dynamically assigned a type and we + // need it to work properly + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + public Type IconType { get; set; } + } + + public void Dispose() + { + Navigation.LocationChanged -= OnLocationChanged; + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Partials/MainLayout.razor b/Moonlight.Frontend/UI/Partials/MainLayout.razor new file mode 100644 index 00000000..77aaa709 --- /dev/null +++ b/Moonlight.Frontend/UI/Partials/MainLayout.razor @@ -0,0 +1,25 @@ +@using ShadcnBlazor.Extras.AlertDialogs +@using ShadcnBlazor.Extras.Alerts +@using ShadcnBlazor.Extras.Dialogs +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Sidebars + +@inherits LayoutComponentBase + + + + + + + +
+ + + @Body +
+ + + + +
+
\ No newline at end of file diff --git a/Moonlight.Frontend/UI/Partials/NavUser.razor b/Moonlight.Frontend/UI/Partials/NavUser.razor new file mode 100644 index 00000000..7763d39a --- /dev/null +++ b/Moonlight.Frontend/UI/Partials/NavUser.razor @@ -0,0 +1,76 @@ +@using System.Security.Claims +@using LucideBlazor +@using Microsoft.AspNetCore.Components.Authorization +@using ShadcnBlazor.Avatars +@using ShadcnBlazor.Dropdowns +@using ShadcnBlazor.Interop +@using ShadcnBlazor.Sidebars + +@inject NavigationManager Navigation + + + + + + + + + + GH + +
+ @Username + @Email +
+ + +
+
+
+ + +
+ + + GH + +
+ @Username + @Email +
+
+
+ + + + Log out + +
+
+
+
+ +@code +{ + [CascadingParameter] public SidebarProvider SidebarProvider { get; set; } + [CascadingParameter] public Task AuthState { get; set; } + + private string Username; + private string Email; + + protected override async Task OnInitializedAsync() + { + var authState = await AuthState; + + Username = authState.User.FindFirst(ClaimTypes.Name)?.Value ?? "N/A"; + Email = authState.User.FindFirst(ClaimTypes.Email)?.Value ?? "N/A"; + } + + private void Logout() => Navigation.NavigateTo("/api/auth/logout", true); +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Views/Index.razor b/Moonlight.Frontend/UI/Views/Index.razor new file mode 100644 index 00000000..01cd6674 --- /dev/null +++ b/Moonlight.Frontend/UI/Views/Index.razor @@ -0,0 +1 @@ +@page "/" \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Views/NotFound.razor b/Moonlight.Frontend/UI/Views/NotFound.razor new file mode 100644 index 00000000..39e85451 --- /dev/null +++ b/Moonlight.Frontend/UI/Views/NotFound.razor @@ -0,0 +1,24 @@ +@page "/notfound" + +@using LucideBlazor +@using ShadcnBlazor.Emptys +@using ShadcnBlazor.Buttons + + + + + + + Page not found + + The page you requested could not be found + + + + + + \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Views/Users/Create.razor b/Moonlight.Frontend/UI/Views/Users/Create.razor new file mode 100644 index 00000000..4c21594d --- /dev/null +++ b/Moonlight.Frontend/UI/Views/Users/Create.razor @@ -0,0 +1,91 @@ +@page "/users/create" + +@using LucideBlazor +@using ShadcnBlazor.Buttons +@using ShadcnBlazor.Labels +@using ShadcnBlazor.Cards +@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Inputs +@using Moonlight.Shared.Http.Requests.Users + +@inject HttpClient HttpClient +@inject NavigationManager Navigation +@inject ToastService ToastService + +
+
+

Create user

+
+ Create a new user +
+
+
+ + +
+
+ +
+ + + +
+ + + +
+ + +
+ +
+ + +
+
+
+
+
+
+ +@code +{ + private CreateUserRequest Request = new(); + + private FormHandler Form; + + private async Task OnSubmitAsync() + { + await HttpClient.PostAsJsonAsync( + "/api/users", + Request, + Constants.SerializerOptions + ); + + await ToastService.SuccessAsync( + "User creation", + $"Successfully created user {Request.Username}" + ); + + Navigation.NavigateTo("/users"); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Views/Users/Edit.razor b/Moonlight.Frontend/UI/Views/Users/Edit.razor new file mode 100644 index 00000000..08b8c8f8 --- /dev/null +++ b/Moonlight.Frontend/UI/Views/Users/Edit.razor @@ -0,0 +1,105 @@ +@page "/users/{Id:int}" + +@using LucideBlazor +@using ShadcnBlazor.Buttons +@using ShadcnBlazor.Labels +@using ShadcnBlazor.Cards +@using ShadcnBlazor.Extras.Common +@using ShadcnBlazor.Extras.FormHandlers +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Inputs +@using Moonlight.Frontend.Mappers +@using Moonlight.Shared.Http.Requests.Users +@using Moonlight.Shared.Http.Responses.Users + +@inject HttpClient HttpClient +@inject NavigationManager Navigation +@inject ToastService ToastService + +
+
+

Update user

+
+ Update an existing user +
+
+
+ + +
+
+ +
+ + + + +
+ + + +
+ + +
+ +
+ + +
+
+
+
+
+
+
+ +@code +{ + [Parameter] public int Id { get; set; } + + private FormHandler Form; + private UpdateUserRequest Request; + private UserResponse User; + + private async Task LoadAsync(LazyLoader _) + { + var user = await HttpClient.GetFromJsonAsync($"api/users/{Id}", Constants.SerializerOptions); + User = user!; + + Request = UserMapper.MapToUpdate(User); + } + + private async Task OnSubmitAsync() + { + await HttpClient.PatchAsJsonAsync( + $"/api/users/{User.Id}", + Request + ); + + await ToastService.SuccessAsync( + "User creation", + $"Successfully updated user {Request.Username}" + ); + + Navigation.NavigateTo("/users"); + } +} \ No newline at end of file diff --git a/Moonlight.Frontend/UI/Views/Users/Index.razor b/Moonlight.Frontend/UI/Views/Users/Index.razor new file mode 100644 index 00000000..3d6ee37d --- /dev/null +++ b/Moonlight.Frontend/UI/Views/Users/Index.razor @@ -0,0 +1,112 @@ +@page "/users" + +@using LucideBlazor +@using ShadcnBlazor.Buttons +@using ShadcnBlazor.DataGrids +@using ShadcnBlazor.Dropdowns +@using ShadcnBlazor.Extras.AlertDialogs +@using ShadcnBlazor.Extras.Toasts +@using ShadcnBlazor.Tabels +@using Moonlight.Shared.Http.Requests +@using Moonlight.Shared.Http.Responses +@using Moonlight.Shared.Http.Responses.Users + +@inject HttpClient HttpClient +@inject AlertDialogService AlertDialogService +@inject ToastService ToastService +@inject NavigationManager Navigation + +
+
+

Users

+
+ Manage users registered in your application +
+
+
+ +
+
+ +
+ + + + + + + +
+ + + + + + + + + Edit + + + + + + Delete + + + + + + +
+
+
+
+
+
+ +@code +{ + private DataGrid Grid; + + private async Task> LoadAsync(DataGridRequest request) + { + var query = $"?startIndex={request.StartIndex}&length={request.Length}"; + var filterOptions = request.Filters.Count > 0 ? new FilterOptions(request.Filters) : null; + + var response = await HttpClient.GetFromJsonAsync>( + $"api/users{query}&filterOptions={filterOptions}", + Constants.SerializerOptions + ); + + return new DataGridResponse(response!.Data, response.TotalLength); + } + + private void Edit(UserResponse response) => Navigation.NavigateTo($"/users/{response.Id}"); + + private async Task DeleteAsync(UserResponse user) + { + await AlertDialogService.ConfirmDangerAsync( + $"Deletion of user {user.Username}", + "Do you really want to delete this user? This action cannot be undone", + async () => + { + await HttpClient.DeleteAsync($"api/users/{user.Id}"); + await ToastService.SuccessAsync("User deletion", $"Successfully deleted user {user.Username}"); + + await Grid.RefreshAsync(); + } + ); + } +} \ No newline at end of file diff --git a/Moonlight.Client/_Imports.razor b/Moonlight.Frontend/UI/_Imports.razor similarity index 64% rename from Moonlight.Client/_Imports.razor rename to Moonlight.Frontend/UI/_Imports.razor index 33fab635..e84226d7 100644 --- a/Moonlight.Client/_Imports.razor +++ b/Moonlight.Frontend/UI/_Imports.razor @@ -6,9 +6,4 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop -@using Moonlight.Client - -@using MoonCore.Blazor.FlyonUi.Components -@using MoonCore.Blazor.FlyonUi.Modals -@using MoonCore.Blazor.FlyonUi.Toasts -@using MoonCore.Blazor.FlyonUi.Alerts \ No newline at end of file +@using Moonlight.Frontend \ No newline at end of file diff --git a/Moonlight.Client/wwwroot/svg/logo.svg b/Moonlight.Frontend/wwwroot/logo.svg similarity index 100% rename from Moonlight.Client/wwwroot/svg/logo.svg rename to Moonlight.Frontend/wwwroot/logo.svg diff --git a/Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyRequest.cs b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyRequest.cs deleted file mode 100644 index 83d76bf6..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/ApiKeys/CreateApiKeyRequest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys; - -public class CreateApiKeyRequest -{ - [Required(ErrorMessage = "You need to specify a description")] - public string Description { get; set; } - - [Required(ErrorMessage = "You need to specify permissions for the api key")] - public string[] Permissions { get; set; } = []; - - [Required(ErrorMessage = "You need to specify an expire date")] - public DateTime ExpiresAt { get; set; } = DateTime.UtcNow.AddDays(30); -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyRequest.cs b/Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyRequest.cs deleted file mode 100644 index 78bd77f9..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/ApiKeys/UpdateApiKeyRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.ApiKeys; - -public class UpdateApiKeyRequest -{ - [Required(ErrorMessage = "You need to specify a description")] - public string Description { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CombineRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CombineRequest.cs deleted file mode 100644 index 269b6326..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CombineRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -public class CombineRequest -{ - [Required(ErrorMessage = "Destination is required")] - public string Destination { get; set; } - - [Required(ErrorMessage = "Files are required")] - public string[] Files { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CompressRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CompressRequest.cs deleted file mode 100644 index 521810e4..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/CompressRequest.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -public class CompressRequest -{ - [Required(ErrorMessage = "Format is required")] - public string Format { get; set; } - - [Required(ErrorMessage = "Destination is required")] - public string Destination { get; set; } - - [Required(ErrorMessage = "Root is required")] - public string Root { get; set; } - - [Required(ErrorMessage = "Items are required")] - public string[] Items { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/DecompressRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Files/DecompressRequest.cs deleted file mode 100644 index d37379c0..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/Files/DecompressRequest.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys.Files; - -public class DecompressRequest -{ - [Required(ErrorMessage = "You need to provide a format")] - public string Format { get; set; } - - [Required(ErrorMessage = "You need to provide a path")] - public string Path { get; set; } - - [Required(ErrorMessage = "You need to provide a destination")] - public string Destination { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/GenerateDiagnoseRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/GenerateDiagnoseRequest.cs deleted file mode 100644 index 91df3c01..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/GenerateDiagnoseRequest.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys; - -public class GenerateDiagnoseRequest -{ - [Required(ErrorMessage = "You need to define providers")] - public string[] Providers { get; set; } = []; -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs deleted file mode 100644 index 0035d00f..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/CreateThemeRequest.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Moonlight.Shared.Misc; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys.Theme; - -public class CreateThemeRequest -{ - [Required(ErrorMessage = "You need to provide a name")] - public string Name { get; set; } - - [Required(ErrorMessage = "You need to provide an author")] - public string Author { get; set; } - - [Required(ErrorMessage = "You need to provide a version")] - public string Version { get; set; } - - public string? UpdateUrl { get; set; } - public string? DonateUrl { get; set; } - - public ApplicationTheme Content { get; set; } = new(); -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs deleted file mode 100644 index 9d800336..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Sys/Theme/UpdateThemeRequest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Moonlight.Shared.Misc; - -namespace Moonlight.Shared.Http.Requests.Admin.Sys.Theme; - -public class UpdateThemeRequest -{ - public bool IsEnabled { get; set; } - - [Required(ErrorMessage = "You need to provide a name")] - public string Name { get; set; } - - [Required(ErrorMessage = "You need to provide an author")] - public string Author { get; set; } - - [Required(ErrorMessage = "You need to provide a version")] - public string Version { get; set; } - - public string? UpdateUrl { get; set; } - public string? DonateUrl { get; set; } - - public ApplicationTheme Content { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs deleted file mode 100644 index 60b4ddd4..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Users; - -public class CreateUserRequest -{ - [Required(ErrorMessage = "You need to provide an email address")] - [EmailAddress(ErrorMessage = "You need to provide a valid email address")] - public string Email { get; set; } - - [Required(ErrorMessage = "You need to provide a username")] - [RegularExpression("^[a-z][a-z0-9]*$", ErrorMessage = "Usernames can only contain lowercase characters and numbers and should not start with a number")] - public string Username { get; set; } - - [Required(ErrorMessage = "You need to provide a password")] - [MinLength(8, ErrorMessage = "Your password needs to be at least 8 characters long")] - [MaxLength(256, ErrorMessage = "Your password should not exceed the length of 256 characters")] - public string Password { get; set; } - - [Required(ErrorMessage = "You need to provide permissions")] - public string[] Permissions { get; set; } = []; -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs deleted file mode 100644 index 81362d31..00000000 --- a/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Moonlight.Shared.Http.Requests.Admin.Users; - -public class UpdateUserRequest -{ - [Required(ErrorMessage = "You need to provide an email address")] - [EmailAddress(ErrorMessage = "You need to provide a valid email address")] - public string Email { get; set; } - - [Required(ErrorMessage = "You need to provide a username")] - [RegularExpression("^[a-z][a-z0-9]*$", ErrorMessage = "Usernames can only contain lowercase characters and numbers and should not start with a number")] - public string Username { get; set; } - - public string? Password { get; set; } - - [Required(ErrorMessage = "You need to provide permissions")] - public string[] Permissions { get; set; } = []; -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/FilterOptions.cs b/Moonlight.Shared/Http/Requests/FilterOptions.cs new file mode 100644 index 00000000..a12790d1 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/FilterOptions.cs @@ -0,0 +1,62 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Moonlight.Shared.Http.Requests; + +public class FilterOptions : IParsable +{ + public Dictionary Filters { get; set; } + + public FilterOptions() + { + Filters = new(); + } + + public FilterOptions(Dictionary filters) + { + Filters = filters; + } + + public static FilterOptions Parse(string s, IFormatProvider? provider) + { + if (!TryParse(s, provider, out var result)) + throw new AggregateException("Unable to parse filter options"); + + return result; + } + + public static bool TryParse( + [NotNullWhen(true)] string? input, + IFormatProvider? provider, + [MaybeNullWhen(false)] out FilterOptions result + ) + { + result = new(); + + if (string.IsNullOrEmpty(input)) + return true; + + var filters = input.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var part in filters) + { + var filterParts = part.Split('$', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (filterParts.Length != 2) + continue; + + result.Filters.Add(filterParts[0], filterParts[1]); + } + + return true; + } + + public override string ToString() + { + var result = ""; + + foreach (var filter in Filters) + result += $"{filter.Key}${filter.Value};"; + + return result; + } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Users/CreateUserRequest.cs b/Moonlight.Shared/Http/Requests/Users/CreateUserRequest.cs new file mode 100644 index 00000000..4b2a2caa --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Users/CreateUserRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Users; + +public class CreateUserRequest +{ + [Required] + [MinLength(3)] + [MaxLength(32)] + public string Username { get; set; } + + [Required] [EmailAddress] public string Email { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Users/UpdateUserRequest.cs b/Moonlight.Shared/Http/Requests/Users/UpdateUserRequest.cs new file mode 100644 index 00000000..100a28f8 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Users/UpdateUserRequest.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Users; + +public class UpdateUserRequest +{ + [Required] + [MinLength(3)] + [MaxLength(32)] + public string Username { get; set; } + + [Required] [EmailAddress] public string Email { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs deleted file mode 100644 index f30ae182..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.ApiKeys; - -public class ApiKeyResponse -{ - public int Id { get; set; } - public string Description { get; set; } - public string[] Permissions { get; set; } = []; - public DateTimeOffset ExpiresAt { get; set; } - public DateTimeOffset CreatedAt { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/CreateApiKeyResponse.cs b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/CreateApiKeyResponse.cs deleted file mode 100644 index 69c2555c..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/CreateApiKeyResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.ApiKeys; - -public class CreateApiKeyResponse -{ - public int Id { get; set; } - public string Secret { get; set; } - public string Description { get; set; } - public string[] Permissions { get; set; } = []; - public DateTimeOffset ExpiresAt { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Hangfire/HangfireStatsResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Hangfire/HangfireStatsResponse.cs deleted file mode 100644 index c925d814..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Hangfire/HangfireStatsResponse.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Hangfire; - -public class HangfireStatsResponse -{ - public long Servers { get; set; } - public long Recurring { get; set; } - public long Enqueued { get; set; } - public long Queues { get; set; } - public long Scheduled { get; set; } - public long Processing { get; set; } - public long Succeeded { get; set; } - public long Failed { get; set; } - public long Deleted { get; set; } - public long? Retries { get; set; } - public long? Awaiting { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs deleted file mode 100644 index 6a23a991..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Sys/DiagnoseProvideResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Sys; - -public class DiagnoseProvideResponse -{ - public string Name { get; set; } - public string Type { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/DownloadUrlResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/DownloadUrlResponse.cs deleted file mode 100644 index aba6a249..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Sys/DownloadUrlResponse.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Sys; - -public class DownloadUrlResponse -{ - public string Url { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/FileSystemEntryResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/FileSystemEntryResponse.cs deleted file mode 100644 index f7bb124a..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Sys/FileSystemEntryResponse.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Sys; - -public class FileSystemEntryResponse -{ - public string Name { get; set; } - public bool IsFolder { get; set; } - public long Size { get; set; } - - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs deleted file mode 100644 index e72ae39e..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Sys/SystemOverviewResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Sys; - -public class SystemOverviewResponse -{ - public int CpuUsage { get; set; } - public long MemoryUsage { get; set; } - public string OperatingSystem { get; set; } - public TimeSpan Uptime { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs b/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs deleted file mode 100644 index 47253750..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/ThemeResponse.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Moonlight.Shared.Misc; - -namespace Moonlight.Shared.Http.Responses.Admin; - -public class ThemeResponse -{ - public int Id { get; set; } - - public bool IsEnabled { get; set; } - - public string Name { get; set; } - public string Author { get; set; } - public string Version { get; set; } - - public string? UpdateUrl { get; set; } - public string? DonateUrl { get; set; } - - public ApplicationTheme Content { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs deleted file mode 100644 index 23f50047..00000000 --- a/Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Admin.Users; - -public class UserResponse -{ - public int Id { get; set; } - public string Username { get; set; } - public string Email { get; set; } - public string[] Permissions { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/AuthClaimResponse.cs b/Moonlight.Shared/Http/Responses/Auth/AuthClaimResponse.cs deleted file mode 100644 index 178c88a6..00000000 --- a/Moonlight.Shared/Http/Responses/Auth/AuthClaimResponse.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Auth; - -public class AuthClaimResponse -{ - // ReSharper disable once UnusedMember.Global - // Its used by the json serializer ^^ - public AuthClaimResponse() - { - - } - - public AuthClaimResponse(string type, string value) - { - Type = type; - Value = value; - } - - public string Type { get; set; } - public string Value { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/AuthSchemeResponse.cs b/Moonlight.Shared/Http/Responses/Auth/AuthSchemeResponse.cs deleted file mode 100644 index 079a08cd..00000000 --- a/Moonlight.Shared/Http/Responses/Auth/AuthSchemeResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Auth; - -public class AuthSchemeResponse -{ - public string DisplayName { get; set; } - public string Identifier { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/ClaimResponse.cs b/Moonlight.Shared/Http/Responses/Auth/ClaimResponse.cs new file mode 100644 index 00000000..4ea1fbf1 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Auth/ClaimResponse.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses.Auth; + +public record ClaimResponse(string Type, string Value); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Auth/SchemeResponse.cs b/Moonlight.Shared/Http/Responses/Auth/SchemeResponse.cs new file mode 100644 index 00000000..b92c8d15 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Auth/SchemeResponse.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses.Auth; + +public record SchemeResponse(string Name, string DisplayName); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/PagedData.cs b/Moonlight.Shared/Http/Responses/PagedData.cs new file mode 100644 index 00000000..1a1c7ed1 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/PagedData.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses; + +public record PagedData(T[] Data, int TotalLength); \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/Users/UserResponse.cs b/Moonlight.Shared/Http/Responses/Users/UserResponse.cs new file mode 100644 index 00000000..a94fff22 --- /dev/null +++ b/Moonlight.Shared/Http/Responses/Users/UserResponse.cs @@ -0,0 +1,3 @@ +namespace Moonlight.Shared.Http.Responses.Users; + +public record UserResponse(int Id, string Username, string Email); \ No newline at end of file diff --git a/Moonlight.Shared/Http/SerializationContext.cs b/Moonlight.Shared/Http/SerializationContext.cs new file mode 100644 index 00000000..c29c69a1 --- /dev/null +++ b/Moonlight.Shared/Http/SerializationContext.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using Moonlight.Shared.Http.Requests.Users; +using Moonlight.Shared.Http.Responses; +using Moonlight.Shared.Http.Responses.Auth; +using Moonlight.Shared.Http.Responses.Users; + +namespace Moonlight.Shared.Http; + +[JsonSerializable(typeof(CreateUserRequest))] +[JsonSerializable(typeof(UpdateUserRequest))] +[JsonSerializable(typeof(ClaimResponse[]))] +[JsonSerializable(typeof(SchemeResponse[]))] +[JsonSerializable(typeof(UserResponse))] +[JsonSerializable(typeof(PagedData))] +public partial class SerializationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Moonlight.Shared/Misc/ApplicationTheme.cs b/Moonlight.Shared/Misc/ApplicationTheme.cs deleted file mode 100644 index f1cd4b4d..00000000 --- a/Moonlight.Shared/Misc/ApplicationTheme.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Moonlight.Shared.Misc; - -public class ApplicationTheme -{ - public string ColorBase100 { get; set; } - public string ColorBase200 { get; set; } - public string ColorBase300 { get; set; } - - public string ColorBaseContent { get; set; } - - public string ColorPrimary { get; set; } - public string ColorPrimaryContent { get; set; } - - public string ColorSecondary { get; set; } - public string ColorSecondaryContent { get; set; } - - public string ColorAccent { get; set; } - public string ColorAccentContent { get; set; } - - public string ColorNeutral { get; set; } - public string ColorNeutralContent { get; set; } - - public string ColorInfo { get; set; } - public string ColorInfoContent { get; set; } - - public string ColorSuccess { get; set; } - public string ColorSuccessContent { get; set; } - - public string ColorWarning { get; set; } - public string ColorWarningContent { get; set; } - - public string ColorError { get; set; } - public string ColorErrorContent { get; set; } - - public float RadiusSelector { get; set; } - public float RadiusField { get; set; } - public float RadiusBox { get; set; } - - public float SizeSelector { get; set; } - public float SizeField { get; set; } - - public float Border { get; set; } - public int Depth { get; set; } - public int Noise { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Misc/FrontendConfiguration.cs b/Moonlight.Shared/Misc/FrontendConfiguration.cs deleted file mode 100644 index cedbdab4..00000000 --- a/Moonlight.Shared/Misc/FrontendConfiguration.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.Shared.Misc; - -public class FrontendConfiguration -{ - public string ApiUrl { get; set; } - public string HostEnvironment { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Moonlight.Shared.csproj b/Moonlight.Shared/Moonlight.Shared.csproj index a2737bac..8a9732e7 100644 --- a/Moonlight.Shared/Moonlight.Shared.csproj +++ b/Moonlight.Shared/Moonlight.Shared.csproj @@ -1,19 +1,9 @@ - - - - net9.0 - enable - enable - - - Moonlight.Shared - shared - Moonlight.Shared - 2.1.15 - Moonlight Panel - A build of the shared classes for moonlight development - https://github.com/Moonlight-Panel/Moonlight - true - true - + + + + net10.0 + enable + enable + + \ No newline at end of file diff --git a/Moonlight.sln b/Moonlight.sln deleted file mode 100644 index 161e93c8..00000000 --- a/Moonlight.sln +++ /dev/null @@ -1,46 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.ApiServer", "Moonlight.ApiServer\Moonlight.ApiServer.csproj", "{B1286CB5-0A6E-4E06-A937-9F5FE1662C5F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.Client", "Moonlight.Client\Moonlight.Client.csproj", "{8E3F3B36-544D-4FB4-94E9-4572FF099B19}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.Shared", "Moonlight.Shared\Moonlight.Shared.csproj", "{C82E4F2A-91D2-4BC7-9AA7-241FDAAFC823}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.ApiServer.Runtime", "Moonlight.ApiServer.Runtime\Moonlight.ApiServer.Runtime.csproj", "{97FC686D-BC8A-4145-90C7-CA86B598441E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moonlight.Client.Runtime", "Moonlight.Client.Runtime\Moonlight.Client.Runtime.csproj", "{72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Runtime", "Runtime", "{DCE3A43F-ACA8-41C6-BE27-3B3AA033B843}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B1286CB5-0A6E-4E06-A937-9F5FE1662C5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1286CB5-0A6E-4E06-A937-9F5FE1662C5F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1286CB5-0A6E-4E06-A937-9F5FE1662C5F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1286CB5-0A6E-4E06-A937-9F5FE1662C5F}.Release|Any CPU.Build.0 = Release|Any CPU - {8E3F3B36-544D-4FB4-94E9-4572FF099B19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8E3F3B36-544D-4FB4-94E9-4572FF099B19}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8E3F3B36-544D-4FB4-94E9-4572FF099B19}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8E3F3B36-544D-4FB4-94E9-4572FF099B19}.Release|Any CPU.Build.0 = Release|Any CPU - {C82E4F2A-91D2-4BC7-9AA7-241FDAAFC823}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C82E4F2A-91D2-4BC7-9AA7-241FDAAFC823}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C82E4F2A-91D2-4BC7-9AA7-241FDAAFC823}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C82E4F2A-91D2-4BC7-9AA7-241FDAAFC823}.Release|Any CPU.Build.0 = Release|Any CPU - {97FC686D-BC8A-4145-90C7-CA86B598441E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {97FC686D-BC8A-4145-90C7-CA86B598441E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {97FC686D-BC8A-4145-90C7-CA86B598441E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {97FC686D-BC8A-4145-90C7-CA86B598441E}.Release|Any CPU.Build.0 = Release|Any CPU - {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {97FC686D-BC8A-4145-90C7-CA86B598441E} = {DCE3A43F-ACA8-41C6-BE27-3B3AA033B843} - {72F21AA4-4721-4B4C-B2FF-CFDCBB1BCB05} = {DCE3A43F-ACA8-41C6-BE27-3B3AA033B843} - EndGlobalSection -EndGlobal diff --git a/Moonlight.slnx b/Moonlight.slnx new file mode 100644 index 00000000..9ad99ce2 --- /dev/null +++ b/Moonlight.slnx @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/README.md b/README.md deleted file mode 100644 index 3063e486..00000000 --- a/README.md +++ /dev/null @@ -1,91 +0,0 @@ -

- - Logo - -

Moonlight Panel

-

- -[![GitHub commits](https://img.shields.io/github/commit-activity/t/Moonlight-Panel/Moonlight/v2_ChangeArchitecture?label=Commits)](https://github.com/Moonlight-Panel/Moonlight/releases) -[![GitHub issues](https://img.shields.io/github/issues/Moonlight-Panel/Moonlight?label=Issues)](https://github.com/Moonlight-Panel/Moonlight/issues) -[![GitHub stars](https://img.shields.io/github/stars/Moonlight-Panel/Moonlight?label=Stars)](https://github.com/Moonlight-Panel/Moonlight) -[![Discord](https://img.shields.io/discord/1075368155830034433?label=Discord&logo=discord)](https://discord.gg/3r9JPbgenb) -[![License: CC0-1.0](https://img.shields.io/badge/license-CC0--1.0-blue.svg?label=License)](https://github.com/Moonlight-Panel/Moonlight/blob/v2/LICENSE) - -**Moonlight Panel** is a free and open-source hosting panel designed to simplify the management of game servers. Built -with a focus on performance, extensibility, and user experience, Moonlight offers a sleek UI and a powerful backend to -give you full control over your hosting environment. - -👉 [Official Website](https://moonlightpanel.dev/) | [Documentation](https://moonlightpa.nl/docs) | [Discord Community](https://discord.gg/3r9JPbgenb) - ---- - -## 🚀 Features - -* **Modern UI**: Responsive and intuitive interface built with Blazor WebAssembly and TailwindCSS 4. -* **Plugin Support**: Extend functionality and customize behavior through a robust plugin system. -* **Theming**: Tailor the look and feel of your panel with customizable themes. -* **OAuth2 Integration**: Seamlessly integrate with external authentication providers. -* **Advanced Permission System**: Precise control over user and admin permissions. -* **Virtual Disks**: Protect against file system exploits with virtual disk support. -* **Scalability**: Designed to handle growing user bases and increased traffic. -* **Diagnostic Tools**: Built-in tools to quickly identify and resolve issues. -* **Comprehensive API**: Automate and integrate Moonlight into your existing workflows. - ---- - -## 🆕 What's New in v2.1 - -Version 2.1 is a complete rewrite of the Moonlight Panel, bringing significant improvements: - -* **Performance Enhancements**: Optimized codebase for faster load times and responsiveness. -* **Stability Improvements**: Robust error handling and diagnostic systems. -* **Enhanced Extensibility**: More powerful plugin system and theming capabilities. -* **Modern Tech Stack**: Leveraging .NET Core, Docker, and Blazor WebAssembly for a smooth experience. - -For a detailed overview, check out our [v2.1 Article](https://moonlightpanel.dev/article/1). - ---- - -## 📦 Getting Started - -For installation instructions, refer to our [Installation Guide](https://moonlightpa.nl/install). - ---- - -## 🧩 Plugin Development - -Moonlight's plugin system allows you to customize and extend the panel's functionality. Whether you're adding new -features or extending existing ones, our [Plugin Development Guide](https://moonlightpa.nl/dev) will help you get -started. - ---- - -## 🛠️ Contributing - -We welcome contributions from the community! Whether it's reporting bugs, suggesting features, or submitting pull -requests, your input helps make Moonlight better. - -* **Bug Reports & Feature Requests**: Use the [Issues](https://github.com/Moonlight-Panel/Moonlight/issues) tab. -* **Pull Requests**: Fork the repository and submit your changes for review. -* **Discussions**: Join our [Discord Community](https://discord.gg/3r9JPbgenb) to engage with other users and - developers. - ---- - -## 📄 License - -This project is licensed under -the [CC0 1.0 Universal License](https://github.com/Moonlight-Panel/Moonlight/blob/v2/LICENSE). - ---- - -## 👥 Contributors - -* **Masu Baumgartner**: Core system & frontend development. -* **Moritz Deiaco**: Core and UI development. -* **ChiaraBm**: Core development for v2.1 -* **Dannyx**: Grammar checks (and translations in v1). -* **Jesper**: Developer of the Moonlight Marketplace -* **Spielepapagei**: Github Actions and CI/CD workflows - -A full list of contributors can be found [here](https://github.com/Moonlight-Panel/Moonlight/graphs/contributors). \ No newline at end of file diff --git a/Resources/Readme/logo.svg b/Resources/Readme/logo.svg deleted file mode 100644 index 193ebfae..00000000 --- a/Resources/Readme/logo.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Resources/Scripts/Commands/PackCommand.cs b/Resources/Scripts/Commands/PackCommand.cs deleted file mode 100644 index 5ed77716..00000000 --- a/Resources/Scripts/Commands/PackCommand.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System.IO.Compression; -using System.Xml.Linq; -using Cocona; -using Microsoft.Extensions.Logging; -using Scripts.Helpers; - -namespace Scripts.Commands; - -public class PackCommand -{ - private readonly string TmpDir = "/tmp/mlbuild"; - private readonly ILogger Logger; - private readonly CsprojHelper CsprojHelper; - private readonly NupkgHelper NupkgHelper; - - private readonly string[] ValidTags = ["apiserver", "frontend", "shared"]; - - public PackCommand( - ILogger logger, - CsprojHelper csprojHelper, - NupkgHelper nupkgHelper - ) - { - CsprojHelper = csprojHelper; - NupkgHelper = nupkgHelper; - Logger = logger; - } - - [Command("pack", Description = "Packs the specified folder/solution into nuget packages")] - public async Task Pack( - [Argument] string solutionDirectory, - [Argument] string outputLocation, - [Option] string buildConfiguration = "Debug" - ) - { - if (!Directory.Exists(solutionDirectory)) - { - Logger.LogError("The specified solution directory does not exist"); - return; - } - - if (!Directory.Exists(outputLocation)) - Directory.CreateDirectory(outputLocation); - - if (Directory.Exists(TmpDir)) - Directory.Delete(TmpDir, true); - - Directory.CreateDirectory(TmpDir); - - // Find the project files - Logger.LogInformation("Searching for projects inside the specified folder"); - - var projects = await CsprojHelper.FindProjectsInPath(solutionDirectory, ValidTags); - - // Show the user - Logger.LogInformation("Found {count} project(s) to check:", projects.Count); - - foreach (var path in projects.Keys) - Logger.LogInformation("- {path}", Path.GetFullPath(path)); - - // Filter out project files which have specific tags specified - Logger.LogInformation("Filtering projects by tags"); - - var apiServerProjects = projects - .Where(x => x.Value.PackageTags.Contains("apiserver", StringComparer.InvariantCultureIgnoreCase)) - .ToArray(); - - var frontendProjects = projects - .Where(x => x.Value.PackageTags.Contains("frontend", StringComparer.InvariantCultureIgnoreCase)) - .ToArray(); - - var sharedProjects = projects - .Where(x => x.Value.PackageTags.Contains("shared", StringComparer.InvariantCultureIgnoreCase)) - .ToArray(); - - Logger.LogInformation( - "Found {apiServerCount} api server project(s), {frontendCount} frontend project(s) and {sharedCount} shared project(s)", - apiServerProjects.Length, - frontendProjects.Length, - sharedProjects.Length - ); - - // Now build all these projects so we can pack them - Logger.LogInformation("Building and packing api server project(s)"); - - foreach (var apiServerProject in apiServerProjects) - { - var csProjectFile = apiServerProject.Key; - var manifest = apiServerProject.Value; - - await CsprojHelper.Build( - csProjectFile, - buildConfiguration - ); - - var nugetFilePath = await CsprojHelper.Pack( - csProjectFile, - TmpDir, - buildConfiguration - ); - - var nugetPackage = ZipFile.Open( - nugetFilePath, - ZipArchiveMode.Update - ); - - await NupkgHelper.RemoveContentFiles(nugetPackage); - - // We don't want to clean moonlight references when we are packing moonlight, - // as it would remove references to its own shared project - - if (!manifest.PackageId.StartsWith("Moonlight.")) - await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight."); - - Logger.LogInformation("Finishing package and copying to output directory"); - - nugetPackage.Dispose(); - - File.Move( - nugetFilePath, - Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)), - true - ); - } - - Logger.LogInformation("Building and packing frontend projects"); - - foreach (var frontendProject in frontendProjects) - { - var csProjectFile = frontendProject.Key; - var manifest = frontendProject.Value; - - await CsprojHelper.Build( - csProjectFile, - buildConfiguration - ); - - var nugetFilePath = await CsprojHelper.Pack( - csProjectFile, - TmpDir, - buildConfiguration - ); - - var nugetPackage = ZipFile.Open( - nugetFilePath, - ZipArchiveMode.Update - ); - - await NupkgHelper.RemoveStaticWebAssets(nugetPackage, "_framework"); - await NupkgHelper.RemoveStaticWebAssets(nugetPackage, "css/style.min.css"); - await NupkgHelper.RemoveContentFiles(nugetPackage); - - // We don't want to clean moonlight references when we are packing moonlight, - // as it would remove references to its own shared project - - if (!manifest.PackageId.StartsWith("Moonlight.")) - await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight."); - - - // Pack razor and html files into src folder - var additionalSrcFiles = new List(); - var basePath = Path.GetDirectoryName(csProjectFile)!; - - additionalSrcFiles.AddRange( - Directory.GetFiles(basePath, "*.razor", SearchOption.AllDirectories) - ); - - additionalSrcFiles.AddRange( - Directory.GetFiles(basePath, "index.html", SearchOption.AllDirectories) - ); - - await NupkgHelper.AddSourceFiles( - nugetPackage, - additionalSrcFiles.ToArray(), - file => "src/" + file.Replace(basePath, "").Trim('/') - ); - - Logger.LogInformation("Finishing package and copying to output directory"); - - nugetPackage.Dispose(); - - File.Move( - nugetFilePath, - Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)), - true - ); - } - - Logger.LogInformation("Building and packing shared projects"); - - foreach (var sharedProject in sharedProjects) - { - var csProjectFile = sharedProject.Key; - var manifest = sharedProject.Value; - - await CsprojHelper.Build( - csProjectFile, - buildConfiguration - ); - - var nugetFilePath = await CsprojHelper.Pack( - csProjectFile, - TmpDir, - buildConfiguration - ); - - var nugetPackage = ZipFile.Open( - nugetFilePath, - ZipArchiveMode.Update - ); - - // We don't want to clean moonlight references when we are packing moonlight, - // as it would remove references to its own shared project - - if (!manifest.PackageId.StartsWith("Moonlight.")) - await NupkgHelper.CleanDependencies(nugetPackage, "Moonlight."); - - nugetPackage.Dispose(); - - File.Move( - nugetFilePath, - Path.Combine(outputLocation, Path.GetFileName(nugetFilePath)), - true - ); - } - } -} \ No newline at end of file diff --git a/Resources/Scripts/Commands/PreBuildCommand.cs b/Resources/Scripts/Commands/PreBuildCommand.cs deleted file mode 100644 index cef8cf76..00000000 --- a/Resources/Scripts/Commands/PreBuildCommand.cs +++ /dev/null @@ -1,265 +0,0 @@ -using System.IO.Compression; -using System.Text; -using System.Xml.Linq; -using Cocona; -using Microsoft.Extensions.Logging; -using Scripts.Helpers; -using Scripts.Models; - -namespace Scripts.Commands; - -public class PreBuildCommand -{ - private readonly NupkgHelper NupkgHelper; - private readonly CsprojHelper CsprojHelper; - private readonly CodeHelper CodeHelper; - private readonly ILogger Logger; - - private const string GeneratedStart = "// MLBUILD Generated Start"; - private const string GeneratedEnd = "// MLBUILD Generated End"; - private const string GeneratedHook = "// MLBUILD_PLUGIN_STARTUP_HERE"; - - private readonly string[] ValidTags = ["frontend", "apiserver", "shared"]; - - public PreBuildCommand( - CsprojHelper csprojHelper, - NupkgHelper nupkgHelper, - CodeHelper codeHelper, - ILogger logger - ) - { - CsprojHelper = csprojHelper; - NupkgHelper = nupkgHelper; - CodeHelper = codeHelper; - Logger = logger; - } - - [Command("prebuild")] - public async Task Prebuild( - [Argument] string moonlightDirectory, - [Argument] string pluginsDirectory - ) - { - var projects = await CsprojHelper.FindProjectsInPath(moonlightDirectory, ValidTags); - - var nugetManifests = await GetNugetManifests(pluginsDirectory); - - Logger.LogInformation("Following plugins found:"); - - foreach (var manifest in nugetManifests) - { - Logger.LogInformation( - "- {id} ({version}) [{tags}]", - manifest.Id, - manifest.Version, - string.Join(", ", manifest.Tags) - ); - } - - try - { - Logger.LogInformation("Adjusting csproj files"); - - foreach (var project in projects) - { - var csProjectPath = project.Key; - - await using var fs = File.Open( - csProjectPath, - FileMode.Open, - FileAccess.ReadWrite, - FileShare.ReadWrite - ); - - var document = await XDocument.LoadAsync(fs, LoadOptions.None, CancellationToken.None); - fs.Position = 0; - - var dependenciesToAdd = nugetManifests - .Where(x => x.Tags.Any(tag => - project.Value.PackageTags.Contains(tag, StringComparer.InvariantCultureIgnoreCase))) - .ToArray(); - - await CsprojHelper.CleanDependencies(document, "MoonlightBuildDeps"); - await CsprojHelper.AddDependencies(document, dependenciesToAdd, "MoonlightBuildDeps"); - - fs.Position = 0; - await document.SaveAsync(fs, SaveOptions.None, CancellationToken.None); - - await fs.FlushAsync(); - fs.Close(); - } - - Logger.LogInformation("Restoring projects"); - - foreach (var csProjectPath in projects.Keys) - await CsprojHelper.Restore(csProjectPath); - - Logger.LogInformation("Generating plugin startup"); - - foreach (var currentTag in ValidTags) - { - Logger.LogInformation("Checking for '{currentTag}' projects", currentTag); - - var projectsWithTag = projects - .Where(x => - x.Value.PackageTags.Contains(currentTag, StringComparer.InvariantCultureIgnoreCase) - ) - .ToArray(); - - foreach (var project in projectsWithTag) - { - var csProjectPath = project.Key; - - var currentDependencies = nugetManifests - .Where(x => x.Tags.Contains(currentTag)) - .ToArray(); - - var classPaths = await FindStartupClasses(currentDependencies); - - var code = new StringBuilder(); - - code.AppendLine(GeneratedStart); - - foreach (var path in classPaths) - code.AppendLine($"pluginStartups.Add(new global::{path}());"); - - code.Append(GeneratedEnd); - - var filesToSearch = Directory.GetFiles( - Path.GetDirectoryName(csProjectPath)!, - "*.cs", - SearchOption.AllDirectories - ); - - foreach (var file in filesToSearch) - { - var content = await File.ReadAllTextAsync(file); - - if (!content.Contains(GeneratedHook, StringComparison.InvariantCultureIgnoreCase)) - continue; - - Logger.LogInformation("Injecting generated code to: {path}", Path.GetFullPath(file)); - - content = content.Replace( - GeneratedHook, - code.ToString(), - StringComparison.InvariantCultureIgnoreCase - ); - - await File.WriteAllTextAsync(file, content); - } - } - } - } - catch (Exception) - { - Logger.LogInformation("An error occured while prebuilding moonlight. Removing csproj modifications"); - - foreach (var project in projects) - { - await CsprojHelper.CleanDependencies(project.Key, "MoonlightBuildDeps"); - - var path = Path.GetDirectoryName(project.Key)!; - await RemoveGeneratedCode(path); - } - - throw; - } - } - - [Command("prebuild-reset")] - public async Task PrebuildReset( - [Argument] string moonlightDir - ) - { - var projects = await CsprojHelper.FindProjectsInPath(moonlightDir, ValidTags); - - Logger.LogInformation("Reverting csproj changes"); - - foreach (var project in projects) - { - Logger.LogInformation("Removing dependencies: {project}", project.Key); - await CsprojHelper.CleanDependencies(project.Key, "MoonlightBuildDeps"); - - Logger.LogInformation("Removing generated code: {project}", project.Key); - var path = Path.GetDirectoryName(project.Key)!; - await RemoveGeneratedCode(path); - } - } - - private async Task GetNugetManifests(string nugetDir) - { - var nugetFiles = Directory.GetFiles( - nugetDir, - "*.nupkg", - SearchOption.AllDirectories - ); - - var manifests = new List(); - - foreach (var nugetFilePath in nugetFiles) - { - using var nugetPackage = ZipFile.Open(nugetFilePath, ZipArchiveMode.Read); - var manifest = await NupkgHelper.GetManifest(nugetPackage); - - if (manifest == null) - continue; - - manifests.Add(manifest); - } - - return manifests.ToArray(); - } - - private async Task FindStartupClasses(NupkgManifest[] dependencies) - { - var nugetPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".nuget", - "packages" - ); - - var filesToScan = dependencies - .SelectMany(dependency => - { - var dependencySrcPath = Path.Combine(nugetPath, dependency.Id.ToLower(), dependency.Version, "src"); - - Logger.LogDebug("Checking {dependencySrcPath}", dependencySrcPath); - - if (!Directory.Exists(dependencySrcPath)) - return []; - - return Directory.GetFiles(dependencySrcPath, "*.cs", SearchOption.AllDirectories); - }) - .ToArray(); - - return await CodeHelper.FindPluginStartups(filesToScan); - } - - private async Task RemoveGeneratedCode(string dir) - { - var filesToSearch = Directory.GetFiles( - dir, - "*.cs", - SearchOption.AllDirectories - ); - - foreach (var file in filesToSearch) - { - var content = await File.ReadAllTextAsync(file); - - if (!content.Contains(GeneratedStart) || !content.Contains(GeneratedEnd)) - continue; - - var startIndex = content.IndexOf(GeneratedStart, StringComparison.InvariantCultureIgnoreCase); - var endIndex = content.IndexOf(GeneratedEnd, startIndex, StringComparison.InvariantCultureIgnoreCase) + - GeneratedEnd.Length; - - var cutOut = content.Substring(startIndex, endIndex - startIndex); - - content = content.Replace(cutOut, GeneratedHook); - - await File.WriteAllTextAsync(file, content); - } - } -} \ No newline at end of file diff --git a/Resources/Scripts/Helpers/CodeHelper.cs b/Resources/Scripts/Helpers/CodeHelper.cs deleted file mode 100644 index 92ea1da6..00000000 --- a/Resources/Scripts/Helpers/CodeHelper.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.Extensions.Logging; - -namespace Scripts.Helpers; - -public class CodeHelper -{ - private readonly ILogger Logger; - - public CodeHelper(ILogger logger) - { - Logger = logger; - } - - public async Task FindPluginStartups(string[] filesToSearch) - { - var result = new List(); - - var mscorlib = MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - - var trees = new List(); - - foreach (var file in filesToSearch) - { - Logger.LogDebug("Reading {file}", file); - - var content = await File.ReadAllTextAsync(file); - var tree = CSharpSyntaxTree.ParseText(content); - trees.Add(tree); - } - - var compilation = CSharpCompilation.Create("Analysis", trees, [mscorlib]); - - foreach (var tree in trees) - { - var model = compilation.GetSemanticModel(tree); - var root = await tree.GetRootAsync(); - - var classDeclarations = root - .DescendantNodes() - .OfType(); - - foreach (var classDeclaration in classDeclarations) - { - var symbol = model.GetDeclaredSymbol(classDeclaration); - - if (symbol == null) - continue; - - var hasAttribute = symbol.GetAttributes().Any(attr => - { - if (attr.AttributeClass == null) - return false; - - return attr.AttributeClass.Name == "PluginStartup"; - }); - - if (!hasAttribute) - continue; - - var classPath = $"{symbol.ContainingNamespace.ToDisplayString()}.{classDeclaration.Identifier.ValueText}"; - - Logger.LogInformation("Detected startup in class: {classPath}", classPath); - - result.Add(classPath); - } - } - - return result.ToArray(); - } -} \ No newline at end of file diff --git a/Resources/Scripts/Helpers/CommandHelper.cs b/Resources/Scripts/Helpers/CommandHelper.cs deleted file mode 100644 index 162759b9..00000000 --- a/Resources/Scripts/Helpers/CommandHelper.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Diagnostics; - -namespace Scripts.Helpers; - -public class CommandHelper -{ - public async Task Run(string program, string arguments, string? workingDir = null) - { - var process = await RunRaw(program, arguments, workingDir); - - await process.WaitForExitAsync(); - - if (process.ExitCode != 0) - throw new Exception($"The command '{program} {arguments}' failed with exit code: {process.ExitCode}"); - } - - private Task RunRaw(string program, string arguments, string? workingDir = null) - { - var psi = new ProcessStartInfo() - { - FileName = program, - Arguments = arguments, - WorkingDirectory = string.IsNullOrEmpty(workingDir) ? Directory.GetCurrentDirectory() : workingDir - }; - - var process = Process.Start(psi)!; - - return Task.FromResult(process); - } -} \ No newline at end of file diff --git a/Resources/Scripts/Helpers/CsprojHelper.cs b/Resources/Scripts/Helpers/CsprojHelper.cs deleted file mode 100644 index 22f91fd6..00000000 --- a/Resources/Scripts/Helpers/CsprojHelper.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Collections.Frozen; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using Scripts.Models; - -namespace Scripts.Helpers; - -public class CsprojHelper -{ - private readonly ILogger Logger; - private readonly CommandHelper CommandHelper; - - public CsprojHelper(ILogger logger, CommandHelper commandHelper) - { - Logger = logger; - CommandHelper = commandHelper; - } - - #region Add dependencies - - public async Task AddDependencies(string path, NupkgManifest[] dependencies, string label) - { - await using var fs = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite); - fs.Position = 0; - - await AddDependencies(fs, dependencies, label); - - await fs.FlushAsync(); - fs.Close(); - } - - public async Task AddDependencies(Stream stream, NupkgManifest[] dependencies, string label) - { - var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); - - await AddDependencies(xmlDocument, dependencies, label); - - stream.Position = 0; - await xmlDocument.SaveAsync(stream, SaveOptions.DisableFormatting, CancellationToken.None); - } - - public Task AddDependencies(XDocument document, NupkgManifest[] dependencies, string label) - { - var project = document.Element("Project")!; - - var itemGroup = new XElement("ItemGroup"); - itemGroup.SetAttributeValue("Label", label); - - foreach (var dependency in dependencies) - { - var depElement = new XElement("PackageReference"); - depElement.SetAttributeValue("Include", dependency.Id); - depElement.SetAttributeValue("Version", dependency.Version); - - itemGroup.Add(depElement); - } - - project.Add(itemGroup); - - return Task.CompletedTask; - } - - #endregion - - #region Clean dependencies - - public async Task CleanDependencies(string path, string label) - { - var document = XDocument.Load(path, LoadOptions.None); - - await CleanDependencies(document, label); - - document.Save(path, SaveOptions.DisableFormatting); - } - - public async Task CleanDependencies(Stream stream, string label) - { - var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); - - await CleanDependencies(xmlDocument, label); - - stream.Position = 0; - await xmlDocument.SaveAsync(stream, SaveOptions.DisableFormatting, CancellationToken.None); - } - - public Task CleanDependencies(XDocument document, string label) - { - var itemGroupsToRemove = document - .Descendants("ItemGroup") - .Where(x => x.Attribute("Label")?.Value.Contains(label) ?? false) - .ToArray(); - - itemGroupsToRemove.Remove(); - - return Task.CompletedTask; - } - - #endregion - - #region Read - - public async Task Read(string path) - { - await using var fileStream = File.Open( - path, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite - ); - - var manifest = await Read(fileStream); - - fileStream.Close(); - - return manifest; - } - - public async Task Read(Stream stream) - { - var xmlDocument = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); - return await Read(xmlDocument); - } - - public Task Read(XDocument document) - { - var manifest = new CsprojManifest(); - - var ns = document.Root!.GetDefaultNamespace(); - - manifest.IsPackable = document - .Descendants(ns + "IsPackable") - .FirstOrDefault()?.Value.Equals("true", StringComparison.InvariantCultureIgnoreCase) ?? false; - - manifest.PackageId = document - .Descendants(ns + "PackageId") - .FirstOrDefault()?.Value ?? "N/A"; - - manifest.Version = document - .Descendants(ns + "Version") - .FirstOrDefault()?.Value ?? "N/A"; - - manifest.PackageTags = document - .Descendants(ns + "PackageTags") - .FirstOrDefault()?.Value - .Split(";", StringSplitOptions.RemoveEmptyEntries) ?? []; - - return Task.FromResult(manifest); - } - - #endregion - - public async Task Restore(string path) - { - var basePath = Path.GetFullPath(Path.GetDirectoryName(path)!); - var fileName = Path.GetFileName(path); - - Logger.LogInformation("Restore: {basePath} - {fileName}", basePath, fileName); - - await CommandHelper.Run( - "/usr/bin/dotnet", - $"restore {fileName}", - basePath - ); - } - - public async Task Build(string file, string configuration) - { - var basePath = Path.GetFullPath(Path.GetDirectoryName(file)!); - var fileName = Path.GetFileName(file); - - await CommandHelper.Run( - "/usr/bin/dotnet", - $"build {fileName} --configuration {configuration}", - basePath - ); - } - - public async Task Pack(string file, string output, string configuration) - { - var basePath = Path.GetFullPath(Path.GetDirectoryName(file)!); - var fileName = Path.GetFileName(file); - - await CommandHelper.Run( - "/usr/bin/dotnet", - $"pack {fileName} --output {output} --configuration {configuration}", - basePath - ); - - var nugetFilesPaths = Directory.GetFiles( - output, - "*.nupkg", - SearchOption.TopDirectoryOnly - ); - - if (nugetFilesPaths.Length == 0) - throw new Exception("No nuget packages were built"); - - if (nugetFilesPaths.Length > 1) - throw new Exception("More than one nuget package has been built"); - - return nugetFilesPaths.First(); - } - - public async Task> FindProjectsInPath(string path, string[] validTags) - { - var projectFiles = Directory.GetFiles( - path, - "*.csproj", - SearchOption.AllDirectories - ); - - var projects = new Dictionary(); - - foreach (var projectFile in projectFiles) - { - var manifest = await Read(projectFile); - - // Ignore all projects which have no matching tags - if (!manifest.PackageTags.Any(projectTag => - validTags.Contains(projectTag, StringComparer.InvariantCultureIgnoreCase))) - continue; - - projects.Add(projectFile, manifest); - } - - return projects.ToFrozenDictionary(); - } -} \ No newline at end of file diff --git a/Resources/Scripts/Helpers/NupkgHelper.cs b/Resources/Scripts/Helpers/NupkgHelper.cs deleted file mode 100644 index b0158553..00000000 --- a/Resources/Scripts/Helpers/NupkgHelper.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System.IO.Compression; -using System.Xml.Linq; -using Microsoft.Extensions.Logging; -using Scripts.Models; - -namespace Scripts.Helpers; - -public class NupkgHelper -{ - private readonly ILogger Logger; - - public NupkgHelper(ILogger logger) - { - Logger = logger; - } - - public async Task GetManifest(ZipArchive nugetPackage) - { - var nuspecEntry = nugetPackage.Entries.FirstOrDefault( - x => x.Name.EndsWith(".nuspec") - ); - - if (nuspecEntry == null) - return null; - - await using var fs = nuspecEntry.Open(); - - var nuspec = await XDocument.LoadAsync(fs, LoadOptions.None, CancellationToken.None); - - var ns = nuspec.Root!.GetDefaultNamespace(); - var metadata = nuspec.Root!.Element(ns + "metadata")!; - - var id = metadata.Element(ns + "id")!.Value; - var version = metadata.Element(ns + "version")!.Value; - var tags = metadata.Element(ns + "tags")!.Value; - - return new NupkgManifest() - { - Id = id, - Version = version, - Tags = tags.Split(";", StringSplitOptions.RemoveEmptyEntries) - }; - } - - public async Task CleanDependencies(ZipArchive nugetPackage, string filter) - { - var nuspecEntry = nugetPackage.Entries.FirstOrDefault( - x => x.Name.EndsWith(".nuspec") - ); - - if (nuspecEntry == null) - { - Logger.LogWarning("No nuspec file to modify found in nuget package"); - return; - } - - await ModifyXmlInPackage(nugetPackage, nuspecEntry, document => - { - var ns = document.Root!.GetDefaultNamespace(); - - return document - .Descendants(ns + "dependency") - .Where(x => x.Attribute("id")?.Value.StartsWith(filter) ?? false); - }); - } - - public async Task RemoveContentFiles(ZipArchive nugetPackage) - { - foreach (var entry in nugetPackage.Entries.ToArray()) - { - if (!entry.FullName.StartsWith("contentFiles") && !entry.FullName.StartsWith("content")) - continue; - - Logger.LogDebug("Removing content file: {path}", entry.FullName); - entry.Delete(); - } - - var nuspecFile = nugetPackage - .Entries - .FirstOrDefault(x => x.Name.EndsWith(".nuspec")); - - if (nuspecFile == null) - { - Logger.LogWarning("Nuspec file missing. Unable to remove content files references from nuspec file"); - return; - } - - await ModifyXmlInPackage( - nugetPackage, - nuspecFile, - document => - { - var ns = document.Root!.GetDefaultNamespace(); - return document.Descendants(ns + "contentFiles"); - } - ); - } - - public async Task ModifyXmlInPackage( - ZipArchive nugetPackage, - ZipArchiveEntry entry, - Func> filter - ) - { - var oldPath = entry.FullName; - await using var oldFs = entry.Open(); - - var document = await XDocument.LoadAsync( - oldFs, - LoadOptions.None, - CancellationToken.None - ); - - var itemsToRemove = filter.Invoke(document); - var items = itemsToRemove.ToArray(); - - foreach (var item in items) - item.Remove(); - - oldFs.Close(); - entry.Delete(); - - var newEntry = nugetPackage.CreateEntry(oldPath); - var newFs = newEntry.Open(); - - await document.SaveAsync(newFs, SaveOptions.None, CancellationToken.None); - - await newFs.FlushAsync(); - newFs.Close(); - - return newEntry; - } - - public async Task RemoveStaticWebAssets(ZipArchive nugetPackage, string filter) - { - var filterWithPath = $"staticwebassets/{filter}"; - - foreach (var entry in nugetPackage.Entries.ToArray()) - { - if (!entry.FullName.StartsWith(filterWithPath)) - continue; - - Logger.LogDebug("Removing file: {name}", entry.FullName); - entry.Delete(); - } - - var buildTargetEntry = nugetPackage.Entries.FirstOrDefault(x => - x.FullName == "build/Microsoft.AspNetCore.StaticWebAssets.props" - ); - - if (buildTargetEntry == null) - { - Logger.LogWarning("Unable to find Microsoft.AspNetCore.StaticWebAssets.props to remove file references"); - return; - } - - Logger.LogDebug("Removing file references"); - - await ModifyXmlInPackage(nugetPackage, buildTargetEntry, - document => document - .Descendants("StaticWebAsset") - .Where(x => - { - var relativePath = x.Element("RelativePath")!.Value; - return relativePath.StartsWith(filter); - }) - ); - } - - public async Task AddSourceFiles(ZipArchive nugetPackage, string[] files, Func buildPath) - { - foreach (var sourceFile in files) - { - var path = buildPath.Invoke(sourceFile); - - Logger.LogDebug("Adding additional files as src: {path}", path); - - await using var fs = File.Open( - sourceFile, - FileMode.Open, - FileAccess.Read, - FileShare.ReadWrite - ); - - var entry = nugetPackage.CreateEntry(path); - await using var entryFs = entry.Open(); - - await fs.CopyToAsync(entryFs); - await entryFs.FlushAsync(); - - fs.Close(); - entryFs.Close(); - } - } -} \ No newline at end of file diff --git a/Resources/Scripts/Models/CsprojManifest.cs b/Resources/Scripts/Models/CsprojManifest.cs deleted file mode 100644 index 9d21fc6f..00000000 --- a/Resources/Scripts/Models/CsprojManifest.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Scripts.Models; - -public class CsprojManifest -{ - public bool IsPackable { get; set; } - public string Version { get; set; } - public string PackageId { get; set; } - public string[] PackageTags { get; set; } -} \ No newline at end of file diff --git a/Resources/Scripts/Models/NupkgManifest.cs b/Resources/Scripts/Models/NupkgManifest.cs deleted file mode 100644 index 4b2f3bc8..00000000 --- a/Resources/Scripts/Models/NupkgManifest.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Scripts.Models; - -public class NupkgManifest -{ - public string Id { get; set; } - public string Version { get; set; } - public string[] Tags { get; set; } -} \ No newline at end of file diff --git a/Resources/Scripts/Program.cs b/Resources/Scripts/Program.cs deleted file mode 100644 index 33c12965..00000000 --- a/Resources/Scripts/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Cocona; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using MoonCore.Extensions; -using Scripts.Commands; -using Scripts.Helpers; - -Console.WriteLine("Moonlight Build Helper Script"); -Console.WriteLine(); - -var builder = CoconaApp.CreateBuilder(args); - -builder.Logging.ClearProviders(); -builder.Logging.AddMoonCore(); - -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); - -var app = builder.Build(); - -app.AddCommands(); -app.AddCommands(); - -await app.RunAsync(); \ No newline at end of file diff --git a/Resources/Scripts/Scripts.csproj b/Resources/Scripts/Scripts.csproj deleted file mode 100644 index bd23916a..00000000 --- a/Resources/Scripts/Scripts.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - - dotnet-moonlight - 2.1.0 - dotnet-moonlight - true - dotnet-moonlight - https://github.com/Moonlight-Panel/Moolight - git - - \ No newline at end of file diff --git a/Resources/Scripts/generateNuget.sh b/Resources/Scripts/generateNuget.sh deleted file mode 100644 index acfa0068..00000000 --- a/Resources/Scripts/generateNuget.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# We are building the packages in the debug mode because they are meant for development -# purposes only. When build for production, release builds will be used ofc - -set -e - -echo "Creating nuget packages" -mkdir -p finalPackages - -echo "+ ApiServer Server" -dotnet build -c Debug Moonlight.ApiServer -dotnet pack -c Debug Moonlight.ApiServer --output finalPackages/ -dotnet run --project Resources/Scripts/Scripts.csproj content finalPackages/Moonlight.ApiServer.2.1.0.nupkg ".*" - -echo "+ Client" -dotnet build -c Debug Moonlight.Client -dotnet pack -c Debug Moonlight.Client --output finalPackages/ -dotnet run --project Resources/Scripts/Scripts.csproj staticWebAssets finalPackages/Moonlight.Client.2.1.0.nupkg "_framework\/.*" style.min.css -dotnet run --project Resources/Scripts/Scripts.csproj src finalPackages/Moonlight.Client.2.1.0.nupkg Moonlight.Client/ *.razor -dotnet run --project Resources/Scripts/Scripts.csproj src finalPackages/Moonlight.Client.2.1.0.nupkg Moonlight.Client/ wwwroot/*.html - -echo "+ Shared library" -dotnet build -c Debug Moonlight.Shared -dotnet pack -c Debug Moonlight.Shared --output finalPackages/ \ No newline at end of file diff --git a/Resources/Scripts/prepareNugetOverride.sh b/Resources/Scripts/prepareNugetOverride.sh deleted file mode 100644 index ae7c1c30..00000000 --- a/Resources/Scripts/prepareNugetOverride.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -bash Resources/Scripts/generateNuget.sh - -echo "+ Copying to nuget override" -cp finalPackages/*.nupkg ~/NugetOverride/ -rm -r ~/.nuget/packages/moonlight.* \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 00000000..e18117fd --- /dev/null +++ b/compose.yaml @@ -0,0 +1,36 @@ +services: + api: + # Meta + image: webapp.api + + # Build + pull_policy: build + build: + context: . + dockerfile: Moonlight.Api/Dockerfile + args: + HTTP_PROXY: ${HTTP_PROXY} + HTTPS_PROXY: ${HTTPS_PROXY} + + # Networks + ports: + - "8080:8080" + + # Env Settings + environment: + # Database settings + - "WebApp__Database__Host=${DATABASE_HOST}" + - "WebApp__Database__Port=${DATABASE_PORT}" + - "WebApp__Database__Username=${DATABASE_USERNAME}" + - "WebApp__Database__Password=${DATABASE_PASSWORD}" + - "WebApp__Database__Database=${DATABASE_DATABASE}" + + # OIDC + - "WebApp__OIDC__Authority=${OIDC_AUTHORITY}" + - "WebApp__OIDC__ClientId=${OIDC_CLIENT_ID}" + - "WebApp__OIDC__Client_Secret=${OIDC_CLIENT_SECRET}" + - "WebApp__OIDC__RequireHttpsMetadata=${OIDC_REQUIRE_HTTPS_METADATA}" + + # Logging + - "Logging__LogLevel__Default=Information" + - "Logging__LogLevel__Microsoft.AspNetCore=Warning" \ No newline at end of file