feat: implement runtime configuration and API client enhancements

This commit is contained in:
WhiteX 2025-06-13 20:45:52 +03:00 committed by rzmk
parent 4663256eed
commit 78cabbc665
6 changed files with 225 additions and 4 deletions

View file

@ -61,15 +61,31 @@ ENV PORT=80 LEARNHOUSE_PORT=9000 HOSTNAME=0.0.0.0
COPY ./extra/start.sh /app/start.sh
RUN chmod +x /app/start.sh
# Add enhanced URL and domain patching
# Add runtime configuration and enhanced patching
RUN echo '#!/bin/bash\n\
echo "Generating runtime configuration..."\n\
mkdir -p /app/web/public\n\
cat > /app/web/public/runtime-config.js << EOF\n\
window.RUNTIME_CONFIG = {\n\
LEARNHOUSE_API_URL: "${NEXT_PUBLIC_LEARNHOUSE_API_URL:-}",\n\
LEARNHOUSE_BACKEND_URL: "${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL:-}",\n\
LEARNHOUSE_DOMAIN: "${NEXT_PUBLIC_LEARNHOUSE_DOMAIN:-}",\n\
LEARNHOUSE_DEFAULT_ORG: "${NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG:-default}",\n\
LEARNHOUSE_MULTI_ORG: "${NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG:-false}",\n\
LEARNHOUSE_TOP_DOMAIN: "${NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN:-}"\n\
}\n\
EOF\n\
\n\
echo "Runtime configuration generated successfully"\n\
\n\
echo "Enhanced patching of NextAuth cookies and domains..."\n\
find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain:[^,}]*,/domain: undefined,/g" {} \\;\n\
find /app/web/.next -type f -name "*.js" -exec sed -i "s/domain: *process.env.LEARNHOUSE_COOKIE_DOMAIN/domain: undefined/g" {} \\;\n\
find /app/web/.next -type f -name "*.js" -exec sed -i "s/\.domain\s*=\s*[^;]*;/\.domain = undefined;/g" {} \\;\n\
echo "Cookie domain patches complete."\n\
\n\
echo "Starting application..."\n\
sh /app/start.sh' > /app/patched-start.sh && chmod +x /app/patched-start.sh
sh /app/start.sh' > /app/runtime-config-start.sh && chmod +x /app/runtime-config-start.sh
# Use the patched start script
CMD ["/app/patched-start.sh"]
# Use the runtime config script
CMD ["/app/runtime-config-start.sh"]

75
Dockerfile_nextjs Normal file
View file

@ -0,0 +1,75 @@
# syntax=docker.io/docker/dockerfile:1
FROM node:18-alpine AS base
# Step 1. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
# Omit --production flag for TypeScript devDependencies
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i; \
# Allow install without lockfile, so example works even without Node.js installed locally
else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
fi
COPY src ./src
COPY public ./public
COPY next.config.js .
COPY tsconfig.json .
# Environment variables must be present at build time
# https://github.com/vercel/next.js/discussions/14030
ARG ENV_VARIABLE
ENV ENV_VARIABLE=${ENV_VARIABLE}
ARG NEXT_PUBLIC_ENV_VARIABLE
ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}
# Next.js collects completely anonymous telemetry data about general usage. Learn more here: https://nextjs.org/telemetry
# Uncomment the following line to disable telemetry at build time
# ENV NEXT_TELEMETRY_DISABLED 1
# Build Next.js based on the preferred package manager
RUN \
if [ -f yarn.lock ]; then yarn build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then pnpm build; \
else npm run build; \
fi
# Note: It is not necessary to add an intermediate step that does a full copy of `node_modules` here
# Step 2. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Environment variables must be redefined at run time
ARG ENV_VARIABLE
ENV ENV_VARIABLE=${ENV_VARIABLE}
ARG NEXT_PUBLIC_ENV_VARIABLE
ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}
# Uncomment the following line to disable telemetry at run time
# ENV NEXT_TELEMETRY_DISABLED 1
# Note: Don't expose ports here, Compose will handle that for us
CMD ["node", "server.js"]

