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
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,20 @@
},
"devDependencies": {
"concurrently": "9.2.1"
},
"pnpm": {
"overrides": {
"minimatch@<4": "3.1.5",
"minimatch@>=9.0.0 <10.0.0": "9.0.9",
"minimatch@>=10.0.0": "10.2.4",
"rollup@>=4.0.0 <4.59.0": "4.59.0",
"multer@<2.1.0": "2.1.0",
"serialize-javascript@<7.0.3": "7.0.4",
"ajv@>=6.0.0 <6.14.0": "6.14.0",
"ajv@>=8.0.0 <8.18.0": "8.18.0",
"diff@>=4.0.0 <4.0.4": "4.0.4",
"qs@>=6.7.0 <6.14.2": "6.14.2",
"hono@<4.11.10": "4.11.10"
}
}
}
3,909 changes: 2,095 additions & 1,814 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions teammapper-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ Copy `.env.default` to `.env` and configure the variables below.
| `POSTGRES_TEST_PASSWORD` | Test database password | - |
| `POSTGRES_TEST_DATABASE` | Test database name | - |

## Static File Serving & SPA Routing

In production, the compiled frontend is copied into the backend's `client/` directory and served by NestJS:

- **`/assets/*`** — Served directly by Express static middleware with cache headers (24h for images/fonts).
- **All other known routes** — Served by `ServeStaticModule` which returns `index.html` for client-side routing.

The `ServeStaticModule` uses a `renderPath` regex to restrict the SPA fallback to known frontend routes only:

```
/ → About page
/map → Map landing
/map/:id → Map editor
/app/settings → Settings
/app/shortcuts → Shortcuts
```

**Why `renderPath` is needed:** Without it, any unknown path returns `index.html` with HTTP 200. This causes bots and crawlers that ignore `<base href="/">` to resolve the relative asset paths in the HTML against the current URL, creating infinitely nesting request loops (e.g. `/map/assets/icons/assets/icons/...`).

**When adding new frontend routes:** If a new top-level route is added to the Angular router (in `root.routes.ts`), the `renderPath` regex in `app.module.ts` must be updated to include it.

## Typeorm
For a list of commands check https://github.com/typeorm/typeorm/blob/master/docs/using-cli.md

