Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
809890b
fix: enable bundling and refine included files in vercel.json
GTPSHAX May 4, 2026
4141028
fix: update includeFiles in vercel.json for improved bundling
GTPSHAX May 4, 2026
18ed85e
feat: add initial vercel.json configuration for deployment
GTPSHAX May 4, 2026
6790e7b
fix: remove duplicate dependencies in package.json
GTPSHAX May 4, 2026
04215be
feat: add config section with includeFiles for Vercel deployment
GTPSHAX May 4, 2026
e211362
fix: update dependencies in package.json for core and handlers packages
GTPSHAX May 4, 2026
6d46b6f
chore: remove vercel.json configuration file
GTPSHAX May 4, 2026
2cb1b0c
feat: add initial vercel.json configuration for deployment
GTPSHAX May 4, 2026
b0e5622
fix: set bundle to false in Vercel configuration
GTPSHAX May 4, 2026
b7a6ac1
fix: update environment variable names for OpenAI configuration
GTPSHAX May 4, 2026
5f3f985
fix: correct environment variable name for OpenAI base URL in deploym…
GTPSHAX May 4, 2026
d3fde18
fix: ensure OpenAI API key is properly handled as a string
GTPSHAX May 4, 2026
7a737f7
fix: ensure OpenAI API key is handled as a string
GTPSHAX May 4, 2026
13de81a
fix: add Cloudflare Insights URL to script-src in helmet middleware
GTPSHAX May 4, 2026
13c8902
fix: include assets directory in Vercel configuration
GTPSHAX May 4, 2026
72c7f63
feat: add Vercel configuration for deployment
GTPSHAX May 4, 2026
a94d161
feat: add Vercel configuration for deployment
GTPSHAX May 4, 2026
b19d8d3
fix: update helmet middleware to include APP_API_BASE_URL and adjust …
GTPSHAX May 4, 2026
71f36b3
fix: update script-src directive in helmet middleware to include Clou…
GTPSHAX May 4, 2026
99e0be9
fix: simplify CORS origin handling and remove unnecessary host checks
GTPSHAX May 4, 2026
00c6d2a
fix: rename APP_ORIGIN_URL to APP_ORIGIN_HOST in environment configur…
GTPSHAX May 4, 2026
b8206d6
fix: rename APP_ORIGIN_URL to APP_ORIGIN_HOST and update CORS origin …
GTPSHAX May 4, 2026
41fafc0
fix: rename APP_ORIGIN_HOST to APP_ORIGIN_HOSTS in configuration files
GTPSHAX May 4, 2026
6975aa9
fix: rename APP_ORIGIN_URL to APP_ORIGIN_HOSTS in configuration files
GTPSHAX May 4, 2026
c84a432
feat: add boot.js files for API and web with dotenv configuration
GTPSHAX May 4, 2026
846ebef
fix: update start and dev scripts to use boot.js for API and web appl…
GTPSHAX May 4, 2026
6b00c35
fix: rename APP_ORIGIN_HOST to APP_ORIGIN_HOSTS in CORS middleware fo…
GTPSHAX May 4, 2026
9db6253
fix: update deploy button environment variable from APP_ORIGIN_URL to…
GTPSHAX May 4, 2026
9b199a2
docs: regenerate api-reference
GTPSHAX May 4, 2026
badf9fb
feat: add OpenAI configuration to .env and .env.schema files
GTPSHAX May 4, 2026
c8996db
fix: improve CORS middleware to correctly extract origin from request…
GTPSHAX May 4, 2026
29d0059
fix: enhance CORS middleware to correctly extract origin from request…
GTPSHAX May 4, 2026
4f58da4
fix: update connect-src directive to use removePathFromUrl helper fun…
GTPSHAX May 4, 2026
d7106f3
fix: update target file path in stripConsoleMiddleware to use __dirname
GTPSHAX May 4, 2026
b909175
feat: add initial package.json for scripts module
GTPSHAX May 4, 2026
3b28726
Merge branch 'main' of https://github.com/NgodingWok/modul-ajar-gener…
GTPSHAX May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# --- App configuration ---
APP_NAME="My App"
APP_ORIGIN_URL="*"
# Comma-separated list of allowed CORS
APP_ORIGIN_HOSTS="*"
APP_PORT=3000
APP_HOST="0.0.0.0"
# Base URL for API requests
APP_API_BASE_URL=http://localhost:3000/api
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by OPENAI_API_BASE_URL and not use the built-in API routes)
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by APP_API_BASE_URL and not use the built-in API routes)
APP_USE_BUILTIN_API=true

