Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions PROJECT_SNAPSHOT.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,33 @@ These are real, current issues a new contributor should be aware of, not hypothe
pre-existing, unmodified test. See [`docs/development/testing.md`](docs/development/testing.md).
- **No CI test/lint gate** β€” all three deploy workflows build and deploy directly with no
`dotnet test` step. See [`docs/deployment/ci-cd.md`](docs/deployment/ci-cd.md).
- **Pdf/Word exam export needs Chromium native libraries on the deploy target, not yet confirmed
present on Azure Web App or either VPS** (2026-07-16, extended 2026-07-17 to Pdf β€” see
`docs/business/exams-and-content.md` and `docs/deployment/overview.md`):
`HeadlessBrowserRenderProvider` launches a headless Chromium (`chrome-headless-shell` via
PuppeteerSharp) to render exam formulas (both Pdf/Word) and, as of 2026-07-17, to print the whole
Pdf export via Chromium's native print engine too β€” needs ~20 native shared libraries (`libatk`,
`libcups`, `libgbm`, `libasound`, etc. β€” see `docs/deployment/overview.md` for the full list) that
a bare Linux App Service/VPS typically doesn't have preinstalled. If missing: **Word** formula
rendering falls back to unrendered raw `$...$` text rather than crashing (degrades silently); but
**Pdf now fails entirely** if Chromium can't launch, since Pdf generation itself depends on it, not
just formulas. Needs verifying/installing on all three deploy targets before this is
production-ready β€” more urgent now than when only Word formulas depended on it.
- **No crash-recovery for the shared headless-browser singleton**: if the one Chromium process
`HeadlessBrowserRenderProvider` keeps alive for the app's lifetime dies (OOM-killed, crashes), it
stays dead β€” no disconnect detection or auto-relaunch exists yet. Every Pdf/Word export (formula
rendering, and now Pdf printing) would fail until the whole app restarts. Flagged, not yet built.
- **Pdf export needs real fonts + fontconfig on the deploy target too, not yet confirmed present,
and the failure mode is worse than the missing-library case above** (2026-07-17, found by direct
reproduction β€” see `docs/deployment/overview.md`): a minimal host has no fontconfig/fonts by
default. MathJax formulas still render fine (they're drawn as vector paths, not real fonts), but
**all other text renders as nothing** β€” no error, no fallback font, just blank space β€” while
borders/colors/images still render normally. The resulting Pdf looks like an empty, correctly
laid-out template with no readable content, which reads as a data or template bug, not a missing-
font one, unless you already know to suspect fonts. Minimum fix: install `fontconfig` +
`fonts-liberation` and confirm `Arial`/`Helvetica`/`sans-serif` actually resolve to it (a bare
fontconfig install with no alias rules can still pick an unrelated, e.g. monospace, font). Word is
unaffected (rendered by the reader's own Word/LibreOffice, not this server).

None of the above block day-to-day feature work, but they should inform priorities and should not
be treated as "someone already fixed this."
Expand Down Expand Up @@ -182,6 +209,74 @@ be treated as "someone already fixed this."
`IdentityService` now reads the caller's IP off the inbound request and `CoreProvider` sends it as
a `TRUSTED_FORWARDED_IP` header on those four outgoing calls (`logout` unaffected β€” gama-api didn't
ask for it there).
- **Word and PowerPoint exam export rewritten as fully native OOXML; Spire and HtmlToOpenXml both
fully removed from the solution** (2026-07-16 to 2026-07-17 β€” see
[`docs/business/exams-and-content.md`](docs/business/exams-and-content.md)): the `Word` branch of
`ExamSerivce.ExportExamAsync` builds a `.docx` by hand-emitting `DocumentFormat.OpenXml` elements
directly (`ExamWordDocumentBuilder.cs`/`ExamWordRichText.cs`) β€” no HTML-to-OOXML conversion layer
at all, after HtmlToOpenXml.dll (an earlier intermediate step) proved unable to produce genuinely
native-quality Word tables (silently applied its own default `TableGrid` style, mishandled
bare-pixel widths). The `PowerPoint` branch (`ExamPresentationBuilder.cs`) got the same treatment,
replacing paid Spire.Presentation's `AddFromHtml` β€” one slide per question after a title/summary
slide, PresentationML's own `ThemePart`/`SlideMasterPart`/`SlideLayoutPart` hierarchy built from
scratch, absolutely-positioned shapes instead of flowing tables, options grid as a native DrawingML
table. Known PowerPoint gap: slides use plain-text runs only (`BuildRichParagraphs`) β€” no bold/
italic/color formatting (formulas are handled, see below). Both `Spire.Officefor.NETStandard` and
`HtmlToOpenXml.dll` package references are
gone from every `.csproj` and `Directory.Packages.props`. Two OOXML schema traps worth remembering:
every `w:tbl` needs an explicit `w:tblGrid` right after `w:tblPr` (its absence makes Word silently
repair/collapse the table on open) and a table cell's content must end with a paragraph, not a
table (a cell whose last child is a nested `w:tbl` renders as if it broke out of the cell).
Question/option text can contain MathJax-style `$...$` LaTeX (confirmed from real Core exam data,
including non-trivial `\begin{gathered}...\end{gathered}` constructs) β€” a singleton headless-browser
provider renders these to PNGs using the real MathJax engine inside a headless Chromium tab
(PuppeteerSharp), since partial-LaTeX .NET parsers failed on the messier real-world formulas; at
this point Word embedded the resulting PNGs natively and PowerPoint didn't call formula rendering
at all β€” both superseded by native `m:oMath` for Word/PowerPoint, see the OMML bullet below.
Concurrent renders are capped at `Environment.ProcessorCount` via a semaphore so a
burst of simultaneous export requests queues instead of overwhelming the shared browser process.
Also fixed in passing: `CoreExamInformationResponse.RemainedSeconds` was typed `bool` but Core
actually returns a signed integer (broke deserialization for any exam); the QR code data URI had
an invalid MIME type (`img/png` instead of `image/png`); embedded images were being encoded at
full source resolution regardless of declared display size, needlessly bloating every export.
- **Pdf exam export rewritten off Spire, reusing the Word pipeline's Chromium instance**
(2026-07-17): `IMathFormulaRenderProvider`/`MathJaxFormulaRenderProvider` renamed to
`IHeadlessBrowserRenderProvider`/`HeadlessBrowserRenderProvider` to reflect its now-broader
responsibility, and gained `RenderPdfAsync` β€” prints formula-rendered HTML to PDF via Chromium's
own native print engine (`PrintBackground: true`, A4, 0.5in left/right margins), reusing the same
singleton browser/concurrency-limiter rather than adding a second Chromium instance or a separate
PDF library. Pdf is deliberately the one format that still renders from real HTML (via the
`exam.word.html` Handlebars template, name predates the Word rewrite) instead of native OOXML β€”
PDF is painted pixels, not an editable document, so the "HTML can't produce a genuinely native
table" problem that motivated the Word/PowerPoint rewrites doesn't apply to it. Watermark for Pdf
is a `position:fixed` (deliberately, not `absolute` β€” Chromium's print engine repeats fixed-position
elements on every printed page) diagonal `<div>` injected before printing, HTML-encoded. See the
deployment risk noted above β€” Chromium native libraries are required for Pdf exports (and Word/
PowerPoint's MathJax formula rendering) to work at all.
- **Word/PowerPoint formulas switched from rasterized PNG to native OOXML Math (`m:oMath`)**
(2026-07-18, see [`docs/business/exams-and-content.md`](docs/business/exams-and-content.md)):
motivated by Word/PowerPoint's actual audience being teachers who edit/reuse the export (unlike
Pdf, read by students) β€” a raster formula can't be edited, and PowerPoint previously dropped
formulas entirely (the known gap above). MathJax's existing `tex-svg.js` already emits a hidden
MathML annotation by default (`assistiveMml:!0`) alongside the SVG it renders, so no separate
MathJax bundle/render pass was needed; that MathML is converted to OOXML Math via a newly-vendored
`wwwroot/lib/mathml2omml/mathml2omml.js` (npm `mathml2omml` 0.5.0, LGPL-3.0-or-later, a
from-scratch reimplementation β€” deliberately not Microsoft's own `MML2OMML.xsl`, which isn't
safely redistributable), running in the same headless Chromium page as MathJax, so no new .NET
dependency. Two real bugs found and patched in the vendored copy by validating against
`DocumentFormat.OpenXml`'s `OpenXmlValidator` (not just "is this well-formed XML," a materially
weaker check that missed both): (1) the library's `stringify()` wrote text node content with zero
XML escaping, producing invalid XML for any formula whose text contained a literal `<`/`&`; (2)
`addScriptlevel()` added a duplicate, schema-invalid `<m:argPr><m:scrLvl>` for every invisible-
spacing `mstyle` MathJax emits inside `\begin{gathered}` piecewise constructs. Word inserts
`m:oMath` as a direct sibling of `w:r` runs, inline with text, same as Word's own equation editor.
PowerPoint has no such direct slot in DrawingML's `a:p` schema β€” equations there require the
`mc:AlternateContent`/`a14:m` markup-compatibility wrapper (PowerPoint 2010+), and each formula
becomes its own dedicated paragraph rather than staying inline mid-sentence, since AlternateContent
isn't valid mixed into one paragraph alongside plain runs. Both paths fall back to the previous
rendered-PNG `<img>` per formula if the MathML→OMML conversion throws. Pdf is unchanged (still
images, via `RenderFormulasAsync`) since its HTML+Chromium-print pipeline has no OOXML to insert
native math into anyway.

## Documentation completeness

Expand Down
2 changes: 1 addition & 1 deletion docs/api/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ string is parsed internally instead) β€” when `CoreId`, `id` is resolved against

