Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

37 new backoffice web UI as blazorrazor pages #88

Merged
merged 34 commits into from
Jul 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
aab895e
feat: Created basic blazor project with server interactivity
Metawolve Jun 10, 2024
3af8aa9
feat: Created basic blazor project with server interactivity
Metawolve Jun 10, 2024
e8c9b07
feat: Implemented calling the api for data with a basic list of knosl…
Metawolve Jun 16, 2024
847ab68
feat: Added client-side project and oidc login
Metawolve Jun 24, 2024
743701c
Merge
Metawolve Jun 24, 2024
0d35ab5
chore: Removed http context access which was only added for testing
Metawolve Jun 24, 2024
55a9017
feat: Replaced blazor web app by blazor webassembly standalone app wi…
Metawolve Jun 28, 2024
ab856f1
Merge branch 'main' into 37-new-backoffice-web-ui-as-blazorrazor-pages
Metawolve Jun 28, 2024
3e0e835
build: Installed MudBlazor UI component library
Metawolve Jun 28, 2024
f9ec5ed
feat: Implemented MudBlazor
Metawolve Jun 28, 2024
ca26719
Merge branch 'main' into 37-new-backoffice-web-ui-as-blazorrazor-pages
Metawolve Jun 30, 2024
3026aa3
build: Installed packages for parsing and editing markdown
Metawolve Jul 1, 2024
227ea41
feat: Added region to minIO config because it needs to be set to eu-w…
Metawolve Jul 8, 2024
5c96580
fix: Fixed a bug where large image was not fully loaded
Metawolve Jul 9, 2024
0034d26
feat: Added api endpoint to delete an image
Metawolve Jul 9, 2024
71392b8
feat: Added services to manage images and knowledge base with api calls
Metawolve Jul 9, 2024
635004e
fix: Fixed a bug when updating a knowledge entry with other links
Metawolve Jul 9, 2024
8b0d247
feat: Added knowledge base management to backoffice
Metawolve Jul 9, 2024
3e07cef
fix: Fixed a bug when deleting an image related to a fursuit badge
Metawolve Jul 9, 2024
21f71cd
fix: Fixed a bug when deleting an image related to a table registration
Metawolve Jul 10, 2024
ece5dbd
fix: Fixed content scrolling in knowledge entry dialog
Metawolve Jul 11, 2024
8e3964d
feat: Improved async loading of images in knowledge base
Metawolve Jul 11, 2024
037217b
feat: Added a page for image management
Metawolve Jul 12, 2024
3295e0c
Merge branch 'main' into 37-new-backoffice-web-ui-as-blazorrazor-pages
Metawolve Jul 12, 2024
452380d
build (deps): Moved request classes to models project so the backoffi…
Metawolve Jul 14, 2024
4dbcd09
Merge branch '37-new-backoffice-web-ui-as-blazorrazor-pages' of https…
Metawolve Jul 14, 2024
86b5057
Merge branch 'main' into 37-new-backoffice-web-ui-as-blazorrazor-pages
Metawolve Jul 14, 2024
8006527
chore(docker): only copy required files to build stage
Fenrikur Jul 14, 2024
29ff868
feat(docker): create image for backoffice
Fenrikur Jul 14, 2024
d274bcb
chore(just): add backoffice and .env support
Fenrikur Jul 14, 2024
c68dff9
chore(docker): fix http2 directive in nginx.conf
Fenrikur Jul 14, 2024
f5ba6b8
chore(compose): minIO config in appsettings.sample.json
Fenrikur Jul 14, 2024
db8e389
feat: Added form validation
Metawolve Jul 14, 2024
58ae388
Merge branch '37-new-backoffice-web-ui-as-blazorrazor-pages' of https…
Metawolve Jul 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*.json
**/obj
**/bin
app
artifacts
docker
docker-compose.yml
Expand Down
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
EF_MOBILE_APP_BACKEND_PORT=7371
EF_MOBILE_APP_BACKOFFICE_PORT=7140
EF_MOBILE_APP_MYSQL_VERSION=10.11.8-MariaDB
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Application Specific Settings
appsettings.json
appsettings-backoffice.json
firebase.json


Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG MYSQL_VERSION=10.11.8-MariaDB
COPY . /app/
COPY ./src/ /app/src/
COPY ./test/ /app/test/
COPY ./ef-app_backend-dotnet-core.sln /app/
COPY ./NuGet.config /app/
WORKDIR /app
RUN dotnet restore \
&& dotnet build src/Eurofurence.App.Server.Web/Eurofurence.App.Server.Web.csproj --configuration Release \
Expand Down
16 changes: 16 additions & 0 deletions Dockerfile-backoffice
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
COPY ./src/ /app/src/
COPY ./test/ /app/test/
COPY ./ef-app_backend-dotnet-core.sln /app/
COPY ./NuGet.config /app/
WORKDIR /app
RUN dotnet restore \
&& dotnet build src/Eurofurence.App.Backoffice/Eurofurence.App.Backoffice.csproj --configuration Release \
&& dotnet publish src/Eurofurence.App.Backoffice/Eurofurence.App.Backoffice.csproj --output "/app/backoffice" --configuration Release
ENTRYPOINT dotnet artifacts/Eurofurence.App.Backoffice.dll http://*:30002
EXPOSE 30002