Expand Down
14 changes: 7 additions & 7 deletions teammapper-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,20 @@
"@golevelup/ts-jest": "^1.2.1",
"@jest/globals": "^30.2.0",
"@nestjs/schematics": "^11.0.9",
"@nestjs/testing": "^11.1.13",
"@stylistic/eslint-plugin": "^5.8.0",
"@nestjs/testing": "^11.1.14",
"@stylistic/eslint-plugin": "^5.9.0",
"@types/cache-manager": "5.0.0",
"@types/cookie-parser": "^1.4.10",
"@types/cron": "^2.4.3",
"@types/express": "^5.0.6",
"@types/express-serve-static-core": "^5.1.1",
"@types/jest": "30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.2.3",
"@types/node": "^25.3.3",
"@types/supertest": "^6.0.3",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.55.0",
"@typescript-eslint/parser": "^8.55.0",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "4.4.4",
Expand All @@ -95,7 +95,7 @@
"eslint-plugin-nestjs": "1.2.3",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-typeorm": "0.0.19",
"globals": "^17.3.0",
"globals": "^17.4.0",
"jest": "30.2.0",
"prettier": "^3.8.1",
"socket.io-client": "^4.8.3",
Expand All @@ -105,7 +105,7 @@
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0"
"typescript-eslint": "^8.56.1"
},
"jest": {
"moduleFileExtensions": [
Expand Down
6 changes: 6 additions & 0 deletions teammapper-backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import { SettingsModule } from './settings/settings.module'
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'client', 'browser'),
exclude: ['/assets/'],
// Only serve index.html for known SPA routes to prevent infinite
// request loops. Without this, any unknown path (e.g.
// /map/assets/icons/foo.png) returns index.html with HTTP 200,
// causing bots/crawlers that ignore <base href> to recursively
// resolve relative paths into ever-deeper nested URLs.
renderPath: /^\/($|map(\/[^/]*)?$|app\/(settings|shortcuts)$)/,
}),
],
})
Expand Down
50 changes: 25 additions & 25 deletions teammapper-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,19 @@
"format": "npx prettier . --write"
},
"dependencies": {
"@angular-devkit/build-angular": "21.1.4",
"@angular/animations": "21.1.4",
"@angular/cdk": "21.1.4",
"@angular/cli": "21.1.4",
"@angular/common": "21.1.4",
"@angular/compiler": "21.1.4",
"@angular/compiler-cli": "21.1.4",
"@angular/core": "21.1.4",
"@angular/forms": "21.1.4",
"@angular/material": "21.1.4",
"@angular/platform-browser": "21.1.4",
"@angular/platform-browser-dynamic": "21.1.4",
"@angular/router": "21.1.4",
"@angular-devkit/build-angular": "21.2.0",
"@angular/animations": "21.2.0",
"@angular/cdk": "21.2.0",
"@angular/cli": "21.2.0",
"@angular/common": "21.2.0",
"@angular/compiler": "21.2.0",
"@angular/compiler-cli": "21.2.0",
"@angular/core": "21.2.0",
"@angular/forms": "21.2.0",
"@angular/material": "21.2.0",
"@angular/platform-browser": "21.2.0",
"@angular/platform-browser-dynamic": "21.2.0",
"@angular/router": "21.2.0",
"@fortawesome/angular-fontawesome": "^4.0.0",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
Expand All @@ -66,7 +66,7 @@
"d3": "7.9.0",
"deep-object-diff": "^1.1.9",
"dompurify": "3.3.1",
"jspdf": "^4.1.0",
"jspdf": "^4.2.0",
"localforage": "1.10.0",
"ngx-color-picker": "^20.1.1",
"ngx-toastr": "^19.1.0",
Expand All @@ -81,40 +81,40 @@
},
"devDependencies": {
"@angular-builders/jest": "^21.0.3",
"@angular-devkit/architect": "0.2101.4",
"@angular-devkit/core": "21.1.4",
"@angular-devkit/schematics": "21.1.4",
"@angular-devkit/architect": "0.2102.0",
"@angular-devkit/core": "21.2.0",
"@angular-devkit/schematics": "21.2.0",
"@angular-eslint/builder": "21.2.0",
"@angular-eslint/eslint-plugin": "21.2.0",
"@angular-eslint/eslint-plugin-template": "21.2.0",
"@angular-eslint/schematics": "21.2.0",
"@angular-eslint/template-parser": "21.2.0",
"@angular/language-service": "21.1.4",
"@angular/language-service": "21.2.0",
"@compodoc/compodoc": "^1.2.1",
"@eslint/js": "^9.39.2",
"@playwright/test": "^1.58.2",
"@schematics/angular": "^21.1.4",
"@schematics/angular": "^21.2.0",
"@types/d3": "7.4.3",
"@types/jest": "30.0.0",
"@types/mousetrap": "1.6.15",
"@types/node": "^25.2.3",
"@typescript-eslint/eslint-plugin": "^8.55.0",
"@typescript-eslint/parser": "^8.55.0",
"@types/node": "^25.3.3",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"angular-eslint": "^21.2.0",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.15.0",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.3.0",
"globals": "^17.4.0",
"jest": "^30.2.0",
"jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^30.2.0",
"jest-preset-angular": "^16.0.0",
"jest-preset-angular": "^16.1.1",
"minimist": "^1.2.8",
"prettier": "^3.8.1",
"ts-node": "~10.9.2",
"typescript": "~5.9.3",
"typescript-eslint": "^8.55.0"
"typescript-eslint": "^8.56.1"
},
"optionalDependencies": {
"@nx/nx-darwin-arm64": "22.5.1",
Expand Down
Loading