diff --git a/app/.env.development b/app/.env.development new file mode 100644 index 00000000..8872edda --- /dev/null +++ b/app/.env.development @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_URI=http://localhost:5623 +NEXT_PUBLIC_API_WS_URI=ws://localhost:5623 diff --git a/app/.env.example b/app/.env.example deleted file mode 100644 index 18cee82e..00000000 --- a/app/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -NEXT_PUBLIC_API_URI= -NEXT_PUBLIC_API_WS_URI= diff --git a/app/.env.production b/app/.env.production new file mode 100644 index 00000000..8872edda --- /dev/null +++ b/app/.env.production @@ -0,0 +1,2 @@ +NEXT_PUBLIC_API_URI=http://localhost:5623 +NEXT_PUBLIC_API_WS_URI=ws://localhost:5623 diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 00000000..a246bc8c --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,27 @@ +FROM node:20-alpine AS base +WORKDIR /app +EXPOSE 3000 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS exporter +WORKDIR /app +COPY src . +RUN dotnet run --project "Service.Orchestrator/src/HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" -- schema export --output /app/schema.graphql + +FROM base AS installer +COPY /app/package.json /app/pnpm-lock.yaml* ./ +RUN corepack enable pnpm && pnpm i --frozen-lockfile + +FROM base AS builder +COPY app . +COPY --from=installer /app/node_modules ./node_modules +COPY --from=exporter /app/schema.graphql ./ +ENV NODE_ENV production +RUN corepack enable pnpm && pnpm run build + +FROM base AS runner +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +ENV PORT 3000 +ENV NODE_ENV production +ENV NEXT_PUBLIC_API_URI http://localhost:5263 +CMD HOSTNAME="0.0.0.0" node server.js \ No newline at end of file diff --git a/app/next.config.js b/app/next.config.js index 394a415f..50ce4079 100644 --- a/app/next.config.js +++ b/app/next.config.js @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const config = { + output: 'standalone', reactStrictMode: true, compiler: { relay: { diff --git a/app/package.json b/app/package.json index 8757f00f..0da04527 100644 --- a/app/package.json +++ b/app/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@giantnodes/react": "1.0.0-canary.18", + "@giantnodes/theme": "1.0.0-canary.18", "@hookform/resolvers": "^3.4.0", "@tabler/icons-react": "^3.4.0", "clsx": "^2.1.1", @@ -37,6 +38,7 @@ "filesize": "^10.1.2", "graphql-ws": "^5.16.0", "next": "^14.2.3", + "next-runtime-env": "^3.2.2", "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 920b6c20..dac40bb7 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -8,6 +8,9 @@ dependencies: '@giantnodes/react': specifier: 1.0.0-canary.18 version: 1.0.0-canary.18(react-dom@18.3.1)(react@18.3.1)(tailwindcss@3.4.3) + '@giantnodes/theme': + specifier: 1.0.0-canary.18 + version: 1.0.0-canary.18(tailwindcss@3.4.3) '@hookform/resolvers': specifier: ^3.4.0 version: 3.4.0(react-hook-form@7.51.4) @@ -29,6 +32,9 @@ dependencies: next: specifier: ^14.2.3 version: 14.2.3(react-dom@18.3.1)(react@18.3.1) + next-runtime-env: + specifier: ^3.2.2 + version: 3.2.2(next@14.2.3)(react@18.3.1) next-themes: specifier: ^0.3.0 version: 0.3.0(react-dom@18.3.1)(react@18.3.1) @@ -108,7 +114,7 @@ devDependencies: version: 9.1.0(eslint@8.57.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + version: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0) eslint-plugin-jsx-a11y: specifier: ^6.8.0 version: 6.8.0(eslint@8.57.0) @@ -3163,7 +3169,7 @@ packages: dependencies: confusing-browser-globals: 1.0.11 eslint: 8.57.0 - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0) object.assign: 4.1.4 object.entries: 1.1.6 semver: 6.3.1 @@ -3196,7 +3202,7 @@ packages: dependencies: eslint: 8.57.0 eslint-config-airbnb-base: 15.0.0(eslint-plugin-import@2.29.1)(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) eslint-plugin-react: 7.34.1(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -3302,6 +3308,35 @@ packages: - supports-color dev: true + /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + debug: 3.2.7 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.0)(eslint-import-resolver-typescript@3.5.5)(eslint@8.57.0): resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} engines: {node: '>=4'} @@ -3337,6 +3372,41 @@ packages: - supports-color dev: true + /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.9.0)(eslint@8.57.0): + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 7.9.0(eslint@8.57.0)(typescript@5.4.5) + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.9.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0) + hasown: 2.0.0 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0): resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} engines: {node: '>=4.0'} @@ -4495,6 +4565,16 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /next-runtime-env@3.2.2(next@14.2.3)(react@18.3.1): + resolution: {integrity: sha512-S5S6NxIf3XeaVc9fLBN2L5Jzu+6dLYCXeOaPQa1RzKRYlG2BBayxXOj6A4VsciocyNkJMazW1VAibtbb1/ZjAw==} + peerDependencies: + next: ^14 + react: ^18 + dependencies: + next: 14.2.3(react-dom@18.3.1)(react@18.3.1) + react: 18.3.1 + dev: false + /next-themes@0.3.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==} peerDependencies: diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx index 0896c8ac..4495a1eb 100644 --- a/app/src/app/layout.tsx +++ b/app/src/app/layout.tsx @@ -2,6 +2,7 @@ import '@/styles/global.css' import { clsx } from 'clsx' import { Inter } from 'next/font/google' +import { PublicEnvScript } from 'next-runtime-env' import React from 'react' import ApplicationProviders from '@/app/provider' @@ -17,7 +18,9 @@ type AppLayoutProps = React.PropsWithChildren & { const AppLayout: React.FC = ({ children, dialog }) => ( - + + + {children} diff --git a/app/src/libraries/relay/network.ts b/app/src/libraries/relay/network.ts index cca7c0c3..f5fcdf02 100644 --- a/app/src/libraries/relay/network.ts +++ b/app/src/libraries/relay/network.ts @@ -1,10 +1,11 @@ import type { CacheConfig, GraphQLResponse, RequestParameters, SubscribeFunction, Variables } from 'relay-runtime' import { createClient } from 'graphql-ws' +import { env } from 'next-runtime-env' import { Network, Observable, QueryResponseCache } from 'relay-runtime' -const HTTP_ENDPOINT = `${process.env.NEXT_PUBLIC_API_URI}/graphql` -const WS_ENDPOINT = `${process.env.NEXT_PUBLIC_API_WS_URI}/graphql` +const HTTP_ENDPOINT = `${env('NEXT_PUBLIC_API_URI')}/graphql` +const WS_ENDPOINT = `${env('NEXT_PUBLIC_API_WS_URI')}/graphql` const CACHE_TTL = 5 * 1000 const IS_SERVER = typeof window === typeof undefined diff --git a/app/tailwind.config.ts b/app/tailwind.config.ts index 1d269523..3cdbe698 100644 --- a/app/tailwind.config.ts +++ b/app/tailwind.config.ts @@ -3,7 +3,11 @@ import ReactAriaComponents from 'tailwindcss-react-aria-components' import { giantnodes } from '@giantnodes/theme' const config: Config = { - content: ['./src/**/*.{ts,tsx}', './node_modules/@giantnodes/theme/dist/**/*.{js,cjs}'], + content: [ + './src/**/*.{ts,tsx}', + './node_modules/@giantnodes/theme/dist/**/*.{js,mjs,cjs}', + './node_modules/@giantnodes/react/dist/**/*.{js,mjs,cjs}', + ], plugins: [giantnodes(), ReactAriaComponents()], darkMode: 'class', } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..7b09d695 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +version: "3.9" + +services: + database: + image: postgres + container_name: database + restart: unless-stopped + ports: + - 5432:5432 + environment: + POSTGRES_USER: giantnodes + POSTGRES_PASSWORD: password + volumes: + - /path/to/store/pgdata:/var/lib/postgresql/data/ + + orchestrator: + build: + context: . + dockerfile: src/Service.Orchestrator/src/HttpApi/Dockerfile + container_name: orchestrator + restart: unless-stopped + ports: + - 5623:5623 + depends_on: + - database + environment: + ConnectionStrings__DatabaseConnection: "Host=database;Database=video-management;Username=giantnodes;Password=password;Include Error Detail=true" + + encoder-1: + build: + context: . + dockerfile: src/Service.Encoder/src/Console/Dockerfile + container_name: encoder + restart: unless-stopped + ports: + - 5600:5600 + depends_on: + - database + environment: + ConnectionStrings__DatabaseConnection: "Host=database;Database=video-management;Username=giantnodes;Password=password;Include Error Detail=true" + + dashboard: + build: + context: . + dockerfile: app/Dockerfile + container_name: dashboard + restart: unless-stopped + ports: + - 3000:3000 + depends_on: + - orchestrator + environment: + NEXT_PUBLIC_API_URI: http://localhost:5623 diff --git a/src/Service.Encoder/src/Console/Dockerfile b/src/Service.Encoder/src/Console/Dockerfile index 3af3adc1..94d84c55 100644 --- a/src/Service.Encoder/src/Console/Dockerfile +++ b/src/Service.Encoder/src/Console/Dockerfile @@ -1,18 +1,17 @@ -FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base -WORKDIR /app +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +EXPOSE 5600 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["Console/Console.csproj", "Console/"] -RUN dotnet restore "Console/Console.csproj" -COPY . . -WORKDIR "/src/Console" -RUN dotnet build "Console.csproj" -c Release -o /app/build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app +COPY src . +RUN dotnet restore "Service.Encoder/src/Console/Giantnodes.Service.Encoder.Console.csproj" +RUN dotnet build "Service.Encoder/src/Console/Giantnodes.Service.Encoder.Console.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "Console.csproj" -c Release -o /app/publish /p:UseAppHost=false +RUN dotnet publish "Service.Encoder/src/Console/Giantnodes.Service.Encoder.Console.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Console.dll"] +ENV ASPNETCORE_HTTP_PORTS 5600 +ENTRYPOINT ["dotnet", "Giantnodes.Service.Encoder.Console.dll"] \ No newline at end of file diff --git a/src/Service.Encoder/src/Console/Program.cs b/src/Service.Encoder/src/Console/Program.cs index df6664be..85e3d29d 100644 --- a/src/Service.Encoder/src/Console/Program.cs +++ b/src/Service.Encoder/src/Console/Program.cs @@ -14,7 +14,7 @@ public static async Task Main(string[] args) { var host = CreateHostBuilder(args).Build(); - FFmpeg.SetExecutablesPath(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ffmpeg"); + FFmpeg.SetExecutablesPath(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.Create), "ffmpeg"); await FFmpegDownloader.GetLatestVersion(FFmpegVersion.Official, FFmpeg.ExecutablesPath); await host.RunAsync(); diff --git a/src/Service.Orchestrator/src/HttpApi/Cors/CorsConfigureOptions.cs b/src/Service.Orchestrator/src/HttpApi/Cors/CorsConfigureOptions.cs index 94270a9b..9ff3cdf7 100644 --- a/src/Service.Orchestrator/src/HttpApi/Cors/CorsConfigureOptions.cs +++ b/src/Service.Orchestrator/src/HttpApi/Cors/CorsConfigureOptions.cs @@ -5,7 +5,7 @@ namespace Giantnodes.Service.Orchestrator.HttpApi.Cors; public class CorsConfigureOptions : IConfigureNamedOptions { - private const string ConfigurationSectionKey = "AllowedOrigins"; + private const string ConfigurationSectionKey = "CorsOrigins"; private readonly IConfiguration _configuration; @@ -18,16 +18,16 @@ public void Configure(CorsOptions options) { var origins = _configuration.GetValue(ConfigurationSectionKey); if (string.IsNullOrWhiteSpace(origins)) - throw new InvalidOperationException($"The connection string '{ConfigurationSectionKey}' cannot be null or empty."); + throw new InvalidOperationException($"The configuration section '{ConfigurationSectionKey}' cannot be null or empty."); - options.AddDefaultPolicy(builder => - { - builder - .AllowAnyHeader() - .AllowAnyMethod() - .AllowCredentials() - .WithOrigins(origins.Split(",")); - }); + options + .AddDefaultPolicy(builder => + { + builder + .AllowAnyMethod() + .AllowAnyHeader() + .WithOrigins(origins.Split(",")); + }); } public void Configure(string? name, CorsOptions options) diff --git a/src/Service.Orchestrator/src/HttpApi/Dockerfile b/src/Service.Orchestrator/src/HttpApi/Dockerfile index d8ad20af..06dc0abe 100644 --- a/src/Service.Orchestrator/src/HttpApi/Dockerfile +++ b/src/Service.Orchestrator/src/HttpApi/Dockerfile @@ -1,20 +1,17 @@ -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base -WORKDIR /app -EXPOSE 80 -EXPOSE 443 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +EXPOSE 5623 -FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build -WORKDIR /src -COPY ["src/Giantnodes.Service.Orchestrator.HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj", "src/Giantnodes.Service.Orchestrator.HttpApi/"] -RUN dotnet restore "src/Giantnodes.Service.Orchestrator.HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" -COPY . . -WORKDIR "/src/src/Giantnodes.Service.Orchestrator.HttpApi" -RUN dotnet build "Giantnodes.Service.Orchestrator.HttpApi.csproj" -c Release -o /app/build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +WORKDIR /app +COPY src . +RUN dotnet restore "Service.Orchestrator/src/HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" +RUN dotnet build "Service.Orchestrator/src/HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" -c Release -o /app/build FROM build AS publish -RUN dotnet publish "Giantnodes.Service.Orchestrator.HttpApi.csproj" -c Release -o /app/publish /p:UseAppHost=false +RUN dotnet publish "Service.Orchestrator/src/HttpApi/Giantnodes.Service.Orchestrator.HttpApi.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Giantnodes.Service.Orchestrator.HttpApi.dll"] +ENV ASPNETCORE_HTTP_PORTS 5623 +ENTRYPOINT ["dotnet", "Giantnodes.Service.Orchestrator.HttpApi.dll"] \ No newline at end of file diff --git a/src/Service.Orchestrator/src/HttpApi/appsettings.Development.json b/src/Service.Orchestrator/src/HttpApi/appsettings.Development.json index 86403140..462866aa 100644 --- a/src/Service.Orchestrator/src/HttpApi/appsettings.Development.json +++ b/src/Service.Orchestrator/src/HttpApi/appsettings.Development.json @@ -1,5 +1,5 @@ { - "AllowedOrigins": "http://localhost:3000", + "CorsOrigins": "*", "ConnectionStrings": { "DatabaseConnection": "Host=localhost;Database=Giantnodes.Service.Orchestrator;Username=postgres;Password=password;Include Error Detail=true" }, diff --git a/src/Service.Orchestrator/src/HttpApi/appsettings.json b/src/Service.Orchestrator/src/HttpApi/appsettings.json index 33449a27..463603a3 100644 --- a/src/Service.Orchestrator/src/HttpApi/appsettings.json +++ b/src/Service.Orchestrator/src/HttpApi/appsettings.json @@ -1,11 +1,10 @@ { + "CorsOrigins": "*", "ConnectionStrings": { "DatabaseConnection": "" }, "Serilog": { - "Using": [ - "Serilog.Sinks.Console" - ], + "Using": ["Serilog.Sinks.Console"], "MinimumLevel": { "Default": "Information", "Override": {