View file

@ -0,0 +1,16 @@
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html lang="en">
<Head>
{/* Load runtime configuration before any app code */}
<script src="/runtime-config.js" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

View file

@ -0,0 +1,59 @@
import { getApiUrl } from './runtimeConfig';
/**
* Creates an API client that uses runtime configuration
* @returns {Object} API client object with fetch method
*/
export function createApiClient() {
return {
/**
* Fetch data from the API with the correct runtime URL
* @param {string} path - API path to fetch from
* @param {Object} options - Fetch options
* @returns {Promise<any>} - JSON response
*/
fetch: async (path, options = {}) => {
const url = getApiUrl(path);
console.log(`[API] Fetching from: ${url}`);
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
return response.json();
},
/**
* Post data to the API with the correct runtime URL
* @param {string} path - API path to post to
* @param {Object} data - Data to post
* @param {Object} options - Additional fetch options
* @returns {Promise<any>} - JSON response
*/
post: async (path, data, options = {}) => {
const url = getApiUrl(path);
console.log(`[API] Posting to: ${url}`);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options.headers,
},
body: JSON.stringify(data),
...options,
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
return response.json();
}
};
}
// Create a singleton instance for easier imports
export const apiClient = createApiClient();

View file

@ -0,0 +1,52 @@
/**
* Utility functions to access runtime configuration values
*/
/**
* Gets configuration from runtime values (window.RUNTIME_CONFIG) with fallback to environment variables
* This ensures dynamic configuration at runtime instead of build time
*/
export function useRuntimeConfig() {
// For server-side rendering, use environment variables
if (typeof window === 'undefined') {
return {
apiUrl: process.env.NEXT_PUBLIC_LEARNHOUSE_API_URL,
backendUrl: process.env.NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL,
domain: process.env.NEXT_PUBLIC_LEARNHOUSE_DOMAIN,
defaultOrg: process.env.NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG || 'default',
multiOrg: process.env.NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG === 'true',
topDomain: process.env.NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN
};
}
// For client-side, prioritize runtime config over baked-in environment variables
const config = window.RUNTIME_CONFIG || {};
return {
apiUrl: config.LEARNHOUSE_API_URL || process.env.NEXT_PUBLIC_LEARNHOUSE_API_URL,
backendUrl: config.LEARNHOUSE_BACKEND_URL || process.env.NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL,
domain: config.LEARNHOUSE_DOMAIN || process.env.NEXT_PUBLIC_LEARNHOUSE_DOMAIN,
defaultOrg: config.LEARNHOUSE_DEFAULT_ORG || process.env.NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG || 'default',
multiOrg: config.LEARNHOUSE_MULTI_ORG === 'true' || process.env.NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG === 'true',
topDomain: config.LEARNHOUSE_TOP_DOMAIN || process.env.NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN
};
}
/**
* Gets the API URL with the correct domain
* @param {string} path - The API path
* @returns {string} - The complete API URL
*/
export function getApiUrl(path) {
const config = useRuntimeConfig();
return `${config.apiUrl || `https://${config.domain}/api/v1/`}${path}`;
}
/**
* Gets the backend URL with the correct domain
* @param {string} path - The backend path
* @returns {string} - The complete backend URL
*/
export function getBackendUrl(path) {
const config = useRuntimeConfig();
return `${config.backendUrl || `https://${config.domain}/`}${path}`;
}

View file

@ -7,6 +7,9 @@ services:
- NEXT_PUBLIC_LEARNHOUSE_API_URL=${NEXT_PUBLIC_LEARNHOUSE_API_URL}
- NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL=${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL}
- NEXT_PUBLIC_LEARNHOUSE_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_DOMAIN}
- NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG=${NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG}
- NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG=${NEXT_PUBLIC_LEARNHOUSE_MULTI_ORG}
- NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_TOP_DOMAIN}
# Mount the persistent volume to the actual uploads directory (/app/api/content)
volumes:
- app-uploads:/app/api/content