Fix local account service configuration for dev usage (#386)
* Adjust account service defaults for local development * Add runtime service configuration for account endpoints * Switch runtime service config to YAML
This commit is contained in:
parent
19b1034518
commit
888e3a6ae5
@ -50,9 +50,10 @@ make init-db # initialize database (optional)
|
||||
|
||||
## Frontend configuration
|
||||
|
||||
The Next.js homepage reads `NEXT_PUBLIC_ACCOUNT_SERVICE_URL` to reach the Account Service. When the variable is not set it
|
||||
defaults to `https://localhost:8443`, matching the hostname used by the development TLS certificate. Override the environment
|
||||
variable if your local certificates use a different host or when connecting to a remote Account Service instance.
|
||||
The Next.js homepage now resolves service endpoints through `ui/homepage/config/runtime-service-config.yaml`. The runtime
|
||||
configuration selects values based on `NEXT_PUBLIC_RUNTIME_ENV` (falling back to `NODE_ENV` and the file's
|
||||
`defaultEnvironment`). Use `NEXT_PUBLIC_ACCOUNT_SERVICE_URL` for ad-hoc overrides, otherwise adjust the YAML file to specify
|
||||
environment-specific URLs such as `http://localhost:8080` for development/test and `https://account.svc.plus` for production.
|
||||
|
||||
## Account service configuration
|
||||
|
||||
|
||||
@ -2,22 +2,26 @@ log:
|
||||
level: info
|
||||
|
||||
server:
|
||||
addr: ":8443"
|
||||
addr: ":8080"
|
||||
readTimeout: 15s
|
||||
writeTimeout: 15s
|
||||
publicUrl: "https://account.svc.plus"
|
||||
publicUrl: "http://localhost:8080"
|
||||
allowedOrigins:
|
||||
- "https://account.svc.plus"
|
||||
- "https://localhost:8443"
|
||||
- "http://localhost:8080"
|
||||
- "http://127.0.0.1:8080"
|
||||
- "http://localhost:3001"
|
||||
- "http://127.0.0.1:3001"
|
||||
- "http://localhost:3000"
|
||||
- "http://127.0.0.1:3000"
|
||||
tls:
|
||||
enabled: true
|
||||
certFile: "/etc/ssl/svc.plus.pem"
|
||||
keyFile: "/etc/ssl/svc.plus.rsa.key"
|
||||
caFile: "/etc/ssl/svc.plus.ca.pem"
|
||||
enabled: false
|
||||
certFile: ""
|
||||
keyFile: ""
|
||||
caFile: ""
|
||||
clientCAFile: ""
|
||||
redirectHttp: true
|
||||
redirectHttp: false
|
||||
|
||||
store:
|
||||
driver: "postgres"
|
||||
|
||||
@ -10,6 +10,7 @@ import Footer from '@components/Footer'
|
||||
import { AskAIButton } from '@components/AskAIButton'
|
||||
import { useLanguage } from '@i18n/LanguageProvider'
|
||||
import { translations } from '@i18n/translations'
|
||||
import { getAccountServiceBaseUrl } from '@lib/serviceConfig'
|
||||
|
||||
import { WeChatIcon } from '../components/icons/WeChatIcon'
|
||||
|
||||
@ -53,7 +54,7 @@ export default function LoginContent({ children }: LoginContentProps) {
|
||||
[],
|
||||
)
|
||||
|
||||
const accountServiceBaseUrl = (process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || 'https://localhost:8443').replace(/\/$/, '')
|
||||
const accountServiceBaseUrl = getAccountServiceBaseUrl()
|
||||
const githubAuthUrl = process.env.NEXT_PUBLIC_GITHUB_AUTH_URL || '/api/auth/github'
|
||||
const wechatAuthUrl = process.env.NEXT_PUBLIC_WECHAT_AUTH_URL || '/api/auth/wechat'
|
||||
const loginUrl = process.env.NEXT_PUBLIC_LOGIN_URL || `${accountServiceBaseUrl}/api/auth/login`
|
||||
|
||||
@ -10,6 +10,7 @@ import Footer from '@components/Footer'
|
||||
import { AskAIButton } from '@components/AskAIButton'
|
||||
import { useLanguage } from '@i18n/LanguageProvider'
|
||||
import { translations } from '@i18n/translations'
|
||||
import { getAccountServiceBaseUrl } from '@lib/serviceConfig'
|
||||
|
||||
import { WeChatIcon } from '../components/icons/WeChatIcon'
|
||||
|
||||
@ -22,7 +23,7 @@ export default function RegisterContent() {
|
||||
const searchParams = useSearchParams()
|
||||
const router = useRouter()
|
||||
|
||||
const accountServiceBaseUrl = (process.env.NEXT_PUBLIC_ACCOUNT_SERVICE_URL || 'https://localhost:8443').replace(/\/$/, '')
|
||||
const accountServiceBaseUrl = getAccountServiceBaseUrl()
|
||||
const githubAuthUrl = process.env.NEXT_PUBLIC_GITHUB_AUTH_URL || '/api/auth/github'
|
||||
const wechatAuthUrl = process.env.NEXT_PUBLIC_WECHAT_AUTH_URL || '/api/auth/wechat'
|
||||
const registerUrl = process.env.NEXT_PUBLIC_REGISTER_URL || `${accountServiceBaseUrl}/api/auth/register`
|
||||
|
||||
17
ui/homepage/config/runtime-service-config.yaml
Normal file
17
ui/homepage/config/runtime-service-config.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
defaultEnvironment: production
|
||||
defaults:
|
||||
accountService:
|
||||
baseUrl: https://account.svc.plus
|
||||
environments:
|
||||
development:
|
||||
accountService:
|
||||
baseUrl: http://localhost:8080
|
||||
test:
|
||||
accountService:
|
||||
baseUrl: http://localhost:8080
|
||||
production:
|
||||
accountService:
|
||||
baseUrl: https://account.svc.plus
|
||||
production_tls:
|
||||
accountService:
|
||||
baseUrl: https://account.svc.plus:8443
|
||||
@ -1,6 +1,119 @@
|
||||
const DEFAULT_ACCOUNT_SERVICE_URL = 'https://localhost:8443'
|
||||
import runtimeServiceConfigSource from '../config/runtime-service-config.yaml'
|
||||
|
||||
type AccountServiceRuntimeConfig = {
|
||||
baseUrl?: string
|
||||
}
|
||||
|
||||
type EnvironmentRuntimeConfig = {
|
||||
accountService?: AccountServiceRuntimeConfig
|
||||
}
|
||||
|
||||
type RuntimeServiceConfig = {
|
||||
defaultEnvironment?: string
|
||||
defaults?: EnvironmentRuntimeConfig
|
||||
environments?: Record<string, EnvironmentRuntimeConfig>
|
||||
}
|
||||
|
||||
type StackEntry = {
|
||||
indent: number
|
||||
value: Record<string, unknown>
|
||||
}
|
||||
|
||||
function parseSimpleYaml(source: string): RuntimeServiceConfig {
|
||||
const lines = source
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.replace(/#.*$/, ''))
|
||||
.map((line) => line.replace(/\s+$/, ''))
|
||||
.filter((line) => line.trim().length > 0)
|
||||
|
||||
const root: Record<string, unknown> = {}
|
||||
const stack: StackEntry[] = [{ indent: -1, value: root }]
|
||||
|
||||
for (const line of lines) {
|
||||
const indent = line.match(/^\s*/)![0].length
|
||||
const trimmed = line.trim()
|
||||
|
||||
const separatorIndex = trimmed.indexOf(':')
|
||||
if (separatorIndex === -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
const key = trimmed.slice(0, separatorIndex).trim()
|
||||
const rawValue = trimmed.slice(separatorIndex + 1).trim()
|
||||
|
||||
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
||||
stack.pop()
|
||||
}
|
||||
|
||||
const parent = stack[stack.length - 1].value
|
||||
|
||||
if (rawValue.length === 0) {
|
||||
const child: Record<string, unknown> = {}
|
||||
parent[key] = child
|
||||
stack.push({ indent, value: child })
|
||||
} else {
|
||||
parent[key] = rawValue
|
||||
}
|
||||
}
|
||||
|
||||
return root as RuntimeServiceConfig
|
||||
}
|
||||
|
||||
const runtimeServiceConfig = parseSimpleYaml(runtimeServiceConfigSource)
|
||||
|
||||
const DEFAULT_ACCOUNT_SERVICE_URL = 'https://account.svc.plus'
|
||||
const DEFAULT_SERVER_SERVICE_URL = 'http://localhost:8090'
|
||||
|
||||
const runtimeEnvironments: Record<string, EnvironmentRuntimeConfig> =
|
||||
runtimeServiceConfig.environments ?? {}
|
||||
|
||||
type RuntimeEnvironmentName = keyof typeof runtimeEnvironments
|
||||
|
||||
function normalizeEnvKey(value?: string | null): string | undefined {
|
||||
if (!value) return undefined
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')
|
||||
}
|
||||
|
||||
function resolveRuntimeEnvironment(): RuntimeEnvironmentName | undefined {
|
||||
const envCandidates = [
|
||||
process.env.NEXT_PUBLIC_RUNTIME_ENV,
|
||||
process.env.NEXT_RUNTIME_ENV,
|
||||
process.env.RUNTIME_ENV,
|
||||
process.env.APP_ENV,
|
||||
process.env.NODE_ENV,
|
||||
runtimeServiceConfig.defaultEnvironment,
|
||||
]
|
||||
|
||||
for (const candidate of envCandidates) {
|
||||
const normalizedCandidate = normalizeEnvKey(candidate)
|
||||
if (!normalizedCandidate) continue
|
||||
|
||||
const matchedEntry = Object.keys(runtimeEnvironments).find(
|
||||
(key) => normalizeEnvKey(key) === normalizedCandidate,
|
||||
) as RuntimeEnvironmentName | undefined
|
||||
|
||||
if (matchedEntry) {
|
||||
return matchedEntry
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getRuntimeAccountServiceBaseUrl(): string | undefined {
|
||||
const environmentName = resolveRuntimeEnvironment()
|
||||
const runtimeDefaults = runtimeServiceConfig.defaults?.accountService?.baseUrl
|
||||
const environmentValue = environmentName
|
||||
? runtimeEnvironments[environmentName]?.accountService?.baseUrl
|
||||
: undefined
|
||||
|
||||
return environmentValue ?? runtimeDefaults
|
||||
}
|
||||
|
||||
function readEnvValue(...keys: string[]): string | undefined {
|
||||
for (const key of keys) {
|
||||
const raw = process.env[key]
|
||||
@ -20,7 +133,8 @@ function normalizeBaseUrl(baseUrl: string): string {
|
||||
|
||||
export function getAccountServiceBaseUrl(): string {
|
||||
const configured = readEnvValue('ACCOUNT_SERVICE_URL', 'NEXT_PUBLIC_ACCOUNT_SERVICE_URL')
|
||||
return normalizeBaseUrl(configured ?? DEFAULT_ACCOUNT_SERVICE_URL)
|
||||
const runtimeConfigured = getRuntimeAccountServiceBaseUrl()
|
||||
return normalizeBaseUrl(configured ?? runtimeConfigured ?? DEFAULT_ACCOUNT_SERVICE_URL)
|
||||
}
|
||||
|
||||
export function getServerServiceBaseUrl(): string {
|
||||
|
||||
@ -51,6 +51,13 @@ const nextConfig = {
|
||||
compress: false, // 压缩交给 Nginx,省 Node CPU
|
||||
images: { unoptimized: true }, // 关闭服务端图片处理
|
||||
featureToggles,
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.ya?ml$/i,
|
||||
type: 'asset/source',
|
||||
})
|
||||
return config
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
||||
9
ui/homepage/types/global.d.ts
vendored
Normal file
9
ui/homepage/types/global.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
declare module '*.yaml' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module '*.yml' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user