From afa4c421052660e18f69f6d97907226217b39445 Mon Sep 17 00:00:00 2001 From: myoffice-sync Date: Wed, 5 Aug 2026 11:31:19 +0000 Subject: [PATCH] sync build tree from private myoffice --- .dockerignore | 18 - .editorconfig | 16 - .gitattributes | 67 --- .gitignore | 404 +----------------- Docker/Dockerfile.api | 39 -- Docker/Dockerfile.web | 34 -- Docker/README.md | 57 --- Docker/build.ps1 | 158 ------- Docker/build.sh | 133 ------ Docker/dc-postgres-pgadmin.yml | 27 -- Docker/docker-compose.demo.yml | 96 ----- Docker/nginx.conf | 17 - Docker/pgadmin/entrypoint.sh | 11 - Docker/pgadmin/servers.json | 14 - MyOffice.SPA/.gitignore | 42 -- MyOffice.SPA/karma.conf.js | 45 -- MyOffice.SPA/src/app/app.component.spec.ts | 39 -- .../forgot-password.component.spec.ts | 23 - .../locked/locked.component.spec.ts | 23 - .../page404/page404.component.spec.ts | 24 -- .../page500/page500.component.spec.ts | 24 -- .../signin/signin.component.spec.ts | 24 -- .../signup/signup.component.spec.ts | 24 -- .../app/core/models/selectable.model.spec.ts | 7 - .../src/app/core/service/auth.service.spec.ts | 18 - .../src/app/core/utils/safe-redirect.spec.ts | 22 - .../dashboard2/dashboard2.component.spec.ts | 27 -- .../income/dashboard.component.spec.ts | 27 -- .../outcome/dashboard.component.spec.ts | 27 -- .../layout/header/header.component.spec.ts | 23 - .../page-loader/page-loader.component.spec.ts | 23 - .../right-sidebar.component.spec.ts | 23 - .../layout/sidebar/sidebar.component.spec.ts | 23 - .../user-profile.component.spec.ts | 24 -- .../breadcrumb/breadcrumb.component.spec.ts | 27 -- .../date-period/date-period.component.spec.ts | 23 - .../feather-icons.component.spec.ts | 27 -- .../file-upload/file-upload.component.spec.ts | 26 -- .../src/environments/environment.docker.ts | 18 - .../appsettings.shared.Docker.json | 5 - MyOffice.Tests/Account/AccountAclTests.cs | 65 --- .../Account/AccountMotionBalancingTests.cs | 30 -- MyOffice.Tests/Account/MotionAddAclTests.cs | 173 -------- .../Auth/OpenIddictRedirectUriTests.cs | 22 - MyOffice.Tests/Auth/PasswordHasherTests.cs | 56 --- MyOffice.Tests/Data/DemoDataSeederTests.cs | 54 --- MyOffice.Tests/MyOffice.Tests.csproj | 29 -- MyOffice.Web/appsettings.Docker.json | 31 -- README.md | 10 +- add_migration_postgres.bat | 2 - add_migration_sqlite.bat | 2 - back_start.bat | 1 - build.ps1 | 32 -- gulpfile.js | 15 - history_for_print.txt | 23 - linux_deploy.sh | 19 - proxmox/README.md | 8 +- proxmox/sync-public.ps1 | 131 ++++-- proxmox/sync-public.sh | 215 ++++++---- spa_start.bat | 2 - 60 files changed, 249 insertions(+), 2400 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .editorconfig delete mode 100644 .gitattributes delete mode 100644 Docker/Dockerfile.api delete mode 100644 Docker/Dockerfile.web delete mode 100644 Docker/README.md delete mode 100644 Docker/build.ps1 delete mode 100644 Docker/build.sh delete mode 100644 Docker/dc-postgres-pgadmin.yml delete mode 100644 Docker/docker-compose.demo.yml delete mode 100644 Docker/nginx.conf delete mode 100644 Docker/pgadmin/entrypoint.sh delete mode 100644 Docker/pgadmin/servers.json delete mode 100644 MyOffice.SPA/.gitignore delete mode 100644 MyOffice.SPA/karma.conf.js delete mode 100644 MyOffice.SPA/src/app/app.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/forgot-password/forgot-password.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/locked/locked.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/page404/page404.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/page500/page500.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/signin/signin.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/authentication/signup/signup.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/core/models/selectable.model.spec.ts delete mode 100644 MyOffice.SPA/src/app/core/service/auth.service.spec.ts delete mode 100644 MyOffice.SPA/src/app/core/utils/safe-redirect.spec.ts delete mode 100644 MyOffice.SPA/src/app/dashboard/dashboard2/dashboard2.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/dashboard/income/dashboard.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/layout/header/header.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/layout/page-loader/page-loader.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/layout/right-sidebar/right-sidebar.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/layout/sidebar/sidebar.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/pages/user-profile/user-profile.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/shared/components/date-period/date-period.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/shared/components/feather-icons/feather-icons.component.spec.ts delete mode 100644 MyOffice.SPA/src/app/shared/components/file-upload/file-upload.component.spec.ts delete mode 100644 MyOffice.SPA/src/environments/environment.docker.ts delete mode 100644 MyOffice.Shared/appsettings.shared.Docker.json delete mode 100644 MyOffice.Tests/Account/AccountAclTests.cs delete mode 100644 MyOffice.Tests/Account/AccountMotionBalancingTests.cs delete mode 100644 MyOffice.Tests/Account/MotionAddAclTests.cs delete mode 100644 MyOffice.Tests/Auth/OpenIddictRedirectUriTests.cs delete mode 100644 MyOffice.Tests/Auth/PasswordHasherTests.cs delete mode 100644 MyOffice.Tests/Data/DemoDataSeederTests.cs delete mode 100644 MyOffice.Tests/MyOffice.Tests.csproj delete mode 100644 MyOffice.Web/appsettings.Docker.json delete mode 100644 add_migration_postgres.bat delete mode 100644 add_migration_sqlite.bat delete mode 100644 back_start.bat delete mode 100644 build.ps1 delete mode 100644 gulpfile.js delete mode 100644 history_for_print.txt delete mode 100644 linux_deploy.sh delete mode 100644 spa_start.bat diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 8b525bc..0000000 --- a/.dockerignore +++ /dev/null @@ -1,18 +0,0 @@ -**/.git -**/.vs -**/bin -**/obj -**/node_modules -**/dist -**/_Published -**/TestResults -**/logs -**/*.user -**/*.db -**/*.db-shm -**/*.db-wal -Docker/data -.cursor -agent-transcripts -**/Coverage -**/.angular diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index c6819ea..0000000 --- a/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = tabs -indent_size = 4 -insert_final_newline = false -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 4ffeaf0..0000000 --- a/.gitattributes +++ /dev/null @@ -1,67 +0,0 @@ -############################################################################### -# Set default behavior to automatically normalize line endings. -############################################################################### -* text=auto - -# Shell scripts used inside Linux containers must stay LF (CRLF breaks `set -eu`). -Docker/**/*.sh text eol=lf -*.sh text eol=lf - -############################################################################### -# Set default behavior for command prompt diff. -# -# This is need for earlier builds of msysgit that does not have it on by -# default for csharp files. -# Note: This is only used by command line -############################################################################### -#*.cs diff=csharp - -############################################################################### -# Set the merge driver for project and solution files -# -# Merging from the command prompt will add diff markers to the files if there -# are conflicts (Merging from VS is not affected by the settings below, in VS -# the diff markers are never inserted). Diff markers may cause the following -# file extensions to fail to load in VS. An alternative would be to treat -# these files as binary and thus will always conflict and require user -# intervention with every merge. To do so, just uncomment the entries below -############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary - -############################################################################### -# behavior for image files -# -# image files are treated as binary by default. -############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary - -############################################################################### -# diff behavior for common document formats -# -# Convert binary document formats to text before diffing them. This feature -# is only available from the command line. Turn it on by uncommenting the -# entries below. -############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index 233073b..cafcbe4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,394 +1,12 @@ -## 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/master/VisualStudio.gitignore - -# User-specific files -*.rsuser -*.suo +**/bin/ +**/obj/ +**/node_modules/ +**/.angular/ +_Published/ +myoffice-publish.tar.gz *.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -[Ww][Ii][Nn]32/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ -bld/ -[Bb]in/ -[Oo]bj/ -[Oo]ut/ -[Ll]og/ -[Ll]ogs/ - -# Visual Studio 2015/2017 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# ASP.NET Scaffolding -ScaffoldingReadMe.txt - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Coverlet is a free, cross platform Code Coverage Tool -coverage*.json -coverage*.xml -coverage*.info - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ - -# Ionide (cross platform F# VS Code tools) working folder -.ionide/ - -.vscode/ - -/MyOffice.SPA/.vscode -/MyOffice.SPA/dist/ -/MyOffice.SPA/src/environments/environment.ts -/MyOffice.SPA/src/environments/environment.prod.ts - -/MyOffice.Web/.config -/MyOffice.Web/appsettings.Production.json -/MyOffice.Web/appsettings.Development.json -/MyOffice.Web/tempkey.jwk -/MyOffice.Web/rsa_key.pem -/MyOffice.Web/**/*.pem -/MyOffice.Web/keys/ -*.pem -/MyOffice.Web/AppDbContext.Dev.db -/MyOffice.Web/AppDbContext.db -/MyOffice.Web/AppDbContext.Dev.db-shm -/MyOffice.Web/AppDbContext.Dev.db-wal - -/_Published - -package-lock.json -!MyOffice.SPA/package-lock.json -.angular -/.template.config/template.json -/MyOffice.Shared/appsettings.shared.Development.json -/MyOffice.Shared/appsettings.shared.Production.json -/linux_deploy_mybank.bat -/linux_deploy_office.bat -linux_deploy_office_public.bat -/Docker/data -/MyOffice.SPA/package-lock.json +.env +env.local +MyOffice.SPA/src/environments/environment.ts +MyOffice.Shared/appsettings.shared.Development.json +MyOffice.Shared/appsettings.shared.Production.json diff --git a/Docker/Dockerfile.api b/Docker/Dockerfile.api deleted file mode 100644 index 36b667d..0000000 --- a/Docker/Dockerfile.api +++ /dev/null @@ -1,39 +0,0 @@ -# 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"] diff --git a/Docker/Dockerfile.web b/Docker/Dockerfile.web deleted file mode 100644 index 01c4231..0000000 --- a/Docker/Dockerfile.web +++ /dev/null @@ -1,34 +0,0 @@ -# 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 diff --git a/Docker/README.md b/Docker/README.md deleted file mode 100644 index 194e667..0000000 --- a/Docker/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# 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. diff --git a/Docker/build.ps1 b/Docker/build.ps1 deleted file mode 100644 index f7103c6..0000000 --- a/Docker/build.ps1 +++ /dev/null @@ -1,158 +0,0 @@ -# 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." - } -} diff --git a/Docker/build.sh b/Docker/build.sh deleted file mode 100644 index 5d5ed25..0000000 --- a/Docker/build.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/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 diff --git a/Docker/dc-postgres-pgadmin.yml b/Docker/dc-postgres-pgadmin.yml deleted file mode 100644 index e841c27..0000000 --- a/Docker/dc-postgres-pgadmin.yml +++ /dev/null @@ -1,27 +0,0 @@ -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 diff --git a/Docker/docker-compose.demo.yml b/Docker/docker-compose.demo.yml deleted file mode 100644 index 615990b..0000000 --- a/Docker/docker-compose.demo.yml +++ /dev/null @@ -1,96 +0,0 @@ -# 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 diff --git a/Docker/nginx.conf b/Docker/nginx.conf deleted file mode 100644 index 7f03e5f..0000000 --- a/Docker/nginx.conf +++ /dev/null @@ -1,17 +0,0 @@ -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"; - } -} diff --git a/Docker/pgadmin/entrypoint.sh b/Docker/pgadmin/entrypoint.sh deleted file mode 100644 index 74df87e..0000000 --- a/Docker/pgadmin/entrypoint.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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 "$@" diff --git a/Docker/pgadmin/servers.json b/Docker/pgadmin/servers.json deleted file mode 100644 index 6107165..0000000 --- a/Docker/pgadmin/servers.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Servers": { - "1": { - "Name": "myoffice", - "Group": "Servers", - "Host": "postgres", - "Port": 5432, - "MaintenanceDB": "myoffice", - "Username": "myoffice", - "SSLMode": "prefer", - "PassFile": "/var/lib/pgadmin/pgpass" - } - } -} diff --git a/MyOffice.SPA/.gitignore b/MyOffice.SPA/.gitignore deleted file mode 100644 index 0711527..0000000 --- a/MyOffice.SPA/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# Compiled output -/dist -/tmp -/out-tsc -/bazel-out - -# Node -/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# Miscellaneous -/.angular/cache -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings - -# System files -.DS_Store -Thumbs.db diff --git a/MyOffice.SPA/karma.conf.js b/MyOffice.SPA/karma.conf.js deleted file mode 100644 index b5ce0eb..0000000 --- a/MyOffice.SPA/karma.conf.js +++ /dev/null @@ -1,45 +0,0 @@ -// Karma configuration file, see link for more information -// https://karma-runner.github.io/1.0/config/configuration-file.html - -module.exports = function(config) { - config.set({ - basePath: '', - frameworks: ['jasmine', '@angular-devkit/build-angular'], - plugins: [ - require('karma-jasmine'), - require('karma-chrome-launcher'), - require('karma-jasmine-html-reporter'), - require('karma-coverage'), - require('@angular-devkit/build-angular/plugins/karma') - ], - client: { - jasmine: { - // you can add configuration options for Jasmine here - // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html - // for example, you can disable the random execution with `random: false` - // or set a specific seed with `seed: 4321` - - }, - clearContext: false // leave Jasmine Spec Runner output visible in browser - }, - jasmineHtmlReporter: { - suppressAll: true // removes the duplicated traces - }, - coverageReporter: { - dir: require('path').join(__dirname, './coverage/angular-template'), - subdir: '.', - reporters: [ - { type: 'html' }, - { type: 'text-summary' } - ] - }, - reporters: ['progress', 'kjhtml'], - port: 9876, - colors: true, - logLevel: config.LOG_INFO, - autoWatch: true, - browsers: ['Chrome'], - singleRun: false, - restartOnFileChange: true - }); -}; diff --git a/MyOffice.SPA/src/app/app.component.spec.ts b/MyOffice.SPA/src/app/app.component.spec.ts deleted file mode 100644 index 39d0388..0000000 --- a/MyOffice.SPA/src/app/app.component.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { AppComponent } from './app.component'; - -describe('AppComponent', - () => { - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - AppComponent - ], - }).compileComponents(); - }); - - it('should create the app', - () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'spire'`, - () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('spire'); - }); - - it('should render title', - () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('.content span')?.textContent).toContain('spire app is running!'); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/forgot-password/forgot-password.component.spec.ts b/MyOffice.SPA/src/app/authentication/forgot-password/forgot-password.component.spec.ts deleted file mode 100644 index cc3bdc0..0000000 --- a/MyOffice.SPA/src/app/authentication/forgot-password/forgot-password.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { ForgotPasswordComponent } from './forgot-password.component'; -describe('ForgotPasswordComponent', - () => { - let component: ForgotPasswordComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ForgotPasswordComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(ForgotPasswordComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/locked/locked.component.spec.ts b/MyOffice.SPA/src/app/authentication/locked/locked.component.spec.ts deleted file mode 100644 index 88664c6..0000000 --- a/MyOffice.SPA/src/app/authentication/locked/locked.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { LockedComponent } from './locked.component'; -describe('LockedComponent', - () => { - let component: LockedComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [LockedComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(LockedComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/page404/page404.component.spec.ts b/MyOffice.SPA/src/app/authentication/page404/page404.component.spec.ts deleted file mode 100644 index f4a6b82..0000000 --- a/MyOffice.SPA/src/app/authentication/page404/page404.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Page404Component } from './page404.component'; - -describe('Page404Component', - () => { - let component: Page404Component; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [Page404Component], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(Page404Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/page500/page500.component.spec.ts b/MyOffice.SPA/src/app/authentication/page500/page500.component.spec.ts deleted file mode 100644 index adaba77..0000000 --- a/MyOffice.SPA/src/app/authentication/page500/page500.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { Page500Component } from './page500.component'; - -describe('Page500Component', - () => { - let component: Page500Component; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [Page500Component], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(Page500Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/signin/signin.component.spec.ts b/MyOffice.SPA/src/app/authentication/signin/signin.component.spec.ts deleted file mode 100644 index 639f76b..0000000 --- a/MyOffice.SPA/src/app/authentication/signin/signin.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { SigninComponent } from './signin.component'; - -describe('SigninComponent', - () => { - let component: SigninComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [SigninComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(SigninComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/authentication/signup/signup.component.spec.ts b/MyOffice.SPA/src/app/authentication/signup/signup.component.spec.ts deleted file mode 100644 index e43607c..0000000 --- a/MyOffice.SPA/src/app/authentication/signup/signup.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { SignupComponent } from './signup.component'; - -describe('SignupComponent', - () => { - let component: SignupComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [SignupComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(SignupComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/core/models/selectable.model.spec.ts b/MyOffice.SPA/src/app/core/models/selectable.model.spec.ts deleted file mode 100644 index 1c4ee7b..0000000 --- a/MyOffice.SPA/src/app/core/models/selectable.model.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { SelectableModel } from './selectable.model'; - -describe('SelectableModel', () => { - it('should create an instance', () => { - expect(new SelectableModel()).toBeTruthy(); - }); -}); diff --git a/MyOffice.SPA/src/app/core/service/auth.service.spec.ts b/MyOffice.SPA/src/app/core/service/auth.service.spec.ts deleted file mode 100644 index 9b6666e..0000000 --- a/MyOffice.SPA/src/app/core/service/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TestBed } from '@angular/core/testing'; - -import { AuthService } from './auth.service'; - -describe('AuthService', - () => { - let service: AuthService; - - beforeEach(() => { - TestBed.configureTestingModule({}); - service = TestBed.inject(AuthService); - }); - - it('should be created', - () => { - expect(service).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/core/utils/safe-redirect.spec.ts b/MyOffice.SPA/src/app/core/utils/safe-redirect.spec.ts deleted file mode 100644 index 4e295e0..0000000 --- a/MyOffice.SPA/src/app/core/utils/safe-redirect.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { getSafeRedirectUrl } from './safe-redirect'; - -describe('getSafeRedirectUrl', () => { - it('returns fallback when raw is empty', () => { - expect(getSafeRedirectUrl(null)).toBe('/dashboard/rests'); - expect(getSafeRedirectUrl(undefined)).toBe('/dashboard/rests'); - expect(getSafeRedirectUrl('')).toBe('/dashboard/rests'); - }); - - it('allows relative in-app paths', () => { - expect(getSafeRedirectUrl('/accounts')).toBe('/accounts'); - expect(getSafeRedirectUrl('%2Fdashboard%2Frests')).toBe('/dashboard/rests'); - }); - - it('blocks open redirects', () => { - expect(getSafeRedirectUrl('//evil.com')).toBe('/dashboard/rests'); - expect(getSafeRedirectUrl('https://evil.com')).toBe('/dashboard/rests'); - expect(getSafeRedirectUrl('http://evil.com')).toBe('/dashboard/rests'); - expect(getSafeRedirectUrl('/path?next=https://evil.com')).toBe('/dashboard/rests'); - }); -}); - diff --git a/MyOffice.SPA/src/app/dashboard/dashboard2/dashboard2.component.spec.ts b/MyOffice.SPA/src/app/dashboard/dashboard2/dashboard2.component.spec.ts deleted file mode 100644 index 00dd7f9..0000000 --- a/MyOffice.SPA/src/app/dashboard/dashboard2/dashboard2.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Dashboard2Component } from './dashboard2.component'; - -describe('Dashboard2Component', - () => { - let component: Dashboard2Component; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [Dashboard2Component] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(Dashboard2Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/dashboard/income/dashboard.component.spec.ts b/MyOffice.SPA/src/app/dashboard/income/dashboard.component.spec.ts deleted file mode 100644 index 00dd7f9..0000000 --- a/MyOffice.SPA/src/app/dashboard/income/dashboard.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Dashboard2Component } from './dashboard2.component'; - -describe('Dashboard2Component', - () => { - let component: Dashboard2Component; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [Dashboard2Component] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(Dashboard2Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.spec.ts b/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.spec.ts deleted file mode 100644 index 00dd7f9..0000000 --- a/MyOffice.SPA/src/app/dashboard/outcome/dashboard.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { Dashboard2Component } from './dashboard2.component'; - -describe('Dashboard2Component', - () => { - let component: Dashboard2Component; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [Dashboard2Component] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(Dashboard2Component); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/layout/header/header.component.spec.ts b/MyOffice.SPA/src/app/layout/header/header.component.spec.ts deleted file mode 100644 index 4cff7eb..0000000 --- a/MyOffice.SPA/src/app/layout/header/header.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { HeaderComponent } from './header.component'; -describe('HeaderComponent', - () => { - let component: HeaderComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [HeaderComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(HeaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/layout/page-loader/page-loader.component.spec.ts b/MyOffice.SPA/src/app/layout/page-loader/page-loader.component.spec.ts deleted file mode 100644 index d2479a5..0000000 --- a/MyOffice.SPA/src/app/layout/page-loader/page-loader.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { PageLoaderComponent } from './page-loader.component'; -describe('PageLoaderComponent', - () => { - let component: PageLoaderComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [PageLoaderComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(PageLoaderComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/layout/right-sidebar/right-sidebar.component.spec.ts b/MyOffice.SPA/src/app/layout/right-sidebar/right-sidebar.component.spec.ts deleted file mode 100644 index 2528b67..0000000 --- a/MyOffice.SPA/src/app/layout/right-sidebar/right-sidebar.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { RightSidebarComponent } from './right-sidebar.component'; -describe('RightSidebarComponent', - () => { - let component: RightSidebarComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [RightSidebarComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(RightSidebarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/layout/sidebar/sidebar.component.spec.ts b/MyOffice.SPA/src/app/layout/sidebar/sidebar.component.spec.ts deleted file mode 100644 index 5d604f1..0000000 --- a/MyOffice.SPA/src/app/layout/sidebar/sidebar.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { SidebarComponent } from './sidebar.component'; -describe('SidebarComponent', - () => { - let component: SidebarComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [SidebarComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(SidebarComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/pages/user-profile/user-profile.component.spec.ts b/MyOffice.SPA/src/app/pages/user-profile/user-profile.component.spec.ts deleted file mode 100644 index bc71b08..0000000 --- a/MyOffice.SPA/src/app/pages/user-profile/user-profile.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { UserProfileComponent } from './user-profile.component'; - -describe('UserProfileComponent', - () => { - let component: UserProfileComponent; - let fixture: ComponentFixture; - beforeEach( - waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [UserProfileComponent], - }).compileComponents(); - }) - ); - beforeEach(() => { - fixture = TestBed.createComponent(UserProfileComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts b/MyOffice.SPA/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts deleted file mode 100644 index e8f9133..0000000 --- a/MyOffice.SPA/src/app/shared/components/breadcrumb/breadcrumb.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BreadcrumbComponent } from './breadcrumb.component'; - -describe('BreadcrumbComponent', - () => { - let component: BreadcrumbComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [BreadcrumbComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(BreadcrumbComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/shared/components/date-period/date-period.component.spec.ts b/MyOffice.SPA/src/app/shared/components/date-period/date-period.component.spec.ts deleted file mode 100644 index 81cf7ca..0000000 --- a/MyOffice.SPA/src/app/shared/components/date-period/date-period.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { DatePeriodComponent } from './date-period.component'; - -describe('DatePeriodComponent', () => { - let component: DatePeriodComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ DatePeriodComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(DatePeriodComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/MyOffice.SPA/src/app/shared/components/feather-icons/feather-icons.component.spec.ts b/MyOffice.SPA/src/app/shared/components/feather-icons/feather-icons.component.spec.ts deleted file mode 100644 index c1e8bbf..0000000 --- a/MyOffice.SPA/src/app/shared/components/feather-icons/feather-icons.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FeatherIconsComponent } from './feather-icons.component'; - -describe('FeatherIconsComponent', - () => { - let component: FeatherIconsComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [FeatherIconsComponent] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(FeatherIconsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/app/shared/components/file-upload/file-upload.component.spec.ts b/MyOffice.SPA/src/app/shared/components/file-upload/file-upload.component.spec.ts deleted file mode 100644 index 622f59d..0000000 --- a/MyOffice.SPA/src/app/shared/components/file-upload/file-upload.component.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { FileUploadComponent } from './file-upload.component'; - -describe('FileUploadComponent', - () => { - let component: FileUploadComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [FileUploadComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(FileUploadComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', - () => { - expect(component).toBeTruthy(); - }); - }); diff --git a/MyOffice.SPA/src/environments/environment.docker.ts b/MyOffice.SPA/src/environments/environment.docker.ts deleted file mode 100644 index cfff301..0000000 --- a/MyOffice.SPA/src/environments/environment.docker.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Browser → host ports (defaults; Docker build may overwrite via API_PUBLIC_URL). -export const environment = { - production: true, - apiUrl: 'http://localhost:32081', - identityServer: 'http://localhost:32081/', - allowedUrls: ['http://localhost:32081'], - // Plain HTTP demo stack — do not require HTTPS for OIDC. - requireHttps: false, - externalLogins: { - google: { - clientId: '' - }, - auth0: { - clientId: '', - domain: '' - } - } -}; diff --git a/MyOffice.Shared/appsettings.shared.Docker.json b/MyOffice.Shared/appsettings.shared.Docker.json deleted file mode 100644 index 86dc9f0..0000000 --- a/MyOffice.Shared/appsettings.shared.Docker.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "ConnectionStrings": { - "npgsql": "Host=postgres;Port=5432;Database=myoffice;Username=myoffice;Password=myoffice" - } -} diff --git a/MyOffice.Tests/Account/AccountAclTests.cs b/MyOffice.Tests/Account/AccountAclTests.cs deleted file mode 100644 index f931d5c..0000000 --- a/MyOffice.Tests/Account/AccountAclTests.cs +++ /dev/null @@ -1,65 +0,0 @@ -using MyOffice.Data.Models.Accounts; -using MyOffice.Services.Account; -using AccountEntity = MyOffice.Data.Models.Accounts.Account; - -namespace MyOffice.Tests.Accounts; - -public class AccountAclTests -{ - private static AccountEntity CreateAccount(Guid userId, bool write, bool manage) - { - return new AccountEntity - { - Id = Guid.NewGuid(), - Name = "Cash", - AccessRights = - [ - new AccountAccess - { - UserId = userId, - IsAllowRead = true, - IsAllowWrite = write, - IsAllowManage = manage, - } - ] - }; - } - - [Fact] - public void CanWrite_true_when_flag_set() - { - var userId = Guid.NewGuid(); - var account = CreateAccount(userId, write: true, manage: false); - - Assert.True(AccountAcl.CanWrite(account, userId)); - Assert.False(AccountAcl.CanManage(account, userId)); - } - - [Fact] - public void CanManage_true_when_flag_set() - { - var userId = Guid.NewGuid(); - var account = CreateAccount(userId, write: false, manage: true); - - Assert.False(AccountAcl.CanWrite(account, userId)); - Assert.True(AccountAcl.CanManage(account, userId)); - } - - [Fact] - public void CanWrite_false_for_other_user() - { - var account = CreateAccount(Guid.NewGuid(), write: true, manage: true); - - Assert.False(AccountAcl.CanWrite(account, Guid.NewGuid())); - Assert.False(AccountAcl.CanManage(account, Guid.NewGuid())); - } - - [Fact] - public void CanWrite_false_when_access_rights_null() - { - var account = new AccountEntity { Id = Guid.NewGuid(), Name = "X", AccessRights = null }; - - Assert.False(AccountAcl.CanWrite(account, Guid.NewGuid())); - Assert.False(AccountAcl.CanManage(account, Guid.NewGuid())); - } -} diff --git a/MyOffice.Tests/Account/AccountMotionBalancingTests.cs b/MyOffice.Tests/Account/AccountMotionBalancingTests.cs deleted file mode 100644 index 4a3163a..0000000 --- a/MyOffice.Tests/Account/AccountMotionBalancingTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MyOffice.Services.Account; - -namespace MyOffice.Tests.Accounts; - -public class AccountMotionBalancingTests -{ - [Fact] - public void ResolveAmounts_income_primary_creates_expense_on_balancing() - { - var (plus, minus) = AccountMotionBalancing.ResolveAmounts(primaryPlus: 100m, amountBalancing: 100m); - - Assert.Equal(0m, plus); - Assert.Equal(100m, minus); - } - - [Fact] - public void ResolveAmounts_expense_primary_creates_income_on_balancing() - { - var (plus, minus) = AccountMotionBalancing.ResolveAmounts(primaryPlus: 0m, amountBalancing: 50m); - - Assert.Equal(50m, plus); - Assert.Equal(0m, minus); - } - - [Fact] - public void BalancingItemName_prefixes_account_name() - { - Assert.Equal("+Wallet", AccountMotionBalancing.BalancingItemName("Wallet")); - } -} diff --git a/MyOffice.Tests/Account/MotionAddAclTests.cs b/MyOffice.Tests/Account/MotionAddAclTests.cs deleted file mode 100644 index 7088c74..0000000 --- a/MyOffice.Tests/Account/MotionAddAclTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using AutoMapper; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Moq; -using MyOffice.Data.Models.Accounts; -using MyOffice.Data.Models.Items; -using MyOffice.Data.Repositories.Account; -using MyOffice.Data.Repositories.Currency; -using MyOffice.Data.Repositories.Item; -using MyOffice.Services.Account; -using MyOffice.Services.Account.Domain; -using MyOffice.Services.Identity; -using MyOffice.Services.Item; - -using AccountEntity = MyOffice.Data.Models.Accounts.Account; - -namespace MyOffice.Tests.Accounts; - -public class MotionAddAclTests -{ - [Fact] - public async Task MotionAddAsync_returns_forbidden_without_write_access() - { - var userId = Guid.NewGuid(); - var accountId = Guid.NewGuid(); - var account = new AccountEntity - { - Id = accountId, - Name = "Cash", - AccessRights = - [ - new AccountAccess - { - UserId = userId, - IsAllowRead = true, - IsAllowWrite = false, - IsAllowManage = false, - } - ] - }; - - var accountRepo = new Mock(); - accountRepo - .Setup(x => x.GetAsync(userId, accountId, It.IsAny())) - .ReturnsAsync(account); - - var service = CreateService(accountRepo.Object); - - var exec = await service.MotionAddAsync( - userId, - accountId, - new MotionAddUpdate - { - Date = DateTime.UtcNow, - Item = "Coffee", - Plus = 0, - Minus = 10, - }); - - Assert.Equal(MotionAddStatus.forbidden, exec.Status); - } - - [Fact] - public async Task MotionAddAsync_skips_balancing_when_counterparty_not_writable() - { - var userId = Guid.NewGuid(); - var primaryId = Guid.NewGuid(); - var balancingId = Guid.NewGuid(); - - var primary = new AccountEntity - { - Id = primaryId, - Name = "Cash", - AccessRights = - [ - new AccountAccess { UserId = userId, IsAllowRead = true, IsAllowWrite = true } - ] - }; - var balancing = new AccountEntity - { - Id = balancingId, - Name = "Bank", - AccessRights = - [ - new AccountAccess { UserId = userId, IsAllowRead = true, IsAllowWrite = false } - ] - }; - - var accountRepo = new Mock(); - accountRepo - .Setup(x => x.GetAsync(userId, primaryId, It.IsAny())) - .ReturnsAsync(primary); - accountRepo - .Setup(x => x.GetAsync(userId, balancingId, It.IsAny())) - .ReturnsAsync(balancing); - - var motionRepo = new Mock(); - motionRepo - .Setup(x => x.AddAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(true); - - var service = CreateService(accountRepo.Object, motionRepo.Object); - - var exec = await service.MotionAddAsync( - userId, - primaryId, - new MotionAddUpdate - { - Date = DateTime.UtcNow, - Item = "Transfer", - Plus = 0, - Minus = 25, - AccountId = balancingId.ToString("N"), - AmountBalancing = 25, - }); - - Assert.Equal(MotionAddStatus.success, exec.Status); - Assert.NotNull(exec.Result); - Assert.Single(exec.Result!); - motionRepo.Verify(x => x.AddAsync(It.IsAny(), It.IsAny()), Times.Once); - } - - private static ItemService CreateItemServiceStub() - { - var nextId = 1; - var itemRepo = new Mock(); - var itemGlobalRepo = new Mock(); - - itemGlobalRepo - .Setup(x => x.GetByNameAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync((ItemGlobal?)null); - itemGlobalRepo - .Setup(x => x.AddAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(true); - itemRepo - .Setup(x => x.GetByGlobalAsync(It.IsAny(), It.IsAny(), It.IsAny())) - .ReturnsAsync((Item?)null); - itemRepo - .Setup(x => x.AddAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(true) - .Callback((i, _) => i.Id = nextId++); - - return new ItemService( - Mock.Of(), - itemRepo.Object, - itemGlobalRepo.Object, - new MapperConfiguration(_ => { }, NullLoggerFactory.Instance).CreateMapper()); - } - - private static AccountService CreateService( - IAccountRepository accountRepository, - IMotionRepository? motionRepository = null) - { - var mapper = new MapperConfiguration( - cfg => cfg.AddProfile(), - NullLoggerFactory.Instance).CreateMapper(); - - return new AccountService( - Mock.Of>(), - mapper, - Mock.Of(), - Mock.Of(), - Mock.Of(), - accountRepository, - Mock.Of(), - Mock.Of(), - motionRepository ?? Mock.Of(), - Mock.Of(), - Mock.Of(), - CreateItemServiceStub(), - Mock.Of()); - } -} diff --git a/MyOffice.Tests/Auth/OpenIddictRedirectUriTests.cs b/MyOffice.Tests/Auth/OpenIddictRedirectUriTests.cs deleted file mode 100644 index ac21b6c..0000000 --- a/MyOffice.Tests/Auth/OpenIddictRedirectUriTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using MyOffice.Web.Auth; - -namespace MyOffice.Tests.Auth; - -public class OpenIddictRedirectUriTests -{ - [Fact] - public void BuildRedirectUri_uses_localhost_when_host_empty() - { - var uri = OpenIddictSeeder.BuildRedirectUri(null); - - Assert.Equal("http://localhost:4300/silent-refresh.html", uri.ToString()); - } - - [Fact] - public void BuildRedirectUri_appends_silent_refresh_and_trims_slash() - { - var uri = OpenIddictSeeder.BuildRedirectUri("https://app.example.com/"); - - Assert.Equal("https://app.example.com/silent-refresh.html", uri.ToString()); - } -} diff --git a/MyOffice.Tests/Auth/PasswordHasherTests.cs b/MyOffice.Tests/Auth/PasswordHasherTests.cs deleted file mode 100644 index 6182366..0000000 --- a/MyOffice.Tests/Auth/PasswordHasherTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using MyOffice.Web.Identity.Domain; -using MyOffice.Web.Identity.Repositories; - -namespace MyOffice.Tests.Auth; - -public class PasswordHasherTests -{ - private readonly PasswordHasher _hasher = new(); - private readonly ApplicationUser _user = new() { Id = Guid.NewGuid(), UserName = "test@example.com" }; - - [Fact] - public void HashPassword_then_verify_succeeds_with_identity_format() - { - var hash = _hasher.HashPassword(_user, "P@ssw0rd!"); - - Assert.Equal(PasswordVerificationResult.Success, _hasher.VerifyHashedPassword(_user, hash, "P@ssw0rd!")); - } - - [Fact] - public void VerifyHashedPassword_fails_for_wrong_password() - { - var hash = _hasher.HashPassword(_user, "P@ssw0rd!"); - - Assert.Equal(PasswordVerificationResult.Failed, _hasher.VerifyHashedPassword(_user, hash, "wrong")); - } - - [Fact] - public void HashPassword_produces_distinct_hashes_due_to_salt() - { - var a = _hasher.HashPassword(_user, "same"); - var b = _hasher.HashPassword(_user, "same"); - - Assert.NotEqual(a, b); - } - - [Fact] - public void VerifyHashedPassword_legacy_hash_returns_rehash_needed() - { - var legacy = PasswordHasher.HashLegacyForTests("legacy-secret"); - - Assert.Equal( - PasswordVerificationResult.SuccessRehashNeeded, - _hasher.VerifyHashedPassword(_user, legacy, "legacy-secret")); - } - - [Fact] - public void VerifyHashedPassword_legacy_wrong_password_fails() - { - var legacy = PasswordHasher.HashLegacyForTests("legacy-secret"); - - Assert.Equal( - PasswordVerificationResult.Failed, - _hasher.VerifyHashedPassword(_user, legacy, "nope")); - } -} diff --git a/MyOffice.Tests/Data/DemoDataSeederTests.cs b/MyOffice.Tests/Data/DemoDataSeederTests.cs deleted file mode 100644 index d03991d..0000000 --- a/MyOffice.Tests/Data/DemoDataSeederTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using MyOffice.DbContext; - -namespace MyOffice.Tests.Data; - -public class DemoDataSeederTests -{ - [Fact] - public async Task SeedIfEmpty_creates_demo_users_catalog_and_motions() - { - var path = Path.Combine(Path.GetTempPath(), $"myoffice-seed-{Guid.NewGuid():N}.db"); - var connection = new ConnectionConfiguration("sqlite", $"Data Source={path}"); - var options = new DbContextOptionsBuilder(); - DbContextServiceCollectionExtensions.ConfigureDbContextOptions(options, connection); - - await using (var db = new AppDbContext(AppDbContextProvidersEnum.sqlite, options.Options)) - { - await DatabaseBootstrapper.InitializeAsync(db); - - Assert.Equal(3, await db.Users.CountAsync()); - Assert.Equal(9, await db.Currencies.CountAsync()); - Assert.Equal(9, await db.CurrencyRates.CountAsync()); - Assert.Equal(9, await db.AccountCategories.CountAsync()); - Assert.Equal(27, await db.Accounts.CountAsync()); - Assert.Equal(300, await db.Motions.CountAsync()); - - var uahUser = await db.Users.SingleAsync(x => x.Email == "user_UAH@user_UAH.userUAH"); - Assert.Equal("UAH", uahUser.CurrencyId); - - var uahRates = await db.CurrencyRates - .Include(x => x.Currency) - .Where(x => x.Currency!.UserId == uahUser.Id) - .ToListAsync(); - Assert.Equal(1m, uahRates.Single(x => x.Currency!.CurrencyGlobalId == "UAH").Rate); - Assert.Equal(42m, uahRates.Single(x => x.Currency!.CurrencyGlobalId == "USD").Rate); - Assert.Equal(50m, uahRates.Single(x => x.Currency!.CurrencyGlobalId == "EUR").Rate); - - Assert.Contains(await db.AccountCategories.Where(x => x.UserId == uahUser.Id).Select(x => x.Name).ToListAsync(), - x => x == "Готівка"); - Assert.Contains(await db.ItemCategories.Where(x => x.UserId == uahUser.Id).Select(x => x.Name).ToListAsync(), - x => x == "Доходи"); - - await DemoDataSeeder.SeedIfEmptyAsync(db); - Assert.Equal(3, await db.Users.CountAsync()); - } - - SqliteConnection.ClearAllPools(); - if (File.Exists(path)) - { - File.Delete(path); - } - } -} diff --git a/MyOffice.Tests/MyOffice.Tests.csproj b/MyOffice.Tests/MyOffice.Tests.csproj deleted file mode 100644 index f1d43c2..0000000 --- a/MyOffice.Tests/MyOffice.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net10.0 - enable - enable - false - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MyOffice.Web/appsettings.Docker.json b/MyOffice.Web/appsettings.Docker.json deleted file mode 100644 index 88a0d2c..0000000 --- a/MyOffice.Web/appsettings.Docker.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "DatabaseProvider": "npgsql", - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "Kestrel": { - "EndPoints": { - "Http": { - "Url": "http://*:8080" - } - } - }, - "FrontEnd": { - "Host": "http://localhost:32080" - }, - "Cors": { - "AllowedOrigins": [ - "http://localhost:32080", - "http://127.0.0.1:32080" - ] - }, - "OpenIddict": { - "SigningKeyPath": "", - "EncryptionKeyPath": "", - "UseEphemeralKeys": true, - "AllowHttp": true - } -} diff --git a/README.md b/README.md index 36a493e..62a859b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MyOffice -ASP.NET Core API + Angular SPA. Full source is in this repository; runnable binaries are published as **Gitea Releases** (`myoffice-publish.tar.gz`). +ASP.NET Core API + Angular SPA. This repository has the **source needed to build**, plus Proxmox install scripts. Runnable binaries are **Gitea Releases** (`myoffice-publish.tar.gz`). ## Install on Proxmox (one line) @@ -10,9 +10,9 @@ On the Proxmox host: bash -c "$(curl -fsSL https://mygit.ase.com.ua/alexandr/myoffice_public/raw/branch/master/proxmox/myoffice.sh)" ``` -That creates an LXC with PostgreSQL + ASP.NET runtime, downloads the latest release, and starts the app on port **9100**. Proxy with your nginx LXC to `http://:9100`. +Creates an LXC with PostgreSQL + ASP.NET runtime, downloads the latest release, starts the app on port **9100**. Point your nginx LXC at `http://:9100`. -Optional environment variables before the command: +Optional: ```bash export GITEA_URL="https://mygit.ase.com.ua" @@ -24,7 +24,7 @@ export RELEASE_TAG="latest" # or v1.2.3 Details: [proxmox/README.md](proxmox/README.md). -## Build from source (manual) +## Build from source Requirements: .NET 10 SDK, Node.js 20+. @@ -43,8 +43,6 @@ cd MyOffice.SPA && npm install && npm start # http://localhost:4300 # Run MyOffice.Web (API http://localhost:9100); set FrontEnd:Host to the SPA origin ``` -Docker demo (SPA + API + Postgres + pgAdmin): see [Docker/README.md](Docker/README.md). - ## Demo logins Seeded on empty DB: diff --git a/add_migration_postgres.bat b/add_migration_postgres.bat deleted file mode 100644 index cf1a3a0..0000000 --- a/add_migration_postgres.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd ./MyOffice.Migration.Postgres -dotnet ef migrations add %1 -- %2 \ No newline at end of file diff --git a/add_migration_sqlite.bat b/add_migration_sqlite.bat deleted file mode 100644 index 08a4d64..0000000 --- a/add_migration_sqlite.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd ./MyOffice.Migration.Sqlite -dotnet ef migrations add %1 -- %2 \ No newline at end of file diff --git a/back_start.bat b/back_start.bat deleted file mode 100644 index 7665dd0..0000000 --- a/back_start.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet watch run --project .\MyOffice.Web\ -lp "watch" -v \ No newline at end of file diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index 35dc413..0000000 --- a/build.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -$params = @{ - "publish_folder" = "_Published" -}; - -function Write2 { - Param ([string] $str1) - - Write-Output "-----------------------------------------------" - Write-Output $str1 - Write-Output "-----------------------------------------------" -} - -$dotnet = $args | where { $_ -eq "dotnet" } -$spa = $args | where { $_ -eq "spa" } - - -if ((!$dotnet) -and (!$spa)) { - $dotnet = "dotnet" - $spa = "spa" -} - -if ($dotnet -eq "dotnet") { - Write2 "dotnet publish" - dotnet publish MyOffice.Web\MyOffice.Web.csproj -c Release -f net6.0 -o $params["publish_folder"] -} -if ($spa -eq "spa") { - Write2 "ng build" - cd MyOffice.SPA - $path = "..\" + $params["publish_folder"] + "\wwwroot" - ng build --output-path $path - cd .. -} diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index b51ca03..0000000 --- a/gulpfile.js +++ /dev/null @@ -1,15 +0,0 @@ -// JavaScript source code - -/// -//"use strict"; - -var gulp = require('gulp'); -var exec = require('child_process').exec; - -gulp.task('test', - function() { - return new Promise(function(resolve, reject) { - console.log('done'); - resolve(); - }); - }); \ No newline at end of file diff --git a/history_for_print.txt b/history_for_print.txt deleted file mode 100644 index 35e6298..0000000 --- a/history_for_print.txt +++ /dev/null @@ -1,23 +0,0 @@ -allow run ps1 - Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass - -generate key (one time) - ssh-keygen - -copy to remote - ssh-copy-id root@192.168.1.215 - -connect without password - ssh root@192.168.1.215 - -wsl host folders - cd /mnt/d/WORK/MyOffice - -terminal history to file - history > history_for_print.txt - -ssh remote - ssh root@192.168.1.215 'systemctl status service.name' - -update remote - rsync -r --progress --exclude appsettings*.json _Published/ root@192.168.1.215:/var/www/XXX \ No newline at end of file diff --git a/linux_deploy.sh b/linux_deploy.sh deleted file mode 100644 index d6ddf08..0000000 --- a/linux_deploy.sh +++ /dev/null @@ -1,19 +0,0 @@ -host=$1 -servicename=$2 -path=$3 - -clear -echo "-= SERVICE STOP =-" -ssh root@$host ''systemctl is-active $servicename'' -ssh root@$host ''systemctl stop $servicename'' -ssh root@$host ''systemctl is-active $servicename'' - -echo "-= COPY FILES =-" -rsync -r --info=progress2 --exclude appsettings*.json _Published/ root@$host:$path -rsync -r --ignore-existing --info=progress2 _Published/appsettings*.json root@$1:$path - -echo "-= SERVICE START =-" -ssh root@$host ''systemctl start $servicename'' - -echo "-= SERVICE STATUS =-" -ssh root@$host ''systemctl status $servicename'' diff --git a/proxmox/README.md b/proxmox/README.md index 9d6587a..a62f472 100644 --- a/proxmox/README.md +++ b/proxmox/README.md @@ -5,9 +5,12 @@ Three-tier setup on **your Gitea**: | Tier | Repo | Visibility | Contents | |------|------|------------|----------| | 1. Dev | `myoffice` | Private | Full source + Actions | -| 2. Public code | `myoffice-public` | Public | Full source (manual build) + Proxmox scripts | +| 2. Public code | `myoffice-public` | Public | Build tree (`.publishinclude`) + Proxmox scripts + public README | | 3. Published app | Releases on `myoffice-public` | Public | `myoffice-publish.tar.gz` | +Public sync **does not** include Docker, private `.gitignore`, `*.bat`, tests, docs, or private Actions (`.gitea/`). See `.publishinclude`. + + CT install downloads the **release** tarball (not source). No Docker/nginx/SDK/Node inside the CT — proxy with your existing nginx LXC to Kestrel `:9100`. ```text @@ -53,7 +56,8 @@ $env:GITEA_TOKEN = '' - `proxmox/publish.ps1` → `_Published/` + `myoffice-publish.tar.gz` - `proxmox/release.ps1` → Gitea Release asset -- `proxmox/sync-public.ps1` → full source + public `README.md` (from `README.public.md`) +- `proxmox/sync-public.ps1` → allowlisted build tree + public `README.md` (from `README.public.md`) + Or via Gitea Actions on the private repo (`.gitea/workflows/publish.yml`): push `master` → tag `latest`, or push tag `v*` / manual dispatch. diff --git a/proxmox/sync-public.ps1 b/proxmox/sync-public.ps1 index c49675c..4a331db 100644 --- a/proxmox/sync-public.ps1 +++ b/proxmox/sync-public.ps1 @@ -1,9 +1,9 @@ -# Sync full source from the private repo to the public Gitea repo (manual build + Proxmox scripts). +# Sync buildable source + Proxmox scripts to the public Gitea repo. +# Allowlist: .publishinclude | Skips: Docker, private .gitignore, *.bat, tests, docs, CI, junk +# # Usage: # $env:GITEA_TOKEN = '' -# .\proxmox\sync-public.ps1 -GiteaUrl https://gitea.example.com -Owner org -Repo myoffice-public -Push -# -# Copies tracked git files (plus generates public README.md). Skips private CI, secrets, build junk. +# .\proxmox\sync-public.ps1 -GiteaUrl https://gitea.example.com -Owner org -Repo myoffice_public -Push [CmdletBinding()] param( @@ -31,20 +31,36 @@ $repoRoot = Resolve-Path (Join-Path $scriptDir '..') Set-Location $repoRoot $GiteaUrl = $GiteaUrl.TrimEnd('/') +$includeFile = Join-Path $repoRoot '.publishinclude' +if (-not (Test-Path $includeFile)) { + throw "Missing $includeFile" +} if (-not $WorkDir) { $WorkDir = Join-Path $env:TEMP 'myoffice-public-sync' } -function Test-SkipPath([string]$RelPath) { +function Test-SkipRel([string]$RelPath) { $n = $RelPath -replace '\\', '/' + $base = Split-Path -Leaf $n + + if ($n -eq 'Docker' -or $n.StartsWith('Docker/')) { return $true } + if ($base -eq '.gitignore') { return $true } + if ($base -like '*.bat') { return $true } + + if ($n -eq 'MyOffice.Tests' -or $n.StartsWith('MyOffice.Tests/')) { return $true } + if ($n -eq 'docs' -or $n.StartsWith('docs/')) { return $true } if ($n -eq '.gitea' -or $n.StartsWith('.gitea/')) { return $true } - if ($n -eq '_Published' -or $n.StartsWith('_Published/')) { return $true } - if ($n -eq 'myoffice-publish.tar.gz') { return $true } - if ($n -eq 'Docker/data' -or $n.StartsWith('Docker/data/')) { return $true } - if ($n -eq '.env' -or $n.StartsWith('.env.') -or $n -eq 'env.local') { return $true } + if ($base -eq '.dockerignore' -or $base -eq 'gulpfile.js' -or $base -eq 'build.ps1' -or $base -eq 'linux_deploy.sh') { return $true } if ($n -eq 'proxmox/README.public.md') { return $true } - if ($n.EndsWith('.user') -or $n.EndsWith('.suo')) { return $true } + + if ($n -match '(^|/)node_modules(/|$)' -or $n -match '(^|/)(bin|obj|\.angular)(/|$)') { return $true } + if ($n -eq '_Published' -or $n.StartsWith('_Published/') -or $base -eq 'myoffice-publish.tar.gz') { return $true } + if ($base -eq '.env' -or $base -eq 'env.local' -or $base -like '.env.*') { return $true } + if ($base -eq 'appsettings.Docker.json' -or $base -eq 'appsettings.shared.Docker.json' -or $base -eq 'environment.docker.ts') { return $true } + if ($base -like '*.user' -or $base -like '*.suo') { return $true } + if ($base -eq 'karma.conf.js' -or $base -like '*.spec.ts') { return $true } + return $false } @@ -59,7 +75,7 @@ else { } Write-Host "-----------------------------------------------" -Write-Host "Sync full source -> $Owner/$Repo ($Branch)" +Write-Host "Sync allowlist (.publishinclude) -> $Owner/$Repo ($Branch)" Write-Host "WorkDir: $WorkDir" Write-Host "-----------------------------------------------" @@ -70,9 +86,7 @@ New-Item -ItemType Directory -Path $WorkDir | Out-Null $cloned = $false git clone --depth 1 --branch $Branch $remote $WorkDir 2>$null -if ($LASTEXITCODE -eq 0) { - $cloned = $true -} +if ($LASTEXITCODE -eq 0) { $cloned = $true } if (-not $cloned) { Write-Host "Clone failed or empty repo — initializing new git repo..." @@ -89,33 +103,71 @@ Get-ChildItem -Path $WorkDir -Force | Where-Object { $_.Name -ne '.git' } | ForE Remove-Item $_.FullName -Recurse -Force } -$tracked = git -C $repoRoot ls-files +$tracked = @(git -C $repoRoot ls-files) +$trackedSet = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) +foreach ($t in $tracked) { [void]$trackedSet.Add(($t -replace '\\', '/')) } + $copied = 0 $skipped = 0 -foreach ($f in $tracked) { - if (Test-SkipPath $f) { - $skipped++ - continue + +function Copy-Rel([string]$Rel) { + $script:n = $Rel -replace '\\', '/' + if (Test-SkipRel $n) { + $script:skipped++ + return } - $src = Join-Path $repoRoot $f - if (-not (Test-Path -LiteralPath $src)) { - continue - } - $dest = Join-Path $WorkDir $f + $src = Join-Path $repoRoot $n + if (-not (Test-Path -LiteralPath $src)) { return } + $dest = Join-Path $WorkDir $n $destDir = Split-Path -Parent $dest if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } Copy-Item -LiteralPath $src -Destination $dest -Force - $copied++ + $script:copied++ } -Write-Host "Copied $copied tracked files (skipped $skipped)." + +Get-Content -LiteralPath $includeFile | ForEach-Object { + $line = $_ + if ($line -match '#') { $line = $line.Substring(0, $line.IndexOf('#')) } + $line = $line.Trim() + if (-not $line) { return } + + $norm = $line -replace '\\', '/' + if ($norm.EndsWith('/')) { + $prefix = $norm + $dir = $norm.TrimEnd('/') + if (-not (Test-Path (Join-Path $repoRoot $dir))) { + Write-Host "Skip missing dir: $dir" + return + } + foreach ($t in $tracked) { + $tn = $t -replace '\\', '/' + if ($tn.StartsWith($prefix)) { Copy-Rel $tn } + } + Write-Host "Synced $dir/" + } + else { + if (Test-SkipRel $norm) { + $skipped++ + Write-Host "Skip excluded: $norm" + return + } + $src = Join-Path $repoRoot $norm + if (-not (Test-Path -LiteralPath $src)) { + Write-Host "Skip missing file: $norm" + return + } + Copy-Rel $norm + Write-Host "Synced $norm" + } +} + +Write-Host "Copied $copied files (skipped $skipped)." $repoRawBase = "$GiteaUrl/$Owner/$Repo/raw/branch/$Branch" $template = Join-Path $scriptDir 'README.public.md' -if (-not (Test-Path $template)) { - throw "Missing $template" -} +if (-not (Test-Path $template)) { throw "Missing $template" } $readme = Get-Content -LiteralPath $template -Raw -Encoding utf8 $readme = $readme. Replace('__GITEA_URL__', $GiteaUrl). @@ -126,6 +178,21 @@ $readme = $readme. Set-Content -LiteralPath (Join-Path $WorkDir 'README.md') -Value $readme -Encoding utf8 -NoNewline Write-Host "Wrote public README.md" +@( + '**/bin/', + '**/obj/', + '**/node_modules/', + '**/.angular/', + '_Published/', + 'myoffice-publish.tar.gz', + '*.user', + '.env', + 'env.local', + 'MyOffice.SPA/src/environments/environment.ts', + 'MyOffice.Shared/appsettings.shared.Development.json', + 'MyOffice.Shared/appsettings.shared.Production.json' +) | Set-Content -LiteralPath (Join-Path $WorkDir '.gitignore') -Encoding utf8 + git -C $WorkDir add -A $status = git -C $WorkDir status --porcelain if (-not $status) { @@ -133,16 +200,14 @@ if (-not $status) { } else { git -C $WorkDir -c user.email='myoffice-sync@local' -c user.name='myoffice-sync' ` - commit -m "sync full source from private myoffice" + commit -m "sync build tree from private myoffice" Write-Host "Committed public snapshot." } if ($Push) { Write-Host "Pushing to origin $Branch..." git -C $WorkDir push -u origin "HEAD:$Branch" - if ($LASTEXITCODE -ne 0) { - throw "git push failed with exit code $LASTEXITCODE" - } + if ($LASTEXITCODE -ne 0) { throw "git push failed with exit code $LASTEXITCODE" } Write-Host "Pushed." } else { diff --git a/proxmox/sync-public.sh b/proxmox/sync-public.sh index c1e6097..020be44 100755 --- a/proxmox/sync-public.sh +++ b/proxmox/sync-public.sh @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# Sync full source from the private repo to the public Gitea repo (manual build + Proxmox scripts). +# Sync buildable source + Proxmox scripts to the public Gitea repo. +# Allowlist: .publishinclude | Skips: Docker, .gitignore, *.bat, tests, docs, CI, junk +# # Usage: # export GITEA_TOKEN='' -# ./proxmox/sync-public.sh --gitea-url https://gitea.example.com --owner org --repo myoffice-public --push -# -# Copies tracked git files (plus generates public README.md). Skips private CI, secrets, build junk. +# ./proxmox/sync-public.sh --gitea-url https://gitea.example.com --owner org --repo myoffice_public --push set -euo pipefail @@ -19,6 +19,7 @@ branch="${BRANCH:-master}" token="${GITEA_TOKEN:-}" work_dir="${WORK_DIR:-}" do_push=false +include_file="$repo_root/.publishinclude" usage() { echo "Usage: $0 --gitea-url URL --owner OWNER [--repo REPO] [--branch BRANCH] [--push]" >&2 @@ -27,73 +28,69 @@ usage() { while [[ $# -gt 0 ]]; do case "$1" in - --gitea-url) - gitea_url="$2" - shift 2 - ;; - --owner) - owner="$2" - shift 2 - ;; - --repo) - repo="$2" - shift 2 - ;; - --branch) - branch="$2" - shift 2 - ;; - --work-dir) - work_dir="$2" - shift 2 - ;; - --push) - do_push=true - shift - ;; - -h | --help) - usage - ;; - *) - echo "Unknown argument: $1" >&2 - usage - ;; + --gitea-url) gitea_url="$2"; shift 2 ;; + --owner) owner="$2"; shift 2 ;; + --repo) repo="$2"; shift 2 ;; + --branch) branch="$2"; shift 2 ;; + --work-dir) work_dir="$2"; shift 2 ;; + --push) do_push=true; shift ;; + -h|--help) usage ;; + *) echo "Unknown argument: $1" >&2; usage ;; esac done -if [[ -z "$gitea_url" || -z "$owner" ]]; then - usage -fi +[[ -n "$gitea_url" && -n "$owner" ]] || usage +[[ -f "$include_file" ]] || { echo "Missing $include_file" >&2; exit 1; } gitea_url="${gitea_url%/}" +[[ -n "$work_dir" ]] || work_dir="${TMPDIR:-/tmp}/myoffice-public-sync" -if [[ -z "$work_dir" ]]; then - work_dir="${TMPDIR:-/tmp}/myoffice-public-sync" -fi - -# Paths never published (private CI, secrets, local junk). -should_skip() { +# Relative path should not be copied into public tree. +should_skip_rel() { local f="$1" - case "$f" in - .gitea | .gitea/*) return 0 ;; - _Published | _Published/*) return 0 ;; - myoffice-publish.tar.gz) return 0 ;; - Docker/data | Docker/data/*) return 0 ;; - .env | .env.* | env.local) return 0 ;; - *.user | *.suo) return 0 ;; - proxmox/README.public.md) return 0 ;; # baked into public README.md - *) return 1 ;; - esac + local base + base="$(basename "$f")" + + # User / policy excludes + [[ "$f" == Docker || "$f" == Docker/* ]] && return 0 + [[ "$base" == .gitignore ]] && return 0 + [[ "$base" == *.bat ]] && return 0 + + # Not required to build or install CT + [[ "$f" == MyOffice.Tests || "$f" == MyOffice.Tests/* ]] && return 0 + [[ "$f" == docs || "$f" == docs/* ]] && return 0 + [[ "$f" == .gitea || "$f" == .gitea/* ]] && return 0 + [[ "$base" == .dockerignore ]] && return 0 + [[ "$base" == gulpfile.js ]] && return 0 + [[ "$base" == build.ps1 ]] && return 0 + [[ "$base" == linux_deploy.sh ]] && return 0 + [[ "$f" == proxmox/README.public.md ]] && return 0 + + # Build junk / secrets / Docker-only appsettings + [[ "$base" == node_modules || "$f" == */node_modules/* ]] && return 0 + [[ "$base" == bin || "$f" == */bin/* ]] && return 0 + [[ "$base" == obj || "$f" == */obj/* ]] && return 0 + [[ "$base" == .angular || "$f" == */.angular/* ]] && return 0 + [[ "$base" == _Published || "$f" == _Published/* ]] && return 0 + [[ "$base" == myoffice-publish.tar.gz ]] && return 0 + [[ "$base" == .env || "$base" == env.local || "$base" == .env.* ]] && return 0 + [[ "$base" == appsettings.Docker.json ]] && return 0 + [[ "$base" == appsettings.shared.Docker.json ]] && return 0 + [[ "$base" == environment.docker.ts ]] && return 0 + [[ "$base" == *.user || "$base" == *.suo ]] && return 0 + + # SPA unit-test harness (not used by ng build --configuration proxmox) + [[ "$base" == karma.conf.js ]] && return 0 + [[ "$base" == *.spec.ts ]] && return 0 + + return 1 } if [[ -n "$token" ]]; then host_part="${gitea_url#https://}" host_part="${host_part#http://}" - if [[ "$gitea_url" == http://* ]]; then - scheme=http - else - scheme=https - fi + scheme=https + [[ "$gitea_url" == http://* ]] && scheme=http remote="${scheme}://oauth2:${token}@${host_part}/${owner}/${repo}.git" else remote="${gitea_url}/${owner}/${repo}.git" @@ -101,16 +98,14 @@ else fi echo "-----------------------------------------------" -echo "Sync full source -> $owner/$repo ($branch)" +echo "Sync allowlist (.publishinclude) -> $owner/$repo ($branch)" echo "WorkDir: $work_dir" echo "-----------------------------------------------" rm -rf "$work_dir" mkdir -p "$work_dir" -if git clone --depth 1 --branch "$branch" "$remote" "$work_dir" 2>/dev/null; then - : -else +if ! git clone --depth 1 --branch "$branch" "$remote" "$work_dir" 2>/dev/null; then echo "Clone failed or empty repo — initializing new git repo..." git -C "$work_dir" init -b "$branch" 2>/dev/null || { git -C "$work_dir" init @@ -124,29 +119,66 @@ find "$work_dir" -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + copied=0 skipped=0 -while IFS= read -r -d '' f; do - if should_skip "$f"; then - skipped=$((skipped + 1)) - continue - fi - src="$repo_root/$f" - if [[ ! -e "$src" ]]; then - continue - fi - dest="$work_dir/$f" - mkdir -p "$(dirname "$dest")" - cp -a "$src" "$dest" - copied=$((copied + 1)) -done < <(git -C "$repo_root" ls-files -z) -echo "Copied $copied tracked files (skipped $skipped)." +copy_tracked_under() { + local prefix="$1" # '' for file, or 'Dir/' + while IFS= read -r -d '' f; do + if [[ -n "$prefix" ]]; then + [[ "$f" == "$prefix"* ]] || continue + else + [[ "$f" == "$2" ]] || continue + fi + if should_skip_rel "$f"; then + skipped=$((skipped + 1)) + continue + fi + src="$repo_root/$f" + [[ -e "$src" ]] || continue + dest="$work_dir/$f" + mkdir -p "$(dirname "$dest")" + cp -a "$src" "$dest" + copied=$((copied + 1)) + done < <(git -C "$repo_root" ls-files -z) +} + +while IFS= read -r line || [[ -n "$line" ]]; do + line="${line%%#*}" + line="$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [[ -z "$line" ]] && continue + + src="$repo_root/$line" + if [[ "$line" == */ ]]; then + dir="${line%/}" + if [[ ! -d "$repo_root/$dir" ]]; then + echo "Skip missing dir: $dir" + continue + fi + copy_tracked_under "${dir}/" + echo "Synced $dir/" + else + if [[ ! -e "$src" ]]; then + echo "Skip missing file: $line" + continue + fi + if should_skip_rel "$line"; then + skipped=$((skipped + 1)) + echo "Skip excluded: $line" + continue + fi + # Only copy if tracked (or exists — LICENSE etc.) + dest="$work_dir/$line" + mkdir -p "$(dirname "$dest")" + cp -a "$src" "$dest" + copied=$((copied + 1)) + echo "Synced $line" + fi +done < "$include_file" + +echo "Copied $copied files (skipped $skipped)." repo_raw_base="${gitea_url}/${owner}/${repo}/raw/branch/${branch}" readme_template="$script_dir/README.public.md" -if [[ ! -f "$readme_template" ]]; then - echo "Missing $readme_template" >&2 - exit 1 -fi +[[ -f "$readme_template" ]] || { echo "Missing $readme_template" >&2; exit 1; } sed \ -e "s|__GITEA_URL__|${gitea_url}|g" \ -e "s|__GITEA_OWNER__|${owner}|g" \ @@ -156,15 +188,28 @@ sed \ "$readme_template" >"$work_dir/README.md" echo "Wrote public README.md" -# Drop private-only docs that confuse public consumers (optional keep docs/) -# Keep docs/ — useful for auth notes. +# Public consumers need a minimal .gitignore so git status stays clean after npm/dotnet restore +cat >"$work_dir/.gitignore" <<'EOF' +**/bin/ +**/obj/ +**/node_modules/ +**/.angular/ +_Published/ +myoffice-publish.tar.gz +*.user +.env +env.local +MyOffice.SPA/src/environments/environment.ts +MyOffice.Shared/appsettings.shared.Development.json +MyOffice.Shared/appsettings.shared.Production.json +EOF git -C "$work_dir" add -A if [[ -z "$(git -C "$work_dir" status --porcelain)" ]]; then echo "No changes to commit." else git -C "$work_dir" -c user.email='myoffice-sync@local' -c user.name='myoffice-sync' \ - commit -m "sync full source from private myoffice" + commit -m "sync build tree from private myoffice" echo "Committed public snapshot." fi diff --git a/spa_start.bat b/spa_start.bat deleted file mode 100644 index 3bf2fd3..0000000 --- a/spa_start.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd MyOffice.SPA -npm start \ No newline at end of file