Docker 런타임 환경 변수 우선 적용
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# 업데이트 요약
|
# 업데이트 요약
|
||||||
|
|
||||||
|
## v1.0.5
|
||||||
|
|
||||||
|
- Docker 운영 컨테이너가 빌드 시점 설정 대신 `.env.production`의 런타임 환경 변수를 우선 읽도록 보강.
|
||||||
|
|
||||||
## v1.0.4
|
## v1.0.4
|
||||||
|
|
||||||
- owner/admin 계정이 없는 운영 DB에서도 환경 변수 관리자 계정으로 첫 owner를 생성하거나 기존 일반 회원을 승격할 수 있도록 보강.
|
- owner/admin 계정이 없는 운영 DB에서도 환경 변수 관리자 계정으로 첫 owner를 생성하거나 기존 일반 회원을 승격할 수 있도록 보강.
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ docker compose --env-file .env.production up -d --build
|
|||||||
- 개발 DB와 운영 DB는 반드시 별도 인스턴스 또는 별도 데이터베이스로 분리
|
- 개발 DB와 운영 DB는 반드시 별도 인스턴스 또는 별도 데이터베이스로 분리
|
||||||
- 운영 DB는 로컬 개발 서버에서 직접 연결하지 않음
|
- 운영 DB는 로컬 개발 서버에서 직접 연결하지 않음
|
||||||
- 운영 환경에서 `DATABASE_URL`이 없으면 샘플 콘텐츠로 대체하지 않고 서버 오류로 실패
|
- 운영 환경에서 `DATABASE_URL`이 없으면 샘플 콘텐츠로 대체하지 않고 서버 오류로 실패
|
||||||
|
- Docker 운영 컨테이너는 `.env.production`의 서버 환경 변수를 런타임 `process.env`에서 우선 읽는다.
|
||||||
|
|
||||||
### 이메일 인증(Resend, 선택)
|
### 이메일 인증(Resend, 선택)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-05-14 v1.0.5
|
||||||
|
|
||||||
|
### Docker 런타임 환경 변수 우선
|
||||||
|
|
||||||
|
Nuxt `runtimeConfig`에 `process.env.*`를 직접 대입하면 Docker 이미지 빌드 시점에 값이 비어 있는 상태로 번들에 들어갈 수 있다. 운영 컨테이너는 `.env.production`을 런타임에 주입하므로, 서버 전용 비밀값과 DB 연결값은 `useRuntimeConfig()`보다 `process.env`를 우선 조회하도록 공통 유틸을 추가했다. 이 기준은 관리자 최초 로그인, 세션 서명, DB 연결, 이메일 OTP 설정에 적용한다.
|
||||||
|
|
||||||
## 2026-05-14 v1.0.4
|
## 2026-05-14 v1.0.4
|
||||||
|
|
||||||
### 최초 관리자 기준을 owner/admin 존재 여부로 변경
|
### 최초 관리자 기준을 owner/admin 존재 여부로 변경
|
||||||
|
|||||||
@@ -595,6 +595,7 @@ components/content/
|
|||||||
- 사용자 설정 화면은 공개 본문 폭에 맞춰 프로필 요약을 상단에 두고, 프로필 입력과 활동 정보를 하단에 배치한다. 비밀번호 변경과 회원 탈퇴는 설정 버튼의 모달 액션으로만 노출한다. 활동 정보의 `마지막 로그인`은 현재 로그인 이전에 저장된 `previous_last_seen_at`을 표시한다.
|
- 사용자 설정 화면은 공개 본문 폭에 맞춰 프로필 요약을 상단에 두고, 프로필 입력과 활동 정보를 하단에 배치한다. 비밀번호 변경과 회원 탈퇴는 설정 버튼의 모달 액션으로만 노출한다. 활동 정보의 `마지막 로그인`은 현재 로그인 이전에 저장된 `previous_last_seen_at`을 표시한다.
|
||||||
- 첫 회원가입 시 관리자 세션도 함께 설정되어 관리자 화면(`/admin`)으로 바로 진입할 수 있다.
|
- 첫 회원가입 시 관리자 세션도 함께 설정되어 관리자 화면(`/admin`)으로 바로 진입할 수 있다.
|
||||||
- 회원 세션 서명은 `MEMBER_SESSION_SECRET`만 사용하며, 값이 없으면 서버 오류로 실패한다.
|
- 회원 세션 서명은 `MEMBER_SESSION_SECRET`만 사용하며, 값이 없으면 서버 오류로 실패한다.
|
||||||
|
- Docker 운영 서버 환경 변수는 이미지 빌드 시점 `runtimeConfig`보다 컨테이너 런타임 `process.env` 값을 우선한다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# 업데이트 이력
|
# 업데이트 이력
|
||||||
|
|
||||||
|
## v1.0.5
|
||||||
|
|
||||||
|
- Docker 운영 이미지에서 빌드 시점 `runtimeConfig`가 비어도 컨테이너 런타임 환경 변수(`DATABASE_URL`, `ADMIN_EMAIL`, `ADMIN_PASSWORD`, `MEMBER_SESSION_SECRET`, Resend 설정)를 우선 읽도록 수정.
|
||||||
|
- 서버 런타임 환경 변수 조회 유틸 추가.
|
||||||
|
- 패키지 버전 `1.0.5`로 갱신.
|
||||||
|
|
||||||
## v1.0.4
|
## v1.0.4
|
||||||
|
|
||||||
- 최초 관리자 부트스트랩 기준을 전체 사용자 수가 아니라 owner/admin 존재 여부로 변경.
|
- 최초 관리자 부트스트랩 기준을 전체 사용자 수가 아니라 owner/admin 존재 여부로 변경.
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sori.studio",
|
"name": "sori.studio",
|
||||||
"version": "1.0.4",
|
"version": "1.0.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"imports": {
|
"imports": {
|
||||||
|
|||||||
3
server/api/auth/bootstrap-status.get.js
vendored
3
server/api/auth/bootstrap-status.get.js
vendored
@@ -1,5 +1,6 @@
|
|||||||
import { getMemberBootstrapState } from '../../repositories/member-repository'
|
import { getMemberBootstrapState } from '../../repositories/member-repository'
|
||||||
import { isResendConfigured } from '../../utils/resend-mail'
|
import { isResendConfigured } from '../../utils/resend-mail'
|
||||||
|
import { getRuntimeEnvValue } from '../../utils/runtime-env'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 최초 관리자 등록 필요 여부·이메일 OTP(Resend) 사용 가능 여부를 조회한다.
|
* 최초 관리자 등록 필요 여부·이메일 OTP(Resend) 사용 가능 여부를 조회한다.
|
||||||
@@ -8,7 +9,7 @@ import { isResendConfigured } from '../../utils/resend-mail'
|
|||||||
export default defineEventHandler(async () => {
|
export default defineEventHandler(async () => {
|
||||||
const base = await getMemberBootstrapState()
|
const base = await getMemberBootstrapState()
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const hasPepper = Boolean(String(config.emailOtpPepper || config.memberSessionSecret || '').trim())
|
const hasPepper = Boolean(getRuntimeEnvValue('EMAIL_OTP_PEPPER', 'emailOtpPepper', getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret')).trim())
|
||||||
const emailOtpConfigured = isResendConfigured(config) && hasPepper
|
const emailOtpConfigured = isResendConfigured(config) && hasPepper
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '../../../repositories/email-otp-repository'
|
} from '../../../repositories/email-otp-repository'
|
||||||
import { generateSixDigitOtp, hashOtpCode, normalizeOtpEmail } from '../../../utils/email-otp'
|
import { generateSixDigitOtp, hashOtpCode, normalizeOtpEmail } from '../../../utils/email-otp'
|
||||||
import { isResendConfigured, sendResendEmail } from '../../../utils/resend-mail'
|
import { isResendConfigured, sendResendEmail } from '../../../utils/resend-mail'
|
||||||
|
import { getRuntimeEnvValue } from '../../../utils/runtime-env'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
email: z.string().trim().email(),
|
email: z.string().trim().email(),
|
||||||
@@ -28,7 +29,7 @@ const MAX_SENDS_PER_HOUR = 5
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
const resolveOtpPepper = (config) => {
|
const resolveOtpPepper = (config) => {
|
||||||
const pepper = String(config.emailOtpPepper || config.memberSessionSecret || '').trim()
|
const pepper = getRuntimeEnvValue('EMAIL_OTP_PEPPER', 'emailOtpPepper', getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret')).trim()
|
||||||
if (!pepper) {
|
if (!pepper) {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
@@ -150,8 +151,8 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await sendResendEmail({
|
await sendResendEmail({
|
||||||
apiKey: String(config.resendApiKey).trim(),
|
apiKey: getRuntimeEnvValue('RESEND_API_KEY', 'resendApiKey').trim(),
|
||||||
from: String(config.resendFromEmail).trim(),
|
from: getRuntimeEnvValue('RESEND_FROM_EMAIL', 'resendFromEmail').trim(),
|
||||||
to: email,
|
to: email,
|
||||||
subject,
|
subject,
|
||||||
html
|
html
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { z } from 'zod'
|
|||||||
import { createError, readBody } from 'h3'
|
import { createError, readBody } from 'h3'
|
||||||
import { updateMemberPasswordByEmail } from '../../../repositories/member-repository'
|
import { updateMemberPasswordByEmail } from '../../../repositories/member-repository'
|
||||||
import { verifyAndConsumeEmailOtp } from '../../../repositories/email-otp-repository'
|
import { verifyAndConsumeEmailOtp } from '../../../repositories/email-otp-repository'
|
||||||
|
import { getRuntimeEnvValue } from '../../../utils/runtime-env'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
email: z.string().trim().email(),
|
email: z.string().trim().email(),
|
||||||
@@ -24,8 +25,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const pepper = getRuntimeEnvValue('EMAIL_OTP_PEPPER', 'emailOtpPepper', getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret')).trim()
|
||||||
const pepper = String(config.emailOtpPepper || config.memberSessionSecret || '').trim()
|
|
||||||
if (!pepper) {
|
if (!pepper) {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { verifyAndConsumeEmailOtp } from '../../repositories/email-otp-repositor
|
|||||||
import { setMemberSession } from '../../utils/member-auth'
|
import { setMemberSession } from '../../utils/member-auth'
|
||||||
import { setAdminSession } from '../../utils/admin-auth'
|
import { setAdminSession } from '../../utils/admin-auth'
|
||||||
import { isResendConfigured } from '../../utils/resend-mail'
|
import { isResendConfigured } from '../../utils/resend-mail'
|
||||||
|
import { getRuntimeEnvValue } from '../../utils/runtime-env'
|
||||||
|
|
||||||
const signupSchema = z.object({
|
const signupSchema = z.object({
|
||||||
username: z.string().trim().min(1),
|
username: z.string().trim().min(1),
|
||||||
@@ -24,7 +25,7 @@ const isSignupOtpRequired = (config, bootstrap) => {
|
|||||||
if (bootstrap.needsAdminSetup) {
|
if (bootstrap.needsAdminSetup) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const hasPepper = Boolean(String(config.emailOtpPepper || config.memberSessionSecret || '').trim())
|
const hasPepper = Boolean(getRuntimeEnvValue('EMAIL_OTP_PEPPER', 'emailOtpPepper', getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret')).trim())
|
||||||
return isResendConfigured(config) && hasPepper
|
return isResendConfigured(config) && hasPepper
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
message: '이메일 인증번호를 입력해 주세요.'
|
message: '이메일 인증번호를 입력해 주세요.'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const pepper = String(config.emailOtpPepper || config.memberSessionSecret || '').trim()
|
const pepper = getRuntimeEnvValue('EMAIL_OTP_PEPPER', 'emailOtpPepper', getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret')).trim()
|
||||||
const verify = await verifyAndConsumeEmailOtp({
|
const verify = await verifyAndConsumeEmailOtp({
|
||||||
email: emailNorm,
|
email: emailNorm,
|
||||||
purpose: 'signup',
|
purpose: 'signup',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import postgres from 'postgres'
|
import postgres from 'postgres'
|
||||||
|
import { getRuntimeEnvValue } from '../utils/runtime-env'
|
||||||
|
|
||||||
let client = null
|
let client = null
|
||||||
|
|
||||||
@@ -13,9 +14,9 @@ const isProductionRuntime = () => process.env.NODE_ENV === 'production'
|
|||||||
* @returns {ReturnType<typeof postgres> | null} PostgreSQL 클라이언트
|
* @returns {ReturnType<typeof postgres> | null} PostgreSQL 클라이언트
|
||||||
*/
|
*/
|
||||||
export const getPostgresClient = () => {
|
export const getPostgresClient = () => {
|
||||||
const config = useRuntimeConfig()
|
const databaseUrl = getRuntimeEnvValue('DATABASE_URL', 'databaseUrl').trim()
|
||||||
|
|
||||||
if (!config.databaseUrl) {
|
if (!databaseUrl) {
|
||||||
if (isProductionRuntime()) {
|
if (isProductionRuntime()) {
|
||||||
throw new Error('DATABASE_URL_REQUIRED')
|
throw new Error('DATABASE_URL_REQUIRED')
|
||||||
}
|
}
|
||||||
@@ -24,7 +25,7 @@ export const getPostgresClient = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
client = postgres(config.databaseUrl, {
|
client = postgres(databaseUrl, {
|
||||||
max: 5,
|
max: 5,
|
||||||
idle_timeout: 20,
|
idle_timeout: 20,
|
||||||
connect_timeout: 10
|
connect_timeout: 10
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import bcrypt from 'bcrypt'
|
|||||||
import { safeCompare, setAdminSession } from '../../../../utils/admin-auth'
|
import { safeCompare, setAdminSession } from '../../../../utils/admin-auth'
|
||||||
import { getAdminUserByEmail, getMemberBootstrapState, touchUserActivity, upsertBootstrapOwner } from '../../../../repositories/member-repository'
|
import { getAdminUserByEmail, getMemberBootstrapState, touchUserActivity, upsertBootstrapOwner } from '../../../../repositories/member-repository'
|
||||||
import { setMemberSession } from '../../../../utils/member-auth'
|
import { setMemberSession } from '../../../../utils/member-auth'
|
||||||
|
import { getRuntimeEnvValue } from '../../../../utils/runtime-env'
|
||||||
|
|
||||||
const loginSchema = z.object({
|
const loginSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
@@ -26,9 +27,8 @@ const createBootstrapUsername = (email) => {
|
|||||||
* @returns {Promise<import('../../../../repositories/member-repository').MemberUser | null>} 생성된 관리자
|
* @returns {Promise<import('../../../../repositories/member-repository').MemberUser | null>} 생성된 관리자
|
||||||
*/
|
*/
|
||||||
const createBootstrapAdminUser = async (credentials) => {
|
const createBootstrapAdminUser = async (credentials) => {
|
||||||
const config = useRuntimeConfig()
|
const adminEmail = getRuntimeEnvValue('ADMIN_EMAIL', 'adminEmail').trim().toLowerCase()
|
||||||
const adminEmail = String(config.adminEmail || '').trim().toLowerCase()
|
const adminPassword = getRuntimeEnvValue('ADMIN_PASSWORD', 'adminPassword')
|
||||||
const adminPassword = String(config.adminPassword || '')
|
|
||||||
|
|
||||||
if (!adminEmail || !adminPassword || credentials.email.trim().toLowerCase() !== adminEmail || !safeCompare(credentials.password, adminPassword)) {
|
if (!adminEmail || !adminPassword || credentials.email.trim().toLowerCase() !== adminEmail || !safeCompare(credentials.password, adminPassword)) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createHmac, timingSafeEqual } from 'node:crypto'
|
import { createHmac, timingSafeEqual } from 'node:crypto'
|
||||||
import { createError, deleteCookie, getCookie, setCookie } from 'h3'
|
import { createError, deleteCookie, getCookie, setCookie } from 'h3'
|
||||||
|
import { getRuntimeEnvValue } from './runtime-env'
|
||||||
|
|
||||||
const adminSessionCookieName = 'sori_admin_session'
|
const adminSessionCookieName = 'sori_admin_session'
|
||||||
const sessionMaxAge = 60 * 60 * 12
|
const sessionMaxAge = 60 * 60 * 12
|
||||||
@@ -9,16 +10,16 @@ const sessionMaxAge = 60 * 60 * 12
|
|||||||
* @returns {string} 세션 서명 비밀값
|
* @returns {string} 세션 서명 비밀값
|
||||||
*/
|
*/
|
||||||
const getSessionSecret = () => {
|
const getSessionSecret = () => {
|
||||||
const config = useRuntimeConfig()
|
const adminPassword = getRuntimeEnvValue('ADMIN_PASSWORD', 'adminPassword')
|
||||||
|
|
||||||
if (!config.adminPassword) {
|
if (!adminPassword) {
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
message: '관리자 비밀번호 환경 변수가 없습니다.'
|
message: '관리자 비밀번호 환경 변수가 없습니다.'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.adminPassword
|
return adminPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createHmac, timingSafeEqual } from 'node:crypto'
|
import { createHmac, timingSafeEqual } from 'node:crypto'
|
||||||
import { createError, deleteCookie, getCookie, setCookie } from 'h3'
|
import { createError, deleteCookie, getCookie, setCookie } from 'h3'
|
||||||
|
import { getRuntimeEnvValue } from './runtime-env'
|
||||||
|
|
||||||
const memberSessionCookieName = 'sori_member_session'
|
const memberSessionCookieName = 'sori_member_session'
|
||||||
const sessionMaxAge = 60 * 60 * 24 * 14
|
const sessionMaxAge = 60 * 60 * 24 * 14
|
||||||
@@ -9,8 +10,7 @@ const sessionMaxAge = 60 * 60 * 24 * 14
|
|||||||
* @returns {string} 세션 서명 비밀값
|
* @returns {string} 세션 서명 비밀값
|
||||||
*/
|
*/
|
||||||
const getSessionSecret = () => {
|
const getSessionSecret = () => {
|
||||||
const config = useRuntimeConfig()
|
const sessionSecret = getRuntimeEnvValue('MEMBER_SESSION_SECRET', 'memberSessionSecret').trim()
|
||||||
const sessionSecret = String(config.memberSessionSecret || '').trim()
|
|
||||||
|
|
||||||
if (!sessionSecret) {
|
if (!sessionSecret) {
|
||||||
throw createError({
|
throw createError({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createError } from 'h3'
|
import { createError } from 'h3'
|
||||||
|
import { getRuntimeEnvValue } from './runtime-env'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resend가 서버 설정으로 사용 가능한지
|
* Resend가 서버 설정으로 사용 가능한지
|
||||||
@@ -6,8 +7,8 @@ import { createError } from 'h3'
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export const isResendConfigured = (config) => {
|
export const isResendConfigured = (config) => {
|
||||||
const key = String(config?.resendApiKey || '').trim()
|
const key = getRuntimeEnvValue('RESEND_API_KEY', 'resendApiKey', String(config?.resendApiKey || '')).trim()
|
||||||
const from = String(config?.resendFromEmail || '').trim()
|
const from = getRuntimeEnvValue('RESEND_FROM_EMAIL', 'resendFromEmail', String(config?.resendFromEmail || '')).trim()
|
||||||
return Boolean(key && from)
|
return Boolean(key && from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
server/utils/runtime-env.js
Normal file
36
server/utils/runtime-env.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 서버 런타임 환경 변수 값을 조회한다.
|
||||||
|
* @param {string} envName - process.env 변수명
|
||||||
|
* @param {string} configName - Nuxt runtimeConfig 키
|
||||||
|
* @param {string} fallback - 기본값
|
||||||
|
* @returns {string} 환경 변수 값
|
||||||
|
*/
|
||||||
|
export const getRuntimeEnvValue = (envName, configName, fallback = '') => {
|
||||||
|
const directValue = process.env[envName]
|
||||||
|
if (typeof directValue === 'string' && directValue.length > 0) {
|
||||||
|
return directValue
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const configValue = config?.[configName]
|
||||||
|
|
||||||
|
return typeof configValue === 'string' && configValue.length > 0
|
||||||
|
? configValue
|
||||||
|
: fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 숫자형 서버 런타임 환경 변수 값을 조회한다.
|
||||||
|
* @param {string} envName - process.env 변수명
|
||||||
|
* @param {string} configName - Nuxt runtimeConfig 키
|
||||||
|
* @param {number} fallback - 기본값
|
||||||
|
* @returns {number} 환경 변수 숫자 값
|
||||||
|
*/
|
||||||
|
export const getRuntimeEnvNumber = (envName, configName, fallback) => {
|
||||||
|
const value = getRuntimeEnvValue(envName, configName, '')
|
||||||
|
const parsed = Number(value)
|
||||||
|
|
||||||
|
return Number.isFinite(parsed) && parsed > 0
|
||||||
|
? parsed
|
||||||
|
: fallback
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user