External systems this frontend talks to at runtime, plus the configuration surfaces that control them.
The single most important external dependency. Configured by environment.resourceApiURI and consumed via ApiService (src/app/services/api.service.ts), which prepends the base URL to every request.
| Configuration | Base URL |
|---|---|
production |
https://api.techinterview.space |
staging |
(environment.staging.ts) |
development (default npm start) |
https://api.techinterview.space — production, by design or by accident; treat as production. |
local (npm run start:local) |
https://localhost:5001 |
Endpoints used by the frontend follow the convention /api/<area>/<action>. Examples grounded in source:
/api/account/me,/api/account/my-salaries,/api/account/check-totp,/api/account/unsubscribe-me/:token—AuthorizationService./api/auth/login,/api/auth/register,/api/auth/refresh,/api/auth/logout,/api/auth/forgot-password,/api/auth/reset-password,/api/auth/resend-verification,/api/auth/google,/api/auth/github—AuthApiService.- Feature-area services under
src/app/services/follow the same pattern (companies, surveys, salaries, interviews, telegram, github, m2m clients, etc.).
For exact paths, read the relevant service. We do not maintain an endpoint inventory in docs because the source is authoritative and routes change.
The backend returns JSON-shaped errors documented by the zod schema in AuthInterceptor:
{ Status, ExceptionType, InnerExceptionMessage, Message, RequestId, StackTrace }The interceptor surfaces Message via AlertService for 400s. 403 redirects to /no-permission. 401 triggers token refresh (see authentication.md). 0 (network) redirects to /server-unavailable.
DateParserInterceptor (registered third in AppModule) walks every JSON response body and converts ISO-8601 strings to Date instances. Don't re-parse dates in feature code; the interceptor already did it.
Google and GitHub OAuth do not run in the browser directly — the frontend only redirects to the backend, which mediates the OAuth flow and returns to /auth-callback with tokens.
- Initiation:
AuthApiService.getGoogleAuthUrl(returnUrl)andgetGitHubAuthUrl(returnUrl)build the URL withfrontendReturnUrlquery string. - Callback:
/auth-callback(SPA route) reads tokens from the URL and hands them toAuthService.handleOAuthCallback(...).
User-uploaded and shared imagery is served from DigitalOcean Spaces:
https://techinterview.fra1.cdn.digitaloceanspaces.com
Referenced by MetaTagService.DEFAULT_IMAGE_URL and allowed in the SSR Content-Security-Policy img-src directive (src/server.ts). When introducing new image hosts, update both places.
Wired via ngx-google-analytics. The provider is per-environment:
production—NgxGoogleAnalyticsModule.forRoot('G-6GS9Y7GJD3')plusNgxGoogleAnalyticsRouterModule(src/environments/environment.prod.ts).development,local,staging—googleAnalytics.imports: []. No analytics in dev/local.
AppModule spreads environment.googleAnalytics.imports into its imports array, so a non-prod build registers nothing.
The CSP in src/server.ts allows googletagmanager.com, google-analytics.com, and *.analytics.google.com for script-src, connect-src, and img-src.
To compare human vs search-crawler vs AI-agent traffic, both sides emit a custom GA4 event visitor_hit carrying a visitor_type param (event-scoped custom dimension registered in GA4):
- Humans —
VisitorAnalyticsService(src/app/services/visitor-analytics.service.ts) firesvisitor_hitwithvisitor_type='human'on each client-side navigation, via the existingGoogleAnalyticsService. Active only when GA is configured (production). - Bots — client-side GA never runs for non-JS clients, so the SSR server sends
visitor_hitfor search-crawler / ai-agent requests via the GA4 Measurement Protocol — an outboundPOST https://www.google-analytics.com/mp/collectfromsrc/server/ga-measurement-protocol.util.ts. Fire-and-forget, env-gated onGA_MP_API_SECRET(no-op when unset). The bot User-Agent is deliberately NOT forwarded, otherwise GA4's automatic bot filter drops the hit. This is a server→server call, not subject to the browser CSP.
Manual GA4 setup (Measurement Protocol API secret + visitor_type custom dimension) is documented in ga-ssr-visitor-type.md.
In production the Angular app is served from this Node process. It is also an external surface in its own right, since it sets the headers that ship to browsers:
- Security headers (every response):
X-Frame-Options: SAMEORIGINStrict-Transport-Security: max-age=31536000; includeSubDomainsX-Content-Type-Options: nosniffContent-Security-Policy— see below.
Content-Security-Policyallows: self for everything; inline scripts/styles (Angular hydration), Google Tag Manager, Google Analytics, Google Fonts, jsDelivr (icons), DigitalOcean Spaces CDN, Google user-content avatars,data:images, andconnect-srcto the production API. When adding a third-party script/style/host, update this header and verify the prod build still loads.Linkheader (RFC 8288) — every response advertises</.well-known/api-catalog>; rel="api-catalog"and</.well-known/agent-skills/index.json>; rel="agent-skills"so AI agents can discover the API surface and skill index without scraping HTML. If you add a new agent-relevant well-known resource, add it here too.- Compression —
compression()middleware on text responses. - Static assets — served from
dist/interviewer/browserwithmaxAge: '1y'and no fallback index. Hashing in production builds (outputHashing: 'all') makes long caching safe. /sitemap.xml— redirected to the canonical sitemap byredirectToCanonicalSitemap(src/server/sitemap-redirect.util.ts)./.well-known/api-catalog— RFC 9727 linkset pointing at the backend OpenAPI spec (/swagger/v1/swagger.json), Swagger UI (/), and/health. Content lives insrc/server/well-known.ts. Served asapplication/linkset+jsonwith a 1-day s-maxage./.well-known/agent-skills/index.json— Agent Skills Discovery RFC v0.2.0 index. Currently exposes onesite-overviewskill. The skill markdown is hashed at startup; bumping the markdown will change thesha256automatically./.well-known/agent-skills/site-overview/SKILL.md— plain markdown describing site surfaces, served astext/markdown.- Markdown content negotiation — when a client sends
Accept: text/markdown, the SSR handler converts the rendered HTML to markdown viaturndownand returnsContent-Type: text/markdown; charset=utf-8with anX-Markdown-Tokensheader (byte length, used as a coarse token proxy). Browsers without an explicit markdown accept still get HTML. The handler also setsVary: Acceptso caches keep the two representations separate. - Visitor classification — every request is classified human / search-crawler / ai-agent (
src/server/visitor-type.util.ts); the result is set onres.locals.visitorTypeand theX-Visitor-Typeheader, logged for non-human visitors, and (for bots) sent to GA4 via the Measurement Protocol (see Google Analytics section). - All other routes —
Cache-Control: public, max-age=300, s-maxage=3600and the Angular SSR engine. - Port —
process.env.PORT || 4000.
- Docker registry — DigitalOcean container registry. Image is built and pushed on every push to
main(.github/workflows/deploy.yml). - Deploy host — a single VM accessed over SSH (
appleboy/ssh-action). The workflowscpsdocker-compose.frontend.yml, runsdocker compose pull, thendocker compose up -d. Secrets used:DO_TOKEN,CR,IP,PORT,SSH_KEY_PRIVATE.
No CDN or edge proxy is configured at the application layer.
Several runtime dependencies inspect window, document, or navigator at module evaluation time. They are why src/ssr-polyfills.ts exists. When adding a dependency, run a server-side build (npm run build && npm run serve:ssr:interviewer) and load an SSR route to confirm the server can boot.
| Setting | Where it lives | What changes |
|---|---|---|
| Backend API URL | environment*.ts → resourceApiURI |
All HTTP calls |
| OAuth callback | environment*.ts → auth.redirectUri |
OAuth callback URL |
| Logout callback | environment*.ts → auth.postLogoutRedirectUri |
Backend logout redirect |
| Google Analytics tag | environment*.ts → googleAnalytics.imports |
GA wiring |
| Maintenance mode | environment*.ts → isUnderMaintenance |
Hijacks router to /maintenance |
| Active environment | angular.json → architect.build.configurations.<config>.fileReplacements |
Which environment.ts is bundled |
| SSR port | process.env.PORT (default 4000) |
Listening port |
| GA4 Measurement Protocol secret | process.env.GA_MP_API_SECRET (GitHub Actions secret → SSH deploy env → docker-compose substitution) |
Enables server-side visitor_hit sends; unset = no-op |
| CSP / security headers | src/server.ts |
Allowed third-party origins |
| Production bundle budgets | angular.json → configurations.production.budgets |
Build fails if exceeded |