feat: initialize CalBook project with comprehensive scheduling, admin, and deployment infrastructure

This commit is contained in:
2026-05-07 13:04:02 +02:00
parent 51acfe9488
commit ee48a93824
133 changed files with 26049 additions and 0 deletions

118
scripts/export-backup.sh Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# export-backup.sh Standalone-Export für CalBook (braucht nur Docker + .env)
# Funktioniert mit jeder CalBook-Version, auch ohne die Backup-API.
# Output: calbook-backup-YYYY-MM-DD.json
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ROOT_DIR}/.env"
if [[ ! -f "${ENV_FILE}" ]]; then
echo "Fehler: .env nicht gefunden in ${ROOT_DIR}"
exit 1
fi
get_env() {
local key="$1"
local line val
line="$(grep -E "^${key}=" "${ENV_FILE}" | head -n1 || true)"
[[ -z "${line}" ]] && { echo ""; return; }
val="${line#*=}"
val="${val#\"}"; val="${val%\"}"
val="${val#\'}"; val="${val%\'}"
echo "${val}"
}
if ! command -v docker >/dev/null 2>&1; then
echo "Fehler: Docker wird benötigt."
exit 1
fi
STACK_NAME="$(get_env STACK_NAME)"
[[ -z "${STACK_NAME}" ]] && STACK_NAME="calbook"
CONTAINER="${STACK_NAME}-db"
if ! docker ps --format '{{.Names}}' | grep -qx "${CONTAINER}"; then
echo "Fehler: DB-Container '${CONTAINER}' läuft nicht."
exit 1
fi
OUTPUT_FILE="calbook-backup-$(date '+%Y-%m-%d').json"
TEMP_DIR="$(mktemp -d)"
trap 'rm -rf "${TEMP_DIR}"' EXIT
echo "Exportiere Datenbank via Container '${CONTAINER}'..."
run_query() {
docker exec "${CONTAINER}" psql -U calbook -d calbook -t -A -F $'\t' -c "$1" > "$2"
}
# ── Settings ─────────────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT key, value FROM \"Setting\" WHERE key NOT IN ('PUBLIC_URL', 'NEXTAUTH_URL', 'APP_BASE_URL') ORDER BY key) t;" \
"${TEMP_DIR}/settings.json"
# ── Users ────────────────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT id, name, email, \"hashedPassword\", role, slug, bio, \"avatarUrl\", timezone, \"isActive\", \"createdAt\", \"updatedAt\" FROM \"User\" ORDER BY email) t;" \
"${TEMP_DIR}/users.json"
# ── Calendar connections ─────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT id, \"userId\", name, \"bookingAllowedWeekdays\", \"bookingDayStartTime\", \"bookingDayEndTime\", \"bookingDayRangesJson\", url, username, \"notificationEmail\", \"encryptedPassword\", color, \"syncEnabled\", \"lastSyncedAt\", \"createdAt\", \"updatedAt\" FROM \"CalendarConn\" ORDER BY \"createdAt\") t;" \
"${TEMP_DIR}/calendars.json"
# ── Appointments ─────────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT * FROM \"Appointment\" ORDER BY \"startAt\") t;" \
"${TEMP_DIR}/appointments.json"
# ── Busy blocks ──────────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT * FROM \"BusyBlock\" ORDER BY \"startAt\") t;" \
"${TEMP_DIR}/busyblocks.json"
# ── Delivery issues ──────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT * FROM \"DeliveryIssue\" ORDER BY \"firstSeenAt\") t;" \
"${TEMP_DIR}/delivery.json"
# ── Sync runs + logs ─────────────────────────────────
run_query "SELECT json_agg(t) FROM (SELECT r.*, (SELECT json_agg(e) FROM (SELECT * FROM \"CalendarSyncLogEntry\" e WHERE e.\"syncRunId\" = r.id ORDER BY e.\"createdAt\") e) AS entries FROM \"CalendarSyncRun\" r ORDER BY r.\"startedAt\") t;" \
"${TEMP_DIR}/syncruns.json"
# ── Assemble via node (keine externen Dependencies) ──
echo "Erstelle Backup-Datei..."
EXPORTED_AT="$(date -u +%Y-%m-%dT%H:%M:%S.000Z)"
CALDAV_KEY="$(get_env CALDAV_ENCRYPTION_KEY)"
node - "$CALDAV_KEY" "$EXPORTED_AT" "$OUTPUT_FILE" "$TEMP_DIR" << 'NODEEOF'
const fs = require('fs');
const caldavKey = process.argv[2] || "";
const exportedAt = process.argv[3];
const outputFile = process.argv[4];
const tempDir = process.argv[5];
const files = ['settings','users','calendars','appointments','busyblocks','delivery','syncruns'];
const data = {};
for (const f of files) {
const raw = fs.readFileSync(tempDir + '/' + f + '.json', 'utf8').trim();
data[f] = raw ? JSON.parse(raw) : [];
}
const backup = {
version: 1,
exportedAt: exportedAt,
caldavEncryptionKey: caldavKey || undefined,
settings: data.settings || [],
users: data.users || [],
calendarConns: data.calendars || [],
appointments: data.appointments || [],
busyBlocks: data.busyblocks || [],
deliveryIssues: data.delivery || [],
syncRuns: data.syncruns || []
};
fs.writeFileSync(outputFile, JSON.stringify(backup, null, 2));
NODEEOF
echo
echo "Backup erstellt: ${OUTPUT_FILE}"
echo "Größe: $(du -h "${OUTPUT_FILE}" | cut -f1)"
echo
echo "Import auf neuer Instanz:"
echo " Admin → Backup → Datei auswählen → Importieren"