# --- CORS configuration ---
Expand All @@ -17,4 +18,4 @@ CORS_TRUSTED_CDN_HOSTS="cdn.example.com"
# --- OpenAI configuration ---
OPENAI_API_KEY="your-openai-api-key-here"
OPENAI_MODEL="gpt-3.5-turbo"
OPENAI_API_BASE_URL="https://api.openai.com/v1"
OPENAI_BASE_URL="https://api.openai.com/v1"
7 changes: 4 additions & 3 deletions .env.schema
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# --- App configuration ---
APP_NAME=
APP_ORIGIN_URL=
# Comma-separated list of allowed CORS
APP_ORIGIN_HOSTS=
APP_PORT=
APP_HOST=
# Base URL for API requests
APP_API_BASE_URL=
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by OPENAI_API_BASE_URL and not use the built-in API routes)
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by APP_API_BASE_URL and not use the built-in API routes)
APP_USE_BUILTIN_API=

# --- CORS configuration ---
Expand All @@ -17,4 +18,4 @@ CORS_TRUSTED_CDN_HOSTS=
# --- OpenAI configuration ---
OPENAI_API_KEY=
OPENAI_MODEL=
OPENAI_API_BASE_URL=
OPENAI_BASE_URL=
9 changes: 7 additions & 2 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# --- App configuration ---
APP_ORIGIN_URL="*"
APP_ORIGIN_HOSTS="*"
APP_PORT=3000
APP_HOST="0.0.0.0"

# --- CORS configuration ---
# Comma-separated list of trusted hosts for CORS validation
CORS_TRUSTED_HOSTS="localhost,example.com"
# Comma-separated list of trusted CDN hosts for CORS validation
CORS_TRUSTED_CDN_HOSTS="cdn.example.com"
CORS_TRUSTED_CDN_HOSTS="cdn.example.com"

# --- OpenAI configuration ---
OPENAI_API_KEY="your-openai-api-key-here"
OPENAI_MODEL="gpt-3.5-turbo"
OPENAI_BASE_URL="https://api.openai.com/v1"
9 changes: 7 additions & 2 deletions apps/api/.env.schema
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# --- App configuration ---
APP_ORIGIN_URL=
APP_ORIGIN_HOSTS=
APP_PORT=
APP_HOST=

# --- CORS configuration ---
# Comma-separated list of trusted hosts for CORS validation
CORS_TRUSTED_HOSTS=
# Comma-separated list of trusted CDN hosts for CORS validation
CORS_TRUSTED_CDN_HOSTS=
CORS_TRUSTED_CDN_HOSTS=

# --- OpenAI configuration ---
OPENAI_API_KEY=
OPENAI_MODEL=
OPENAI_BASE_URL=
7 changes: 7 additions & 0 deletions apps/api/boot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { config } from 'dotenv'
import { getEnvPath } from '@repo/utils/utils.js'

const envPath = getEnvPath()
config({ path: envPath || undefined, override: true })

import('./index.js')
71 changes: 41 additions & 30 deletions apps/api/middleware/cors.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,23 @@
import consola from 'consola'

const APP_ORIGIN_URL = process.env.APP_ORIGIN_URL || 'http://localhost:3000'
const APP_ORIGIN_HOST = new URL(APP_ORIGIN_URL).host
const APP_ORIGIN_HOSTS = process.env.APP_ORIGIN_HOSTS || 'localhost'
const isDevelopment = process.env.NODE_ENV !== 'production'

