feat(web): serve the web app under /app and send every billing link there (WS-B)
apps/web was never deployed, so /billing on the public domain returned the landing page and d3ro.dev (desktop "upgrade") did not resolve. - apps/web runs with basePath /app and output standalone; /download and /releases redirect to the site's #download. A Dockerfile and a d3ro-web compose service (port 3002) deploy it to the NAS with the other images. - The site bridge worker forwards /app/* to WEB_APP_ORIGIN (the tunnel host) and rewrites upstream redirects; everything else still goes to Pages. With no origin configured /app answers 503 instead of the landing page. - Desktop upgrade, desktop Stripe return, mobile subscription management, the web checkout/portal returns and the site all use billingUrl(); the return query is success=1 / canceled=1, which the billing page reads. The billing page highlights ?tier=pro|pro_plus, and signing in from a billing link returns to the same plan. - auth/callback pins the redirect origin in production and rejects protocol-relative next= values (open redirect). - Mobile legal links use SITE_URLS (fixes the missing slash on /terms). - Compose drops the unused NEXT_PUBLIC_API_URL and the dead wwwroot legal mounts; deploy scripts add the web image and the SUPABASE_* values the NAS compose already required; .dockerignore keeps app .env files out of images. - Supabase auth redirects allow /app/** (remote dashboard must match). Policy: docs/REFACTOR_POLICY.md Wave 3, W3-3 and W3-4.
This commit is contained in:
parent
88f24d84a1
commit
b6fe588a7c
30 changed files with 493 additions and 95 deletions
45
apps/web/src/lib/web-app-url.ts
Normal file
45
apps/web/src/lib/web-app-url.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// apps/web/src/lib/web-app-url.ts
|
||||
// 웹앱 안에서 쓰는 절대 URL·복귀 경로 헬퍼. 주소 정본은 @d3ro/core/web-urls 다.
|
||||
//
|
||||
// 결제사(Stripe·Payple)와 OAuth 는 절대 URL 을 요구한다. 운영은 d3ro.chanpaca.net/app,
|
||||
// 로컬 개발은 localhost:3000/app 이므로 origin 만 현재 브라우저 것으로 바꿔 끼운다.
|
||||
|
||||
import {
|
||||
billingUrl,
|
||||
PUBLIC_SITE_ORIGIN,
|
||||
WEB_APP_BASE_PATH,
|
||||
type BillingReturn
|
||||
} from '@d3ro/core/web-urls'
|
||||
|
||||
/** 로그인 뒤 돌아갈 경로를 proxy 가 (app) 레이아웃에 넘길 때 쓰는 요청 헤더. */
|
||||
export const RETURN_PATH_HEADER = 'x-d3ro-return-path'
|
||||
|
||||
const DEFAULT_RETURN_PATH = '/dashboard'
|
||||
|
||||
/**
|
||||
* 로그인 뒤 이동할 웹앱 내부 경로(basePath 제외)만 통과시킨다.
|
||||
* `//evil.example`, `/\evil.example` 같은 외부 이동은 기본 경로로 바꾼다.
|
||||
*/
|
||||
export function safeReturnPath(value: string | null | undefined): string {
|
||||
if (!value || !value.startsWith('/') || value.startsWith('//') || value.startsWith('/\\')) {
|
||||
return DEFAULT_RETURN_PATH
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/** 로그인 페이지 경로(basePath 제외). 돌아갈 곳이 기본값이면 쿼리를 붙이지 않는다. */
|
||||
export function loginPath(returnPath: string | null | undefined): string {
|
||||
const next = safeReturnPath(returnPath)
|
||||
return next === DEFAULT_RETURN_PATH ? '/login' : `/login?next=${encodeURIComponent(next)}`
|
||||
}
|
||||
|
||||
/** `origin` 기준 웹앱 절대 URL. `path` 는 basePath 를 뺀 앱 경로('/billing' 등). */
|
||||
export function appUrlFor(origin: string, path: string): string {
|
||||
return `${origin}${WEB_APP_BASE_PATH}${path}`
|
||||
}
|
||||
|
||||
/** 브라우저에서 결제 페이지 절대 URL. 쿼리 형식은 core billingUrl() 을 그대로 따른다. */
|
||||
export function browserBillingUrl(result?: BillingReturn): string {
|
||||
const canonical = billingUrl(result ? { result } : {})
|
||||
return `${window.location.origin}${canonical.slice(PUBLIC_SITE_ORIGIN.length)}`
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue