13 KiB
13 KiB
Database Migration Plan for Narasi Ahli
Migration Overview
This document outlines the database schema changes and migration steps needed to implement the Narasi Ahli features.
Current Database State Analysis
Existing Users Table
The current users table already contains most of the required profile fields:
- ✅
fullname(nama lengkap) - ✅
email(email) - ✅
phone_number(can be used for WhatsApp) - ✅
last_education(pendidikan terakhir) - ❌ Missing:
degree(gelar) - ❌ Missing:
whatsapp_number(separate from phone) - ❌ Missing:
last_job_title(pekerjaan terakhir)
Migration Steps
Step 1: Update Users Table
-- Add new columns to users table
ALTER TABLE users
ADD COLUMN degree VARCHAR(100),
ADD COLUMN whatsapp_number VARCHAR(20),
ADD COLUMN last_job_title VARCHAR(255);
-- Add indexes for better query performance
CREATE INDEX idx_users_whatsapp_number ON users(whatsapp_number);
CREATE INDEX idx_users_last_job_title ON users(last_job_title);
Step 2: Create Education History Table
CREATE TABLE education_histories (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
school_name VARCHAR(255) NOT NULL,
major VARCHAR(255) NOT NULL,
education_level VARCHAR(100) NOT NULL,
graduation_year INTEGER NOT NULL,
certificate_image VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_education_histories_user_id
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Indexes
CREATE INDEX idx_education_histories_user_id ON education_histories(user_id);
CREATE INDEX idx_education_histories_graduation_year ON education_histories(graduation_year);
CREATE INDEX idx_education_histories_education_level ON education_histories(education_level);
-- Constraints
ALTER TABLE education_histories
ADD CONSTRAINT chk_graduation_year
CHECK (graduation_year >= 1950 AND graduation_year <= 2030);
Step 3: Create Work History Table
CREATE TABLE work_histories (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
job_title VARCHAR(255) NOT NULL,
company_name VARCHAR(255) NOT NULL,
start_date DATE NOT NULL,
end_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_work_histories_user_id
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Indexes
CREATE INDEX idx_work_histories_user_id ON work_histories(user_id);
CREATE INDEX idx_work_histories_start_date ON work_histories(start_date);
CREATE INDEX idx_work_histories_company_name ON work_histories(company_name);
-- Constraints
ALTER TABLE work_histories
ADD CONSTRAINT chk_work_dates
CHECK (end_date IS NULL OR end_date >= start_date);
Step 4: Create Research Journals Table
CREATE TABLE research_journals (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
journal_title VARCHAR(500) NOT NULL,
publisher VARCHAR(255) NOT NULL,
journal_url VARCHAR(1000) NOT NULL,
published_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_research_journals_user_id
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Indexes
CREATE INDEX idx_research_journals_user_id ON research_journals(user_id);
CREATE INDEX idx_research_journals_publisher ON research_journals(publisher);
CREATE INDEX idx_research_journals_published_date ON research_journals(published_date);
Step 5: Create Communication Tables
-- Conversations table
CREATE TABLE conversations (
id SERIAL PRIMARY KEY,
participant1_id INTEGER NOT NULL,
participant2_id INTEGER NOT NULL,
last_message_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_conversations_participant1
FOREIGN KEY (participant1_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT fk_conversations_participant2
FOREIGN KEY (participant2_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT chk_different_participants
CHECK (participant1_id != participant2_id)
);
-- Unique constraint to prevent duplicate conversations
CREATE UNIQUE INDEX idx_conversations_participants
ON conversations(LEAST(participant1_id, participant2_id), GREATEST(participant1_id, participant2_id));
-- Indexes
CREATE INDEX idx_conversations_participant1 ON conversations(participant1_id);
CREATE INDEX idx_conversations_participant2 ON conversations(participant2_id);
CREATE INDEX idx_conversations_last_message_at ON conversations(last_message_at);
-- Chat messages table
CREATE TABLE chat_messages (
id SERIAL PRIMARY KEY,
conversation_id INTEGER NOT NULL,
sender_id INTEGER NOT NULL,
message_text TEXT,
message_type VARCHAR(20) NOT NULL DEFAULT 'text',
file_url VARCHAR(500),
file_name VARCHAR(255),
file_size BIGINT,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_chat_messages_conversation
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
CONSTRAINT fk_chat_messages_sender
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT chk_message_type
CHECK (message_type IN ('text', 'image', 'file', 'audio')),
CONSTRAINT chk_message_content
CHECK (
(message_type = 'text' AND message_text IS NOT NULL) OR
(message_type != 'text' AND file_url IS NOT NULL)
)
);
-- Indexes
CREATE INDEX idx_chat_messages_conversation_id ON chat_messages(conversation_id);
CREATE INDEX idx_chat_messages_sender_id ON chat_messages(sender_id);
CREATE INDEX idx_chat_messages_created_at ON chat_messages(created_at);
CREATE INDEX idx_chat_messages_is_read ON chat_messages(is_read);
Step 6: Create AI Chat Tables
-- AI Chat Sessions table
CREATE TABLE ai_chat_sessions (
id SERIAL PRIMARY KEY,
ai_session_id VARCHAR(100) NOT NULL UNIQUE,
user_id INTEGER NOT NULL,
agent_id VARCHAR(100),
title VARCHAR(255) NOT NULL,
message_count INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ai_chat_sessions_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT chk_session_status
CHECK (status IN ('active', 'archived', 'deleted')),
CONSTRAINT chk_message_count
CHECK (message_count >= 0)
);
-- Indexes
CREATE INDEX idx_ai_chat_sessions_user_id ON ai_chat_sessions(user_id);
CREATE INDEX idx_ai_chat_sessions_status ON ai_chat_sessions(status);
CREATE INDEX idx_ai_chat_sessions_created_at ON ai_chat_sessions(created_at);
-- AI Chat Messages table
CREATE TABLE ai_chat_messages (
id SERIAL PRIMARY KEY,
session_id INTEGER NOT NULL,
message_type VARCHAR(20) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ai_chat_messages_session
FOREIGN KEY (session_id) REFERENCES ai_chat_sessions(id) ON DELETE CASCADE,
CONSTRAINT chk_ai_message_type
CHECK (message_type IN ('user', 'assistant'))
);
-- Indexes
CREATE INDEX idx_ai_chat_messages_session_id ON ai_chat_messages(session_id);
CREATE INDEX idx_ai_chat_messages_created_at ON ai_chat_messages(created_at);
CREATE INDEX idx_ai_chat_messages_message_type ON ai_chat_messages(message_type);
Step 7: Create AI Chat Logs Table
CREATE TABLE ai_chat_logs (
id SERIAL PRIMARY KEY,
session_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
start_date TIMESTAMP NOT NULL,
end_date TIMESTAMP,
total_duration BIGINT DEFAULT 0, -- in seconds
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_ai_chat_logs_session
FOREIGN KEY (session_id) REFERENCES ai_chat_sessions(id) ON DELETE CASCADE,
CONSTRAINT fk_ai_chat_logs_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
CONSTRAINT chk_chat_log_dates
CHECK (end_date IS NULL OR end_date >= start_date),
CONSTRAINT chk_total_duration
CHECK (total_duration >= 0)
);
-- Indexes
CREATE INDEX idx_ai_chat_logs_session_id ON ai_chat_logs(session_id);
CREATE INDEX idx_ai_chat_logs_user_id ON ai_chat_logs(user_id);
CREATE INDEX idx_ai_chat_logs_start_date ON ai_chat_logs(start_date);
GORM Entity Updates
Updated Users Entity
// Add to existing Users struct in app/database/entity/users/users.entity.go
type Users struct {
// ... existing fields ...
// New fields for Narasi Ahli
Degree *string `json:"degree" gorm:"type:varchar(100)"`
WhatsappNumber *string `json:"whatsapp_number" gorm:"type:varchar(20);index"`
LastJobTitle *string `json:"last_job_title" gorm:"type:varchar(255);index"`
// ... rest of existing fields ...
}
Register New Entities in Database Models
Update app/database/index.database.go:
func Models() []interface{} {
return []interface{}{
// ... existing entities ...
// New Narasi Ahli entities
entity.EducationHistory{},
entity.WorkHistory{},
entity.ResearchJournals{},
entity.Conversations{},
entity.ChatMessages{},
entity.AIChatSessions{},
entity.AIChatMessages{},
entity.AIChatLogs{},
}
}
Data Migration Considerations
Existing Data Handling
- Users Table Updates: The new columns are nullable, so existing users won't be affected
- Profile Completion: Consider adding a profile completion percentage field
- Data Validation: Implement validation for phone number formats and email addresses
Migration Scripts
-- Migration: 001_add_narasi_ahli_user_fields
-- Run this first
ALTER TABLE users
ADD COLUMN IF NOT EXISTS degree VARCHAR(100),
ADD COLUMN IF NOT EXISTS whatsapp_number VARCHAR(20),
ADD COLUMN IF NOT EXISTS last_job_title VARCHAR(255);
-- Migration: 002_create_education_histories
-- Run after users table update
-- (Include the education_histories table creation from Step 2)
-- Migration: 003_create_work_histories
-- (Include the work_histories table creation from Step 3)
-- Migration: 004_create_research_journals
-- (Include the research_journals table creation from Step 4)
-- Migration: 005_create_communication_tables
-- (Include both conversations and chat_messages tables from Step 5)
-- Migration: 006_create_ai_chat_tables
-- (Include all AI chat related tables from Steps 6 and 7)
Performance Optimization
Indexing Strategy
- User-based queries: All tables have user_id indexes
- Date-based queries: Indexes on created_at, start_date, end_date
- Search functionality: Indexes on searchable text fields
- Unique constraints: Prevent duplicate conversations and AI sessions
Query Optimization
- Pagination: All list queries should use LIMIT/OFFSET
- Preloading: Use GORM's Preload for related data
- Caching: Consider Redis for frequently accessed data
- Connection pooling: Optimize database connection settings
Security Considerations
Data Protection
- User isolation: All queries filter by user_id
- File upload validation: Strict file type and size limits
- SQL injection prevention: Use parameterized queries
- Data encryption: Consider encrypting sensitive fields
Privacy Compliance
- Data retention: Implement data cleanup policies
- User consent: Track user consent for data processing
- Data export: Provide user data export functionality
- Right to delete: Implement cascading deletes for user data
Rollback Plan
Rollback Steps
- Remove new entities from GORM Models() function
- Drop new tables in reverse order:
DROP TABLE IF EXISTS ai_chat_logs; DROP TABLE IF EXISTS ai_chat_messages; DROP TABLE IF EXISTS ai_chat_sessions; DROP TABLE IF EXISTS chat_messages; DROP TABLE IF EXISTS conversations; DROP TABLE IF EXISTS research_journals; DROP TABLE IF EXISTS work_histories; DROP TABLE IF EXISTS education_histories; - Remove new columns from users table:
ALTER TABLE users DROP COLUMN IF EXISTS degree, DROP COLUMN IF EXISTS whatsapp_number, DROP COLUMN IF EXISTS last_job_title;
Backup Strategy
- Full database backup before migration
- Table-level backups for critical tables
- Test restoration on staging environment
- Monitor disk space during migration
Testing Plan
Migration Testing
- Unit tests for each entity
- Integration tests for relationships
- Performance tests for large datasets
- Migration rollback tests
Data Integrity Tests
- Foreign key constraints validation
- Check constraints validation
- Unique constraints validation
- Data type validation
This migration plan ensures a smooth transition to the new Narasi Ahli database schema while maintaining data integrity and performance.