1085 lines
51 KiB
JavaScript
1085 lines
51 KiB
JavaScript
// syncDatabase.js
|
||
|
||
import { initializeDatabase, syncModelsWithUpdates, syncModelsAlways, sequelize } from './sequelize.js';
|
||
|
||
// Helper: Query mit Timeout
|
||
const queryWithTimeout = async (query, timeoutMs = 30000, description = 'Query') => {
|
||
const timeoutPromise = new Promise((_, reject) => {
|
||
setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs);
|
||
});
|
||
|
||
try {
|
||
const result = await Promise.race([
|
||
sequelize.query(query),
|
||
timeoutPromise
|
||
]);
|
||
return result;
|
||
} catch (error) {
|
||
if (error.message.includes('Timeout')) {
|
||
console.warn(`⚠️ ${description} hat Timeout nach ${timeoutMs}ms - überspringe...`);
|
||
return [null, 0]; // Return empty result
|
||
}
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// Helper: Retry wrapper for transient pool/connection issues
|
||
const runWithRetry = async (fn, { retries = 3, delayMs = 2000, description = 'operation' } = {}) => {
|
||
let lastError;
|
||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||
try {
|
||
return await fn();
|
||
} catch (error) {
|
||
lastError = error;
|
||
const isAcquireTimeout = error?.name === 'SequelizeConnectionAcquireTimeoutError'
|
||
|| error?.message?.includes('ConnectionAcquireTimeoutError')
|
||
|| error?.message?.includes('Operation timeout');
|
||
if (!isAcquireTimeout || attempt === retries) {
|
||
throw error;
|
||
}
|
||
console.warn(`⚠️ ${description} fehlgeschlagen (AcquireTimeout). Retry ${attempt}/${retries} in ${delayMs}ms...`);
|
||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||
}
|
||
}
|
||
throw lastError;
|
||
};
|
||
|
||
// Helper: Prüft ob Tabelle existiert
|
||
const tableExists = async (schema, tableName) => {
|
||
try {
|
||
const result = await sequelize.query(`
|
||
SELECT EXISTS (
|
||
SELECT FROM information_schema.tables
|
||
WHERE table_schema = '${schema}'
|
||
AND table_name = '${tableName}'
|
||
);
|
||
`, { type: sequelize.QueryTypes.SELECT });
|
||
return result[0]?.exists || false;
|
||
} catch (error) {
|
||
return false;
|
||
}
|
||
};
|
||
import initializeTypes from './initializeTypes.js';
|
||
import initializeSettings from './initializeSettings.js';
|
||
import initializeUserRights from './initializeUserRights.js';
|
||
import initializeImageTypes from './initializeImageTypes.js';
|
||
import initializeFalukant from './initializeFalukant.js';
|
||
import setupAssociations from '../models/associations.js';
|
||
import models from '../models/index.js';
|
||
import { createTriggers } from '../models/trigger.js';
|
||
import initializeForum from './initializeForum.js';
|
||
import initializeChat from './initializeChat.js';
|
||
import initializeMatch3Data from './initializeMatch3.js';
|
||
import updateExistingMatch3Levels from './updateExistingMatch3Levels.js';
|
||
import initializeTaxi from './initializeTaxi.js';
|
||
import initializeWidgetTypes from './initializeWidgetTypes.js';
|
||
|
||
// Normale Synchronisation (nur bei STAGE=dev Schema-Updates)
|
||
const syncDatabase = async () => {
|
||
try {
|
||
// Zeige den aktuellen Stage an
|
||
const currentStage = process.env.STAGE || 'nicht gesetzt';
|
||
console.log(`🚀 Starte Datenbank-Synchronisation (Stage: ${currentStage})`);
|
||
|
||
if (currentStage !== 'dev') {
|
||
console.log('⚠️ WARNUNG: Automatische Schema-Updates sind deaktiviert');
|
||
console.log('💡 Setze STAGE=dev in der .env Datei für automatische Schema-Updates');
|
||
console.log('🔒 Produktionsmodus: Nur normale Synchronisation ohne Schema-Änderungen');
|
||
} else {
|
||
console.log('✅ Entwicklungsmodus aktiv - Schema-Updates sind aktiviert');
|
||
}
|
||
|
||
console.log("Initializing database schemas...");
|
||
await initializeDatabase();
|
||
|
||
// Dashboard: Widget-Typen-Tabelle (mögliche Widgets)
|
||
console.log("Ensuring widget_type table exists...");
|
||
try {
|
||
await sequelize.query(`
|
||
CREATE TABLE IF NOT EXISTS type.widget_type (
|
||
id SERIAL PRIMARY KEY,
|
||
label VARCHAR(255) NOT NULL,
|
||
endpoint VARCHAR(255) NOT NULL,
|
||
description VARCHAR(255),
|
||
order_id INTEGER NOT NULL DEFAULT 0
|
||
);
|
||
`);
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte type.widget_type nicht anlegen:', e?.message || e);
|
||
}
|
||
|
||
// Dashboard: Benutzer-Konfiguration (Widgets pro User)
|
||
console.log("Ensuring user_dashboard table exists...");
|
||
try {
|
||
await sequelize.query(`
|
||
CREATE TABLE IF NOT EXISTS community.user_dashboard (
|
||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||
config JSONB NOT NULL DEFAULT '{"widgets":[]}'::jsonb,
|
||
CONSTRAINT user_dashboard_user_fk
|
||
FOREIGN KEY (user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE
|
||
);
|
||
`);
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte community.user_dashboard nicht anlegen:', e?.message || e);
|
||
}
|
||
|
||
// Vokabeltrainer: Tabellen sicherstellen (auch ohne manuell ausgeführte Migrations)
|
||
// Hintergrund: In Produktion sind Schema-Updates deaktiviert, und Migrations werden nicht automatisch ausgeführt.
|
||
// Damit API/Menu nicht mit "relation does not exist" (42P01) scheitert, legen wir die Tabellen idempotent an.
|
||
console.log("Ensuring Vocab-Trainer tables exist...");
|
||
try {
|
||
await sequelize.query(`
|
||
CREATE TABLE IF NOT EXISTS community.vocab_language (
|
||
id SERIAL PRIMARY KEY,
|
||
owner_user_id INTEGER NOT NULL,
|
||
name TEXT NOT NULL,
|
||
share_code TEXT NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_language_owner_fk
|
||
FOREIGN KEY (owner_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_language_share_code_uniq UNIQUE (share_code)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_language_subscription (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL,
|
||
language_id INTEGER NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_language_subscription_user_fk
|
||
FOREIGN KEY (user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_language_subscription_language_fk
|
||
FOREIGN KEY (language_id)
|
||
REFERENCES community.vocab_language(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_language_subscription_uniq UNIQUE (user_id, language_id)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS vocab_language_owner_idx
|
||
ON community.vocab_language(owner_user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_language_subscription_user_idx
|
||
ON community.vocab_language_subscription(user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_language_subscription_language_idx
|
||
ON community.vocab_language_subscription(language_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_chapter (
|
||
id SERIAL PRIMARY KEY,
|
||
language_id INTEGER NOT NULL,
|
||
title TEXT NOT NULL,
|
||
created_by_user_id INTEGER NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_chapter_language_fk
|
||
FOREIGN KEY (language_id)
|
||
REFERENCES community.vocab_language(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_chapter_creator_fk
|
||
FOREIGN KEY (created_by_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE
|
||
);
|
||
CREATE INDEX IF NOT EXISTS vocab_chapter_language_idx
|
||
ON community.vocab_chapter(language_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_lexeme (
|
||
id SERIAL PRIMARY KEY,
|
||
language_id INTEGER NOT NULL,
|
||
text TEXT NOT NULL,
|
||
normalized TEXT NOT NULL,
|
||
created_by_user_id INTEGER NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_lexeme_language_fk
|
||
FOREIGN KEY (language_id)
|
||
REFERENCES community.vocab_language(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_lexeme_creator_fk
|
||
FOREIGN KEY (created_by_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_lexeme_unique_per_language UNIQUE (language_id, normalized)
|
||
);
|
||
CREATE INDEX IF NOT EXISTS vocab_lexeme_language_idx
|
||
ON community.vocab_lexeme(language_id);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_chapter_lexeme (
|
||
id SERIAL PRIMARY KEY,
|
||
chapter_id INTEGER NOT NULL,
|
||
learning_lexeme_id INTEGER NOT NULL,
|
||
reference_lexeme_id INTEGER NOT NULL,
|
||
created_by_user_id INTEGER NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_chlex_chapter_fk
|
||
FOREIGN KEY (chapter_id)
|
||
REFERENCES community.vocab_chapter(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_chlex_learning_fk
|
||
FOREIGN KEY (learning_lexeme_id)
|
||
REFERENCES community.vocab_lexeme(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_chlex_reference_fk
|
||
FOREIGN KEY (reference_lexeme_id)
|
||
REFERENCES community.vocab_lexeme(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_chlex_creator_fk
|
||
FOREIGN KEY (created_by_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_chlex_unique UNIQUE (chapter_id, learning_lexeme_id, reference_lexeme_id)
|
||
);
|
||
CREATE INDEX IF NOT EXISTS vocab_chlex_chapter_idx
|
||
ON community.vocab_chapter_lexeme(chapter_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_chlex_learning_idx
|
||
ON community.vocab_chapter_lexeme(learning_lexeme_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_chlex_reference_idx
|
||
ON community.vocab_chapter_lexeme(reference_lexeme_id);
|
||
|
||
// Kurs-Tabellen
|
||
CREATE TABLE IF NOT EXISTS community.vocab_course (
|
||
id SERIAL PRIMARY KEY,
|
||
owner_user_id INTEGER NOT NULL,
|
||
title TEXT NOT NULL,
|
||
description TEXT,
|
||
language_id INTEGER NOT NULL,
|
||
native_language_id INTEGER,
|
||
difficulty_level INTEGER DEFAULT 1,
|
||
is_public BOOLEAN DEFAULT false,
|
||
share_code TEXT,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_course_owner_fk
|
||
FOREIGN KEY (owner_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_language_fk
|
||
FOREIGN KEY (language_id)
|
||
REFERENCES community.vocab_language(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_native_language_fk
|
||
FOREIGN KEY (native_language_id)
|
||
REFERENCES community.vocab_language(id)
|
||
ON DELETE SET NULL,
|
||
CONSTRAINT vocab_course_share_code_uniq UNIQUE (share_code)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_course_lesson (
|
||
id SERIAL PRIMARY KEY,
|
||
course_id INTEGER NOT NULL,
|
||
chapter_id INTEGER,
|
||
lesson_number INTEGER NOT NULL,
|
||
title TEXT NOT NULL,
|
||
description TEXT,
|
||
week_number INTEGER,
|
||
day_number INTEGER,
|
||
lesson_type TEXT DEFAULT 'vocab',
|
||
audio_url TEXT,
|
||
cultural_notes TEXT,
|
||
target_minutes INTEGER,
|
||
target_score_percent INTEGER DEFAULT 80,
|
||
requires_review BOOLEAN DEFAULT false,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_course_lesson_course_fk
|
||
FOREIGN KEY (course_id)
|
||
REFERENCES community.vocab_course(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_lesson_chapter_fk
|
||
FOREIGN KEY (chapter_id)
|
||
REFERENCES community.vocab_chapter(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_lesson_unique UNIQUE (course_id, lesson_number)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_course_enrollment (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL,
|
||
course_id INTEGER NOT NULL,
|
||
enrolled_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_course_enrollment_user_fk
|
||
FOREIGN KEY (user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_enrollment_course_fk
|
||
FOREIGN KEY (course_id)
|
||
REFERENCES community.vocab_course(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_enrollment_unique UNIQUE (user_id, course_id)
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS community.vocab_course_progress (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL,
|
||
course_id INTEGER NOT NULL,
|
||
lesson_id INTEGER NOT NULL,
|
||
completed BOOLEAN DEFAULT false,
|
||
score INTEGER DEFAULT 0,
|
||
last_accessed_at TIMESTAMP WITHOUT TIME ZONE,
|
||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||
CONSTRAINT vocab_course_progress_user_fk
|
||
FOREIGN KEY (user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_progress_course_fk
|
||
FOREIGN KEY (course_id)
|
||
REFERENCES community.vocab_course(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_progress_lesson_fk
|
||
FOREIGN KEY (lesson_id)
|
||
REFERENCES community.vocab_course_lesson(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_course_progress_unique UNIQUE (user_id, lesson_id)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS vocab_course_owner_idx
|
||
ON community.vocab_course(owner_user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_language_idx
|
||
ON community.vocab_course(language_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_native_language_idx
|
||
ON community.vocab_course(native_language_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_public_idx
|
||
ON community.vocab_course(is_public);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_course_idx
|
||
ON community.vocab_course_lesson(course_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_chapter_idx
|
||
ON community.vocab_course_lesson(chapter_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_week_idx
|
||
ON community.vocab_course_lesson(course_id, week_number);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_lesson_type_idx
|
||
ON community.vocab_course_lesson(lesson_type);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_enrollment_user_idx
|
||
ON community.vocab_course_enrollment(user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_enrollment_course_idx
|
||
ON community.vocab_course_enrollment(course_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_progress_user_idx
|
||
ON community.vocab_course_progress(user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_progress_course_idx
|
||
ON community.vocab_course_progress(course_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_course_progress_lesson_idx
|
||
ON community.vocab_course_progress(lesson_id);
|
||
|
||
// Grammatik-Übungstypen
|
||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise_type (
|
||
id SERIAL PRIMARY KEY,
|
||
name TEXT NOT NULL UNIQUE,
|
||
description TEXT,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW()
|
||
);
|
||
|
||
// Grammatik-Übungen
|
||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise (
|
||
id SERIAL PRIMARY KEY,
|
||
lesson_id INTEGER NOT NULL,
|
||
exercise_type_id INTEGER NOT NULL,
|
||
exercise_number INTEGER NOT NULL,
|
||
title TEXT NOT NULL,
|
||
instruction TEXT,
|
||
question_data JSONB NOT NULL,
|
||
answer_data JSONB NOT NULL,
|
||
explanation TEXT,
|
||
created_by_user_id INTEGER NOT NULL,
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT NOW(),
|
||
CONSTRAINT vocab_grammar_exercise_lesson_fk
|
||
FOREIGN KEY (lesson_id)
|
||
REFERENCES community.vocab_course_lesson(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_grammar_exercise_type_fk
|
||
FOREIGN KEY (exercise_type_id)
|
||
REFERENCES community.vocab_grammar_exercise_type(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_grammar_exercise_creator_fk
|
||
FOREIGN KEY (created_by_user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_grammar_exercise_unique UNIQUE (lesson_id, exercise_number)
|
||
);
|
||
|
||
// Fortschritt für Grammatik-Übungen
|
||
CREATE TABLE IF NOT EXISTS community.vocab_grammar_exercise_progress (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL,
|
||
exercise_id INTEGER NOT NULL,
|
||
attempts INTEGER DEFAULT 0,
|
||
correct_attempts INTEGER DEFAULT 0,
|
||
last_attempt_at TIMESTAMP WITHOUT TIME ZONE,
|
||
completed BOOLEAN DEFAULT false,
|
||
completed_at TIMESTAMP WITHOUT TIME ZONE,
|
||
CONSTRAINT vocab_grammar_exercise_progress_user_fk
|
||
FOREIGN KEY (user_id)
|
||
REFERENCES community."user"(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_grammar_exercise_progress_exercise_fk
|
||
FOREIGN KEY (exercise_id)
|
||
REFERENCES community.vocab_grammar_exercise(id)
|
||
ON DELETE CASCADE,
|
||
CONSTRAINT vocab_grammar_exercise_progress_unique UNIQUE (user_id, exercise_id)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_lesson_idx
|
||
ON community.vocab_grammar_exercise(lesson_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_type_idx
|
||
ON community.vocab_grammar_exercise(exercise_type_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_progress_user_idx
|
||
ON community.vocab_grammar_exercise_progress(user_id);
|
||
CREATE INDEX IF NOT EXISTS vocab_grammar_exercise_progress_exercise_idx
|
||
ON community.vocab_grammar_exercise_progress(exercise_id);
|
||
|
||
-- Standard-Übungstypen einfügen
|
||
INSERT INTO community.vocab_grammar_exercise_type (name, description) VALUES
|
||
('gap_fill', 'Lückentext-Übung'),
|
||
('multiple_choice', 'Multiple-Choice-Fragen'),
|
||
('sentence_building', 'Satzbau-Übung'),
|
||
('transformation', 'Satzumformung'),
|
||
('conjugation', 'Konjugations-Übung'),
|
||
('declension', 'Deklinations-Übung')
|
||
ON CONFLICT (name) DO NOTHING;
|
||
`);
|
||
console.log("✅ Vocab-Trainer Tabellen sind vorhanden.");
|
||
console.log("✅ Vocab-Course Tabellen sind vorhanden.");
|
||
console.log("✅ Vocab-Grammar-Exercise Tabellen sind vorhanden.");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte Vocab-Trainer Tabellen nicht sicherstellen:', e?.message || e);
|
||
}
|
||
|
||
// Migration: ChurchApplication supervisor_id nullable machen (kritisch für Funktionalität)
|
||
console.log("Making church_application supervisor_id nullable...");
|
||
try {
|
||
await sequelize.query(`
|
||
DO $$
|
||
BEGIN
|
||
-- Prüfe ob supervisor_id NOT NULL Constraint existiert
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'church_application'
|
||
AND column_name = 'supervisor_id'
|
||
AND is_nullable = 'NO'
|
||
) THEN
|
||
ALTER TABLE falukant_data.church_application
|
||
ALTER COLUMN supervisor_id DROP NOT NULL;
|
||
RAISE NOTICE 'supervisor_id NOT NULL Constraint entfernt';
|
||
END IF;
|
||
END
|
||
$$;
|
||
`);
|
||
console.log("✅ church_application supervisor_id ist jetzt nullable");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte church_application supervisor_id nicht nullable machen:', e?.message || e);
|
||
}
|
||
|
||
// Relationship-/Marriage-Proposal-Änderungen loggen (keine Einträge löschen; ohne db:migrate)
|
||
console.log("Ensuring relationship change log (falukant) exists...");
|
||
try {
|
||
await sequelize.query(`
|
||
CREATE TABLE IF NOT EXISTS falukant_log.relationship_change_log (
|
||
id serial PRIMARY KEY,
|
||
changed_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
table_name character varying(64) NOT NULL,
|
||
operation character varying(16) NOT NULL,
|
||
record_id integer,
|
||
payload_old jsonb,
|
||
payload_new jsonb
|
||
);
|
||
`);
|
||
await sequelize.query(`
|
||
CREATE INDEX IF NOT EXISTS relationship_change_log_changed_at_idx
|
||
ON falukant_log.relationship_change_log (changed_at);
|
||
`);
|
||
await sequelize.query(`
|
||
CREATE INDEX IF NOT EXISTS relationship_change_log_table_operation_idx
|
||
ON falukant_log.relationship_change_log (table_name, operation);
|
||
`);
|
||
await sequelize.query(`
|
||
CREATE OR REPLACE FUNCTION falukant_log.log_relationship_change()
|
||
RETURNS TRIGGER AS $$
|
||
DECLARE
|
||
v_record_id INTEGER;
|
||
v_payload_old JSONB;
|
||
v_payload_new JSONB;
|
||
BEGIN
|
||
IF TG_OP = 'INSERT' THEN
|
||
v_record_id := NEW.id;
|
||
v_payload_old := NULL;
|
||
v_payload_new := to_jsonb(NEW);
|
||
ELSIF TG_OP = 'UPDATE' THEN
|
||
v_record_id := NEW.id;
|
||
v_payload_old := to_jsonb(OLD);
|
||
v_payload_new := to_jsonb(NEW);
|
||
ELSIF TG_OP = 'DELETE' THEN
|
||
v_record_id := OLD.id;
|
||
v_payload_old := to_jsonb(OLD);
|
||
v_payload_new := NULL;
|
||
END IF;
|
||
INSERT INTO falukant_log.relationship_change_log (
|
||
table_name, operation, record_id, payload_old, payload_new
|
||
) VALUES (
|
||
TG_TABLE_NAME, TG_OP, v_record_id, v_payload_old, v_payload_new
|
||
);
|
||
IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF;
|
||
END;
|
||
$$ LANGUAGE plpgsql;
|
||
`);
|
||
await sequelize.query(`
|
||
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.relationship;
|
||
CREATE TRIGGER trg_log_relationship_change
|
||
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.relationship
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION falukant_log.log_relationship_change();
|
||
`);
|
||
await sequelize.query(`
|
||
DROP TRIGGER IF EXISTS trg_log_relationship_change ON falukant_data.marriage_proposals;
|
||
CREATE TRIGGER trg_log_relationship_change
|
||
AFTER INSERT OR UPDATE OR DELETE ON falukant_data.marriage_proposals
|
||
FOR EACH ROW
|
||
EXECUTE FUNCTION falukant_log.log_relationship_change();
|
||
`);
|
||
console.log("✅ relationship_change_log und Trigger sind vorhanden.");
|
||
} catch (e) {
|
||
console.warn('⚠️ relationship_change_log/Trigger konnten nicht sichergestellt werden:', e?.message || e);
|
||
}
|
||
|
||
// Preishistorie für Produkte (Zeitreihe) – nur Schema/Struktur, noch ohne Logik
|
||
console.log("Ensuring falukant_log.product_price_history exists...");
|
||
try {
|
||
await sequelize.query(`
|
||
CREATE TABLE IF NOT EXISTS falukant_log.product_price_history (
|
||
id serial PRIMARY KEY,
|
||
product_id integer NOT NULL,
|
||
region_id integer NOT NULL,
|
||
price numeric(12,2) NOT NULL,
|
||
recorded_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
`);
|
||
await sequelize.query(`
|
||
CREATE INDEX IF NOT EXISTS product_price_history_product_region_recorded_idx
|
||
ON falukant_log.product_price_history (product_id, region_id, recorded_at);
|
||
`);
|
||
console.log("✅ product_price_history ist vorhanden.");
|
||
} catch (e) {
|
||
console.warn('⚠️ product_price_history konnte nicht sichergestellt werden:', e?.message || e);
|
||
}
|
||
|
||
// Vorab: Stelle kritische Spalten sicher, damit Index-Erstellung nicht fehlschlägt
|
||
console.log("Pre-ensure Taxi columns (traffic_light) ...");
|
||
try {
|
||
await sequelize.query(`
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'taxi' AND table_name = 'taxi_map_tile' AND column_name = 'traffic_light'
|
||
) THEN
|
||
ALTER TABLE taxi.taxi_map_tile
|
||
ADD COLUMN traffic_light BOOLEAN NOT NULL DEFAULT false;
|
||
END IF;
|
||
END
|
||
$$;
|
||
`);
|
||
console.log("✅ traffic_light-Spalte ist vorhanden");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte traffic_light-Spalte nicht vorab sicherstellen:', e?.message || e);
|
||
}
|
||
|
||
// Cleanup: Entferne verwaiste Einträge vor Schema-Updates (nur wenn Schema-Updates aktiviert)
|
||
if (currentStage === 'dev') {
|
||
console.log("Cleaning up orphaned entries...");
|
||
try {
|
||
// Cleanup user_param_visibility
|
||
const result1 = await sequelize.query(`
|
||
DELETE FROM community.user_param_visibility
|
||
WHERE param_id NOT IN (
|
||
SELECT id FROM community.user_param
|
||
);
|
||
`);
|
||
const deletedCount1 = result1[1] || 0;
|
||
if (deletedCount1 > 0) {
|
||
console.log(`✅ ${deletedCount1} verwaiste user_param_visibility Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup stock mit ungültigen branch_id (0 oder nicht existierend)
|
||
const result2 = await sequelize.query(`
|
||
DELETE FROM falukant_data.stock
|
||
WHERE branch_id = 0 OR branch_id NOT IN (
|
||
SELECT id FROM falukant_data.branch
|
||
);
|
||
`);
|
||
const deletedCount2 = result2[1] || 0;
|
||
if (deletedCount2 > 0) {
|
||
console.log(`✅ ${deletedCount2} verwaiste stock Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup knowledge mit ungültigen character_id oder product_id
|
||
const result3 = await sequelize.query(`
|
||
DELETE FROM falukant_data.knowledge
|
||
WHERE character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR product_id NOT IN (
|
||
SELECT id FROM falukant_type.product
|
||
);
|
||
`);
|
||
const deletedCount3 = result3[1] || 0;
|
||
if (deletedCount3 > 0) {
|
||
console.log(`✅ ${deletedCount3} verwaiste knowledge Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup notification mit ungültigen user_id
|
||
const result4 = await sequelize.query(`
|
||
DELETE FROM falukant_log.notification
|
||
WHERE user_id NOT IN (
|
||
SELECT id FROM falukant_data.falukant_user
|
||
);
|
||
`);
|
||
const deletedCount4 = result4[1] || 0;
|
||
if (deletedCount4 > 0) {
|
||
console.log(`✅ ${deletedCount4} verwaiste notification Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup promotional_gift mit ungültigen sender_character_id oder recipient_character_id
|
||
const result5 = await sequelize.query(`
|
||
DELETE FROM falukant_log.promotional_gift
|
||
WHERE sender_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR recipient_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
);
|
||
`);
|
||
const deletedCount5 = result5[1] || 0;
|
||
if (deletedCount5 > 0) {
|
||
console.log(`✅ ${deletedCount5} verwaiste promotional_gift Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup user_house mit ungültigen house_type_id oder user_id
|
||
const result6 = await sequelize.query(`
|
||
DELETE FROM falukant_data.user_house
|
||
WHERE house_type_id NOT IN (
|
||
SELECT id FROM falukant_type.house
|
||
) OR user_id NOT IN (
|
||
SELECT id FROM falukant_data.falukant_user
|
||
);
|
||
`);
|
||
const deletedCount6 = result6[1] || 0;
|
||
if (deletedCount6 > 0) {
|
||
console.log(`✅ ${deletedCount6} verwaiste user_house Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup child_relation mit ungültigen father_character_id, mother_character_id oder child_character_id
|
||
const result7 = await sequelize.query(`
|
||
DELETE FROM falukant_data.child_relation
|
||
WHERE father_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR mother_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR child_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
);
|
||
`);
|
||
const deletedCount7 = result7[1] || 0;
|
||
if (deletedCount7 > 0) {
|
||
console.log(`✅ ${deletedCount7} verwaiste child_relation Einträge entfernt`);
|
||
}
|
||
|
||
if (deletedCount1 === 0 && deletedCount2 === 0 && deletedCount3 === 0 && deletedCount4 === 0 && deletedCount5 === 0 && deletedCount6 === 0 && deletedCount7 === 0) {
|
||
console.log("✅ Keine verwaisten Einträge gefunden");
|
||
}
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte verwaiste Einträge nicht bereinigen:', e?.message || e);
|
||
}
|
||
}
|
||
|
||
console.log("Setting up associations...");
|
||
setupAssociations();
|
||
|
||
console.log("Synchronizing models...");
|
||
await syncModelsWithUpdates(models);
|
||
|
||
console.log("Initializing settings...");
|
||
await runWithRetry(
|
||
() => initializeSettings(),
|
||
{ retries: 3, delayMs: 2000, description: 'initializeSettings' }
|
||
);
|
||
|
||
console.log("Initializing types...");
|
||
await initializeTypes();
|
||
|
||
console.log("Initializing user rights...");
|
||
await initializeUserRights();
|
||
|
||
console.log("Initializing image types...");
|
||
await initializeImageTypes();
|
||
|
||
console.log("Initializing forums...");
|
||
await initializeForum();
|
||
|
||
console.log("Initializing Falukant...");
|
||
await initializeFalukant();
|
||
|
||
console.log("Creating triggers...");
|
||
await createTriggers();
|
||
|
||
console.log("Initializing chat...");
|
||
await initializeChat();
|
||
|
||
// Match3-Initialisierung NACH der Model-Synchronisation
|
||
console.log("Initializing Match3...");
|
||
await initializeMatch3Data();
|
||
|
||
// Match3-Levels aktualisieren NACH der Initialisierung
|
||
console.log("Updating existing Match3 levels...");
|
||
await updateExistingMatch3Levels();
|
||
|
||
console.log("Initializing Taxi...");
|
||
await initializeTaxi();
|
||
|
||
console.log("Initializing widget types...");
|
||
await initializeWidgetTypes();
|
||
|
||
console.log('Database synchronization complete.');
|
||
} catch (error) {
|
||
console.error('Unable to synchronize the database:', error);
|
||
}
|
||
};
|
||
|
||
// Deployment-Synchronisation (immer Schema-Updates)
|
||
const syncDatabaseForDeployment = async () => {
|
||
try {
|
||
// WICHTIG: Bei Caching-Problemen das Script neu starten
|
||
// Node.js cached ES-Module, daher müssen Models neu geladen werden
|
||
console.log('📦 Lade Models neu (Node.js Module-Cache wird verwendet)...');
|
||
|
||
// Zeige den aktuellen Stage an
|
||
const currentStage = process.env.STAGE || 'nicht gesetzt';
|
||
console.log(`🚀 Starte Datenbank-Synchronisation für Deployment (Stage: ${currentStage})`);
|
||
console.log('✅ Deployment-Modus: Schema-Updates sind immer aktiviert');
|
||
|
||
console.log("Initializing database schemas...");
|
||
await initializeDatabase();
|
||
|
||
// Vorab: Stelle kritische Spalten sicher, damit Index-Erstellung nicht fehlschlägt
|
||
console.log("Pre-ensure Taxi columns (traffic_light) ...");
|
||
try {
|
||
await sequelize.query(`
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'taxi' AND table_name = 'taxi_map_tile' AND column_name = 'traffic_light'
|
||
) THEN
|
||
ALTER TABLE taxi.taxi_map_tile
|
||
ADD COLUMN traffic_light BOOLEAN NOT NULL DEFAULT false;
|
||
END IF;
|
||
END
|
||
$$;
|
||
`);
|
||
console.log("✅ traffic_light-Spalte ist vorhanden");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte traffic_light-Spalte nicht vorab sicherstellen:', e?.message || e);
|
||
}
|
||
|
||
// Migration: Transport product_id und size nullable machen
|
||
console.log("Making transport product_id and size nullable...");
|
||
try {
|
||
await sequelize.query(`
|
||
DO $$
|
||
BEGIN
|
||
-- Prüfe ob product_id NOT NULL Constraint existiert
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'transport'
|
||
AND column_name = 'product_id'
|
||
AND is_nullable = 'NO'
|
||
) THEN
|
||
ALTER TABLE falukant_data.transport
|
||
ALTER COLUMN product_id DROP NOT NULL;
|
||
RAISE NOTICE 'product_id NOT NULL Constraint entfernt';
|
||
END IF;
|
||
|
||
-- Prüfe ob size NOT NULL Constraint existiert
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'transport'
|
||
AND column_name = 'size'
|
||
AND is_nullable = 'NO'
|
||
) THEN
|
||
ALTER TABLE falukant_data.transport
|
||
ALTER COLUMN size DROP NOT NULL;
|
||
RAISE NOTICE 'size NOT NULL Constraint entfernt';
|
||
END IF;
|
||
END
|
||
$$;
|
||
`);
|
||
console.log("✅ Transport product_id und size sind jetzt nullable");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte Transport-Spalten nicht nullable machen:', e?.message || e);
|
||
}
|
||
|
||
// Migration: ChurchApplication supervisor_id nullable machen
|
||
console.log("Making church_application supervisor_id nullable...");
|
||
try {
|
||
await sequelize.query(`
|
||
DO $$
|
||
BEGIN
|
||
-- Prüfe ob supervisor_id NOT NULL Constraint existiert
|
||
IF EXISTS (
|
||
SELECT 1 FROM information_schema.columns
|
||
WHERE table_schema = 'falukant_data'
|
||
AND table_name = 'church_application'
|
||
AND column_name = 'supervisor_id'
|
||
AND is_nullable = 'NO'
|
||
) THEN
|
||
ALTER TABLE falukant_data.church_application
|
||
ALTER COLUMN supervisor_id DROP NOT NULL;
|
||
RAISE NOTICE 'supervisor_id NOT NULL Constraint entfernt';
|
||
END IF;
|
||
END
|
||
$$;
|
||
`);
|
||
console.log("✅ church_application supervisor_id ist jetzt nullable");
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte church_application supervisor_id nicht nullable machen:', e?.message || e);
|
||
}
|
||
|
||
// Cleanup: Entferne verwaiste Einträge vor Schema-Updates
|
||
console.log("Cleaning up orphaned entries...");
|
||
try {
|
||
// Cleanup user_param_visibility (optimiert mit LEFT JOIN)
|
||
console.log(" → Prüfe user_param_visibility...");
|
||
const result1 = await queryWithTimeout(`
|
||
DELETE FROM community.user_param_visibility
|
||
WHERE param_id NOT IN (
|
||
SELECT id FROM community.user_param
|
||
);
|
||
`, 30000, 'user_param_visibility cleanup');
|
||
const deletedCount1 = result1[1] || 0;
|
||
if (deletedCount1 > 0) {
|
||
console.log(`✅ ${deletedCount1} verwaiste user_param_visibility Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup stock mit ungültigen branch_id (0 oder nicht existierend)
|
||
console.log(" → Prüfe stock...");
|
||
const result2 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.stock
|
||
WHERE branch_id = 0 OR branch_id NOT IN (
|
||
SELECT id FROM falukant_data.branch
|
||
);
|
||
`, 30000, 'stock cleanup');
|
||
const deletedCount2 = result2[1] || 0;
|
||
if (deletedCount2 > 0) {
|
||
console.log(`✅ ${deletedCount2} verwaiste stock Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup knowledge mit ungültigen character_id oder product_id
|
||
console.log(" → Prüfe knowledge...");
|
||
const result3 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.knowledge
|
||
WHERE character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR product_id NOT IN (
|
||
SELECT id FROM falukant_type.product
|
||
);
|
||
`, 30000, 'knowledge cleanup');
|
||
const deletedCount3 = result3[1] || 0;
|
||
if (deletedCount3 > 0) {
|
||
console.log(`✅ ${deletedCount3} verwaiste knowledge Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup notification mit ungültigen user_id
|
||
console.log(" → Prüfe notification...");
|
||
const result4 = await queryWithTimeout(`
|
||
DELETE FROM falukant_log.notification
|
||
WHERE user_id NOT IN (
|
||
SELECT id FROM falukant_data.falukant_user
|
||
);
|
||
`, 30000, 'notification cleanup');
|
||
const deletedCount4 = result4[1] || 0;
|
||
if (deletedCount4 > 0) {
|
||
console.log(`✅ ${deletedCount4} verwaiste notification Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup promotional_gift mit ungültigen sender_character_id oder recipient_character_id
|
||
console.log(" → Prüfe promotional_gift...");
|
||
const result5 = await queryWithTimeout(`
|
||
DELETE FROM falukant_log.promotional_gift
|
||
WHERE sender_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR recipient_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
);
|
||
`, 30000, 'promotional_gift cleanup');
|
||
const deletedCount5 = result5[1] || 0;
|
||
if (deletedCount5 > 0) {
|
||
console.log(`✅ ${deletedCount5} verwaiste promotional_gift Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup user_house mit ungültigen house_type_id oder user_id
|
||
console.log(" → Prüfe user_house...");
|
||
const result6 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.user_house
|
||
WHERE house_type_id NOT IN (
|
||
SELECT id FROM falukant_type.house
|
||
) OR user_id NOT IN (
|
||
SELECT id FROM falukant_data.falukant_user
|
||
);
|
||
`, 30000, 'user_house cleanup');
|
||
const deletedCount6 = result6[1] || 0;
|
||
if (deletedCount6 > 0) {
|
||
console.log(`✅ ${deletedCount6} verwaiste user_house Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup child_relation mit ungültigen father_character_id, mother_character_id oder child_character_id
|
||
console.log(" → Prüfe child_relation...");
|
||
const result7 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.child_relation
|
||
WHERE father_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR mother_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR child_character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
);
|
||
`, 30000, 'child_relation cleanup');
|
||
const deletedCount7 = result7[1] || 0;
|
||
if (deletedCount7 > 0) {
|
||
console.log(`✅ ${deletedCount7} verwaiste child_relation Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup political_office mit ungültigen character_id, office_type_id oder region_id
|
||
console.log(" → Prüfe political_office...");
|
||
const result8 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.political_office
|
||
WHERE character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR office_type_id NOT IN (
|
||
SELECT id FROM falukant_type.political_office_type
|
||
) OR region_id NOT IN (
|
||
SELECT id FROM falukant_data.region
|
||
);
|
||
`, 30000, 'political_office cleanup');
|
||
const deletedCount8 = result8[1] || 0;
|
||
if (deletedCount8 > 0) {
|
||
console.log(`✅ ${deletedCount8} verwaiste political_office Einträge entfernt`);
|
||
}
|
||
|
||
// Cleanup church_office mit ungültigen character_id, office_type_id oder region_id (nur wenn Tabelle existiert)
|
||
if (await tableExists('falukant_data', 'church_office')) {
|
||
console.log(" → Prüfe church_office...");
|
||
const result11 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.church_office
|
||
WHERE character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR office_type_id NOT IN (
|
||
SELECT id FROM falukant_type.church_office_type
|
||
) OR region_id NOT IN (
|
||
SELECT id FROM falukant_data.region
|
||
);
|
||
`, 30000, 'church_office cleanup');
|
||
const deletedCount11 = result11[1] || 0;
|
||
if (deletedCount11 > 0) {
|
||
console.log(`✅ ${deletedCount11} verwaiste church_office Einträge entfernt`);
|
||
}
|
||
}
|
||
|
||
// Cleanup church_application mit ungültigen character_id, office_type_id, region_id oder supervisor_id (nur wenn Tabelle existiert)
|
||
if (await tableExists('falukant_data', 'church_application')) {
|
||
console.log(" → Prüfe church_application...");
|
||
const result12 = await queryWithTimeout(`
|
||
DELETE FROM falukant_data.church_application
|
||
WHERE character_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
) OR office_type_id NOT IN (
|
||
SELECT id FROM falukant_type.church_office_type
|
||
) OR region_id NOT IN (
|
||
SELECT id FROM falukant_data.region
|
||
) OR (supervisor_id IS NOT NULL AND supervisor_id NOT IN (
|
||
SELECT id FROM falukant_data.character
|
||
));
|
||
`, 30000, 'church_application cleanup');
|
||
const deletedCount12 = result12[1] || 0;
|
||
if (deletedCount12 > 0) {
|
||
console.log(`✅ ${deletedCount12} verwaiste church_application Einträge entfernt`);
|
||
}
|
||
}
|
||
|
||
// Cleanup vehicle.condition: Legacy-Nulls + Range clamp (UI zeigt sonst "Unbekannt")
|
||
console.log(" → Prüfe vehicle.condition...");
|
||
const result9 = await queryWithTimeout(`
|
||
UPDATE falukant_data.vehicle
|
||
SET condition = 100
|
||
WHERE condition IS NULL;
|
||
`, 30000, 'vehicle condition NULL update');
|
||
const updatedNullConditions = result9[1] || 0;
|
||
if (updatedNullConditions > 0) {
|
||
console.log(`✅ ${updatedNullConditions} vehicle.condition NULL → 100 gesetzt`);
|
||
}
|
||
const result10 = await queryWithTimeout(`
|
||
UPDATE falukant_data.vehicle
|
||
SET condition = GREATEST(0, LEAST(100, condition))
|
||
WHERE condition < 0 OR condition > 100;
|
||
`, 30000, 'vehicle condition clamp');
|
||
const clampedConditions = result10[1] || 0;
|
||
if (clampedConditions > 0) {
|
||
console.log(`✅ ${clampedConditions} vehicle.condition Werte auf 0..100 geklemmt`);
|
||
}
|
||
|
||
if (deletedCount1 === 0 && deletedCount2 === 0 && deletedCount3 === 0 && deletedCount4 === 0 && deletedCount5 === 0 && deletedCount6 === 0 && deletedCount7 === 0 && deletedCount8 === 0 && deletedCount11 === 0 && deletedCount12 === 0 && updatedNullConditions === 0 && clampedConditions === 0) {
|
||
console.log("✅ Keine verwaisten Einträge gefunden");
|
||
}
|
||
} catch (e) {
|
||
console.warn('⚠️ Konnte verwaiste Einträge nicht bereinigen:', e?.message || e);
|
||
}
|
||
|
||
console.log("Setting up associations...");
|
||
setupAssociations();
|
||
|
||
console.log("Synchronizing models with schema updates...");
|
||
await syncModelsAlways(models);
|
||
|
||
console.log("Initializing settings...");
|
||
await initializeSettings();
|
||
|
||
console.log("Initializing types...");
|
||
await initializeTypes();
|
||
|
||
console.log("Initializing user rights...");
|
||
await initializeUserRights();
|
||
|
||
console.log("Initializing image types...");
|
||
await initializeImageTypes();
|
||
|
||
console.log("Initializing forums...");
|
||
await initializeForum();
|
||
|
||
console.log("Initializing Falukant...");
|
||
await initializeFalukant();
|
||
|
||
console.log("Creating triggers...");
|
||
await createTriggers();
|
||
|
||
console.log("Initializing chat...");
|
||
await initializeChat();
|
||
|
||
// Match3-Initialisierung NACH der Model-Synchronisation UND nach der Erstellung aller Tabellen
|
||
console.log("Initializing Match3...");
|
||
await initializeMatch3Data();
|
||
|
||
// Match3-Levels aktualisieren NACH der Initialisierung
|
||
console.log("Updating existing Match3 levels...");
|
||
await updateExistingMatch3Levels();
|
||
|
||
console.log("Initializing Taxi...");
|
||
await initializeTaxi();
|
||
|
||
console.log("Initializing widget types...");
|
||
await initializeWidgetTypes();
|
||
|
||
console.log('Database synchronization for deployment complete.');
|
||
} catch (error) {
|
||
console.error('Unable to synchronize the database for deployment:', error);
|
||
throw error; // Fehler weiterwerfen
|
||
}
|
||
};
|
||
|
||
export { syncDatabase, syncDatabaseForDeployment };
|