# Technical documentation — prsa_cio

Evidence date: 2026-10-06.

Source: `git@bitbucket.org:overdrive/prsa_cio.git`, `master` at `2832986` (2023-09-20).

Inspected by cloning `origin/master`. The source tree was not modified.

## Stack

| Item | Value | Evidence |
|---|---|---|
| Language | C# | Projects under `src/` |
| SDK style | `Microsoft.NET.Sdk` and `Microsoft.NET.Sdk.Web` | `.csproj` files |
| Target framework | `net6.0` on all four projects | `.csproj` |
| Web UI | ASP.NET Core Razor Pages | `PRSA.App` |
| API | ASP.NET Core controllers | `PRSA.API` |
| ORM | EF Core 7.0.10, SQL Server provider | `PRSA.DAL.csproj` |
| EF model origin | EF Core Power Tools, `CodeGenerationMode` 2, fluent API only, database names preserved | `efpt.config.json` and the auto-generated header on `PRSAContext.cs` |
| API docs | Swashbuckle.AspNetCore 6.5.0 | `PRSA.API.csproj` |
| Razor | Runtime compilation 6.0.21 | `PRSA.App.csproj` |
| Excel | COM interop, embed interop types, version 1.9, guid `00020813-0000-0000-c000-000000000046` | `PRSA.Services.csproj` |
| UI kit on disk | AdminLTE, Bootstrap, jQuery, jquery-validation, Font Awesome (`all.min.css`) | `wwwroot` |
| UI kit on the live page | Bootstrap 4.5.2, jQuery 3.6.0, DataTables 1.11.5 from public CDNs | `Index.cshtml` |
| IDE marker | Visual Studio 17, solution format 12 | `prsa_cio.sln` |
| CI image | `mcr.microsoft.com/dotnet/sdk:7.0` | `bitbucket-pipelines.yml` |
| Scanner | `dotnet-sonarscanner` plus OpenJDK 17 JRE | Same file |

Nullable and implicit usings are enabled on all projects. `PRSAContext` and entity files are marked `#nullable disable` by the generator.

## Repository layout

```text
prsa_cio.sln
bitbucket-pipelines.yml
.gitignore
src/api/PRSA.API/          Web API host
src/app/PRSA.App/          Razor Pages host
src/bl/PRSA.Services/      File upload service
src/dal/PRSA.DAL/          EF context and entities
src/dal/PRSA.DAL/DB/       Generated entities
src/dal/PRSA.DAL/efpt.config.json
```

Solution folders and project nesting:

| Folder | Project | Path |
|---|---|---|
| 01. Web App | `PRSA.App` | `src/app/PRSA.App` |
| 02. API | `PRSA.API` | `src/api/PRSA.API` |
| 03. BL | `PRSA.Services` | `src/bl/PRSA.Services` |
| 04. DAL | `PRSA.DAL` | `src/dal/PRSA.DAL` |

Project references:

- `PRSA.API` → `PRSA.Services`
- `PRSA.Services` → `PRSA.DAL`
- `PRSA.App` → `PRSA.DAL`

`PRSA.App` does not reference `PRSA.Services`. The page cannot call `FileUploadService` without a new reference or an HTTP call. No HTTP client is registered.

Namespaces to be aware of: the service folder is `FileUpload`, and the namespace is `PRSA.Services.FileUploads` (plural). The controller lives in namespace `PRSA.API.Controllers.FileUpload` and uses the plural service namespace.

## Request path

### Web

`src/app/PRSA.App/Program.cs`:

1. Read connection string `PRSA`.
2. `AddDbContext<PRSAContext>` with `UseSqlServer`.
3. `AddRazorPages` and Razor runtime compilation.
4. Outside Development: exception handler `/Error` and HSTS.
5. HTTPS redirection, static files, routing, authorization, `MapRazorPages`.

`_ViewStart.cshtml` sets `Layout = null`, so `_Layout.cshtml` is not applied. `Index.cshtml` and `Login.cshtml` are full HTML documents. `Privacy.cshtml` is a fragment with no layout.

### API

`src/api/PRSA.API/Program.cs`:

1. Same `PRSA` connection string and `PRSAContext`.
2. Controllers.
3. `AddScoped<IFileUploadService, FileUploadService>`.
4. Swagger document title `PRSA.API`, version `v1`.
5. Swagger and Swagger UI only in Development. UI endpoint `/swagger/v1/swagger.json`, label `PRSA.API v1`.
6. HTTPS redirection, routing, authorization, `MapControllers`.

There is no CORS policy, no authentication handler, and no global exception handler.

`UseAuthorization` is present on both hosts. No `AddAuthentication` call was found, and no endpoint has `[Authorize]`.

## Routing map

