diff --git a/.github/instructions/configuration.instructions.md b/.github/instructions/configuration.instructions.md new file mode 100644 index 0000000..c1cfd77 --- /dev/null +++ b/.github/instructions/configuration.instructions.md @@ -0,0 +1,39 @@ +--- +applyTo: "Fuchs/**,Fuchs_DataService/**" +--- + +# Configuration & Secrets Instructions + +## Settings Source +- All application settings live in `Fuchs/appsettings.json`. **Never** use `Web.config` or `System.Configuration.ConfigurationManager` for app settings. +- App-specific settings are nested under the `"Fuchs"` key, e.g. `_config["Fuchs:SMS_APIKey"]`. +- Connection strings live under the standard `"ConnectionStrings"` key and are read via `IConfiguration.GetConnectionString(...)`. +- `appsettings.Development.json` (git-ignored) overrides secrets for local development. + +## Startup Order +- Call `FuchsOcmsIntranet.Initialize(configuration)` at app start in `Program.cs` **before** DI registration. +- `Fuchs_intranet` receives `IConfiguration` via its constructor — inject it, never read config statically. + +## Azure Key Vault — Secret Naming +Secret names must satisfy `^[0-9a-zA-Z-]+$` (alphanumerics and hyphens only; no underscores, dots, or spaces). + +- Hierarchy levels are separated by `--` (double hyphen), which maps to `:` in `IConfiguration`. +- Underscores within a name segment are encoded as a single `-` in Key Vault and decoded back to `_` when the key is reconstructed. +- The app prefix `fuchs` is prepended to every secret name. +- Format: `{appname}--{Section}--{key-with-hyphens-for-underscores}` + +### Examples +| Key Vault name | `IConfiguration` key | +|----------------|----------------------| +| `fuchs--ConnectionStrings--ocms-ConnectionString` | `ConnectionStrings:ocms_ConnectionString` | +| `fuchs--Fuchs--SMS-APIKey` | `Fuchs:SMS_APIKey` | +| `fuchs--Fuchs--Email--Main--password` | `Fuchs:Email:Main:password` | + +## Adding a New Secret +1. Replace every `_` in the original config key with `-` for the Key Vault name. +2. Add the entry to `ManagedSecretKeys` in `appsettings.json` using the same hyphenated form **without** the `fuchs--` prefix. +3. Read it through `IConfiguration` with the underscore form (`Fuchs:SMS_APIKey`). + +## Secret Management Wiring +- Secret management is provided by `OCORE_web.Secrets.SecretManagementWebExtensions.AddSecretManagement(...)` (called in `Program.cs`). +- Do **not** create a local `SecretManagementExtensions` stub in Fuchs — it collides with the OCORE_web extension and causes ambiguous extension-method resolution. diff --git a/.github/instructions/controllers.instructions.md b/.github/instructions/controllers.instructions.md new file mode 100644 index 0000000..b7099f9 --- /dev/null +++ b/.github/instructions/controllers.instructions.md @@ -0,0 +1,43 @@ +--- +applyTo: "Fuchs/Controllers/**,Fuchs/code/**" +--- + +# IntranetController Instructions + +## Overview +The Fuchs intranet **is** the entire website, served from `/`. There is a single MVC controller, `IntranetController`, split into **partial files** by domain. There are no areas. + +## Routing +| Route | Action | Purpose | +|-------|--------|---------| +| `/{fn?}/{id?}/{code?}` | `Index` | Returns the SPA shell view `intranet`. `[AllowAnonymous]`. | +| `/do/{fn?}/{id?}/{code?}` | `Do` | Central API dispatcher (GET + POST). `[AllowAnonymous]`, gated internally. | + +## Partial File Layout +Each partial lives in `Fuchs/Controllers/` and maps to a VB original under `Fuchs/code/`: + +| Partial | Domain | Dispatch entry | +|---------|--------|----------------| +| `IntranetController.cs` | Core: routing, auth, login/logout, account, MFR | `Do(...)` switch | +| `IntranetController.Invoices.cs` / `.Invoices2.cs` | Invoices | `Do_Process_Invoices` | +| `IntranetController.Reminder.cs` | Reminders | `Do_Process_Reminder` | +| `IntranetController.Requests.cs` | Requests | `Do_Process_Requests` | +| `IntranetController.Reports.cs` | Reports | `Do_Process_Reports` | +| `IntranetController.Banking.cs` | Banking (MT940) | `Do_Process_Bankings` | + +## Dispatcher Pattern +- `Do(...)` normalizes `fn`/`id`/`code` (lowercase `fn`, null-coalesce others) then routes via a `switch` expression to `Do_Process_*` helpers or inline `Handle*` methods. +- Each domain handler is itself a `switch` on `id` (the sub-function) returning an `IActionResult`. +- Wrap the whole dispatch in a single `try/catch`; log via `_intranet.debug_log(...)` and return `ServerError()` on unhandled exceptions. Do not add per-case try/catch unless a case needs special recovery. + +## Authentication Gate +- The unauthenticated allow-list logic in `Do(...)` must keep its braces: unauthenticated users are rejected with `Unauthorized401()` **only** when the function is not in `_allowedNonAuth`, not `login`/`logout`, and not in `_allowedGet` (checked as both `fn` and `fn|id`). +- Add new anonymous endpoints by extending `_allowedNonAuth` (full-anonymous) or `_allowedGet` (read-only GET links), never by removing the gate. + +## Conventions +- Use the `StdParamlist(...)` helpers to build `SqlParameter` lists; they pre-populate `@authuser` from `UserAccountID`. +- Use `SqlOpt(fn, id, code)` to pass `FIS_SQLOptions` to OCORE SQL helpers. +- Use `DbSec` (`_intranet.GetDbSecurity(UserAccountID)`) for the `Security:` argument on SQL calls. +- Return JSON via the OCORE `JSONAsync(...)` helper, not `Json(...)`. +- Use the status helpers `Unauthorized401()`, `BadRequest400()`, `ServerError(...)` rather than raw `StatusCode(...)`. +- All controller actions that perform I/O must be `async Task`. diff --git a/.github/instructions/csharp.instructions.md b/.github/instructions/csharp.instructions.md new file mode 100644 index 0000000..8e77054 --- /dev/null +++ b/.github/instructions/csharp.instructions.md @@ -0,0 +1,29 @@ +--- +applyTo: "**/*.cs" +--- + +# C# Coding Standards + +## Language & Target +- All code must be written in C# targeting **.NET 10**. +- The original Fuchs intranet was VB.NET; any remaining VB must be converted to C# during migration. Original VB reference lives at `D:\My Programming\PWProjects\Fuchs\Fuchs\Areas\Intranet`. + +## Style +- Follow standard C# naming: **PascalCase** for classes and methods, **camelCase** for locals and parameters, `_camelCase` for private fields. +- `ImplicitUsings` and `Nullable` are enabled in the Fuchs project — honor nullable annotations and avoid redundant `using` directives. +- Prefer modern, performance-oriented features: `async`/`await` for I/O, LINQ for data manipulation, dependency injection for testability, `switch` expressions, target-typed `new`, and collection initializers. +- Only add comments when they match the existing style or explain non-obvious logic. Do not over-comment. + +## File Size +- Keep files to a soft limit of **400** lines (hard max **600**). +- Proactively refactor larger files into smaller, focused classes/partials — this is why `IntranetController` is split into domain partials. + +## Dependency Injection +- Inject dependencies via constructor (`ILogger`, `IConfiguration`, services). Do not use service-locator or static singletons in new class-level code. +- Library classes accept optional loggers (`ILogger?`) defaulting to `NullLogger.Instance` — see `logging.instructions.md`. + +## Packages +- Do not upgrade `Spire.PDF` beyond `8.10.5`. +- For builds failing because `SixLabors.ImageSharp` (v4.0.0+) requires a license, see `imagesharp.instructions.md` before downgrading. +- Keep `MailKit`/`MimeKit` versions aligned with OCORE's referenced versions to avoid `NU1605` package-downgrade-as-error. +- Only add or update packages when necessary; prefer existing/OCORE libraries. diff --git a/.github/instructions/imagesharp.instructions.md b/.github/instructions/imagesharp.instructions.md new file mode 100644 index 0000000..7d5aafc --- /dev/null +++ b/.github/instructions/imagesharp.instructions.md @@ -0,0 +1,29 @@ +--- +applyTo: "Fuchs/**,OCORE/**,OCORE_web/**,OCORE_web_pdf/**" +--- + +# SixLabors ImageSharp Instructions + +## Licensing (v4.0.0+) +- `SixLabors.ImageSharp` v4.0.0+ requires a license; builds fail validation without one. +- **Do not downgrade ImageSharp** to avoid the license requirement. +- Each project that references ImageSharp needs its own discoverable `sixlabors.lic` file in the project root. +- Tracked license files exist for `OCORE`, `OCORE_web`, and `Fuchs`. Use `OCORE_web/OCORE_web/sixlabors.lic` as the canonical Community-license template. +- Do **not** create untracked local duplicates of `sixlabors.lic` — an untracked copy blocks `git pull` when upstream adds a tracked one. + +## License File Format +A single-line Community license payload: +``` +Id=...;Kind=Community;ExpiryDateUtc=...;Key=... +``` + +## v4.0.0 API / Namespace Changes +When updating or fixing ImageSharp-related code, account for these breaking moves: + +| Removed / old | Use instead | +|---------------|-------------| +| `using SixLabors.ImageSharp.ColorSpaces;` | Removed — delete if unused | +| `using SixLabors.ImageSharp.Web.DependencyInjection;` | `using SixLabors.ImageSharp.Web;` | + +- `AddImageSharp(...)` / `UseImageSharp()` resolve from `SixLabors.ImageSharp.Web` (plus `.Commands` and `.Processors` for related types). +- After any ImageSharp version change, rebuild and confirm middleware bootstrap in `OCORE_web/OCORE_web/web/OCORE_appbuilder.cs` still compiles. diff --git a/.github/instructions/ocore.instructions.md b/.github/instructions/ocore.instructions.md new file mode 100644 index 0000000..710fe27 --- /dev/null +++ b/.github/instructions/ocore.instructions.md @@ -0,0 +1,32 @@ +--- +applyTo: "Fuchs/**,Fuchs_DataService/**,OCORE/**,OCORE_web/**,OCORE_web_pdf/**" +--- + +# OCORE Libraries Instructions + +## Preferred Libraries +- Make use of **OCORE** / **OCORE_web** libraries wherever possible for common tasks: logging, configuration, data access, web helpers, and security. +- For PDF-related work, prefer **OCORE_web_pdf** / OCORE PDF functions over rewriting from scratch. +- **Do not use OCMS or OCMS_sharp.** Use only OCORE or OCORE_web. In particular, never use `OCMS.ocms_debug.debug_log`. + +## Common OCORE Entry Points +| Area | Namespace / helper | +|------|--------------------| +| SQL access | `OCORE.SQL.sql` (`getSQLDatatable_async`, `getSQLValue_async`, `SQL_VarChar`, ...) | +| Async MVC/JSON | `OCORE.web.mvc_helper_async` (`JSONAsync`, ...) | +| Security | `OCORE.security` (`DatabaseSecurity`, ...) | +| Dictionaries/commons | `OCORE.commons`, `OCORE.OCORE_dictionaries` | +| Web bootstrapping | `OCORE_web` app-builder / middleware helpers | +| Secret management | `OCORE_web.Secrets.SecretManagementWebExtensions` | + +## SQL Helper Conventions +- Pass parameters using `SQL_VarChar(...)` and the controller `StdParamlist(...)` helpers (auto-injects `@authuser`). +- Always pass `Security: DbSec` and `options: SqlOpt(fn, id, code)` to the OCORE SQL helpers. +- Prefer the `_async` variants for all database I/O. + +## Repository Sync +- OCORE, OCORE_web, OCORE_web_pdf, and OCORE_Charting are separate git repositories pulled alongside Fuchs. +- After pulling OCORE* updates, rebuild and re-run tests, and re-check shared dependency versions (e.g., MailKit/MimeKit) for `NU1605` downgrade conflicts. + +## ImageSharp (used transitively by OCORE/OCORE_web) +- See `imagesharp.instructions.md` for license handling and the v4.0.0 namespace changes. diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md new file mode 100644 index 0000000..aad7a8d --- /dev/null +++ b/.github/instructions/testing.instructions.md @@ -0,0 +1,24 @@ +--- +applyTo: "Fuchs.Tests/**,**/*Tests/**,**/*Tests.cs" +--- + +# Testing Instructions + +## Framework +- Tests use **xUnit** on **.NET 10** (e.g., `Fuchs.Tests`). +- Run via Visual Studio Test Explorer or `dotnet test`. + +## Conventions +- One test class per unit under test; name it `Tests`. +- Name test methods `Method_Scenario_ExpectedResult` (e.g., `ReadBalance_EmptyString_ThrowsInvalidDataException`). +- Use `[Theory]` + `[InlineData]` for parameterized cases; `[Fact]` for single cases. +- Arrange / Act / Assert structure; keep tests deterministic and independent (no shared mutable state, no real network/DB calls). + +## What to Cover +- Pure parsing/algorithmic helpers (MT940 parsing, date/decimal parsing, HTML cleanup, OData envelope handling, entity helpers) — these are the highest-value, side-effect-free targets. +- For library classes that accept `ILogger?`, pass `NullLogger.Instance` (or a test logger) in tests. + +## Workflow +- After any code change, build the full solution and run the affected test project before concluding. +- Treat a green run (e.g., `Fuchs.Tests` all passing) as the validation gate; never leave the suite red. +- When fixing a bug, add or update a test that reproduces it where practical.