Trial#182
Open
ArmyGuy255A wants to merge 17 commits into
Open
Conversation
Azure/static-web-apps-deploy@v1 with skip_app_build=true fails with "Failed to find a default file in the app artifacts folder (deploy)" unless an index.html exists at app_location root. Our payload only contains deploy/photogallery/index.html (the actual Angular bundle). Drop a 5-line meta-refresh stub at deploy/index.html that points to /photogallery/. At runtime, staticwebapp.config.json's edge 301 from / -> /photogallery already handles the redirect before SWA serves anything, so this stub is never returned to a browser; it exists purely to satisfy the action's local validation step. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) The SPA on https://appeid.app/photogallery/ was calling the backend ACA directly at https://ca-photogallery-api-dev.purplesea-ba9de704. eastus2.azurecontainerapps.io/api/* and getting blocked by CORS: Access to XMLHttpRequest at '<backend>/api/config/public' from origin 'https://appeid.app' has been blocked by CORS policy That cascaded into RuntimeConfigService failing to load googleClientId, and the Google sign-in popup refusing to launch. Fix: same-origin everything. nginx-appeid now exposes a /api/* block backed by API_UPSTREAM (see ArmyGuy255A/nginx-appeid#N) so the Angular HttpClient can just talk to / and the edge handles the backend hop. Sets apiUrl: '' in environment.prod.ts to use it. Matches local dev (Vite proxy.conf.json /api -> localhost:5105). Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) Updated nginx-appeid edge proxy now exposes the API at /photogallery/api/* (not /api/*) to match the SPA's actual mount point. Set apiUrl: '/photogallery' so RuntimeConfigService and other consumers build URLs like /photogallery/api/config/public, which nginx then strips back to /api/* for the backend. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…#160) (#168) * Trial (#158) * fix(cicd): stub deploy/index.html so SWA action validation passes (#153) Azure/static-web-apps-deploy@v1 with skip_app_build=true fails with "Failed to find a default file in the app artifacts folder (deploy)" unless an index.html exists at app_location root. Our payload only contains deploy/photogallery/index.html (the actual Angular bundle). Drop a 5-line meta-refresh stub at deploy/index.html that points to /photogallery/. At runtime, staticwebapp.config.json's edge 301 from / -> /photogallery already handles the redirect before SWA serves anything, so this stub is never returned to a browser; it exists purely to satisfy the action's local validation step. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): production apiUrl same-origin via nginx-appeid /api proxy (#155) The SPA on https://appeid.app/photogallery/ was calling the backend ACA directly at https://ca-photogallery-api-dev.purplesea-ba9de704. eastus2.azurecontainerapps.io/api/* and getting blocked by CORS: Access to XMLHttpRequest at '<backend>/api/config/public' from origin 'https://appeid.app' has been blocked by CORS policy That cascaded into RuntimeConfigService failing to load googleClientId, and the Google sign-in popup refusing to launch. Fix: same-origin everything. nginx-appeid now exposes a /api/* block backed by API_UPSTREAM (see ArmyGuy255A/nginx-appeid#N) so the Angular HttpClient can just talk to / and the edge handles the backend hop. Sets apiUrl: '' in environment.prod.ts to use it. Matches local dev (Vite proxy.conf.json /api -> localhost:5105). Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): apiUrl '/photogallery' so requests resolve under SPA path (#157) Updated nginx-appeid edge proxy now exposes the API at /photogallery/api/* (not /api/*) to match the SPA's actual mount point. Set apiUrl: '/photogallery' so RuntimeConfigService and other consumers build URLs like /photogallery/api/config/public, which nginx then strips back to /api/* for the backend. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): RED — failing BasePath routing + ForwardedHeaders xUnit (#160) Adds WebApplicationFactory<Program> integration tests covering the three S1 acceptance criteria: prefix routing on, prefix routing off (regression guard), and X-Forwarded-Proto scheme rewrite. Tests fail until ConfigurationSettings.BasePath, HealthController, UsePathBase, and ForwardedHeaders are wired into Program.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): add ConfigurationSettings.BasePath + HealthzController (#160) BasePath defaults to empty (root) so local raw 'dotnet run' is unaffected; Trial/Production set it to '/photogallery' via appsettings overlays in a follow-up commit. HealthzController exposes /api/healthz as an anonymous, DB-free probe that returns the effective Request.Scheme so the BasePath xUnit suite can assert ForwardedHeaders rewriting under nginx-style X-Forwarded-Proto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(backend): wire ForwardedHeaders + UsePathBase, bind 0.0.0.0 (#160) * Register ForwardedHeadersOptions for XForwardedProto|Host|For|Prefix and clear KnownProxies + KnownNetworks (without this the middleware silently no-ops in ACA / docker because the upstream proxy isn't on 127.0.0.0/8). * Pipeline order: UseForwardedHeaders -> UsePathBase(BasePath) -> UseHttpsRedirection -> UseRouting -> auth -> MapControllers/MapHub. * Default bind address flipped from http://localhost:5105 to http://0.0.0.0:5105 so the local docker stack (S6) can reach the backend via host.docker.internal. * Expose 'public partial class Program {}' so PhotoGallery.Tests can spin up the real pipeline via WebApplicationFactory<Program>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(data): InitializeAsync uses EnsureCreated on non-relational providers (#160) Test seam for the BasePath WebApplicationFactory suite. MigrateAsync only works on a relational provider; the InMemory provider used by xUnit needs EnsureCreatedAsync to materialize the schema. Production / Trial code path (SqlServer) still hits MigrateAsync — IsRelational() is true there. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(backend): reject requests outside configured BasePath with 404 (#160) UsePathBase only strips the matching prefix and otherwise passes through unchanged, which would serve the app at both /api/* and /photogallery/api/* when BasePath is set. The acceptance criterion is strict: with BasePath=/photogallery, GET /api/healthz must return 404 so a misconfigured upstream is visible at the edge rather than masked by silently serving twice. Inline middleware after UsePathBase compares Request.PathBase to the configured BasePath and 404s on mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): GREEN — finalize WAF fixture (env-var config, EF cleanup) (#160) * Program.cs evaluates builder.Configuration before WAF's ConfigureAppConfiguration callbacks run, so inject test config via environment variables (read at host-builder time). * Remove every EF Core registration tied to the SqlServer provider before registering the InMemory provider, including the open-generic IDbContextOptionsConfiguration<>, otherwise EF detects two providers and throws at first context use. * Add Microsoft.AspNetCore.Mvc.Testing 9.0.1 reference. All 245 unit + integration tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): set BasePath=/photogallery in Trial + Production overlays (#160) Trial and Production both sit behind nginx-appeid at https://appeid.app/photogallery/, so the backend serves under that prefix. Development (raw 'dotnet run', no proxy) is intentionally untouched so local iteration stays at root. appsettings.Production.json is new; it carries only the BasePath constant and explicit doc that real secrets come from Key Vault + env vars. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): commit appsettings.Production.json carrying BasePath only (#160) .gitignore excludes appsettings.*.json by default to keep secrets out of the repo. appsettings.Trial.json already breaks that rule (precedent) for exactly this reason: env-shape constants that the build needs but aren't secrets. force-add the new Production overlay — it holds only the public /photogallery mount-prefix and explicit documentation that real secrets come from Key Vault + env vars on the ACA container app. DEPLOY-ORDER: this file MUST land in Production before the nginx-appeid prefix-preserve PR (S5) so nginx isn't forwarding /photogallery/api/* to a backend that only knows /api/*. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Trial (#158) * fix(cicd): stub deploy/index.html so SWA action validation passes (#153) Azure/static-web-apps-deploy@v1 with skip_app_build=true fails with "Failed to find a default file in the app artifacts folder (deploy)" unless an index.html exists at app_location root. Our payload only contains deploy/photogallery/index.html (the actual Angular bundle). Drop a 5-line meta-refresh stub at deploy/index.html that points to /photogallery/. At runtime, staticwebapp.config.json's edge 301 from / -> /photogallery already handles the redirect before SWA serves anything, so this stub is never returned to a browser; it exists purely to satisfy the action's local validation step. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): production apiUrl same-origin via nginx-appeid /api proxy (#155) The SPA on https://appeid.app/photogallery/ was calling the backend ACA directly at https://ca-photogallery-api-dev.purplesea-ba9de704. eastus2.azurecontainerapps.io/api/* and getting blocked by CORS: Access to XMLHttpRequest at '<backend>/api/config/public' from origin 'https://appeid.app' has been blocked by CORS policy That cascaded into RuntimeConfigService failing to load googleClientId, and the Google sign-in popup refusing to launch. Fix: same-origin everything. nginx-appeid now exposes a /api/* block backed by API_UPSTREAM (see ArmyGuy255A/nginx-appeid#N) so the Angular HttpClient can just talk to / and the edge handles the backend hop. Sets apiUrl: '' in environment.prod.ts to use it. Matches local dev (Vite proxy.conf.json /api -> localhost:5105). Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): apiUrl '/photogallery' so requests resolve under SPA path (#157) Updated nginx-appeid edge proxy now exposes the API at /photogallery/api/* (not /api/*) to match the SPA's actual mount point. Set apiUrl: '/photogallery' so RuntimeConfigService and other consumers build URLs like /photogallery/api/config/public, which nginx then strips back to /api/* for the backend. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): RED — failing BasePath routing + ForwardedHeaders xUnit (#160) Adds WebApplicationFactory<Program> integration tests covering the three S1 acceptance criteria: prefix routing on, prefix routing off (regression guard), and X-Forwarded-Proto scheme rewrite. Tests fail until ConfigurationSettings.BasePath, HealthController, UsePathBase, and ForwardedHeaders are wired into Program.cs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): add ConfigurationSettings.BasePath + HealthzController (#160) BasePath defaults to empty (root) so local raw 'dotnet run' is unaffected; Trial/Production set it to '/photogallery' via appsettings overlays in a follow-up commit. HealthzController exposes /api/healthz as an anonymous, DB-free probe that returns the effective Request.Scheme so the BasePath xUnit suite can assert ForwardedHeaders rewriting under nginx-style X-Forwarded-Proto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(backend): wire ForwardedHeaders + UsePathBase, bind 0.0.0.0 (#160) * Register ForwardedHeadersOptions for XForwardedProto|Host|For|Prefix and clear KnownProxies + KnownNetworks (without this the middleware silently no-ops in ACA / docker because the upstream proxy isn't on 127.0.0.0/8). * Pipeline order: UseForwardedHeaders -> UsePathBase(BasePath) -> UseHttpsRedirection -> UseRouting -> auth -> MapControllers/MapHub. * Default bind address flipped from http://localhost:5105 to http://0.0.0.0:5105 so the local docker stack (S6) can reach the backend via host.docker.internal. * Expose 'public partial class Program {}' so PhotoGallery.Tests can spin up the real pipeline via WebApplicationFactory<Program>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(data): InitializeAsync uses EnsureCreated on non-relational providers (#160) Test seam for the BasePath WebApplicationFactory suite. MigrateAsync only works on a relational provider; the InMemory provider used by xUnit needs EnsureCreatedAsync to materialize the schema. Production / Trial code path (SqlServer) still hits MigrateAsync — IsRelational() is true there. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(backend): reject requests outside configured BasePath with 404 (#160) UsePathBase only strips the matching prefix and otherwise passes through unchanged, which would serve the app at both /api/* and /photogallery/api/* when BasePath is set. The acceptance criterion is strict: with BasePath=/photogallery, GET /api/healthz must return 404 so a misconfigured upstream is visible at the edge rather than masked by silently serving twice. Inline middleware after UsePathBase compares Request.PathBase to the configured BasePath and 404s on mismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): GREEN — finalize WAF fixture (env-var config, EF cleanup) (#160) * Program.cs evaluates builder.Configuration before WAF's ConfigureAppConfiguration callbacks run, so inject test config via environment variables (read at host-builder time). * Remove every EF Core registration tied to the SqlServer provider before registering the InMemory provider, including the open-generic IDbContextOptionsConfiguration<>, otherwise EF detects two providers and throws at first context use. * Add Microsoft.AspNetCore.Mvc.Testing 9.0.1 reference. All 245 unit + integration tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): set BasePath=/photogallery in Trial + Production overlays (#160) Trial and Production both sit behind nginx-appeid at https://appeid.app/photogallery/, so the backend serves under that prefix. Development (raw 'dotnet run', no proxy) is intentionally untouched so local iteration stays at root. appsettings.Production.json is new; it carries only the BasePath constant and explicit doc that real secrets come from Key Vault + env vars. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(config): commit appsettings.Production.json carrying BasePath only (#160) .gitignore excludes appsettings.*.json by default to keep secrets out of the repo. appsettings.Trial.json already breaks that rule (precedent) for exactly this reason: env-shape constants that the build needs but aren't secrets. force-add the new Production overlay — it holds only the public /photogallery mount-prefix and explicit documentation that real secrets come from Key Vault + env vars on the ACA container app. DEPLOY-ORDER: this file MUST land in Production before the nginx-appeid prefix-preserve PR (S5) so nginx isn't forwarding /photogallery/api/* to a backend that only knows /api/*. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): RED — SignalR hub negotiate under BasePath (#161) Adds SignalRHubPathBaseTests with four cases: A. POST /photogallery/hubs/photo-progress/negotiate → 200 + connectionId. B. POST /hubs/photo-progress/negotiate (BasePath set) → 404. C. POST /hubs/photo-progress/negotiate (BasePath empty) → 200. D. Any 'url' field in the negotiate response does not bypass BasePath. Reuses S1's WebApplicationFactory<Program> + EF InMemory swap pattern. Auth approach: DISABLE_AUTH=true (same convention S1 adopted) so the seeded testadmin@localhost user satisfies [Authorize] on PhotoProgressHub. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): GREEN — mint JWT via JwtTokenService for hub negotiate (#161) RED proved DISABLE_AUTH=true does NOT satisfy [Authorize] under a WAF host: UseAuthorization's PolicyEvaluator re-authenticates via the explicit JwtBearer scheme and ignores the principal DisableAuthMiddleware attaches to HttpContext.User. Switched to approach A from the issue: mint an HS256 JWT in-process via the same JwtTokenService the app uses, signed with the same Key/Issuer/Audience the JwtBearer scheme validates against. Token is attached as a Bearer header on every negotiate POST. All four SignalRHubPathBaseTests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(backend): serialize BasePath env-var tests via xUnit collection (#161) xUnit parallelizes test classes by default. With S2 introducing a second class that mutates the process-wide BasePath env var (which Program.cs reads at host build time), BasePathRoutingTests and SignalRHubPathBaseTests race: whichever WAF builds its host second sees the loser's BasePath value and the assertion fails. Introduces BasePathEnvVarsCollection (DisableParallelization=true) and joins both test classes to it so they run serially across the assembly. Methods within each class were already serial (xUnit's default). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <copilot@local>
* Trial (#158) * fix(cicd): stub deploy/index.html so SWA action validation passes (#153) Azure/static-web-apps-deploy@v1 with skip_app_build=true fails with "Failed to find a default file in the app artifacts folder (deploy)" unless an index.html exists at app_location root. Our payload only contains deploy/photogallery/index.html (the actual Angular bundle). Drop a 5-line meta-refresh stub at deploy/index.html that points to /photogallery/. At runtime, staticwebapp.config.json's edge 301 from / -> /photogallery already handles the redirect before SWA serves anything, so this stub is never returned to a browser; it exists purely to satisfy the action's local validation step. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): production apiUrl same-origin via nginx-appeid /api proxy (#155) The SPA on https://appeid.app/photogallery/ was calling the backend ACA directly at https://ca-photogallery-api-dev.purplesea-ba9de704. eastus2.azurecontainerapps.io/api/* and getting blocked by CORS: Access to XMLHttpRequest at '<backend>/api/config/public' from origin 'https://appeid.app' has been blocked by CORS policy That cascaded into RuntimeConfigService failing to load googleClientId, and the Google sign-in popup refusing to launch. Fix: same-origin everything. nginx-appeid now exposes a /api/* block backed by API_UPSTREAM (see ArmyGuy255A/nginx-appeid#N) so the Angular HttpClient can just talk to / and the edge handles the backend hop. Sets apiUrl: '' in environment.prod.ts to use it. Matches local dev (Vite proxy.conf.json /api -> localhost:5105). Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): apiUrl '/photogallery' so requests resolve under SPA path (#157) Updated nginx-appeid edge proxy now exposes the API at /photogallery/api/* (not /api/*) to match the SPA's actual mount point. Set apiUrl: '/photogallery' so RuntimeConfigService and other consumers build URLs like /photogallery/api/config/public, which nginx then strips back to /api/* for the backend. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(fe): add build:prod and start:proxy npm scripts Adds two new scripts that bake <base href=/photogallery/> into the Angular bundle at build time, replacing the nginx sub_filter rewrite path: - build:prod -> ng build --configuration production --base-href=/photogallery/ (used by release.yml to produce the SWA artifact) - start:proxy -> ng serve --base-href=/photogallery/ (used when running behind the local nginx-appeid docker stack at https://localhost:8000/photogallery/) Existing start / build scripts are untouched so the raw ng serve dev loop still serves at base href '/' on http://localhost:4300. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): clarify environment.prod.ts comment re build-time base href <base href> is now baked into the bundle by build:prod, so the nginx sub_filter rewrite path is going away in S5. Update the inline comment to reflect that, and call out explicitly why apiUrl is still needed (HttpClient does not apply <base> to absolute paths). Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): document three dev shapes in README Adds a 'Three dev shapes' section to FE.PhotoGallery/README.md spelling out the raw / local-docker / prod loops, the resulting <base href> and apiUrl in each, and the deploy-order constraint relative to the S5 nginx sub_filter removal. Also tightens the Production deployment section to reference release.yml and the new build:prod script instead of the legacy deploy-frontend.yml workflow + ad hoc build invocation. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(ci): use build:prod and assert <base href> in release workflow Replace the ad hoc 'npm run build -- --configuration=production --base-href=/photogallery/' invocation with 'npm run build:prod' so the canonical FE build command lives in package.json and CI just calls it. Add a post-build assertion step that greps the emitted dist/fe.photo-gallery/browser/index.html for the literal <base href="/photogallery/">. If a future Angular CLI / config change ever drops the flag, every relative asset under /photogallery/ would 404 in prod; this fails the build instead. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): point Building section at build:prod Cross-reference the new build:prod script (and the Three dev shapes section) from the existing Building section, so contributors land on the right command for the sub-path-aware bundle without having to read the whole README. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) (#172) * Trial (#158) * fix(cicd): stub deploy/index.html so SWA action validation passes (#153) Azure/static-web-apps-deploy@v1 with skip_app_build=true fails with "Failed to find a default file in the app artifacts folder (deploy)" unless an index.html exists at app_location root. Our payload only contains deploy/photogallery/index.html (the actual Angular bundle). Drop a 5-line meta-refresh stub at deploy/index.html that points to /photogallery/. At runtime, staticwebapp.config.json's edge 301 from / -> /photogallery already handles the redirect before SWA serves anything, so this stub is never returned to a browser; it exists purely to satisfy the action's local validation step. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): production apiUrl same-origin via nginx-appeid /api proxy (#155) The SPA on https://appeid.app/photogallery/ was calling the backend ACA directly at https://ca-photogallery-api-dev.purplesea-ba9de704. eastus2.azurecontainerapps.io/api/* and getting blocked by CORS: Access to XMLHttpRequest at '<backend>/api/config/public' from origin 'https://appeid.app' has been blocked by CORS policy That cascaded into RuntimeConfigService failing to load googleClientId, and the Google sign-in popup refusing to launch. Fix: same-origin everything. nginx-appeid now exposes a /api/* block backed by API_UPSTREAM (see ArmyGuy255A/nginx-appeid#N) so the Angular HttpClient can just talk to / and the edge handles the backend hop. Sets apiUrl: '' in environment.prod.ts to use it. Matches local dev (Vite proxy.conf.json /api -> localhost:5105). Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(fe): apiUrl '/photogallery' so requests resolve under SPA path (#157) Updated nginx-appeid edge proxy now exposes the API at /photogallery/api/* (not /api/*) to match the SPA's actual mount point. Set apiUrl: '/photogallery' so RuntimeConfigService and other consumers build URLs like /photogallery/api/config/public, which nginx then strips back to /api/* for the backend. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(fe): add build:prod and start:proxy npm scripts Adds two new scripts that bake <base href=/photogallery/> into the Angular bundle at build time, replacing the nginx sub_filter rewrite path: - build:prod -> ng build --configuration production --base-href=/photogallery/ (used by release.yml to produce the SWA artifact) - start:proxy -> ng serve --base-href=/photogallery/ (used when running behind the local nginx-appeid docker stack at https://localhost:8000/photogallery/) Existing start / build scripts are untouched so the raw ng serve dev loop still serves at base href '/' on http://localhost:4300. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): clarify environment.prod.ts comment re build-time base href <base href> is now baked into the bundle by build:prod, so the nginx sub_filter rewrite path is going away in S5. Update the inline comment to reflect that, and call out explicitly why apiUrl is still needed (HttpClient does not apply <base> to absolute paths). Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): document three dev shapes in README Adds a 'Three dev shapes' section to FE.PhotoGallery/README.md spelling out the raw / local-docker / prod loops, the resulting <base href> and apiUrl in each, and the deploy-order constraint relative to the S5 nginx sub_filter removal. Also tightens the Production deployment section to reference release.yml and the new build:prod script instead of the legacy deploy-frontend.yml workflow + ad hoc build invocation. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(ci): use build:prod and assert <base href> in release workflow Replace the ad hoc 'npm run build -- --configuration=production --base-href=/photogallery/' invocation with 'npm run build:prod' so the canonical FE build command lives in package.json and CI just calls it. Add a post-build assertion step that greps the emitted dist/fe.photo-gallery/browser/index.html for the literal <base href="/photogallery/">. If a future Angular CLI / config change ever drops the flag, every relative asset under /photogallery/ would 404 in prod; this fails the build instead. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(fe): point Building section at build:prod Cross-reference the new build:prod script (and the Three dev shapes section) from the existing Building section, so contributors land on the right command for the sub-path-aware bundle without having to read the whole README. Refs #162 (epic #159, S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore(e2e): plumb BASE_URL + ignoreHTTPSErrors through playwright.config S4 (#163) needs a single spec to run against three deployment shapes: - http://localhost:4300 (raw ng serve) - https://localhost:8000/photogallery (local docker stack, S6) - https://appeid.app/photogallery (Trial) Reads BASE_URL from env, auto-enables ignoreHTTPSErrors for https targets (self-signed local cert + bootstrap Trial cert), and only spins up the local ng serve webServer when targeting the default raw dev URL so docker/Trial runs don't fight a stray ng serve process. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(e2e): SignalR upload progress through the /photogallery proxy (#163) Closes #163. What this spec proves end-to-end: 1. The SPA-relative '${environment.apiUrl}/hubs/photo-progress' URL resolves correctly when the SPA is served behind a base-path reverse proxy (e.g. '/photogallery/hubs/photo-progress'). 2. The WebSocket upgrade survives the proxy chain (negotiate POST returns 200, Upgrade/Connection headers pass through, hub reaches Connected state). 3. At least one SignalR progress event (ProcessingStarted / ProcessingProgress / ProcessingCompleted / WatermarkCompleted) is delivered to the browser within 30s of an upload. Because no FE component subscribes to PhotoProgressService yet (the service exists; its first UI consumer arrives in a later story), the spec opens its own HubConnection inside the page using the '@microsoft/signalr' browser bundle from node_modules and the exact URL shape from PhotoProgressService.buildHub(). When the consumer ships we'll switch this to assert on the rendered progress DOM. Gated by RUN_SIGNALR_E2E=1 so the default e2e matrix doesn't try to run against environments without the full proxy/backend up. S6 flips the flag on in the docker-stack workflow; Trial validation lands once the deploy ships. Verification: 'npx playwright test ... --list' enumerates the spec across chromium/firefox/webkit cleanly; TypeScript noEmit shows no errors from this file (one pre-existing error in full-flow.spec.ts is out of scope). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* docs(runbook): scaffold local proxy dev runbook (#164) Adds Documentation/Runbooks/local-proxy-dev.md with prereqs, self-signed cert generation, and a placeholder for the start sequence. Subsequent commits fill in start sequence, validation curls, troubleshooting, and known gaps. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(runbook): document start sequence — backend, FE, nginx (#164) Three-stage bring-up: dotnet run with ConfigurationSettings__BasePath, npm run start:proxy at :4300, and docker compose up for nginx-appeid at :8000. Records expected log lines, ASPNETCORE_URLS rationale (0.0.0.0 vs localhost), and flags the #167 dependency inline. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(runbook): add six validation curls (#164) Documents A nginx /healthz, B backend /photogallery/api/healthz, C /photogallery/api/config/public (epic's canonical AC from #164), D SignalR hub negotiate, E manual browser check, and F end-to-end upload. Captures actual nginx /healthz output observed locally on 2026-05-17 against the S5 build. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(runbook): add teardown section (#164) Documents docker compose down + Ctrl+C on the two host processes, plus a fallback PID-kill via Get-NetTCPConnection for cases where the FE/BE shells were detached. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(runbook): add troubleshooting table (#164) Symptom -> likely cause -> fix matrix for the failure modes observed during local validation: 502 (upstream not reachable), 404 on /photogallery/api/* (BasePath not applied OR FE proxy missing route #167), SPA assets 404 (npm start vs npm run start:proxy), TLS cert refused, and the two backend startup blockers seen on this machine (DefaultConnection + Authentication:Jwt:Key). Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(runbook): document known gaps, validation status, and see-also (#164) Closes out the runbook with three final sections: (1) Known gaps until follow-ups land — #167 (FE proxy.conf.json routes) and #170 (VSCode task), plus the backend dev-secrets and S3 start:proxy script issues hit during live validation. (2) Validation status table — A nginx /healthz passes live; B-F remain theoretical pending the gaps above. (3) See also — links to FE.PhotoGallery/README.md three-dev-shapes, D016, the epic, predecessor PRs, and follow-up issues. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(scripts): add local-proxy smoke script (#164) scripts/smoke-local-proxy.ps1 runs the four documented curl checks (nginx /healthz, /photogallery/api/healthz, /photogallery/api/config/public asserting JSON contains googleClientId, and /photogallery/hubs/photo-progress/negotiate accepting either 200 or 401) and prints PASS/FAIL per check. Distinguishes the #167 failure mode (200 OK but SPA-fallback HTML body) from a true 200 by sniffing for <title>Photo Gallery</title> in the response body. Skips the browser smoke and the upload smoke by design — those stay manual in the runbook. Exits non-zero if any automated check fails. Supports -WhatIf for dry runs and -BaseUrl for non-default edges. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(index): link new Runbooks section and local-proxy-dev runbook (#164) Documentation/INDEX.md previously had no Runbooks section despite local-azure-dev.md existing in the folder. Adds a Runbooks subsection with entries for both runbooks. Refs #164 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…te (#165) (#174) * docs(architecture): D018 reverse-proxy sub-path mounting via BasePath Add DESIGN_DECISIONS.md entry capturing the four-part pattern that landed across stories S1-S6 of epic #159: - backend: ConfigurationSettings:BasePath + UseForwardedHeaders + UsePathBase - ForwardedHeadersOptions clears KnownProxies/KnownNetworks (ACA-behind-nginx gotcha) - FE: build-time --base-href=/photogallery/ via build:prod + start:proxy scripts - nginx-appeid: preserves /photogallery prefix, sets X-Forwarded-Prefix, dedicated /photogallery/hubs/ location, sub_filter retired Includes a Mermaid topology diagram showing browser -> nginx -> ACA/SWA routing. Closes #165 (docs deliverable 1 of 2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs(memory): record reverse-proxy /photogallery sub-path pattern (1.6) Append section 1.6 to MEMORY.md capturing the canonical fact for the BasePath / nginx-appeid pattern shipped in epic #159. Memory fact stored: PhotoGallery deploys behind nginx-appeid at /photogallery; ConfigurationSettings:BasePath + UseForwardedHeaders + UsePathBase drive prefix awareness server-side; FE ships <base href=\"/photogallery/\"> at build time via ng build --base-href=/photogallery/ (the npm run build:prod script); nginx preserves the prefix and sets X-Forwarded-Prefix; SignalR hub is reachable at /photogallery/hubs/photo-progress via a dedicated nginx location; the legacy sub_filter rewrite is retired. Closes #165 (docs deliverable 2 of 2). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors 'Backend: Run (Port 5105)' shape: shell + isBackground + background
problemMatcher. Runs 'npm run start:proxy' (ng serve --base-href=/photogallery/)
in a dedicated terminal panel, so the local reverse-proxy dev loop is fully
launchable from the Tasks UI alongside the raw FE ('Frontend: Dev Server (Port
4300)') and backend tasks.
Closes #170
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kend (#177) Adds /photogallery/api and /photogallery/hubs entries to proxy.conf.json so that ng serve --base-href=/photogallery/ behind the local nginx-appeid stack can forward prefixed requests to the dotnet backend at http://localhost:5105. Keeps the bare /api and /hubs entries for raw ng serve mode (no nginx, no BasePath). When BasePath=/photogallery is set on the backend, the bare entries are harmless dead routes (S1 acceptance test enforces backend ignores /api on prefixed mode). Closes #167 Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tack: Local Proxy task (#178) * fix(fe): use Angular 'proxy' configuration instead of --base-href CLI flag Angular 19.2's dev-server builder no longer accepts --base-href as a CLI argument (the S6 runbook predicted this). Move baseHref into a new 'development-proxy' build configuration and a matching 'proxy' serve configuration. Update the start:proxy script to invoke them. Also set host=0.0.0.0 in serve options so the dev server is reachable from inside the nginx-appeid local docker container via host.docker.internal. Refs: #159 (epic), follows #162 (S3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(fe): derive apiUrl from document.baseURI at module load Same bundle works under any reverse-proxy mount point (root, /photogallery/, /tenant-foo/, ...) without rebuilding. The <base href> becomes the single source of truth for the mount path: - Raw mode (<base href='/'>) -> apiUrl = '' -> calls hit /api/*. - Proxy mode (<base href='/photogallery/'>) -> apiUrl = '/photogallery' -> calls hit /photogallery/api/*. Works for both 'software-aware' deployments (build-time --base-href + backend BasePath + nginx pass-through) and 'nginx-aware' deployments (nginx sub_filter rewrites base href; raw FE + raw BE bundles stay mount-blind). Maximum portability for adding future tenants. Refs: #159 (epic). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(vscode): proxy-mode backend task + Stack: Local Proxy compound Adds two tasks so the local-proxy dev shape is fully launchable from the VSCode Tasks UI without manual env-var setup: - 'Backend: Run (Proxy mode, BasePath=/photogallery)': starts the API with BasePath=/photogallery and binds 0.0.0.0:5105 so the nginx-appeid local docker container can reach it via host.docker.internal. - 'Stack: Local Proxy' (compound): runs Docker services -> backend proxy mode -> frontend proxy mode in sequence. After this finishes, bring up the nginx-appeid local stack to complete the loop. Also ignores the scratch backend.{pid,log,err.log} + fe.{pid,log,err.log} files we drop into the repo root when bringing the stack up by hand. Refs: #159 (epic), #170 (proxy FE task). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… CORS (#180) Direct-to-blob uploads from the SPA at https://appeid.app/photogallery/ were failing with 'Http failure response ... 0 Unknown Error' (Angular HttpClient's signature for a CORS preflight rejection). Cause: the blob storage account's CORS allowlist only included the SWA default hostname (agreeable-tree-...azurestaticapps.net) plus localhost:42xx, not the new appeid.app origin nor the localhost:8000 local-proxy origin. Add four origins to the terraform default: - https://appeid.app (prod proxy edge) - https://www.appeid.app (www variant) - https://localhost:8000 (local-proxy docker stack) Live patched via 'az rest PUT blobServices/default' on 2026-05-17 to unblock prod uploads immediately; this commit keeps terraform state in sync so the next plan/apply doesn't revert. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…matchers (#181) Adds two new tasks for bringing up the nginx reverse proxy that mounts the SPA at /photogallery on appeid.app: - 'nginx-appeid: Local proxy stack (https://localhost:8000)': brings up the local docker stack in nginx-appeid/local/. Forwards /photogallery/* to host's ng serve at :4300 and /photogallery/api/* + /photogallery/hubs/* to host's dotnet at :5105 via host.docker.internal. Companion DOWN task included. - 'nginx-appeid: Azure-pointing test stack (http://localhost:8001)': builds the prod nginx-appeid Dockerfile and runs it on host :8001 pointing at the real Azure ACA + SWA endpoints. Lets us validate the prod conf shape (Services/appeid/server-appeid.conf) without deploying. Used in this session to debug the v1.0.0.1 → v1.0.0.2 resolver regression. Companion DOWN task included. Also fixes three stale bits that surfaced while bringing the local-proxy stack up: - 'Frontend: Dev Server (Port 4300)' and 'Frontend: Run (Proxy mode, /photogallery base href)' had problemMatcher patterns ('Application bundle generated' / '✔ Compiled successfully') that Angular 19.2 no longer emits. Use the actual lines ('Building' / 'Application bundle generation complete') so 'Stack: Local Proxy' compound correctly reports ready and chains the next step. - 'Frontend: Run (Proxy mode...)' detail string said to pair with 'Backend: Run (Port 5105)' (the raw-mode backend). Updated to point at 'Backend: Run (Proxy mode, BasePath=/photogallery)' which is the correct pair for the proxy dev shape. - 'Docker: Start Services', 'Docker: Stop Services', 'Docker: View Logs' used the legacy 'docker-compose' (v1 hyphenated binary). Use 'docker compose' (v2 subcommand) consistent with the rest of the repo's tooling. Co-authored-by: Copilot Agent <copilot@photogallery.local> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.