if (!APP_ORIGIN_URL) {
throw new Error('APP_ORIGIN_URL environment variable is required')
}

/**
* Helper function to check if the request host is allowed based on the configured APP_ORIGIN_URL.
* @param {string | undefined} host - The host from the request headers (e.g., 'localhost:3000')
* @returns {boolean}
*/
const isHostAllowed = (host) => {
if (!host) {
return isDevelopment
}

return host === APP_ORIGIN_HOST
}
// Support wildcard '*' or comma-separated list of full origin URLs
// e.g. APP_ORIGIN_HOSTS="modul-ajar.web.id,test.modul-ajar.web.id"
const ALLOWED_ORIGINS = APP_ORIGIN_HOSTS === '*'
? '*'
: APP_ORIGIN_HOSTS.split(',').map(o => o.trim())

/**
* Helper function to check if the request origin is allowed based on the configured APP_ORIGIN_URL.
* @param {string} origin - The origin from the request headers (e.g., 'http://localhost:3000')
* Helper function to check if the request origin is allowed.
* @param {string | undefined} origin - The origin from the request headers (e.g., 'https://modul-ajar.web.id')
* @returns {boolean}
*/
const isOriginAllowed = (origin) => {
if (!origin) {
return isDevelopment
}

return origin === APP_ORIGIN_HOST
if (!origin) return isDevelopment
if (ALLOWED_ORIGINS === '*') return true
return ALLOWED_ORIGINS.includes(origin)
}