| Method | Path | Handler | Notes |
|---|---|---|---|
| GET | `/` | `IndexModel.OnGet` | Reporting page |
| POST | `/` | `IndexModel.OnPostAsync` | Sets a flag. Does not save the file |
| GET | `/Login` | `LoginModel.OnGet` | No `OnPost` |
| POST | `/Index` | none declared | Sign In button `formaction`. Not executed in this inspection |
| GET | `/Privacy` | `PrivacyModel` | Template text |
| GET | `/Error` | `ErrorModel.OnGet` | Non-development exception handler. `IgnoreAntiforgeryToken`. Response cache no-store |
| GET | `/swagger` | Swagger UI | Development only. Launch profile opens it |
| GET | `api/FileUpload/getTrackerData` | `GetTrackerData` | Returns `List<TrackerModel>` |
| POST | `api/FileUpload/uploadFile` | `UploadFile(string filePath)` | Simple-type parameter. Not `[FromBody]` and not a file |

Controller route template is `api/[controller]`. The class name `FileUploadController` resolves to `FileUpload`.

## Local URLs

From `launchSettings.json`. These are developer bindings, not production hosts.

| Host | Profile | URLs |
|---|---|---|
| `PRSA.App` | Project | `https://localhost:7292` and `http://localhost:5279` |
| `PRSA.App` | IIS Express | `http://localhost:58696`, SSL port `44317` |
| `PRSA.API` | Project | `https://localhost:7122` and `http://localhost:5206`, launch URL `swagger` |
| `PRSA.API` | IIS Express | `http://localhost:1519`, SSL port `44389`, launch URL `swagger` |

Both IIS Express blocks set `windowsAuthentication` false and `anonymousAuthentication` true.

`ASPNETCORE_ENVIRONMENT` is `Development` in these profiles.

## Database

Provider: SQL Server.

Connection string key: `ConnectionStrings:PRSA`.

`IncludeConnectionString` in `efpt.config.json` is false, so the scaffold did not write a connection string into the generated context. `OnConfiguring` is not overridden. The hosts supply the connection through `DbContextOptions`.

Design-time hint: `UiHint` value `nb-dewald.prsa_cio.dbo`. **Inferred** reading of that EF Power Tools field: server or alias `nb-dewald`, database `prsa_cio`, catalog `dbo` was the object selected in the tool. The runtime server is **Unknown / Requires Confirmation**. Git history attributes the model commit to Dewald Stander, which matches the hint prefix only as a name similarity.

Schemas and tables, all with an `id` key (EF convention; no `HasKey` call was generated):

### `Core.settings`

`key_name` required varchar(255), `value` required varchar(255), `created_by`, `created_at`, `updated_by`, `updated_at`.

### `Core.users`

`fullname`, `username`, `email`, `ad_token` required varchar(255). `role_id` int. `active` bool. Standard audit columns.

### `Imports.le3`

`sku_code`, `month`, `value` required varchar(255). `supersession` int. Audit columns.

### `Imports.mapping`

`wh`, `region`, `region2`, `bond_duty`, `check` required varchar(10). `item_no`, `rev_sa`, `rev_nam` int. Audit columns.

### `Imports.master_data`

`day`, `month` required varchar(255). `week` datetime. `concat` int. The updater column on the entity is `update_by`, not `updated_by`.

### `Imports.open_orders`

`item_number` int. `total_open` and `variance` decimal(18,2). `tracker` int. Updater column `update_by`.

### `Imports.qlikview_sales`

`item_no`, `supersession` int. `item_description` and `item_description_2pack` required varchar(255). `ytd_volumes`, `supersession_total`, `sum_ytd_volumes`, `variance`, `summary` decimal(18,2). Audit columns use `updated_by`.

### `Imports.sales_forecast`

`sku_code`, `supersession` int. `description`, `month` required varchar(255). `month_sales`, `value` decimal(18,2). `processed` bool. Updater column `update_by`.

### `Imports.stock_balance`

`code`, `item_no`, `unit`, `atp`, `supersession`, `sum_atp`, `tracker`, `variance` int. `description` and `format` required varchar(255). No decimal columns on this entity. Audit uses `updated_by`.

### `Imports.tracker`

`brand_group`, `item_description`, `pack_size`, `stocking_type` required varchar(255). `item_number` int. `stock_cover`, `stock_balance`, `fy-1`, `rf1`, `sales_forecast`, `mtd_sales`, `open_orders`, `perc_sold_incl`, `perc_sold_excl` decimal(18,2). The CLR property for column `fy-1` is `fy_1`. Audit uses `created_by`, `updated_by`, and datetime stamps.

No relationships, indexes, or check constraints are configured in `OnModelCreating`. No migrations directory exists. Schema changes are not versioned in this repository. **Inferred:** the database is maintained outside this repo, and this project only mirrors it.

`FileUploadService` is constructed with `PRSAContext`. The context is a scoped DbContext in a scoped service.

## Excel import mechanics

`UploadTrackerSheet`:

