Skip to content
Closed
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@ Get-Content env.dev.example | ForEach-Object {
Start-Process "http://localhost:8080"
```

### Plain HTML frontend (no Node needed)

`web/index.html` is a single-file frontend with no build step. The API serves
it at `/app`, so it needs no CORS setup and no second terminal. After step 5:

```powershell
Start-Process "http://localhost:8000/app"
```

It covers import (preview, column types, new table or T&E table, skip or
replace duplicates), migrated files with a row viewer and delete, T&E table
row counts, and the deletion log. If the API runs with `API_KEY` set, enter
the same key with the key button in the page header.

---

## API Surface
Expand Down
8 changes: 8 additions & 0 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@

import logging
from contextlib import asynccontextmanager
from pathlib import Path

import psycopg2.pool
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles

from api import db
from api.config import settings
Expand Down Expand Up @@ -72,6 +74,12 @@ async def lifespan(app: FastAPI):
app.include_router(te_routes.router)
app.include_router(audit_routes.router)

# Plain HTML frontend (web/index.html), served same-origin at /app so it needs
# no CORS setup. Skipped when web/ isn't shipped (e.g. an API-only image).
WEB_DIR = Path(__file__).resolve().parent.parent / "web"
if WEB_DIR.is_dir():
app.mount("/app", StaticFiles(directory=WEB_DIR, html=True), name="web")


@app.exception_handler(psycopg2.pool.PoolError)
def pool_exhausted_handler(request: Request, exc: psycopg2.pool.PoolError) -> JSONResponse:
Expand Down
11 changes: 11 additions & 0 deletions tests/test_api_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,14 @@ def test_identifiers_are_never_string_formatted(self):
offenders.append(f"{path.name}:{node.lineno}")
self.assertEqual(offenders, [],
f"execute() called with an f-string at {offenders}")


@pytest.mark.unit
class WebFrontend(unittest.TestCase):
"""The plain HTML frontend in web/ is served same-origin at /app."""

def test_app_serves_index_html(self):
r = client.get("/app/")
self.assertEqual(r.status_code, 200)
self.assertIn("text/html", r.headers["content-type"])
self.assertIn("<title>CSV Migrator</title>", r.text)
Loading
Loading