- Fixed 'relation session_kb_associations does not exist' error in core consolidated migration. - Renamed migration directories from timestamp-based to version-based (6.0.x, 6.1.x, 6.2.x). - Reorganized migrations into dedicated feature folders (products, dashboards, learn, video). - Updated migration execution order in core/shared/utils.rs. - Moves legacy migrations to 6.0.x/6.1.x and workflow to 6.2.0.
401 lines
20 KiB
PL/PgSQL
401 lines
20 KiB
PL/PgSQL
-- Learn Module Database Migration
|
|
-- Learning Management System (LMS) for General Bots
|
|
|
|
-- ============================================================================
|
|
-- CATEGORIES TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
icon TEXT,
|
|
color TEXT,
|
|
parent_id UUID REFERENCES learn_categories(id) ON DELETE SET NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_learn_categories_parent ON learn_categories(parent_id);
|
|
CREATE INDEX idx_learn_categories_sort ON learn_categories(sort_order);
|
|
|
|
-- Insert default categories
|
|
INSERT INTO learn_categories (name, description, icon, color, sort_order) VALUES
|
|
('compliance', 'Treinamentos de Compliance e Conformidade', '📋', '#3b82f6', 1),
|
|
('security', 'Segurança da Informação e LGPD', '🔒', '#ef4444', 2),
|
|
('onboarding', 'Integração de Novos Colaboradores', '🚀', '#10b981', 3),
|
|
('skills', 'Desenvolvimento de Habilidades', '💡', '#f59e0b', 4),
|
|
('leadership', 'Liderança e Gestão', '👔', '#8b5cf6', 5),
|
|
('technical', 'Treinamentos Técnicos', '⚙️', '#6366f1', 6),
|
|
('soft-skills', 'Soft Skills e Comunicação', '💬', '#ec4899', 7),
|
|
('wellness', 'Bem-estar e Qualidade de Vida', '🧘', '#14b8a6', 8);
|
|
|
|
-- ============================================================================
|
|
-- COURSES TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_courses (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id UUID,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
category TEXT NOT NULL DEFAULT 'skills',
|
|
difficulty TEXT NOT NULL DEFAULT 'beginner',
|
|
duration_minutes INTEGER NOT NULL DEFAULT 0,
|
|
thumbnail_url TEXT,
|
|
is_mandatory BOOLEAN NOT NULL DEFAULT FALSE,
|
|
due_days INTEGER,
|
|
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_by UUID,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT valid_difficulty CHECK (difficulty IN ('beginner', 'intermediate', 'advanced'))
|
|
);
|
|
|
|
CREATE INDEX idx_learn_courses_org ON learn_courses(organization_id);
|
|
CREATE INDEX idx_learn_courses_category ON learn_courses(category);
|
|
CREATE INDEX idx_learn_courses_mandatory ON learn_courses(is_mandatory);
|
|
CREATE INDEX idx_learn_courses_published ON learn_courses(is_published);
|
|
CREATE INDEX idx_learn_courses_created ON learn_courses(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- LESSONS TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_lessons (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
content TEXT,
|
|
content_type TEXT NOT NULL DEFAULT 'text',
|
|
lesson_order INTEGER NOT NULL DEFAULT 1,
|
|
duration_minutes INTEGER NOT NULL DEFAULT 0,
|
|
video_url TEXT,
|
|
attachments JSONB NOT NULL DEFAULT '[]',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT valid_content_type CHECK (content_type IN ('text', 'video', 'pdf', 'slides', 'interactive'))
|
|
);
|
|
|
|
CREATE INDEX idx_learn_lessons_course ON learn_lessons(course_id);
|
|
CREATE INDEX idx_learn_lessons_order ON learn_lessons(course_id, lesson_order);
|
|
|
|
-- ============================================================================
|
|
-- QUIZZES TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_quizzes (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
lesson_id UUID REFERENCES learn_lessons(id) ON DELETE CASCADE,
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
title TEXT NOT NULL,
|
|
passing_score INTEGER NOT NULL DEFAULT 70,
|
|
time_limit_minutes INTEGER,
|
|
max_attempts INTEGER,
|
|
questions JSONB NOT NULL DEFAULT '[]',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT valid_passing_score CHECK (passing_score >= 0 AND passing_score <= 100)
|
|
);
|
|
|
|
CREATE INDEX idx_learn_quizzes_course ON learn_quizzes(course_id);
|
|
CREATE INDEX idx_learn_quizzes_lesson ON learn_quizzes(lesson_id);
|
|
|
|
-- ============================================================================
|
|
-- USER PROGRESS TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_user_progress (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
lesson_id UUID REFERENCES learn_lessons(id) ON DELETE CASCADE,
|
|
status TEXT NOT NULL DEFAULT 'not_started',
|
|
quiz_score INTEGER,
|
|
quiz_attempts INTEGER NOT NULL DEFAULT 0,
|
|
time_spent_minutes INTEGER NOT NULL DEFAULT 0,
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ,
|
|
last_accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
CONSTRAINT valid_status CHECK (status IN ('not_started', 'in_progress', 'completed', 'failed')),
|
|
CONSTRAINT valid_quiz_score CHECK (quiz_score IS NULL OR (quiz_score >= 0 AND quiz_score <= 100))
|
|
);
|
|
|
|
CREATE INDEX idx_learn_progress_user ON learn_user_progress(user_id);
|
|
CREATE INDEX idx_learn_progress_course ON learn_user_progress(course_id);
|
|
CREATE INDEX idx_learn_progress_user_course ON learn_user_progress(user_id, course_id);
|
|
CREATE INDEX idx_learn_progress_status ON learn_user_progress(status);
|
|
CREATE INDEX idx_learn_progress_last_accessed ON learn_user_progress(last_accessed_at DESC);
|
|
|
|
-- Unique constraint for course-level progress (where lesson_id is null)
|
|
CREATE UNIQUE INDEX idx_learn_progress_user_course_unique
|
|
ON learn_user_progress(user_id, course_id)
|
|
WHERE lesson_id IS NULL;
|
|
|
|
-- Unique constraint for lesson-level progress
|
|
CREATE UNIQUE INDEX idx_learn_progress_user_lesson_unique
|
|
ON learn_user_progress(user_id, lesson_id)
|
|
WHERE lesson_id IS NOT NULL;
|
|
|
|
-- ============================================================================
|
|
-- COURSE ASSIGNMENTS TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_course_assignments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
assigned_by UUID,
|
|
due_date TIMESTAMPTZ,
|
|
is_mandatory BOOLEAN NOT NULL DEFAULT TRUE,
|
|
assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ,
|
|
reminder_sent BOOLEAN NOT NULL DEFAULT FALSE,
|
|
reminder_sent_at TIMESTAMPTZ
|
|
);
|
|
|
|
CREATE INDEX idx_learn_assignments_user ON learn_course_assignments(user_id);
|
|
CREATE INDEX idx_learn_assignments_course ON learn_course_assignments(course_id);
|
|
CREATE INDEX idx_learn_assignments_due ON learn_course_assignments(due_date);
|
|
CREATE INDEX idx_learn_assignments_pending ON learn_course_assignments(user_id, completed_at)
|
|
WHERE completed_at IS NULL;
|
|
CREATE INDEX idx_learn_assignments_overdue ON learn_course_assignments(due_date)
|
|
WHERE completed_at IS NULL AND due_date IS NOT NULL;
|
|
|
|
-- Unique constraint to prevent duplicate assignments
|
|
CREATE UNIQUE INDEX idx_learn_assignments_unique ON learn_course_assignments(course_id, user_id);
|
|
|
|
-- ============================================================================
|
|
-- CERTIFICATES TABLE
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_certificates (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
score INTEGER NOT NULL,
|
|
certificate_url TEXT,
|
|
verification_code TEXT NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ,
|
|
|
|
CONSTRAINT valid_cert_score CHECK (score >= 0 AND score <= 100)
|
|
);
|
|
|
|
CREATE INDEX idx_learn_certificates_user ON learn_certificates(user_id);
|
|
CREATE INDEX idx_learn_certificates_course ON learn_certificates(course_id);
|
|
CREATE INDEX idx_learn_certificates_verification ON learn_certificates(verification_code);
|
|
CREATE INDEX idx_learn_certificates_issued ON learn_certificates(issued_at DESC);
|
|
|
|
-- Unique constraint to prevent duplicate certificates
|
|
CREATE UNIQUE INDEX idx_learn_certificates_unique ON learn_certificates(user_id, course_id);
|
|
|
|
-- ============================================================================
|
|
-- QUIZ ATTEMPTS TABLE (for detailed tracking)
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_quiz_attempts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
quiz_id UUID NOT NULL REFERENCES learn_quizzes(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL,
|
|
attempt_number INTEGER NOT NULL DEFAULT 1,
|
|
score INTEGER NOT NULL,
|
|
max_score INTEGER NOT NULL,
|
|
passed BOOLEAN NOT NULL,
|
|
time_taken_minutes INTEGER,
|
|
answers JSONB NOT NULL DEFAULT '{}',
|
|
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
completed_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_learn_quiz_attempts_quiz ON learn_quiz_attempts(quiz_id);
|
|
CREATE INDEX idx_learn_quiz_attempts_user ON learn_quiz_attempts(user_id);
|
|
CREATE INDEX idx_learn_quiz_attempts_user_quiz ON learn_quiz_attempts(user_id, quiz_id);
|
|
|
|
-- ============================================================================
|
|
-- LEARNING PATHS TABLE (for structured learning journeys)
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_paths (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
organization_id UUID,
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
thumbnail_url TEXT,
|
|
is_published BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_by UUID,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_learn_paths_org ON learn_paths(organization_id);
|
|
CREATE INDEX idx_learn_paths_published ON learn_paths(is_published);
|
|
|
|
-- ============================================================================
|
|
-- LEARNING PATH COURSES (junction table)
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS learn_path_courses (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
path_id UUID NOT NULL REFERENCES learn_paths(id) ON DELETE CASCADE,
|
|
course_id UUID NOT NULL REFERENCES learn_courses(id) ON DELETE CASCADE,
|
|
course_order INTEGER NOT NULL DEFAULT 1,
|
|
is_required BOOLEAN NOT NULL DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX idx_learn_path_courses_path ON learn_path_courses(path_id);
|
|
CREATE INDEX idx_learn_path_courses_course ON learn_path_courses(course_id);
|
|
CREATE UNIQUE INDEX idx_learn_path_courses_unique ON learn_path_courses(path_id, course_id);
|
|
|
|
-- ============================================================================
|
|
-- SAMPLE DATA FOR DEMONSTRATION
|
|
-- ============================================================================
|
|
|
|
-- Sample mandatory compliance course
|
|
INSERT INTO learn_courses (id, title, description, category, difficulty, duration_minutes, is_mandatory, due_days, is_published) VALUES
|
|
('11111111-1111-1111-1111-111111111111',
|
|
'LGPD - Lei Geral de Proteção de Dados',
|
|
'Treinamento obrigatório sobre a Lei Geral de Proteção de Dados Pessoais. Aprenda os conceitos fundamentais, direitos dos titulares e obrigações da empresa.',
|
|
'compliance', 'beginner', 45, TRUE, 30, TRUE),
|
|
('22222222-2222-2222-2222-222222222222',
|
|
'Código de Conduta e Ética',
|
|
'Conheça nosso código de conduta e ética empresarial. Este treinamento é obrigatório para todos os colaboradores.',
|
|
'compliance', 'beginner', 30, TRUE, 14, TRUE),
|
|
('33333333-3333-3333-3333-333333333333',
|
|
'Segurança da Informação',
|
|
'Aprenda as melhores práticas de segurança da informação para proteger dados da empresa e de clientes.',
|
|
'security', 'intermediate', 60, TRUE, 30, TRUE),
|
|
('44444444-4444-4444-4444-444444444444',
|
|
'Integração de Novos Colaboradores',
|
|
'Bem-vindo à empresa! Este curso apresenta nossa cultura, valores e processos fundamentais.',
|
|
'onboarding', 'beginner', 90, FALSE, NULL, TRUE),
|
|
('55555555-5555-5555-5555-555555555555',
|
|
'Comunicação Efetiva',
|
|
'Desenvolva habilidades de comunicação profissional para trabalhar melhor em equipe.',
|
|
'soft-skills', 'intermediate', 40, FALSE, NULL, TRUE);
|
|
|
|
-- Sample lessons for LGPD course
|
|
INSERT INTO learn_lessons (course_id, title, content, content_type, lesson_order, duration_minutes) VALUES
|
|
('11111111-1111-1111-1111-111111111111', 'Introdução à LGPD',
|
|
'<h2>O que é a LGPD?</h2><p>A Lei Geral de Proteção de Dados (Lei nº 13.709/2018) é a legislação brasileira que regula as atividades de tratamento de dados pessoais.</p><h3>Objetivos da LGPD</h3><ul><li>Proteger os direitos fundamentais de liberdade e privacidade</li><li>Estabelecer regras claras sobre tratamento de dados pessoais</li><li>Fomentar o desenvolvimento econômico e tecnológico</li></ul>',
|
|
'text', 1, 10),
|
|
('11111111-1111-1111-1111-111111111111', 'Conceitos Fundamentais',
|
|
'<h2>Principais Conceitos</h2><h3>Dados Pessoais</h3><p>Informação relacionada a pessoa natural identificada ou identificável.</p><h3>Dados Sensíveis</h3><p>Dados sobre origem racial, convicção religiosa, opinião política, filiação sindical, dados de saúde, vida sexual, dados genéticos ou biométricos.</p><h3>Tratamento de Dados</h3><p>Toda operação realizada com dados pessoais: coleta, armazenamento, uso, compartilhamento, exclusão, etc.</p>',
|
|
'text', 2, 15),
|
|
('11111111-1111-1111-1111-111111111111', 'Direitos dos Titulares',
|
|
'<h2>Direitos Garantidos pela LGPD</h2><ul><li><strong>Confirmação e acesso:</strong> saber se seus dados são tratados e ter acesso a eles</li><li><strong>Correção:</strong> corrigir dados incompletos, inexatos ou desatualizados</li><li><strong>Anonimização e exclusão:</strong> solicitar a anonimização ou eliminação de dados desnecessários</li><li><strong>Portabilidade:</strong> transferir seus dados para outro fornecedor</li><li><strong>Revogação do consentimento:</strong> revogar o consentimento a qualquer momento</li></ul>',
|
|
'text', 3, 10),
|
|
('11111111-1111-1111-1111-111111111111', 'Responsabilidades da Empresa',
|
|
'<h2>Nossas Responsabilidades</h2><p>Como colaboradores, temos o dever de:</p><ul><li>Tratar dados pessoais apenas para finalidades legítimas</li><li>Manter a confidencialidade das informações</li><li>Reportar incidentes de segurança</li><li>Seguir os procedimentos internos de proteção de dados</li></ul>',
|
|
'text', 4, 10);
|
|
|
|
-- Sample quiz for LGPD course
|
|
INSERT INTO learn_quizzes (course_id, title, passing_score, time_limit_minutes, questions) VALUES
|
|
('11111111-1111-1111-1111-111111111111', 'Avaliação - LGPD', 70, 15, '[
|
|
{
|
|
"id": "q1",
|
|
"text": "O que significa LGPD?",
|
|
"question_type": "single_choice",
|
|
"options": [
|
|
{"text": "Lei Geral de Proteção de Dados", "is_correct": true},
|
|
{"text": "Lei Governamental de Privacidade Digital", "is_correct": false},
|
|
{"text": "Legislação Geral de Proteção Digital", "is_correct": false},
|
|
{"text": "Lei de Garantia e Proteção de Dados", "is_correct": false}
|
|
],
|
|
"correct_answers": [0],
|
|
"explanation": "LGPD significa Lei Geral de Proteção de Dados (Lei nº 13.709/2018).",
|
|
"points": 10
|
|
},
|
|
{
|
|
"id": "q2",
|
|
"text": "Quais são considerados dados sensíveis pela LGPD?",
|
|
"question_type": "multiple_choice",
|
|
"options": [
|
|
{"text": "Dados de saúde", "is_correct": true},
|
|
{"text": "Nome e CPF", "is_correct": false},
|
|
{"text": "Origem racial ou étnica", "is_correct": true},
|
|
{"text": "Endereço de e-mail", "is_correct": false},
|
|
{"text": "Convicção religiosa", "is_correct": true}
|
|
],
|
|
"correct_answers": [0, 2, 4],
|
|
"explanation": "Dados sensíveis incluem: origem racial/étnica, convicção religiosa, opinião política, dados de saúde, vida sexual, dados genéticos ou biométricos.",
|
|
"points": 20
|
|
},
|
|
{
|
|
"id": "q3",
|
|
"text": "O titular dos dados tem direito de solicitar a exclusão de seus dados pessoais.",
|
|
"question_type": "true_false",
|
|
"options": [
|
|
{"text": "Verdadeiro", "is_correct": true},
|
|
{"text": "Falso", "is_correct": false}
|
|
],
|
|
"correct_answers": [0],
|
|
"explanation": "Sim, o direito à eliminação de dados é um dos direitos garantidos pela LGPD.",
|
|
"points": 10
|
|
},
|
|
{
|
|
"id": "q4",
|
|
"text": "O que é tratamento de dados segundo a LGPD?",
|
|
"question_type": "single_choice",
|
|
"options": [
|
|
{"text": "Apenas o armazenamento de dados", "is_correct": false},
|
|
{"text": "Toda operação realizada com dados pessoais", "is_correct": true},
|
|
{"text": "Somente a coleta de dados", "is_correct": false},
|
|
{"text": "A venda de dados pessoais", "is_correct": false}
|
|
],
|
|
"correct_answers": [1],
|
|
"explanation": "Tratamento é toda operação: coleta, produção, recepção, classificação, utilização, acesso, reprodução, transmissão, distribuição, processamento, arquivamento, armazenamento, eliminação, etc.",
|
|
"points": 10
|
|
},
|
|
{
|
|
"id": "q5",
|
|
"text": "Qual é o prazo para a empresa responder a uma solicitação do titular dos dados?",
|
|
"question_type": "single_choice",
|
|
"options": [
|
|
{"text": "Imediatamente", "is_correct": false},
|
|
{"text": "Em até 15 dias", "is_correct": true},
|
|
{"text": "Em até 30 dias", "is_correct": false},
|
|
{"text": "Em até 90 dias", "is_correct": false}
|
|
],
|
|
"correct_answers": [1],
|
|
"explanation": "A LGPD estabelece que a empresa deve responder às solicitações dos titulares em até 15 dias.",
|
|
"points": 10
|
|
}
|
|
]');
|
|
|
|
-- Add lessons for other courses
|
|
INSERT INTO learn_lessons (course_id, title, content_type, lesson_order, duration_minutes) VALUES
|
|
('22222222-2222-2222-2222-222222222222', 'Nossa Missão e Valores', 'text', 1, 10),
|
|
('22222222-2222-2222-2222-222222222222', 'Conduta Profissional', 'text', 2, 10),
|
|
('22222222-2222-2222-2222-222222222222', 'Canais de Denúncia', 'text', 3, 10),
|
|
('33333333-3333-3333-3333-333333333333', 'Ameaças Digitais', 'text', 1, 15),
|
|
('33333333-3333-3333-3333-333333333333', 'Senhas Seguras', 'text', 2, 15),
|
|
('33333333-3333-3333-3333-333333333333', 'Phishing e Engenharia Social', 'text', 3, 15),
|
|
('33333333-3333-3333-3333-333333333333', 'Políticas de Segurança', 'text', 4, 15);
|
|
|
|
-- ============================================================================
|
|
-- TRIGGERS FOR UPDATED_AT
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION update_learn_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER trigger_learn_courses_updated_at
|
|
BEFORE UPDATE ON learn_courses
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_learn_updated_at();
|
|
|
|
CREATE TRIGGER trigger_learn_lessons_updated_at
|
|
BEFORE UPDATE ON learn_lessons
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_learn_updated_at();
|
|
|
|
CREATE TRIGGER trigger_learn_quizzes_updated_at
|
|
BEFORE UPDATE ON learn_quizzes
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_learn_updated_at();
|
|
|
|
CREATE TRIGGER trigger_learn_paths_updated_at
|
|
BEFORE UPDATE ON learn_paths
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_learn_updated_at();
|