FROM nginx:1.27
COPY --from=build /app/backoffice/wwwroot/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
RUN openssl req -x509 -nodes -days 365 -subj "/C=DE/ST=HH/O=EF Development/CN=ef.example.com" -addext "subjectAltName=DNS:ef.example.com" -newkey rsa:2048 -keyout /etc/ssl/private/nginx.key -out /etc/ssl/certs/nginx.crt
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

## Run locally using Docker Compose (or Podman)

1. Copy [`appsettings.sample.json`](src/Eurofurence.App.Server.Web/appsettings.sample.json) to `/appsettings.json` (defaults should suffice for local development).
2. Retrieve a JSON with Google Application Credentials for Firebase (see [Initialize the SDK in non-Google environments](https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments)).
3. Store credential file as `/firebase.json`.
4. Run `docker compose up`.
5. Wait until you see a message starting with `Startup complete` from the `backend` container.
6. Go [home](http://localhost:30001/swagger/ui).
7. …
8. Profit.
1. Initialise configuration files for development via `just init` and then customising them with your secrets etc. or by doing the following, manual steps:
1. Copy the [backend `appsettings.sample.json`](src/Eurofurence.App.Server.Web/appsettings.sample.json) to `/appsettings.json` (defaults should suffice for local development).
2. Copy the [backoffice `appsettings.sample.json`](src/Eurofurence.App.Backoffice/wwwroot/appsettings.sample.json) to `/appsettings-backoffice.json` (modify `Oidc.ClientId`).
3. Retrieve a JSON with Google Application Credentials for Firebase (see [Initialize the SDK in non-Google environments](https://firebase.google.com/docs/admin/setup#initialize_the_sdk_in_non-google_environments)) and store credentials in `/firebase.json`.
(see [`firebase.sample.json`](src/Eurofurence.App.Server.Web/firebase.sample.json) for expected format)
2. Build container images using `just containerize` (or `docker compose build`).
3. Run `just up` (or `docker compose up`) to start the application stack.
4. Wait until you see a message starting with `Startup complete` from the `backend` container.
5. Open Swagger UI (see `just swagger`) or backoffice (see `just backoffice`).
6. …
7. Profit.

## Run locally using Docker Compose and [`just`](https://github.com/casey/just)

Expand All @@ -18,16 +21,19 @@ If you have [`just`](https://github.com/casey/just) available on your system, yo
```plain
just --list
Available recipes:
build # Perform restore, build & publish with dotnet
build-cli # Build just CLI tools as single, self-contained executable
clean # Clean build, stack, container images and artifacts
containerize # Build release container using spec from docker-compose.yml
containerize-dev # Build sdk container without executing second stage
default # List available recipes
down # Bring the docker compose stack down and remove volumes
stop # Stop the docker compose stack
swagger # Open swagger UI in default browser
up *ARGS # Start the docker compose stack
backoffice # Open Backoffice UI in default browser
build $MYSQL_VERSION=env_var('EF_MOBILE_APP_MYSQL_VERSION') # Perform restore, build & publish with dotnet
build-cli # Build just CLI tools as single, self-contained executable
clean # Clean build, stack, container images, volumes and artifacts
containerize *ARGS # Build release container using spec from docker-compose.yml
containerize-backoffice-dev # Build sdk container for backoffice without executing second stage
containerize-dev # Build sdk container for backend without executing second stage
default # List available recipes
down # Bring the docker compose stack down and remove volumes
init # Initialize configuration files in root folder
stop # Stop the docker compose stack
swagger # Open Swagger UI in default browser
up *ARGS # Start the docker compose stack
```

## Potentially outdated information below (Here be dragons!)
Expand Down
19 changes: 17 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
context: .
dockerfile: Dockerfile
ports:
- '30001:30001'
- '${EF_MOBILE_APP_BACKEND_PORT}:30001'
volumes:
- './appsettings.json:/app/appsettings.json'
- './firebase.json:/app/firebase.json'
Expand All @@ -31,6 +31,20 @@ services:
condition: service_healthy
entrypoint: /app/db-migration-bundle

backoffice:
image: ghcr.io/eurofurence/ef-app_backend-dotnet-core-backoffice:dev
build:
context: .
dockerfile: Dockerfile-backoffice
ports:
- '${EF_MOBILE_APP_BACKOFFICE_PORT}:443'
volumes:
- './appsettings-backoffice.json:/usr/share/nginx/html/appsettings.json'
- './nginx.conf:/etc/nginx/nginx.conf'
depends_on:
backend:
condition: service_started

db:
image: docker.io/mariadb:latest
environment:
Expand All @@ -49,12 +63,13 @@ services:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio:/data
environment:
- "MINIO_ROOT_USER=minioAccessKey"
- "MINIO_ROOT_PASSWORD=minioVerySecretKey"
command: server /data
command: server /data --console-address :9001
healthcheck:
test: >
/bin/sh -c "
Expand Down
17 changes: 17 additions & 0 deletions ef-app_backend-dotnet-core.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eurofurence.App.Tests.Commo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eurofurence.App.Infrastructure.EntityFramework", "src\Eurofurence.App.Infrastructure.EntityFramework\Eurofurence.App.Infrastructure.EntityFramework.csproj", "{E17D248B-AF94-4175-BB80-681457CA25AA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Backoffice", "Backoffice", "{521BC67A-4A77-4D4F-8B6F-A3146ED548AA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Eurofurence.App.Backoffice", "src\Eurofurence.App.Backoffice\Eurofurence.App.Backoffice.csproj", "{EAECB2D1-BC66-499A-82E5-92AD670607C9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -133,6 +137,18 @@ Global
{E17D248B-AF94-4175-BB80-681457CA25AA}.Release|x64.Build.0 = Release|Any CPU
{E17D248B-AF94-4175-BB80-681457CA25AA}.Release|x86.ActiveCfg = Release|Any CPU
{E17D248B-AF94-4175-BB80-681457CA25AA}.Release|x86.Build.0 = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|x64.ActiveCfg = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|x64.Build.0 = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|x86.ActiveCfg = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Debug|x86.Build.0 = Debug|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|Any CPU.Build.0 = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|x64.ActiveCfg = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|x64.Build.0 = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|x86.ActiveCfg = Release|Any CPU
{EAECB2D1-BC66-499A-82E5-92AD670607C9}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -146,6 +162,7 @@ Global
{184B514D-8548-4C6A-9C52-A521051013CD} = {07D37E4D-3609-4732-8BED-7C3F69694CF0}
{94A3288D-4F78-4744-B8B2-135C772DBE26} = {07D37E4D-3609-4732-8BED-7C3F69694CF0}
{E17D248B-AF94-4175-BB80-681457CA25AA} = {7AC305D7-5603-4073-8893-5104847367AB}
{EAECB2D1-BC66-499A-82E5-92AD670607C9} = {521BC67A-4A77-4D4F-8B6F-A3146ED548AA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D31ABA48-3C64-41D8-8A95-8C729A2343EF}
Expand Down
48 changes: 34 additions & 14 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,34 @@
set dotenv-load := true

# List available recipes
default:
just --list

# Create file with given name and extension from NAME.sample.EXTENSION in PROJECT
_create_from_sample PROJECT NAME EXTENSION:
if [ ! -f "{{NAME}}.{{EXTENSION}}" ]; then cp "src/{{PROJECT}}/{{NAME}}.sample.{{EXTENSION}}" "{{NAME}}.{{EXTENSION}}"; fi
@_create_from_sample PROJECT NAME EXTENSION TARGET:
if [ ! -f "{{TARGET}}.{{EXTENSION}}" ]; then echo "Creating {{TARGET}}.{{EXTENSION}} from sample…"; cp "src/{{PROJECT}}/{{NAME}}.sample.{{EXTENSION}}" "{{TARGET}}.{{EXTENSION}}"; else echo "Skipping {{TARGET}}.{{EXTENSION}} because it already exists."; fi

@_open_url TITLE TARGET_URL:
echo "Opening {{TITLE}} at {{TARGET_URL}} …"
open {{TARGET_URL}}

# Initialize configuration files in root folder
init: (_create_from_sample "Eurofurence.App.Server.Web" "appsettings" "json" "appsettings") (_create_from_sample "Eurofurence.App.Server.Web" "firebase" "json" "firebase") (_create_from_sample "Eurofurence.App.Backoffice/wwwroot" "appsettings" "json" "appsettings-backoffice")

# Start the docker compose stack
up *ARGS: (_create_from_sample "Eurofurence.App.Server.Web" "appsettings" "json") (_create_from_sample "Eurofurence.App.Server.Web" "firebase" "json")
up *ARGS: (init)
docker compose up {{ARGS}}

# Bring the docker compose stack down and remove volumes
[confirm('This is a potentially destructive operation that will delete remove your docker compose volumes! Are you sure?')]
down:
docker compose down -v --remove-orphans
docker compose down -v --remove-orphans || true

# Stop the docker compose stack
stop:
docker compose stop

# Clean build, stack, container images and artifacts
# Clean build files, compose stack, container images and other artifacts
clean:
dotnet clean
rm -rf artifacts build
Expand All @@ -29,24 +39,34 @@ clean:
docker rmi ghcr.io/eurofurence/ef-app_backend-dotnet-core:nightly || true

# Perform restore, build & publish with dotnet
build:
build $MYSQL_VERSION=env_var('EF_MOBILE_APP_MYSQL_VERSION'):
dotnet tool install --global dotnet-ef
dotnet restore
dotnet build src/Eurofurence.App.Server.Web/Eurofurence.App.Server.Web.csproj --configuration Release
dotnet publish src/Eurofurence.App.Tools.CliToolBox/Eurofurence.App.Tools.CliToolBox.csproj --output "$(pwd)/artifacts" --configuration Release
dotnet publish src/Eurofurence.App.Server.Web/Eurofurence.App.Server.Web.csproj --output "$(pwd)/artifacts" --configuration Release
dotnet build src/Eurofurence.App.Backoffice/Eurofurence.App.Backoffice.csproj --configuration Release
dotnet publish src/Eurofurence.App.Tools.CliToolBox/Eurofurence.App.Tools.CliToolBox.csproj --output "$(pwd)/artifacts/cli" --configuration Release
dotnet ef migrations bundle -o "$(pwd)/artifacts/db-migration-bundle" -p src/Eurofurence.App.Server.Web
dotnet publish src/Eurofurence.App.Server.Web/Eurofurence.App.Server.Web.csproj --output "$(pwd)/artifacts/backend" --configuration Release
dotnet publish src/Eurofurence.App.Backoffice/Eurofurence.App.Backoffice.csproj --output "$(pwd)/artifacts/backoffice" --configuration Release

# Build just CLI tools as single, self-contained executable
build-cli:
dotnet publish src/Eurofurence.App.Tools.CliToolBox/Eurofurence.App.Tools.CliToolBox.csproj --output "$(pwd)/artifacts" --configuration Release --sc -p:PublishSingleFile=true -p:DebugType=None -p:DebugSymbols=false -p:GenerateDocumentationFile=false

# Build release container using spec from docker-compose.yml
containerize:
docker compose build
containerize *ARGS:
docker compose build {{ARGS}}

# Build sdk container without executing second stage
# Build sdk container for backend without executing second stage
containerize-dev:
docker build --target build -t ghcr.io/eurofurence/ef-app_backend-dotnet-core:dev-sdk -f Dockerfile .

# Open swagger UI in default browser
swagger:
open http://127.0.0.1:30001/swagger/ui/index.html
# Build sdk container for backoffice without executing second stage
containerize-backoffice-dev:
docker build --target build -t ghcr.io/eurofurence/ef-app_backend-dotnet-core-backoffice:dev-sdk -f Dockerfile-backoffice .

# Open Swagger UI in default browser
swagger: (_open_url "Swagger UI" ("http://localhost:"+env_var('EF_MOBILE_APP_BACKEND_PORT')+"/swagger/ui/index.html"))

# Open Backoffice UI in default browser
backoffice: (_open_url "Backoffice UI" ("https://localhost:"+env_var('EF_MOBILE_APP_BACKOFFICE_PORT')+"/"))
38 changes: 38 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Allow running nginx as non-root user
pid /tmp/nginx.pid;

events { }
http {
# Allow running nginx as non-root user
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp_path;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;

server {
http2 on;
listen 80;

# Support SSL via self-signed cert in development
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /etc/ssl/certs/nginx.crt;
ssl_certificate_key /etc/ssl/private/nginx.key;

location / {
# Configuration required for Blazor WASM stand-alone app
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html =404;

include /etc/nginx/mime.types;
default_type application/octet-stream;
}

# Serve appsettings.json with correct MIME type
location /appsettings.json {
root /usr/share/nginx/html;
add_header Content-Type application/json;
}
}
}
13 changes: 13 additions & 0 deletions src/Eurofurence.App.Backoffice/.config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": 1,
"isRoot": true,
"tools": {
"microsoft.dotnet-msidentity": {
"version": "2.0.8",
"commands": [
"dotnet-msidentity"
],
"rollForward": false
}
}
}
25 changes: 25 additions & 0 deletions src/Eurofurence.App.Backoffice/App.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
<RedirectToLogin />
}
else
{
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace Eurofurence.App.Backoffice.Authentication
{
public class TokenAuthorizationMessageHandler : AuthorizationMessageHandler
{
public TokenAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation, IConfiguration configuration) : base(provider, navigation)
{
ConfigureHandler(new[] { configuration.GetValue<string>("ApiUrl") ?? string.Empty });
}
}
}
Loading