| Verb | Route | Purpose | Auth | Request model | Response model |
|---|---|---|---|---|---|
| GET | `export` | Export an exam to a file, gated by a `SecretKey` header | User | `ExportExamRequestViewModel` (query) + `SecretKey` header | Declared `IActionResult`; error path returns `ApiResponse<Void>`, success path returns a raw `FileContentResult` (binary file), not the envelope |
| GET | `export` | Export an exam to a file | User (requires the caller's `Authorization` header to carry their gama-api legacy JWT, forwarded to gama-api's `exams/start/{id}` β€” see `docs/api/authentication.md`; no longer a separate `SecretKey` header, see below) | `ExportExamRequestViewModel` (query) | Declared `IActionResult`; error path returns `ApiResponse<Void>`, success path returns a raw `FileContentResult` (binary file), not the envelope |

### ExperiencesController
`src/Presentation/Api/Controllers/ExperiencesController.cs` β€” class-level `[Permission(policy: null)]` (User, no anonymous overrides)
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Concretely, from the `.csproj` `ProjectReference`s:
- `Application/Interface` β†’ `Core/Common`, `Core/Data` (defines contracts only, no Domain entities beyond what DTOs need).
- `Infrastructure/Interface` β†’ `Core/Common`, `Core/Data`, `Domain`.
- `Application/Service` β†’ `Core/Data`, `Infrastructure/Interface`, `Application/Interface` (service impls depend on provider *interfaces*, never concrete providers).
- `Infrastructure/Infrastructure` β†’ `Core/Common`, `Core/Data`, `Domain`, `Infrastructure/Interface` (the only project that references EF Core provider packages + concrete SDKs: Azure.Storage.Blobs, AWSSDK.S3, Stripe.net, Resend, Google.Apis.YouTube.v3).
- `Infrastructure/Infrastructure` β†’ `Core/Common`, `Core/Data`, `Domain`, `Infrastructure/Interface` (the only project that references EF Core provider packages + concrete SDKs: Azure.Storage.Blobs, AWSSDK.S3, Stripe.net, Resend, Google.Apis.YouTube.v3, PuppeteerSharp).
- `Presentation/ViewModel` β†’ `Core/Common`, `Domain`.
- `Presentation/Api` β†’ `Application/Interface`, `Presentation/ViewModel`, and `Build` (a non-`Private` reference that forces `Application/Service` + `Infrastructure/Infrastructure` + `Core/Resource` to be built and copied to the API's output, without the API project depending on their *types* directly β€” DI wiring/reflection resolves the concrete implementations at runtime).
- `Test` β†’ `Core/Data`, `Application/Interface`, `Presentation/Api` (tests spin up the real `Startup`/host β€” see `docs/architecture/design-patterns.md` for why this is risky).
Expand All @@ -83,7 +83,7 @@ Concretely, from the `.csproj` `ProjectReference`s:
| `Domain/Specification` | One `ISpecification<TEntity>` class per filter, organized by aggregate folder (`School/`, `Payment/`, `Identity/`, ...). |
| `Application/Interface` | `I<Feature>Service` contracts β€” the only thing controllers and other services depend on. |
| `Application/Service` | Business logic implementations; one class per feature, extends `LocalizableServiceBase<T>` (or `ServiceBase<T>`), talks to `IUnitOfWorkProvider` + provider interfaces, returns `ResultData<T>`. |
| `Infrastructure/Interface` | Contracts for external integrations (`IFileProvider`, `IEmailProvider`, `ICaptchaProvider`, `IPaymentGatewayProvider`, `ICurrencyConverterProvider`, ...) plus `IEntityContext`. |
| `Infrastructure/Interface` | Contracts for external integrations (`IFileProvider`, `IEmailProvider`, `ICaptchaProvider`, `IPaymentGatewayProvider`, `ICurrencyConverterProvider`, `IHeadlessBrowserRenderProvider`, ...) plus `IEntityContext`. |
| `Infrastructure/Infrastructure` | EF `ApplicationDBContext` (`src/Infrastructure/Infrastructure/EntityFramework/Context/ApplicationDBContext.cs`), 215 migration files, and concrete provider implementations grouped by kind under `Provider/`. |
| `Presentation/ViewModel` | Request/response view models with `GamaEdtech.Common.DataAnnotation` validation attributes (e.g. `[Display]`), one folder per feature. |
| `Presentation/Api` | ASP.NET Core host: `Startup.cs`, `Program.cs`, public `Controllers/`, and `Areas/Admin` + `Areas/Finance` controllers. |
Expand Down
Loading