mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 11:59:26 +00:00
feat: implement runtime configuration and API client enhancements
This commit is contained in:
parent
4663256eed
commit
78cabbc665
6 changed files with 225 additions and 4 deletions
|
|
@ -61,15 +61,31 @@ ENV PORT=80 LEARNHOUSE_PORT=9000 HOSTNAME=0.0.0.0
|
||||||
COPY ./extra/start.sh /app/start.sh
|
COPY ./extra/start.sh /app/start.sh
|
||||||
RUN chmod +x /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\
|
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\
|
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:[^,}]*,/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: *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\
|
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\
|
echo "Cookie domain patches complete."\n\
|
||||||
|
\n\
|
||||||
echo "Starting application..."\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
|
# Use the runtime config script
|
||||||
CMD ["/app/patched-start.sh"]
|
CMD ["/app/runtime-config-start.sh"]
|
||||||
75
Dockerfile_nextjs
Normal file
75
Dockerfile_nextjs
Normal 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"]
|
||||||
16
apps/web/pages/_document.js
Normal file
16
apps/web/pages/_document.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
59
apps/web/utils/apiClient.js
Normal file
59
apps/web/utils/apiClient.js
Normal 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();
|
||||||
52
apps/web/utils/runtimeConfig.js
Normal file
52
apps/web/utils/runtimeConfig.js
Normal 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}`;
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,9 @@ services:
|
||||||
- NEXT_PUBLIC_LEARNHOUSE_API_URL=${NEXT_PUBLIC_LEARNHOUSE_API_URL}
|
- NEXT_PUBLIC_LEARNHOUSE_API_URL=${NEXT_PUBLIC_LEARNHOUSE_API_URL}
|
||||||
- NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL=${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL}
|
- NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL=${NEXT_PUBLIC_LEARNHOUSE_BACKEND_URL}
|
||||||
- NEXT_PUBLIC_LEARNHOUSE_DOMAIN=${NEXT_PUBLIC_LEARNHOUSE_DOMAIN}
|
- 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)
|
# Mount the persistent volume to the actual uploads directory (/app/api/content)
|
||||||
volumes:
|
volumes:
|
||||||
- app-uploads:/app/api/content
|
- app-uploads:/app/api/content
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue