215 lines
6.6 KiB
Bash
215 lines
6.6 KiB
Bash
#!/bin/sh
|
|
set -e
|
|
|
|
DATA_DIR=${SITEFORGE_DATA_DIR:-/app/data}
|
|
SEED_DIR=/app/data-seed
|
|
|
|
# Ensure the data directory exists (in case the volume was not mounted)
|
|
mkdir -p "$DATA_DIR"
|
|
|
|
# Seed each default JSON file only when missing.
|
|
# This runs on first boot or a fresh volume, but never overwrites existing data.
|
|
if [ -d "$SEED_DIR" ]; then
|
|
for seed_file in "$SEED_DIR"/*.json; do
|
|
[ -f "$seed_file" ] || continue
|
|
|
|
base_name=$(basename "$seed_file")
|
|
target_file="$DATA_DIR/$base_name"
|
|
|
|
if [ ! -f "$target_file" ]; then
|
|
echo "[siteforge] No $base_name found. Seeding from image defaults..."
|
|
cp "$seed_file" "$target_file"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Merge newly added seeded platform links into existing admin content on upgrades
|
|
# without overwriting user-edited values already present in the live volume.
|
|
merge_seed_platform_links() {
|
|
target_file="$1"
|
|
seed_file="$2"
|
|
|
|
[ -f "$target_file" ] || return 0
|
|
[ -f "$seed_file" ] || return 0
|
|
|
|
node - "$target_file" "$seed_file" <<'NODE'
|
|
const fs = require('fs')
|
|
|
|
const [targetFile, seedFile] = process.argv.slice(2)
|
|
|
|
function readJson(file) {
|
|
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
}
|
|
|
|
function normalizeLinks(value) {
|
|
return Array.isArray(value) ? value.filter(item => item && typeof item === 'object') : []
|
|
}
|
|
|
|
const target = readJson(targetFile)
|
|
const seed = readJson(seedFile)
|
|
|
|
if (!target.siteContent || !seed.siteContent) process.exit(0)
|
|
|
|
const targetLinks = normalizeLinks(target.siteContent.customLinks)
|
|
const seedLinks = normalizeLinks(seed.siteContent.customLinks)
|
|
|
|
let changed = false
|
|
|
|
for (const seedLink of seedLinks) {
|
|
if (seedLink.placement !== 'platforms' || !seedLink.id || !seedLink.label || !seedLink.url) continue
|
|
|
|
const existingIndex = targetLinks.findIndex(link => link.id === seedLink.id)
|
|
if (existingIndex === -1) {
|
|
targetLinks.push(seedLink)
|
|
changed = true
|
|
continue
|
|
}
|
|
|
|
const existing = targetLinks[existingIndex]
|
|
const merged = { ...existing }
|
|
|
|
if (!merged.imageUrl && seedLink.imageUrl) {
|
|
merged.imageUrl = seedLink.imageUrl
|
|
changed = true
|
|
}
|
|
|
|
if (!merged.placement && seedLink.placement) {
|
|
merged.placement = seedLink.placement
|
|
changed = true
|
|
}
|
|
|
|
if (!merged.label && seedLink.label) {
|
|
merged.label = seedLink.label
|
|
changed = true
|
|
}
|
|
|
|
if (!merged.url && seedLink.url) {
|
|
merged.url = seedLink.url
|
|
changed = true
|
|
}
|
|
|
|
targetLinks[existingIndex] = merged
|
|
}
|
|
|
|
if (!changed) process.exit(0)
|
|
|
|
target.siteContent.customLinks = targetLinks
|
|
fs.writeFileSync(targetFile, `${JSON.stringify(target, null, 2)}\n`)
|
|
console.log(`[siteforge] Merged seeded platform links into ${targetFile}`)
|
|
NODE
|
|
}
|
|
|
|
merge_seed_platform_links "$DATA_DIR/admin-content.json" "$SEED_DIR/admin-content.json"
|
|
merge_seed_platform_links "$DATA_DIR/admin-content-draft.json" "$SEED_DIR/admin-content-draft.json"
|
|
|
|
# Backfill missing lesson releasedAt values from seeded defaults on upgrades.
|
|
# This keeps persistent volumes compatible with release-date access gating.
|
|
merge_seed_study_release_dates() {
|
|
target_file="$1"
|
|
seed_file="$2"
|
|
|
|
[ -f "$target_file" ] || return 0
|
|
[ -f "$seed_file" ] || return 0
|
|
|
|
node - "$target_file" "$seed_file" <<'NODE'
|
|
const fs = require('fs')
|
|
|
|
const [targetFile, seedFile] = process.argv.slice(2)
|
|
|
|
function readJson(file) {
|
|
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
}
|
|
|
|
function asArray(value) {
|
|
return Array.isArray(value) ? value : []
|
|
}
|
|
|
|
function mergeStudyDates(targetStudies, seedStudies) {
|
|
let changed = false
|
|
const seedBySlug = new Map()
|
|
|
|
for (const seedStudy of asArray(seedStudies)) {
|
|
if (!seedStudy || typeof seedStudy !== 'object') continue
|
|
if (typeof seedStudy.slug !== 'string' || !seedStudy.slug) continue
|
|
seedBySlug.set(seedStudy.slug, seedStudy)
|
|
}
|
|
|
|
for (const targetStudy of asArray(targetStudies)) {
|
|
if (!targetStudy || typeof targetStudy !== 'object') continue
|
|
if (typeof targetStudy.slug !== 'string' || !targetStudy.slug) continue
|
|
|
|
const seedStudy = seedBySlug.get(targetStudy.slug)
|
|
if (!seedStudy) continue
|
|
|
|
const seedSectionById = new Map()
|
|
for (const seedSection of asArray(seedStudy.sections)) {
|
|
if (!seedSection || typeof seedSection !== 'object') continue
|
|
if (typeof seedSection.id !== 'string' || !seedSection.id) continue
|
|
seedSectionById.set(seedSection.id, seedSection)
|
|
}
|
|
|
|
for (const targetSection of asArray(targetStudy.sections)) {
|
|
if (!targetSection || typeof targetSection !== 'object') continue
|
|
if (typeof targetSection.id !== 'string' || !targetSection.id) continue
|
|
if (typeof targetSection.releasedAt === 'string' && targetSection.releasedAt.trim()) continue
|
|
|
|
const seedSection = seedSectionById.get(targetSection.id)
|
|
if (!seedSection || typeof seedSection.releasedAt !== 'string' || !seedSection.releasedAt.trim()) continue
|
|
|
|
targetSection.releasedAt = seedSection.releasedAt
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
return changed
|
|
}
|
|
|
|
function mergeLegacyColossiansDates(targetSections, seedSections) {
|
|
let changed = false
|
|
const seedById = new Map()
|
|
|
|
for (const seedSection of asArray(seedSections)) {
|
|
if (!seedSection || typeof seedSection !== 'object') continue
|
|
if (typeof seedSection.id !== 'string' || !seedSection.id) continue
|
|
seedById.set(seedSection.id, seedSection)
|
|
}
|
|
|
|
for (const targetSection of asArray(targetSections)) {
|
|
if (!targetSection || typeof targetSection !== 'object') continue
|
|
if (typeof targetSection.id !== 'string' || !targetSection.id) continue
|
|
if (typeof targetSection.releasedAt === 'string' && targetSection.releasedAt.trim()) continue
|
|
|
|
const seedSection = seedById.get(targetSection.id)
|
|
if (!seedSection || typeof seedSection.releasedAt !== 'string' || !seedSection.releasedAt.trim()) continue
|
|
|
|
targetSection.releasedAt = seedSection.releasedAt
|
|
changed = true
|
|
}
|
|
|
|
return changed
|
|
}
|
|
|
|
const target = readJson(targetFile)
|
|
const seed = readJson(seedFile)
|
|
|
|
if (!target.siteContent || !seed.siteContent) process.exit(0)
|
|
|
|
const targetContent = target.siteContent
|
|
const seedContent = seed.siteContent
|
|
|
|
const changedStudies = mergeStudyDates(targetContent.studies, seedContent.studies)
|
|
const changedLegacy = mergeLegacyColossiansDates(targetContent.colossiansStudySections, seedContent.colossiansStudySections)
|
|
|
|
if (!changedStudies && !changedLegacy) process.exit(0)
|
|
|
|
fs.writeFileSync(targetFile, `${JSON.stringify(target, null, 2)}\n`)
|
|
console.log(`[siteforge] Backfilled missing releasedAt values in ${targetFile}`)
|
|
NODE
|
|
}
|
|
|
|
merge_seed_study_release_dates "$DATA_DIR/admin-content.json" "$SEED_DIR/admin-content.json"
|
|
merge_seed_study_release_dates "$DATA_DIR/admin-content-draft.json" "$SEED_DIR/admin-content-draft.json"
|
|
|
|
export SITEFORGE_DATA_DIR="$DATA_DIR"
|
|
exec node server.js
|