Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7be897ef2b | ||
|
|
cf40cd22ef | ||
|
|
3b02c6944f | ||
|
|
683fd37b08 | ||
|
|
14c6183aa2 |
@@ -281,13 +281,20 @@ jobs:
|
||||
standalone: true
|
||||
pull: true
|
||||
endpoint: ${{ secrets.PORTAINER_ENDPOINT_ID_GALACTUS }}
|
||||
# NOTE: the block below is parsed as JSON — no comments inside it.
|
||||
#
|
||||
# API_ORIGIN is deliberately absent. The browser derives the API origin
|
||||
# from the page it loaded (apps/web/src/lib/api.ts), so the deployment
|
||||
# survives the box moving between the tailnet, the office LAN and a
|
||||
# demo domain. Setting it here would pin it again and re-break an https
|
||||
# front door with mixed active content. APP_API_ORIGIN_GALACTUS lives
|
||||
# on only as the URL the verify step probes.
|
||||
env_data: |
|
||||
{
|
||||
"APP_TAG": "${{ github.event.inputs.tag }}",
|
||||
"API_PORT": "3001",
|
||||
"WEB_PORT": "3000",
|
||||
"S3_BUCKET": "jorgecuadros-documents",
|
||||
"API_ORIGIN": "${{ secrets.APP_API_ORIGIN_GALACTUS }}",
|
||||
"WEB_ORIGIN": "${{ secrets.APP_WEB_ORIGIN_GALACTUS }}",
|
||||
"S3_ENDPOINT": "${{ secrets.APP_S3_ENDPOINT_GALACTUS }}",
|
||||
"DATABASE_URL": "${{ secrets.DATABASE_URL_GALACTUS }}",
|
||||
@@ -322,6 +329,12 @@ jobs:
|
||||
run: |
|
||||
set -e
|
||||
apk add --no-cache curl >/dev/null
|
||||
# These secrets are CORS origin LISTS as far as the app is concerned
|
||||
# (WEB_ORIGIN is comma-separated so one deployment can be reached by
|
||||
# LAN IP, tailnet name and demo domain at once). A list is not a URL,
|
||||
# so probe the FIRST entry — keep the runner-reachable origin first.
|
||||
API_ORIGIN=${API_ORIGIN%%,*}
|
||||
WEB_ORIGIN=${WEB_ORIGIN%%,*}
|
||||
fetch_version() {
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS "$1/version" > "$2"; then return 0; fi
|
||||
|
||||
@@ -253,13 +253,19 @@ jobs:
|
||||
type: file
|
||||
pull: true
|
||||
endpoint: ${{ secrets.PORTAINER_ENDPOINT_ID }}
|
||||
# NOTE: the block below is parsed as JSON — no comments inside it.
|
||||
#
|
||||
# API_ORIGIN is deliberately absent. The browser derives the API origin
|
||||
# from the page it loaded (apps/web/src/lib/api.ts), so the deployment
|
||||
# survives the host moving. Setting it here would pin it again and
|
||||
# re-break an https front door with mixed active content. APP_API_ORIGIN
|
||||
# lives on only as the URL the verify step probes.
|
||||
env_data: |
|
||||
{
|
||||
"APP_TAG": "${{ github.event.inputs.tag }}",
|
||||
"API_PORT": "3001",
|
||||
"WEB_PORT": "3000",
|
||||
"S3_BUCKET": "jorgecuadros-documents",
|
||||
"API_ORIGIN": "${{ secrets.APP_API_ORIGIN }}",
|
||||
"WEB_ORIGIN": "${{ secrets.APP_WEB_ORIGIN }}",
|
||||
"S3_ENDPOINT": "${{ secrets.APP_S3_ENDPOINT }}",
|
||||
"DATABASE_URL": "${{ secrets.DATABASE_URL }}",
|
||||
@@ -288,6 +294,12 @@ jobs:
|
||||
run: |
|
||||
set -e
|
||||
apk add --no-cache curl >/dev/null
|
||||
# These secrets are CORS origin LISTS as far as the app is concerned
|
||||
# (WEB_ORIGIN is comma-separated so one deployment can be reached under
|
||||
# several origins at once). A list is not a URL, so probe the FIRST
|
||||
# entry — keep the runner-reachable origin first.
|
||||
API_ORIGIN=${API_ORIGIN%%,*}
|
||||
WEB_ORIGIN=${WEB_ORIGIN%%,*}
|
||||
fetch_version() {
|
||||
for i in $(seq 1 30); do
|
||||
if curl -fsS "$1/version" > "$2"; then return 0; fi
|
||||
|
||||
@@ -96,7 +96,14 @@ NEXT_PUBLIC_API_ORIGIN=http://localhost:3001
|
||||
```
|
||||
|
||||
The API loads `DATABASE_URL`, `SESSION_SECRET`, `WEB_ORIGIN`, and optional
|
||||
`PORT` (default `3001`). The web app only needs `NEXT_PUBLIC_API_ORIGIN`.
|
||||
`PORT` (default `3001`). `WEB_ORIGIN` is comma-separated — list every origin the
|
||||
app is reached under, or credentialed fetches from the missing ones fail CORS.
|
||||
|
||||
The web app needs no API URL of its own: the browser derives it from the page it
|
||||
loaded (same host on port `3001` over plain HTTP, or the same-origin `/api` path
|
||||
behind a TLS proxy). Set `NEXT_PUBLIC_API_ORIGIN` (dev) or `API_ORIGIN` (deploy,
|
||||
read at request time) only to override that — for instance when running the API
|
||||
on a non-default port.
|
||||
|
||||
### 3. Start MySQL
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/api",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.18",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
|
||||
+13
-1
@@ -60,7 +60,19 @@ async function bootstrap() {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.enableCors({ credentials: true, origin: process.env.WEB_ORIGIN ?? "http://localhost:3000" });
|
||||
// The same deployment is reached under several origins — the office LAN IP,
|
||||
// the tailnet name, the demo domain — and the browser derives the API origin
|
||||
// from whichever one served the page (apps/web/src/lib/api.ts). So WEB_ORIGIN
|
||||
// is a comma-separated LIST, not a single value. A request whose Origin is
|
||||
// not listed gets no CORS headers and the credentialed fetch fails, so add an
|
||||
// entry when a new way of reaching the app is introduced. Same-origin setups
|
||||
// (web and API behind one proxy) never hit CORS at all.
|
||||
const webOrigins = (process.env.WEB_ORIGIN ?? "http://localhost:3000")
|
||||
.split(",")
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
app.enableCors({ credentials: true, origin: webOrigins });
|
||||
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 3001;
|
||||
await app.listen(port);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/web",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.18",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 4500",
|
||||
|
||||
@@ -8,19 +8,18 @@ export const metadata = {
|
||||
"Plataforma interna unificada de clientes, servicios y seguros.",
|
||||
};
|
||||
|
||||
// The browser talks to the API cross-origin, so it needs the API URL at
|
||||
// runtime. NEXT_PUBLIC_* would bake it at build time (one URL per image); we
|
||||
// want the URL to come from the deploy .env instead. So read it here on the
|
||||
// server per request and inject it as window.__API_ORIGIN__ (see lib/api.ts).
|
||||
// force-dynamic guarantees process.env is read at request time, never baked
|
||||
// into a static prerender.
|
||||
// API_ORIGIN is an OPTIONAL override, read here on the server per request and
|
||||
// injected as window.__API_ORIGIN__ (see lib/api.ts). NEXT_PUBLIC_* would bake
|
||||
// it at build time (one URL per image); reading it here keeps one image usable
|
||||
// anywhere. Left unset — the normal case — this injects the empty string and
|
||||
// lib/api.ts derives the origin from window.location instead, so the app
|
||||
// follows the server when it moves without an env edit. force-dynamic
|
||||
// guarantees process.env is read at request time, never baked into a static
|
||||
// prerender.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
const apiOrigin =
|
||||
process.env.API_ORIGIN ??
|
||||
process.env.NEXT_PUBLIC_API_ORIGIN ??
|
||||
"http://localhost:3001";
|
||||
const apiOrigin = process.env.API_ORIGIN ?? "";
|
||||
// Same reason as the API origin: read on the server per request so the built
|
||||
// image is not pinned to one build identity in its client bundle.
|
||||
const build = readBuildInfoFromEnv();
|
||||
|
||||
+16
-5
@@ -82,15 +82,26 @@ import type {
|
||||
UserRow,
|
||||
} from "./types";
|
||||
|
||||
// Resolve the API origin at runtime, not build time. In the browser it comes
|
||||
// from window.__API_ORIGIN__, injected server-side by the root layout from the
|
||||
// deploy .env (API_ORIGIN) — so one built image serves any deployment. On the
|
||||
// server (SSR) read process.env directly. NEXT_PUBLIC_API_ORIGIN stays as the
|
||||
// dev/build fallback.
|
||||
// Resolve the API origin at runtime, not build time — so one built image serves
|
||||
// any deployment and the app follows the box when it moves (tailnet today,
|
||||
// 192.168.1.x office LAN later) with no config change.
|
||||
//
|
||||
// In the browser, derive the origin from the page's own location, the way a PHP
|
||||
// app would. An explicit API_ORIGIN (injected as window.__API_ORIGIN__ by the
|
||||
// root layout) still wins when a deployment genuinely splits the two hosts.
|
||||
// On the server (SSR) read process.env directly — a derived origin is
|
||||
// browser-only, and "/api" is not fetchable server-side.
|
||||
function resolveApiOrigin(): string {
|
||||
if (typeof window !== "undefined") {
|
||||
const injected = (window as { __API_ORIGIN__?: string }).__API_ORIGIN__;
|
||||
if (injected) return injected;
|
||||
const { protocol, hostname } = window.location;
|
||||
// Over TLS the API must share the page's origin or the browser blocks the
|
||||
// call as mixed active content. The reverse proxy maps /api to the API.
|
||||
if (protocol === "https:") return "/api";
|
||||
// Plain HTTP: same host, API port. 3001 is the port the API container
|
||||
// publishes everywhere (deploy/galactus/jorgecuadros-app.compose.yml).
|
||||
return `http://${hostname}:3001`;
|
||||
}
|
||||
return (
|
||||
process.env.API_ORIGIN ??
|
||||
|
||||
@@ -132,8 +132,12 @@ services:
|
||||
dns_search:
|
||||
- ${TAILNET_SUFFIX:-tail01aa2.ts.net}
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
# OPTIONAL override of the API URL the browser calls (injected at runtime,
|
||||
# see layout.tsx). Leave it unset: the browser then derives the origin
|
||||
# from the page it loaded — same host on port 3001 over plain HTTP, or
|
||||
# /api behind a TLS-terminating proxy. Set it only when the API really
|
||||
# lives on a different host than the web app.
|
||||
API_ORIGIN: ${API_ORIGIN:-}
|
||||
ports:
|
||||
- "${WEB_PORT:-3000}:3000"
|
||||
depends_on:
|
||||
|
||||
@@ -8,10 +8,21 @@
|
||||
APP_TAG=latest
|
||||
|
||||
# --- Public URLs (what the end user's BROWSER hits) ---------------------------
|
||||
# API_ORIGIN is injected into the web app at runtime and used for browser fetches
|
||||
# + document download links, so it must be browser-reachable (not swarm-internal).
|
||||
# WEB_ORIGIN is the web app's own public origin; the API allows it via CORS.
|
||||
API_ORIGIN=http://192.168.4.212:3001
|
||||
# API_ORIGIN is OPTIONAL and normally left unset. The browser derives the API
|
||||
# origin from the page it loaded (apps/web/src/lib/api.ts): same host on port
|
||||
# 3001 over plain HTTP, or the same-origin /api path when the page is served
|
||||
# over https by a TLS-terminating proxy that maps /api to the API. That is what
|
||||
# lets the same deployment move — tailnet, office LAN, demo domain — untouched.
|
||||
# Set it only when the API genuinely lives on a different host than the web app;
|
||||
# it is used for browser fetches AND document download links, so it must be
|
||||
# browser-reachable (never a swarm-internal name).
|
||||
#API_ORIGIN=http://192.168.4.212:3001
|
||||
#
|
||||
# WEB_ORIGIN is the list of public origins the web app is reached under; the API
|
||||
# allows them via CORS. COMMA-SEPARATED — one deployment is reachable under
|
||||
# several origins (LAN IP, tailnet name, demo domain) and a credentialed fetch
|
||||
# from an origin missing here gets no CORS headers and fails. A same-origin
|
||||
# setup (web + API behind one proxy) never hits CORS at all.
|
||||
WEB_ORIGIN=http://192.168.4.212:3000
|
||||
|
||||
# Published ports on the swarm host.
|
||||
|
||||
@@ -92,8 +92,12 @@ services:
|
||||
labels:
|
||||
io.jorgecuadros.role: "web"
|
||||
environment:
|
||||
# Public API URL the browser calls (injected at runtime, see layout.tsx).
|
||||
API_ORIGIN: ${API_ORIGIN:?API_ORIGIN must be set}
|
||||
# OPTIONAL override of the API URL the browser calls (injected at runtime,
|
||||
# see layout.tsx). Leave it unset: the browser then derives the origin
|
||||
# from the page it loaded — same host on port 3001 over plain HTTP, or
|
||||
# /api behind a TLS-terminating proxy. Set it only when the API really
|
||||
# lives on a different host than the web app.
|
||||
API_ORIGIN: ${API_ORIGIN:-}
|
||||
ports:
|
||||
- target: 3000
|
||||
published: ${WEB_PORT:-3000}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jorgecuadros-platform",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.18",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jorgecuadros/database",
|
||||
"version": "1.0.16",
|
||||
"version": "1.0.18",
|
||||
"private": true,
|
||||
"main": "generated/client/index.js",
|
||||
"types": "generated/client/index.d.ts",
|
||||
|
||||
Reference in New Issue
Block a user