working test deploy config
This commit is contained in:
@@ -3,8 +3,6 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
"Bash(*)",
|
"Bash(*)",
|
||||||
"Read(*)",
|
"Read(*)",
|
||||||
"Edit(*)",
|
|
||||||
"Write(*)",
|
|
||||||
"Glob(*)",
|
"Glob(*)",
|
||||||
"Grep(*)"
|
"Grep(*)"
|
||||||
],
|
],
|
||||||
@@ -12,4 +10,4 @@
|
|||||||
"/workspaces/web_app/.vscode"
|
"/workspaces/web_app/.vscode"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+8
-3
@@ -18,7 +18,12 @@ KMTN_CORS_ORIGINS='["http://localhost:4200", "http://localhost"]'
|
|||||||
# KMTN_ENV=development
|
# KMTN_ENV=development
|
||||||
|
|
||||||
# ── Frontend (frontend service) ───────────────────────────────────────
|
# ── Frontend (frontend service) ───────────────────────────────────────
|
||||||
# URL of the API backend that nginx should proxy /api/ requests to.
|
# Internal API target that nginx proxies /api/ requests to.
|
||||||
# Dev (Docker Compose): http://api:8000 (DNS resolves to the api container)
|
# In Docker Compose this is the api service DNS name.
|
||||||
# Prod: https://api.yourdomain.com (or whatever your API endpoint is)
|
|
||||||
API_UPSTREAM=http://api:8000
|
API_UPSTREAM=http://api:8000
|
||||||
|
|
||||||
|
# Browser-facing API URL. Set this to control how the Angular app reaches the API.
|
||||||
|
# "" (empty) — proxy mode: browser calls /api/* relative paths; nginx forwards to API_UPSTREAM.
|
||||||
|
# "http://yourhost:8000" — direct mode: browser calls the API container directly (CORS required).
|
||||||
|
# Use direct mode when an external ingress layer (e.g., TrueNAS middleware) breaks the nginx proxy chain.
|
||||||
|
API_PUBLIC_URL=""
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ RUN npx ng build --configuration production
|
|||||||
FROM docker.io/library/nginx:alpine
|
FROM docker.io/library/nginx:alpine
|
||||||
COPY --from=build /app/dist/radio-station/browser /usr/share/nginx/html
|
COPY --from=build /app/dist/radio-station/browser /usr/share/nginx/html
|
||||||
COPY nginx.conf.template /etc/nginx/conf.d/nginx.conf.template
|
COPY nginx.conf.template /etc/nginx/conf.d/nginx.conf.template
|
||||||
|
COPY config.json.template /usr/share/nginx/html/config.json.template
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
ENV API_UPSTREAM=http://api:8000
|
ENV API_UPSTREAM=http://api:8000
|
||||||
|
ENV API_PUBLIC_URL=""
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -67,7 +67,13 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
|||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
|
├── docker-compose.yml # Dev compose (builds from source)
|
||||||
|
├── docker-compose.prod.yml # Prod override (applied with -f docker-compose.yml -f docker-compose.prod.yml)
|
||||||
├── docker-compose.dev.yml # Dev-sidecar compose (dev container)
|
├── docker-compose.dev.yml # Dev-sidecar compose (dev container)
|
||||||
|
├── Dockerfile # Frontend: Node build stage + Nginx serve stage
|
||||||
|
├── docker-entrypoint.sh # Renders nginx config + config.json from env vars
|
||||||
|
├── nginx.conf.template # Nginx config template (envsubst)
|
||||||
|
├── config.json.template # Runtime API config template (envsubst)
|
||||||
├── .devcontainer/ # VS Code dev container
|
├── .devcontainer/ # VS Code dev container
|
||||||
│ ├── Dockerfile # Alpine Python 3.12 + Node.js + Angular CLI
|
│ ├── Dockerfile # Alpine Python 3.12 + Node.js + Angular CLI
|
||||||
│ └── devcontainer.json # Dev container config
|
│ └── devcontainer.json # Dev container config
|
||||||
@@ -75,8 +81,9 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
|||||||
├── src/ # Angular 21 frontend
|
├── src/ # Angular 21 frontend
|
||||||
│ ├── app/ # Standalone components
|
│ ├── app/ # Standalone components
|
||||||
│ │ ├── app.routes.ts # Lazy-loaded route map
|
│ │ ├── app.routes.ts # Lazy-loaded route map
|
||||||
|
│ │ ├── app.config.ts # App providers + APP_INITIALIZER (loads config.json)
|
||||||
│ │ ├── hero/ about/ schedule/ donate/ events/ contact/ navbar/ footer/
|
│ │ ├── hero/ about/ schedule/ donate/ events/ contact/ navbar/ footer/
|
||||||
│ │ ├── services/ # HTTP services (program, event, tier)
|
│ │ ├── services/ # HTTP services (program, event, tier, app-config)
|
||||||
│ │ └── interfaces/ # TypeScript types (Program, Event, Tier)
|
│ │ └── interfaces/ # TypeScript types (Program, Event, Tier)
|
||||||
│ ├── environments/ # dev/prod config (apiBaseUrl)
|
│ ├── environments/ # dev/prod config (apiBaseUrl)
|
||||||
│ └── styles/ # SCSS design tokens
|
│ └── styles/ # SCSS design tokens
|
||||||
@@ -85,6 +92,7 @@ Open [http://localhost:4200](http://localhost:4200) and ensure the backend is ru
|
|||||||
│ └── _themes.scss # SCSS → CSS custom properties
|
│ └── _themes.scss # SCSS → CSS custom properties
|
||||||
│
|
│
|
||||||
└── backend/ # FastAPI backend
|
└── backend/ # FastAPI backend
|
||||||
|
├── Dockerfile # Python 3.12-slim + gunicorn/uvicorn
|
||||||
├── requirements.txt
|
├── requirements.txt
|
||||||
├── seed.py # Initial data for all models
|
├── seed.py # Initial data for all models
|
||||||
└── app/
|
└── app/
|
||||||
@@ -130,12 +138,15 @@ curl -X POST http://localhost:8000/api/admin/reset
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
| Variable | Default | Where | Description |
|
||||||
|
|---|---|---|---|
|
||||||
| Variable | Default | Where | Description |
|
| Variable | Default | Where | Description |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `KMTN_DATABASE_URL` | `postgresql+asyncpg://...` (prod) / `sqlite+aiosqlite:///./kmountain.db` (dev) | [backend/app/config.py](backend/app/config.py) | Database connection string |
|
| `KMTN_DATABASE_URL` | `postgresql+asyncpg://...` (prod) / `sqlite+aiosqlite:///./kmountain.db` (dev) | [backend/app/config.py](backend/app/config.py) | Database connection string |
|
||||||
| `KMTN_CORS_ORIGINS` | `["http://localhost:4200"]` | [backend/app/config.py](backend/app/config.py) | CORS allowlist (JSON array) |
|
| `KMTN_CORS_ORIGINS` | `["http://localhost:4200"]` | [backend/app/config.py](backend/app/config.py) | CORS allowlist (JSON array) |
|
||||||
| `KMTN_ENV` | `development` | [backend/app/config.py](backend/app/config.py) | Set to `production` to disable admin endpoints |
|
| `KMTN_ENV` | `development` | [backend/app/config.py](backend/app/config.py) | Set to `production` to disable admin endpoints |
|
||||||
| `apiBaseUrl` | `http://localhost:8000` (dev) / `''` (prod) | [src/environments/](src/environments/) | Where the Angular app finds the API |
|
| `API_UPSTREAM` | `http://api:8000` | [Dockerfile](Dockerfile) | Internal API target for Nginx proxy (Docker DNS) |
|
||||||
|
| `API_PUBLIC_URL` | `""` (empty) | [Dockerfile](Dockerfile) | Browser-facing API URL. Empty = proxy mode; set to direct URL when ingress breaks proxy chain |
|
||||||
|
|
||||||
Environment variables are prefixed with `KMTN_` in the backend. For production, set `KMTN_DATABASE_URL` to your managed PostgreSQL connection and update `KMTN_CORS_ORIGINS` to your actual domain.
|
Environment variables are prefixed with `KMTN_` in the backend. For production, set `KMTN_DATABASE_URL` to your managed PostgreSQL connection and update `KMTN_CORS_ORIGINS` to your actual domain.
|
||||||
|
|
||||||
@@ -182,7 +193,130 @@ Full interactive API docs at [http://localhost:8000/docs](http://localhost:8000/
|
|||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
### Production
|
### Docker Compose (recommended)
|
||||||
|
|
||||||
|
The app ships as two Docker images — a frontend (Angular + Nginx) and a backend (FastAPI + gunicorn). Build, tag, and push them to your registry, then deploy with Docker Compose.
|
||||||
|
|
||||||
|
#### Build and push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Frontend
|
||||||
|
docker build -t <registry>/kmtnflower:latest .
|
||||||
|
docker push <registry>/kmtnflower:latest
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
docker build -t <registry>/kmtnflower-api:latest ./backend
|
||||||
|
docker push <registry>/kmtnflower-api:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Deploy
|
||||||
|
|
||||||
|
On the target machine, create a Docker Compose file (see [docker-compose.test.yml](docker-compose.test.yml) for a reference) that pulls the images and sets the required environment variables:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: kmountain
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: <registry>/kmtnflower-api:latest
|
||||||
|
environment:
|
||||||
|
KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||||
|
KMTN_CORS_ORIGINS: '["http://yourdomain.com"]'
|
||||||
|
KMTN_ENV: production
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: <registry>/kmtnflower:latest
|
||||||
|
environment:
|
||||||
|
API_UPSTREAM: http://api:8000
|
||||||
|
API_PUBLIC_URL: ""
|
||||||
|
ports:
|
||||||
|
- "4200:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API\_PUBLIC\_URL
|
||||||
|
|
||||||
|
The frontend container renders a `config.json` at startup from the `API_PUBLIC_URL` environment variable. This controls how the Angular app reaches the API:
|
||||||
|
|
||||||
|
| Mode | Value | When to use |
|
||||||
|
|---|---|---|
|
||||||
|
| **Proxy** (default) | `""` (empty) | Browser calls `/api/*` relative paths; Nginx forwards to `API_UPSTREAM`. Use when no external ingress interferes. |
|
||||||
|
| **Direct** | `"http://yourhost:8000"` | Browser calls the API container directly. Use when an external ingress layer (e.g., TrueNAS middleware, Kubernetes ingress) breaks the Nginx proxy chain. Requires CORS to be configured on the backend. |
|
||||||
|
|
||||||
|
#### Example: TrueNAS Scale
|
||||||
|
|
||||||
|
For a TrueNAS Scale deployment where the built-in ingress layer may redirect traffic:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: kmountain
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
api:
|
||||||
|
image: truenas.local:35000/kmtnflower-api:latest
|
||||||
|
environment:
|
||||||
|
KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||||
|
KMTN_CORS_ORIGINS: '["http://truenas.local:34200", "http://truenas.local"]'
|
||||||
|
KMTN_ENV: production
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: truenas.local:35000/kmtnflower:latest
|
||||||
|
environment:
|
||||||
|
API_UPSTREAM: http://api:8000
|
||||||
|
API_PUBLIC_URL: "http://truenas.local:8000"
|
||||||
|
ports:
|
||||||
|
- "34200:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** The database auto-seeds with initial data on first startup. To re-seed at any time (dev only): `curl -X POST http://localhost:8000/api/admin/reset`.
|
||||||
|
|
||||||
|
### Bare-metal production
|
||||||
|
|
||||||
For production, you'll need to set up:
|
For production, you'll need to set up:
|
||||||
- A PostgreSQL database (managed or self-hosted)
|
- A PostgreSQL database (managed or self-hosted)
|
||||||
|
|||||||
@@ -59,6 +59,12 @@
|
|||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
"budgets": [
|
"budgets": [
|
||||||
{
|
{
|
||||||
"type": "initial",
|
"type": "initial",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
fastapi==0.115.6
|
fastapi==0.115.6
|
||||||
uvicorn[standard]==0.34.0
|
uvicorn[standard]==0.34.0
|
||||||
|
gunicorn==23.0.0
|
||||||
psycopg2-binary==2.9.10
|
psycopg2-binary==2.9.10
|
||||||
asyncpg==0.30.0
|
asyncpg==0.30.0
|
||||||
aiosqlite==0.20.0
|
aiosqlite==0.20.0
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"apiBaseUrl": "${API_PUBLIC_URL}"
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ services:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
environment:
|
environment:
|
||||||
# Point to your API's production URL
|
# Internal API target for nginx proxy
|
||||||
API_UPSTREAM: https://api.yourdomain.com
|
API_UPSTREAM: https://api.yourdomain.com
|
||||||
|
# Browser-facing API URL. Empty = proxy mode. Set to direct URL if ingress breaks proxy chain.
|
||||||
|
API_PUBLIC_URL: ""
|
||||||
# No port mapping — traffic arrives via reverse proxy / load balancer
|
# No port mapping — traffic arrives via reverse proxy / load balancer
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
# NOTE: Change POSTGRES_PASSWORD in production. Consider using Docker secrets or
|
||||||
|
# a managed PostgreSQL service instead of local credentials.
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: kmountain
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# NOTE: Update KMTN_CORS_ORIGINS in production to your actual domain(s).
|
||||||
|
# Set KMTN_DATABASE_URL to your managed PostgreSQL connection if not using the local db service.
|
||||||
|
api:
|
||||||
|
image: truenas.local:35000/kmtnflower-api:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
KMTN_DATABASE_URL: postgresql+asyncpg://postgres:postgres@db:5432/kmountain
|
||||||
|
KMTN_CORS_ORIGINS: '["http://localhost:4200", "http://localhost"]'
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: truenas.local:35000/kmtnflower:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
API_UPSTREAM: http://api:8000
|
||||||
|
API_PUBLIC_URL: "http://truenas.local:8000"
|
||||||
|
ports:
|
||||||
|
- "4200:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
@@ -39,6 +39,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
API_UPSTREAM: http://api:8000
|
API_UPSTREAM: http://api:8000
|
||||||
|
API_PUBLIC_URL: ""
|
||||||
ports:
|
ports:
|
||||||
- "4200:80"
|
- "4200:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Render the nginx config template with environment variables, then start nginx.
|
# Render config templates with environment variables, then start nginx.
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
envsubst '${API_UPSTREAM}' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf
|
envsubst '${API_UPSTREAM}' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf
|
||||||
|
envsubst '${API_UPSTREAM},${API_PUBLIC_URL}' < /usr/share/nginx/html/config.json.template > /usr/share/nginx/html/config.json
|
||||||
|
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ server {
|
|||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass ${API_UPSTREAM};
|
proxy_pass ${API_UPSTREAM};
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header Origin $http_origin;
|
||||||
|
proxy_set_header Referer $http_referer;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import { provideRouter } from '@angular/router';
|
|||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
import { authInterceptor } from './interceptors/auth.interceptor';
|
import { authInterceptor } from './interceptors/auth.interceptor';
|
||||||
|
import {
|
||||||
|
appConfigInitProvider,
|
||||||
|
appConfigProvider,
|
||||||
|
} from './services/app-config.service';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideBrowserGlobalErrorListeners(),
|
provideBrowserGlobalErrorListeners(),
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideHttpClient(withInterceptors([authInterceptor])),
|
provideHttpClient(withInterceptors([authInterceptor])),
|
||||||
|
appConfigProvider,
|
||||||
|
appConfigInitProvider,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { APP_INITIALIZER, InjectionToken } from '@angular/core';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
|
|
||||||
|
export interface AppConfig {
|
||||||
|
apiBaseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const APP_CONFIG = new InjectionToken<AppConfig>('appConfig');
|
||||||
|
|
||||||
|
const _appConfig: AppConfig = {
|
||||||
|
apiBaseUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function appConfigFactory(http: HttpClient) {
|
||||||
|
return (): Promise<void> => {
|
||||||
|
return lastValueFrom(http.get<AppConfig>('./config.json')).then(
|
||||||
|
(config) => {
|
||||||
|
Object.assign(_appConfig, config);
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// Keep defaults if config.json is not available (dev mode).
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const appConfigProvider = {
|
||||||
|
provide: APP_CONFIG,
|
||||||
|
useValue: _appConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appConfigInitProvider = {
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
useFactory: appConfigFactory,
|
||||||
|
deps: [HttpClient],
|
||||||
|
multi: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getAppConfig(): AppConfig {
|
||||||
|
return _appConfig;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { Injectable, inject } from '@angular/core';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { BehaviorSubject, firstValueFrom, map, Observable, tap } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom, map, Observable, tap } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../environments/environment';
|
import { getAppConfig } from './app-config.service';
|
||||||
import { AppUser, AuthTokenResponse } from '../interfaces/auth';
|
import { AppUser, AuthTokenResponse } from '../interfaces/auth';
|
||||||
|
|
||||||
const TOKEN_KEY = 'kmtn_access_token';
|
const TOKEN_KEY = 'kmtn_access_token';
|
||||||
@@ -12,7 +12,7 @@ const TOKEN_KEY = 'kmtn_access_token';
|
|||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private baseUrl = `${environment.apiBaseUrl}/api/auth`;
|
private baseUrl = `${getAppConfig().apiBaseUrl}/api/auth`;
|
||||||
|
|
||||||
private authStateSubject = new BehaviorSubject<AppUser | null>(null);
|
private authStateSubject = new BehaviorSubject<AppUser | null>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { Injectable, inject } from '@angular/core';
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { Event } from '../interfaces/event';
|
import { Event } from '../interfaces/event';
|
||||||
|
import { getAppConfig } from './app-config.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class EventService {
|
export class EventService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private baseUrl = `${environment.apiBaseUrl}/api/events`;
|
private baseUrl = `${getAppConfig().apiBaseUrl}/api/events`;
|
||||||
|
|
||||||
/** Fetch events, optionally filtered by active status. */
|
/** Fetch events, optionally filtered by active status. */
|
||||||
getEvents(active?: boolean): Observable<Event[]> {
|
getEvents(active?: boolean): Observable<Event[]> {
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { Injectable, inject } from '@angular/core';
|
|||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { Program } from '../interfaces/program';
|
import { Program } from '../interfaces/program';
|
||||||
|
import { getAppConfig } from './app-config.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ProgramService {
|
export class ProgramService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private baseUrl = `${environment.apiBaseUrl}/api/programs`;
|
private baseUrl = `${getAppConfig().apiBaseUrl}/api/programs`;
|
||||||
|
|
||||||
/** Fetch all programs, optionally filtered by day_of_week (1=Mon … 7=Sun). */
|
/** Fetch all programs, optionally filtered by day_of_week (1=Mon … 7=Sun). */
|
||||||
getPrograms(day?: number): Observable<Program[]> {
|
getPrograms(day?: number): Observable<Program[]> {
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import { Injectable, inject } from '@angular/core';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { Tier } from '../interfaces/tier';
|
import { Tier } from '../interfaces/tier';
|
||||||
|
import { getAppConfig } from './app-config.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class TierService {
|
export class TierService {
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private baseUrl = `${environment.apiBaseUrl}/api/tiers`;
|
private baseUrl = `${getAppConfig().apiBaseUrl}/api/tiers`;
|
||||||
|
|
||||||
/** Fetch all tiers ordered by display_order. */
|
/** Fetch all tiers ordered by display_order. */
|
||||||
getTiers(): Observable<Tier[]> {
|
getTiers(): Observable<Tier[]> {
|
||||||
|
|||||||
Reference in New Issue
Block a user