1. `new Application()` and `Workbooks.Open(filePath)`.
2. `Worksheets[1]` and `UsedRange`.
3. Build a `List<tracker>` then `AddRange` and `SaveChanges`.
4. `wb.Close()` on the success path and on the inner catch.
5. The `Application` object is not quit or released in the method.
6. `Open` sits outside the `try`. A failure there is handled by the controller `catch`, which returns `BadRequest(ex.Message)`.

COM interop embeds types so the project can compile against the interop assembly generated on a machine that has the type library. The pipeline runs on Linux (`apt-get` in the script). Whether `dotnet build` of this COM reference succeeds on that image was **not executed**. Treat pipeline build success as **Unknown / Requires Confirmation**.

## Authentication details

None implemented.

`users.ad_token` is a required string column. No code reads it.

Login inputs are not bound. There is no password column on `users`.

## Integrations

| Integration | How it appears | Code that calls it |
|---|---|---|
| SQL Server | EF Core | API tracker read/insert. Web host registers the context only |
| Excel | COM | `FileUploadService` |
| SonarQube | Pipeline `/k:"overdrive_prsa_cio_AYpSpeQrQY-eSvjrqMo7"` | `bitbucket-pipelines.yml` |
| CDN assets | Script and link tags | `Index.cshtml` |
| QlikView | Table name only | None |

No SMTP, queue, blob store, or webhook client is referenced.

## Environment

Gitignored configuration (`/.gitignore`):

- `appsettings.json`
- `appsettings.*.json`
- `secrets.json`
- `bin/`, `obj/`, `*.user`, `*.suo`, `.vs`, logs

Also ignored: `*.dll` and `/packages/`. The `*.dll` rule is broad. It does not remove the COM reference, which is declared in the project file rather than committed as a DLL.

Required configuration that is absent from Git:

| Key | Used by |
|---|---|
| `ConnectionStrings:PRSA` | Both `Program.cs` files |
| `SONAR_TOKEN` | Pipeline |
| `SONAR_HOST_URL` | Pipeline |

No `appsettings.Development.json` is committed either. Local secrets are **Unknown**.

## Security notes for operators

Observed in code, not a pentest:

- `uploadFile` opens a caller-supplied path with Excel on the server. There is no path allow-list.
- `getTrackerData` returns the full table without a credential.
- Caught import errors and controller `catch` blocks return `ex.Message` to the caller. The service's message is HTTP 200.
- The import stamps every row as user `1`.
- Login does not establish a user, so the web success message does not mean a file was stored.
- Swagger is limited to the Development environment check.
- HSTS is enabled on the web app outside Development. The API does not call `UseHsts`.
- No rate limiting, CORS, or CSRF attributes were added by hand. Razor form tag helpers are enabled, so the upload form on `/` gets an antiforgery token. The API does not use antiforgery.

## Background work

No hosted service, `IHostedService`, Hangfire, queue, or cron entry exists.

`sales_forecast.processed` would fit a batch flag. Nothing in this repo updates it.

## Tests and quality

- No test project in `prsa_cio.sln`.
- Pipeline script is `dotnet sonarscanner begin`, `dotnet build`, `dotnet sonarscanner end`.
- Caches: `dotnetcore` and `sonar` (`~/.sonar`).
- Branch filters: `'{master}'` and `'{development}'`.
- The Sonar project key is in the YAML. A `sonar-project.properties` file is not in the repository. The 2026-09-28 onboarding inventory named that file and `sonarqube_scan.bat` on a scan-host working copy. They have no Git history here. If they exist only as uncommitted files on that host, they were not part of `2832986`.

## Local development

No `README` and no documented runbook. From the project files, the two hosts are separate processes:

- `src/app/PRSA.App`
- `src/api/PRSA.API`

Both expect `ConnectionStrings:PRSA` at startup because `UseSqlServer` is called while building services. A connection string was not added during this documentation pass, and the applications were not started.

Excel import additionally needs the Excel COM server on the API machine. That dependency is not in the pipeline image definition.

## Git history notes

| Commit | Effect on structure |
|---|---|
| `9fd87b3` | `.gitignore` only |
| `ea57f0d` | Solution, four projects, generated EF model, AdminLTE pages, placeholder `Class1.cs` |
| `3a4de40` | Replaced the index with the reporting page. Small login edit |
| `20da76a` | Removed `Class1.cs`. Added controller, service, `TrackerModel`, Excel reference. Adjusted `tracker` entity and context |
| `6d47763` | Added pipeline (master step) |
| `2832986` | Added the `development` branch step (three lines) |

Authors recorded by Git: Jaco Brits (`jaco@overdrive.co.za`), Dewald Stander (`dewald@overdrive.co.za`), silmaanie (`maanie@overdrive.co.za`).

## Related documents in Silicon Ark

- `prsa_cio/project-plan.md`
- `prsa_cio/prd.md`
- `prsa_cio/features.md`
- `prsa_cio/business-rules.md`

No earlier Silicon Ark folder for this repository was present at the time of writing.
