sync public allowlist from private myoffice

This commit is contained in:
myoffice-sync
2026-08-05 10:36:17 +00:00
parent 6f7fd61ee9
commit 874796c860
720 changed files with 2841 additions and 69563 deletions
+39
View File
@@ -0,0 +1,39 @@
# MyOffice API (Kestrel :8080) — build from repo root:
# docker build -f Docker/Dockerfile.api -t myoffice-api .
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY MyOffice.sln ./
COPY MyOffice.Core/MyOffice.Core.csproj MyOffice.Core/
COPY MyOffice.Shared/MyOffice.Shared.csproj MyOffice.Shared/
COPY MyOffice.Data.Models/MyOffice.Data.Models.csproj MyOffice.Data.Models/
COPY MyOffice.DbContext/MyOffice.DbContext.csproj MyOffice.DbContext/
COPY MyOffice.Data.Repositories/MyOffice.Data.Repositories.csproj MyOffice.Data.Repositories/
COPY MyOffice.Services/MyOffice.Services.csproj MyOffice.Services/
COPY MyOffice.Migration.Sqlite/MyOffice.Migrations.Sqlite.csproj MyOffice.Migration.Sqlite/
COPY MyOffice.Migration.Postgres/MyOffice.Migrations.Postgres.csproj MyOffice.Migration.Postgres/
COPY MyOffice.Web/MyOffice.Web.csproj MyOffice.Web/
RUN dotnet restore MyOffice.Web/MyOffice.Web.csproj
COPY MyOffice.Core/ MyOffice.Core/
COPY MyOffice.Shared/ MyOffice.Shared/
COPY MyOffice.Data.Models/ MyOffice.Data.Models/
COPY MyOffice.DbContext/ MyOffice.DbContext/
COPY MyOffice.Data.Repositories/ MyOffice.Data.Repositories/
COPY MyOffice.Services/ MyOffice.Services/
COPY MyOffice.Migration.Sqlite/ MyOffice.Migration.Sqlite/
COPY MyOffice.Migration.Postgres/ MyOffice.Migration.Postgres/
COPY MyOffice.Web/ MyOffice.Web/
RUN dotnet publish MyOffice.Web/MyOffice.Web.csproj -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Docker
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "MyOffice.Web.dll"]
+34
View File
@@ -0,0 +1,34 @@
# MyOffice SPA (nginx :80) — build from repo root:
# docker build -f Docker/Dockerfile.web --build-arg API_PUBLIC_URL=http://localhost:32081 -t myoffice-web .
FROM node:20-alpine AS build
WORKDIR /src
ARG API_PUBLIC_URL=http://localhost:32081
COPY MyOffice.SPA/package.json MyOffice.SPA/package-lock.json MyOffice.SPA/.npmrc ./
RUN npm ci
COPY MyOffice.SPA/ ./
# environment.ts is gitignored locally — stub + docker env with public API URL (host port).
RUN cp src/environments/environment.sample.ts src/environments/environment.ts \
&& printf '%s\n' \
'export const environment = {' \
' production: true,' \
" apiUrl: '${API_PUBLIC_URL}'," \
" identityServer: '${API_PUBLIC_URL}/'," \
" allowedUrls: ['${API_PUBLIC_URL}']," \
' requireHttps: false,' \
' externalLogins: {' \
" google: { clientId: '' }," \
" auth0: { clientId: '', domain: '' }" \
' }' \
'};' \
> src/environments/environment.docker.ts \
&& npm run build -- --configuration docker --output-path /out \
&& if [ -d /out/browser ]; then cp -a /out/browser/. /out/ && rm -rf /out/browser; fi
FROM nginx:1.27-alpine AS final
COPY Docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /out /usr/share/nginx/html
EXPOSE 80
+57
View File
@@ -0,0 +1,57 @@
# MyOffice Docker demo
| Service | Default host port | Notes |
|------------|-------------------|--------------------------------|
| `web` | **32080** | Angular SPA (nginx) |
| `api` | **32081** | ASP.NET Core + OpenIddict |
| `pgadmin` | **32082** | pgAdmin 4 |
| `postgres` | *(none)* | Internal only (`postgres:5432`) |
Empty Postgres is migrated and filled with demo users on API startup.
## Commands
```powershell
.\build.ps1 # defaults :32080 / :32081 / :32082
.\build.ps1 32080 32081 32082 # override front, back, pgAdmin
.\build.ps1 up # build + recreate containers
.\build.ps1 rebuild # --no-cache images + recreate (picks up API/CORS code)
.\build.ps1 reset # wipe DB volumes + rebuild (new seed / password user_UAH)
.\build.ps1 down
.\build.ps1 logs
```
Linux/macOS: `./build.sh` (same commands)
**Important:** demo users are seeded only into an empty DB. Changing password in code does nothing until you run `.\build.ps1 reset` (clears `data/demo/postgres`).
Custom ports rebuild the SPA so `apiUrl` / OIDC point at the chosen backend host port, and set `FrontEnd:Host` + CORS for the frontend origin.
## URLs & logins
Defaults (`.\build.ps1`):
| App | URL | Login |
|-----|-----|--------|
| Frontend | http://localhost:32080 | `user_UAH@user_UAH.userUAH` / `user_UAH` |
| Backend | http://localhost:32081 | — |
| pgAdmin | http://localhost:32082 | `admin@example.com` / `admin` |
Postgres is auto-registered in pgAdmin as server **myoffice** (host `postgres`, user/db/password `myoffice`) — no manual setup.
If the server is missing after an older run: `.\build.ps1 reset` (or delete `data/demo/pgadmin`).
## Data folders
Bind mounts next to the compose file (gitignored under `Docker/data/`):
| Host path | Container |
|-----------|-----------|
| `Docker/data/demo/postgres` | `/var/lib/postgresql/data` |
| `Docker/data/demo/pgadmin` | `/var/lib/pgadmin` |
Stop/start keeps these folders. Wipe with `.\build.ps1 reset` or delete the folders manually.
## Notes
- API uses `ASPNETCORE_ENVIRONMENT=Docker`, ephemeral OpenIddict keys, HTTP allowed.
- Changing front/back ports rebuilds the SPA so API/OIDC URLs match.
+158
View File
@@ -0,0 +1,158 @@
# Build and start the MyOffice demo stack (SPA + API + Postgres + pgAdmin).
# Usage:
# .\build.ps1 # front 32080, back 32081, pgAdmin 32082
# .\build.ps1 32080 32081 32082 # override ports
# .\build.ps1 build | up | down | logs
# .\build.ps1 rebuild # --no-cache images + recreate containers
# .\build.ps1 reset # wipe ./data/demo/*, rebuild, start (new demo passwords)
$ErrorActionPreference = 'Stop'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $here
$composeFile = 'docker-compose.demo.yml'
$knownActions = @('build', 'up', 'down', 'logs', 'all', 'rebuild', 'reset')
function Test-Port([string]$value) {
return $value -match '^\d{1,5}$' -and [int]$value -ge 1 -and [int]$value -le 65535
}
function Format-Origin([string]$port) {
if ($port -eq '80') { return 'http://localhost' }
return "http://localhost:$port"
}
function Show-UpInfo {
param([string]$FrontOrigin, [string]$ApiPublicUrl, [string]$PgAdminUrl)
Write-Host ''
Write-Host 'Demo is up:'
Write-Host " Frontend $FrontOrigin"
Write-Host " Backend $ApiPublicUrl"
Write-Host " pgAdmin $PgAdminUrl (admin@example.com / admin; DB auto-connected)"
Write-Host ' Postgres internal host=postgres user/pass/db=myoffice'
Write-Host ''
Write-Host 'Demo logins: user_UAH@user_UAH.userUAH / user_UAH (also USD, EUR)'
}
function Invoke-DockerCompose {
param([Parameter(ValueFromRemainingArguments = $true)][string[]]$ComposeArgs)
# Docker writes progress to stderr; with ErrorAction Stop that aborts the script.
$prev = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
& docker compose -f $composeFile @ComposeArgs
if ($LASTEXITCODE -ne 0) {
throw "docker compose failed with exit code $LASTEXITCODE"
}
}
finally {
$ErrorActionPreference = $prev
}
}
# Parse: [action] [frontPort] [backPort] [pgAdminPort]
# Numeric args arrive as Int32 — always normalize to string first.
$action = 'all'
$frontPort = '32080'
$backPort = '32081'
$pgAdminPort = '32082'
$rest = @($args | ForEach-Object { "$_" })
if ($rest.Count -gt 0 -and $knownActions -contains $rest[0].ToLowerInvariant()) {
$action = $rest[0].ToLowerInvariant()
$rest = @($rest | Select-Object -Skip 1)
}
elseif ($rest.Count -gt 0 -and (Test-Port $rest[0])) {
$action = 'all'
}
if ($rest.Count -ge 1) {
if (-not (Test-Port $rest[0])) {
Write-Error "Invalid front port '$($rest[0])'. Example: .\build.ps1 32080 32081 32082"
}
$frontPort = [string]$rest[0]
}
if ($rest.Count -ge 2) {
if (-not (Test-Port $rest[1])) {
Write-Error "Invalid back port '$($rest[1])'. Example: .\build.ps1 32080 32081 32082"
}
$backPort = [string]$rest[1]
}
if ($rest.Count -ge 3) {
if (-not (Test-Port $rest[2])) {
Write-Error "Invalid pgAdmin port '$($rest[2])'. Example: .\build.ps1 32080 32081 32082"
}
$pgAdminPort = [string]$rest[2]
}
if ($rest.Count -gt 3 -and $action -ne 'down' -and $action -ne 'logs') {
Write-Error 'Too many arguments. Usage: .\build.ps1 [build|up|down|logs|rebuild|reset] [frontPort] [backPort] [pgAdminPort]'
}
$frontOrigin = Format-Origin $frontPort
$apiPublicUrl = "http://localhost:$backPort"
$pgAdminUrl = Format-Origin $pgAdminPort
$env:MYOFFICE_WEB_PORT = $frontPort
$env:MYOFFICE_API_PORT = $backPort
$env:MYOFFICE_PGADMIN_PORT = $pgAdminPort
$env:MYOFFICE_FRONTEND_HOST = $frontOrigin
$env:MYOFFICE_API_PUBLIC_URL = $apiPublicUrl
$env:MYOFFICE_CORS_0 = $frontOrigin
$env:MYOFFICE_CORS_1 = if ($frontPort -eq '80') { 'http://127.0.0.1' } else { "http://127.0.0.1:$frontPort" }
# Avoid stale compose env / cached layers leaving old API code running.
$env:COMPOSE_DOCKER_CLI_BUILD = '1'
$env:DOCKER_BUILDKIT = '1'
switch ($action) {
'build' {
Invoke-DockerCompose build
}
'up' {
New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\postgres') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\pgadmin') -Force | Out-Null
Invoke-DockerCompose up -d --build --force-recreate --remove-orphans
Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl
}
'down' {
Invoke-DockerCompose down
}
'logs' {
Invoke-DockerCompose logs -f
}
'rebuild' {
Write-Host 'Rebuilding images with --no-cache (api + web)...'
Invoke-DockerCompose build --no-cache --pull
Invoke-DockerCompose up -d --force-recreate --remove-orphans
Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl
}
'reset' {
Write-Host 'Stopping stack and wiping ./data/demo/postgres and ./data/demo/pgadmin...'
Invoke-DockerCompose down
foreach ($dir in @('data\demo\postgres', 'data\demo\pgadmin')) {
$path = Join-Path $here $dir
if (Test-Path $path) {
Remove-Item -LiteralPath $path -Recurse -Force
}
New-Item -ItemType Directory -Path $path -Force | Out-Null
}
# Drop old named volumes if they still exist from earlier setups.
docker volume rm myoffice-demo_myoffice_pgdata myoffice-demo_myoffice_pgadmin 2>$null | Out-Null
Write-Host 'Rebuilding images with --no-cache...'
Invoke-DockerCompose build --no-cache --pull
Invoke-DockerCompose up -d --force-recreate --remove-orphans
Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl
Write-Host ''
Write-Host 'DB was reset. Login with password user_UAH (not user1_UAH).'
Write-Host 'Data folders: Docker\data\demo\postgres , Docker\data\demo\pgadmin'
}
'all' {
New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\postgres') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $here 'data\demo\pgadmin') -Force | Out-Null
Invoke-DockerCompose up -d --build --force-recreate --remove-orphans
Show-UpInfo $frontOrigin $apiPublicUrl $pgAdminUrl
}
default {
Write-Error "Unknown action '$action'. Use: build, up, down, logs, rebuild, reset, or no args."
}
}
+133
View File
@@ -0,0 +1,133 @@
#!/usr/bin/env sh
# Build and start the MyOffice demo stack (SPA + API + Postgres + pgAdmin).
# Usage:
# ./build.sh # front 32080, back 32081, pgAdmin 32082
# ./build.sh 32080 32081 32082 # override ports
# ./build.sh build | up | down | logs
# ./build.sh rebuild # --no-cache images + recreate containers
# ./build.sh reset # wipe DB volumes, rebuild, start
set -eu
cd "$(dirname "$0")"
COMPOSE_FILE=docker-compose.demo.yml
ACTION=all
FRONT_PORT=32080
BACK_PORT=32081
PGADMIN_PORT=32082
is_port() {
echo "$1" | grep -Eq '^[0-9]{1,5}$' && [ "$1" -ge 1 ] && [ "$1" -le 65535 ]
}
origin_for() {
if [ "$1" = "80" ]; then
echo "http://localhost"
else
echo "http://localhost:$1"
fi
}
show_up_info() {
echo ""
echo "Demo is up:"
echo " Frontend $1"
echo " Backend $2"
echo " pgAdmin $3 (admin@example.com / admin; DB auto-connected)"
echo " Postgres internal host=postgres user/pass/db=myoffice"
echo ""
echo "Demo logins: user_UAH@user_UAH.userUAH / user_UAH (also USD, EUR)"
}
if [ "${1:-}" = "build" ] || [ "${1:-}" = "up" ] || [ "${1:-}" = "down" ] || [ "${1:-}" = "logs" ] || [ "${1:-}" = "all" ] || [ "${1:-}" = "rebuild" ] || [ "${1:-}" = "reset" ]; then
ACTION=$1
shift
fi
if [ "${1:-}" != "" ]; then
is_port "$1" || { echo "Invalid front port '$1'. Example: ./build.sh 32080 32081 32082" >&2; exit 1; }
FRONT_PORT=$1
shift
fi
if [ "${1:-}" != "" ]; then
is_port "$1" || { echo "Invalid back port '$1'. Example: ./build.sh 32080 32081 32082" >&2; exit 1; }
BACK_PORT=$1
shift
fi
if [ "${1:-}" != "" ]; then
is_port "$1" || { echo "Invalid pgAdmin port '$1'. Example: ./build.sh 32080 32081 32082" >&2; exit 1; }
PGADMIN_PORT=$1
shift
fi
if [ "${1:-}" != "" ] && [ "$ACTION" != "down" ] && [ "$ACTION" != "logs" ]; then
echo "Too many arguments. Usage: ./build.sh [build|up|down|logs|rebuild|reset] [frontPort] [backPort] [pgAdminPort]" >&2
exit 1
fi
FRONT_ORIGIN=$(origin_for "$FRONT_PORT")
API_PUBLIC_URL="http://localhost:$BACK_PORT"
PGADMIN_URL=$(origin_for "$PGADMIN_PORT")
export MYOFFICE_WEB_PORT=$FRONT_PORT
export MYOFFICE_API_PORT=$BACK_PORT
export MYOFFICE_PGADMIN_PORT=$PGADMIN_PORT
export MYOFFICE_FRONTEND_HOST=$FRONT_ORIGIN
export MYOFFICE_API_PUBLIC_URL=$API_PUBLIC_URL
export MYOFFICE_CORS_0=$FRONT_ORIGIN
if [ "$FRONT_PORT" = "80" ]; then
export MYOFFICE_CORS_1=http://127.0.0.1
else
export MYOFFICE_CORS_1="http://127.0.0.1:$FRONT_PORT"
fi
export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1
case "$ACTION" in
build)
docker compose -f "$COMPOSE_FILE" build
;;
up)
mkdir -p data/demo/postgres data/demo/pgadmin
docker compose -f "$COMPOSE_FILE" up -d --build --force-recreate --remove-orphans
show_up_info "$FRONT_ORIGIN" "$API_PUBLIC_URL" "$PGADMIN_URL"
;;
down)
docker compose -f "$COMPOSE_FILE" down
;;
logs)
docker compose -f "$COMPOSE_FILE" logs -f
;;
rebuild)
echo "Rebuilding images with --no-cache (api + web)..."
docker compose -f "$COMPOSE_FILE" build --no-cache --pull
docker compose -f "$COMPOSE_FILE" up -d --force-recreate --remove-orphans
show_up_info "$FRONT_ORIGIN" "$API_PUBLIC_URL" "$PGADMIN_URL"
;;
reset)
echo "Stopping stack and wiping ./data/demo/postgres and ./data/demo/pgadmin..."
docker compose -f "$COMPOSE_FILE" down
rm -rf data/demo/postgres data/demo/pgadmin
mkdir -p data/demo/postgres data/demo/pgadmin
docker volume rm myoffice-demo_myoffice_pgdata myoffice-demo_myoffice_pgadmin 2>/dev/null || true
echo "Rebuilding images with --no-cache..."
docker compose -f "$COMPOSE_FILE" build --no-cache --pull
docker compose -f "$COMPOSE_FILE" up -d --force-recreate --remove-orphans
show_up_info "$FRONT_ORIGIN" "$API_PUBLIC_URL" "$PGADMIN_URL"
echo ""
echo "DB was reset. Login with password user_UAH (not user1_UAH)."
echo "Data folders: Docker/data/demo/postgres , Docker/data/demo/pgadmin"
;;
all)
mkdir -p data/demo/postgres data/demo/pgadmin
docker compose -f "$COMPOSE_FILE" up -d --build --force-recreate --remove-orphans
show_up_info "$FRONT_ORIGIN" "$API_PUBLIC_URL" "$PGADMIN_URL"
;;
*)
echo "Unknown action '$ACTION'. Use: build, up, down, logs, rebuild, reset, or no args." >&2
exit 1
;;
esac
+27
View File
@@ -0,0 +1,27 @@
services:
postgres:
image: postgres:17
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_DB: appdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- ./data/postgres:/var/lib/postgresql/data
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5433:80"
volumes:
- ./data/pgadmin:/var/lib/pgadmin
depends_on:
- postgres
+96
View File
@@ -0,0 +1,96 @@
# Demo stack: SPA + API + Postgres (internal) + pgAdmin.
# Ports from env / build.ps1:
# MYOFFICE_WEB_PORT (32080), MYOFFICE_API_PORT (32081), MYOFFICE_PGADMIN_PORT (32082)
name: myoffice-demo
services:
postgres:
image: postgres:17
container_name: myoffice-postgres
restart: unless-stopped
environment:
POSTGRES_DB: myoffice
POSTGRES_USER: myoffice
POSTGRES_PASSWORD: myoffice
volumes:
# Host path (next to this compose file): ./data/demo/postgres
- ./data/demo/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myoffice -d myoffice"]
interval: 5s
timeout: 5s
retries: 12
networks:
- myoffice
pgadmin:
image: dpage/pgadmin4:9
container_name: myoffice-pgadmin
restart: unless-stopped
# Strip CRLF (Windows checkouts) then create pgpass (0600) and run image entrypoint.
entrypoint:
- /bin/sh
- -c
- sed 's/\r$//' /pgadmin-entrypoint.sh > /tmp/pgadmin-entrypoint.sh && exec /bin/sh /tmp/pgadmin-entrypoint.sh
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
PGADMIN_CONFIG_SERVER_MODE: "False"
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "False"
ports:
- "${MYOFFICE_PGADMIN_PORT:-32082}:80"
volumes:
# Host path: ./data/demo/pgadmin
- ./data/demo/pgadmin:/var/lib/pgadmin
- ./pgadmin/servers.json:/pgadmin4/servers.json:ro
- ./pgadmin/entrypoint.sh:/pgadmin-entrypoint.sh:ro
depends_on:
postgres:
condition: service_healthy
networks:
- myoffice
api:
build:
context: ..
dockerfile: Docker/Dockerfile.api
image: myoffice-api:demo
container_name: myoffice-api
restart: unless-stopped
ports:
- "${MYOFFICE_API_PORT:-32081}:8080"
environment:
ASPNETCORE_ENVIRONMENT: Docker
ASPNETCORE_URLS: http://+:8080
DatabaseProvider: npgsql
ConnectionStrings__npgsql: Host=postgres;Port=5432;Database=myoffice;Username=myoffice;Password=myoffice
FrontEnd__Host: ${MYOFFICE_FRONTEND_HOST:-http://localhost:32080}
Cors__AllowedOrigins__0: ${MYOFFICE_CORS_0:-http://localhost:32080}
Cors__AllowedOrigins__1: ${MYOFFICE_CORS_1:-http://127.0.0.1:32080}
OpenIddict__UseEphemeralKeys: "true"
OpenIddict__AllowHttp: "true"
depends_on:
postgres:
condition: service_healthy
networks:
- myoffice
web:
build:
context: ..
dockerfile: Docker/Dockerfile.web
args:
API_PUBLIC_URL: ${MYOFFICE_API_PUBLIC_URL:-http://localhost:32081}
image: myoffice-web:demo
container_name: myoffice-web
restart: unless-stopped
ports:
- "${MYOFFICE_WEB_PORT:-32080}:80"
depends_on:
- api
networks:
- myoffice
networks:
myoffice:
driver: bridge
+17
View File
@@ -0,0 +1,17 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location / {
try_files $uri $uri/ /index.html;
}
location = /index.html {
add_header Cache-Control "no-cache";
}
}
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# Create pgpass inside the container (mode 0600). Bind-mounted files from Windows
# cannot satisfy pgAdmin's permission check.
set -eu
PASSFILE=/var/lib/pgadmin/pgpass
printf '%s\n' 'postgres:5432:*:myoffice:myoffice' > "$PASSFILE"
chown 5050:0 "$PASSFILE" 2>/dev/null || true
chmod 600 "$PASSFILE"
exec /entrypoint.sh "$@"
+14
View File
@@ -0,0 +1,14 @@
{
"Servers": {
"1": {
"Name": "myoffice",
"Group": "Servers",
"Host": "postgres",
"Port": 5432,
"MaintenanceDB": "myoffice",
"Username": "myoffice",
"SSLMode": "prefer",
"PassFile": "/var/lib/pgadmin/pgpass"
}
}
}