/**
Expand All @@ -42,13 +28,38 @@ const isOriginAllowed = (origin) => {
* @returns {import('express').Response | void}
*/
const cors = (req, res, next) => {
const origin = req.headers.origin || req.headers.host || ''
consola.debug(`CORS check - Origin: ${origin}, Host: ${req.headers.host}`)
const originHeader = req.headers.origin || req.get('origin') || ''

let origin = ''
let fullUrl = ''

if (originHeader) {
try {
const urlObj = new URL(originHeader)
origin = urlObj.hostname
fullUrl = originHeader
} catch (e) {
origin = originHeader
fullUrl = originHeader
}
} else {
// Fallback if no origin is provided (e.g., direct API calls)
const hostHeader = String(req.get('host') || '')
origin = hostHeader.split(':')[0]
fullUrl = req.protocol + '://' + hostHeader
}

consola.debug(`CORS check - Origin Header: ${originHeader}, Extracted Origin: ${origin}, URL: ${fullUrl}`)

res.header('Vary', 'Origin')

if (isOriginAllowed(origin)) {
res.header('Access-Control-Allow-Origin', origin)
const IS_ALLOWED = isOriginAllowed(origin)
consola.debug(`CORS allowed: ${IS_ALLOWED} for origin: ${origin}`)

if (ALLOWED_ORIGINS === '*') {
res.header('Access-Control-Allow-Origin', '*')
} else if (origin && IS_ALLOWED) {
res.header('Access-Control-Allow-Origin', fullUrl)
}

if (req.url.startsWith('/api/')) {
Expand All @@ -64,7 +75,7 @@ const cors = (req, res, next) => {
return res.sendStatus(204)
}

if (!isOriginAllowed(origin) && !isHostAllowed(req.headers.host)) {
if (!isOriginAllowed(origin)) {
consola.warn(`CORS blocked - origin not allowed: ${origin}`)
return res.status(403).json({ status: false, message: '403 Forbidden', error: null })
}
Expand Down
2 changes: 1 addition & 1 deletion apps/api/middleware/helmet.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const helmetMiddleware = helmet({

'upgrade-insecure-requests': isDevelopment ? null : [],
'frame-ancestors': ["'self'", ...CORS_TRUSTED_HOSTS],
'script-src': ["'self'", "'unsafe-inline'", ...CORS_TRUSTED_CDN_HOSTS],
'script-src': ["'self'", "'unsafe-inline'", 'https://static.cloudflareinsights.com', ...CORS_TRUSTED_CDN_HOSTS],
'style-src': ["'self'", "'unsafe-inline'", ...CORS_TRUSTED_CDN_HOSTS],
'img-src': ["'self'", 'data:', 'https://contrib.rocks', ...CORS_TRUSTED_CDN_HOSTS],
'font-src': ["'self'", ...CORS_TRUSTED_CDN_HOSTS]
Expand Down
6 changes: 4 additions & 2 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
"description": "API for the Modul Ajar Generator",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "npx nodemon index.js"
"start": "cross-env NODE_ENV=production node boot.js",
"dev": "cross-env NODE_ENV=development npx nodemon boot.js"
},
"keywords": [],
"author": "NgodingCik",
"license": "Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)",
"type": "module",
"dependencies": {
"@repo/handlers": "^1.0.0",
"@repo/utils": "^1.0.0",
"consola": "^3.4.2",
"dotenv": "^17.4.2",
"express": "^5.2.1",
Expand Down
35 changes: 35 additions & 0 deletions apps/api/vercel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"version": 2,
"builds": [
{
"src": "index.js",
"use": "@vercel/node",
"config": {
"bundle": false,
"includeFiles": [
"**",
"../../packages/**",
"../../context/**",
"../../public/**",
"../../assets/**",
"../../resources/**",
"../../node_modules/@repo/**",
"../../node_modules/openai/**",
"../../node_modules/consola/**",
"../../node_modules/dotenv/**",
"../../node_modules/express/**",
"../../node_modules/docx/**",
"../../node_modules/@dqbd/**",
"../../node_modules/express-rate-limit/**",
"../../node_modules/helmet/**"
]
}
}
],
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.js"
}
]
}
7 changes: 4 additions & 3 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# --- App configuration ---
APP_NAME="My App"
APP_ORIGIN_URL="*"
# Comma-separated list of allowed CORS
APP_ORIGIN_HOSTS="*"
APP_PORT=3000
APP_HOST="0.0.0.0"
# Base URL for API requests
APP_API_BASE_URL=http://localhost:3000/api
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by OPENAI_API_BASE_URL and not use the built-in API routes)
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by APP_API_BASE_URL and not use the built-in API routes)
APP_USE_BUILTIN_API=true

# --- CORS configuration ---
Expand All @@ -17,4 +18,4 @@ CORS_TRUSTED_CDN_HOSTS="cdn.example.com"
# --- OpenAI configuration ---
OPENAI_API_KEY="your-openai-api-key-here"
OPENAI_MODEL="gpt-3.5-turbo"
OPENAI_API_BASE_URL="https://api.openai.com/v1"
OPENAI_BASE_URL="https://api.openai.com/v1"
7 changes: 4 additions & 3 deletions apps/web/.env.schema
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# --- App configuration ---
APP_NAME=
APP_ORIGIN_URL=
# Comma-separated list of allowed CORS
APP_ORIGIN_HOSTS=
APP_PORT=
APP_HOST=
# Base URL for API requests
APP_API_BASE_URL=
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by OPENAI_API_BASE_URL and not use the built-in API routes)
# Flag to determine whether to use the built-in API or an external API (if true, it will use the built-in API; if false, it will use the external API defined by APP_API_BASE_URL and not use the built-in API routes)
APP_USE_BUILTIN_API=

# --- CORS configuration ---
Expand All @@ -17,4 +18,4 @@ CORS_TRUSTED_CDN_HOSTS=
# --- OpenAI configuration ---
OPENAI_API_KEY=
OPENAI_MODEL=
OPENAI_API_BASE_URL=
OPENAI_BASE_URL=
7 changes: 7 additions & 0 deletions apps/web/boot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { config } from 'dotenv'
import { getEnvPath } from '@repo/utils/utils.js'

const envPath = getEnvPath()
config({ path: envPath || undefined, override: true })

import('./index.js')
Loading
Loading