Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
11694de
feat(db): add database backup and initialization system
kithmina1999 Jun 6, 2025
dc22d1e
fix(db): improve database initialization script and docker setup
kithmina1999 Jun 6, 2025
0987fb1
refactor(docker): improve command formatting and fix shell script issues
kithmina1999 Jun 6, 2025
1442c57
refactor(docker): improve postgres container initialization script
kithmina1999 Jun 6, 2025
f89e3e8
refactor(database): improve database initialization and backup handling
kithmina1999 Jun 6, 2025
c5e480a
fix(db): correct variable syntax in pg_dump command
kithmina1999 Jun 6, 2025
f142046
fix(docker): correct bash syntax in postgres backup command
kithmina1999 Jun 6, 2025
81a6c44
refactor(info-tab-footer): enhance member selection and comment handling
chamikaJ Jun 6, 2025
9c7fad7
Merge pull request #151 from kithmina1999/feat/docker-db-backup
chamikaJ Jun 23, 2025
f1d504f
Update LANGUAGE_TYPE enum to include 'alb' and 'de' for Albanian and …
chamikaJ Jun 24, 2025
5d0777f
Merge pull request #183 from Worklenz/fix/added-i18n-alb-and-de
chamikaJ Jun 24, 2025
39e09be
add support for zh_cn
jiuhao47 Jun 30, 2025
f352d82
Merge branch 'main' of https://github.com/Worklenz/worklenz into deve…
chamikaJ Jul 1, 2025
b500c80
refactor(KanbanGroup, TaskCard): simplify card creation logic and enh…
shancds Jul 7, 2025
3206af1
refactor(TaskCard): enhance subtask visibility animation and styling
shancds Jul 7, 2025
8dcd029
refactor(EnhancedKanbanBoard): improve drag-and-drop handling and tas…
shancds Jul 7, 2025
26b47aa
refactor(i18n): optimize translation loading and initialization
chamikaJ Jul 7, 2025
aa1fb1c
feat(performance): optimize resource loading and initialization
chamikaJ Jul 7, 2025
bc08592
refactor(vite.config): clean up unnecessary whitespace in configurati…
chamikaJ Jul 7, 2025
bdc3050
feat(database): add performance indexes for optimized task queries
chamikaJ Jul 7, 2025
5a9ceb4
feat(EnhancedKanbanBoard): add task dependency check during drag-and-…
shancds Jul 7, 2025
a3f317c
refactor(vite.config): simplify chunking strategy and optimize asset …
chamikaJ Jul 7, 2025
0b96d59
refactor(tasks-controller): remove Redis caching logic for task retri…
chamikaJ Jul 7, 2025
3887cc4
refactor(tasks-controller): enhance SQL query structure and improve t…
chamikaJ Jul 7, 2025
6c03bf7
Merge pull request #243 from shancds/test/row-kanban-board-v1.1.3
chamikaJ Jul 7, 2025
9ec422c
Merge branch 'release/v2.0.4-bug-fix' of https://github.com/Worklenz/…
chamikaJ Jul 7, 2025
8533a44
refactor(tasks-controller): enhance getTasksV3 method for performance…
chamikaJ Jul 7, 2025
1348991
refactor(index.html): update production tracking ID logic
chamikaJ Jul 7, 2025
978d915
feat(database): add performance indexes and materialized view for opt…
chamikaJ Jul 7, 2025
b025313
feat(project-drawer): enhance project data fetching and error handling
chamikaJ Jul 7, 2025
a44b276
feat(email-templates): update release note template for Worklenz 2.1.0
chamikaJ Jul 8, 2025
03b3f55
fix(project-list): enhance grouped request parameters handling
chamikaJ Jul 8, 2025
56d6a53
Merge pull request #208 from jiuhao47/main
chamikaJ Jul 8, 2025
e750023
Merge branch 'development' of https://github.com/Worklenz/worklenz in…
chamikaJ Jul 8, 2025
f06851f
feat(localization): add and update translations for multiple languages
chamikaJ Jul 8, 2025
8fb33e3
feat(localization): enhance task drawer translations and UI consistency
chamikaJ Jul 8, 2025
66e0111
refactor(task-drawer): update tab behavior and enhance link handling …
chamikaJ Jul 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions backup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
set -eu

# Adjust these as needed:
CONTAINER=worklenz_db
DB_NAME=worklenz_db
DB_USER=postgres
BACKUP_DIR=./pg_backups
mkdir -p "$BACKUP_DIR"

timestamp=$(date +%Y-%m-%d_%H-%M-%S)
outfile="${BACKUP_DIR}/${DB_NAME}_${timestamp}.sql"
echo "Creating backup $outfile ..."

docker exec -t "$CONTAINER" pg_dump -U "$DB_USER" -d "$DB_NAME" > "$outfile"
echo "Backup saved to $outfile"
70 changes: 58 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ services:
POSTGRES_DB: ${DB_NAME:-worklenz_db}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
healthcheck:
test: [ "CMD-SHELL", "pg_isready -d ${DB_NAME:-worklenz_db} -U ${DB_USER:-postgres}" ]
test:
[
"CMD-SHELL",
"pg_isready -d ${DB_NAME:-worklenz_db} -U ${DB_USER:-postgres}",
]
interval: 10s
timeout: 5s
retries: 5
Expand All @@ -93,23 +97,65 @@ services:
volumes:
- worklenz_postgres_data:/var/lib/postgresql/data
- type: bind
source: ./worklenz-backend/database
target: /docker-entrypoint-initdb.d
source: ./worklenz-backend/database/sql
target: /docker-entrypoint-initdb.d/sql
consistency: cached
- type: bind
source: ./worklenz-backend/database/migrations
target: /docker-entrypoint-initdb.d/migrations
consistency: cached
- type: bind
source: ./worklenz-backend/database/00_init.sh
target: /docker-entrypoint-initdb.d/00_init.sh
consistency: cached
- type: bind
source: ./pg_backups
target: /docker-entrypoint-initdb.d/pg_backups
command: >
bash -c '
if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi

find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '"'"'
for f; do
dos2unix "$f" 2>/dev/null || true
chmod +x "$f"
done
'"'"' sh {} +

exec docker-entrypoint.sh postgres
'
db-backup:
image: postgres:15
container_name: worklenz_db_backup
environment:
POSTGRES_USER: ${DB_USER:-postgres}
POSTGRES_DB: ${DB_NAME:-worklenz_db}
POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
depends_on:
db:
condition: service_healthy
volumes:
- ./pg_backups:/pg_backups #host dir for backups files
#setup bassh loop to backup data evey 24h
command: >
bash -c ' if command -v apt-get >/dev/null 2>&1; then
apt-get update && apt-get install -y dos2unix
elif command -v apk >/dev/null 2>&1; then
apk add --no-cache dos2unix
fi && find /docker-entrypoint-initdb.d -type f -name "*.sh" -exec sh -c '\''
dos2unix "{}" 2>/dev/null || true
chmod +x "{}"
'\'' \; && exec docker-entrypoint.sh postgres '
bash -c 'while true; do
sleep 86400;
PGPASSWORD=$$POSTGRES_PASSWORD pg_dump -h worklenz_db -U $$POSTGRES_USER -d $$POSTGRES_DB \
> /pg_backups/worklenz_db_$$(date +%Y-%m-%d_%H-%M-%S).sql;
find /pg_backups -type f -name "*.sql" -mtime +30 -delete;
done'
restart: unless-stopped
networks:
- worklenz

volumes:
worklenz_postgres_data:
worklenz_minio_data:

pgdata:

networks:
worklenz:
55 changes: 0 additions & 55 deletions worklenz-backend/database/00-init-db.sh

This file was deleted.

88 changes: 88 additions & 0 deletions worklenz-backend/database/00_init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash
set -e

echo "Starting database initialization..."

SQL_DIR="/docker-entrypoint-initdb.d/sql"
MIGRATIONS_DIR="/docker-entrypoint-initdb.d/migrations"
BACKUP_DIR="/docker-entrypoint-initdb.d/pg_backups"

# --------------------------------------------
# 🗄️ STEP 1: Attempt to restore latest backup
# --------------------------------------------

if [ -d "$BACKUP_DIR" ]; then
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/*.sql 2>/dev/null | head -n 1)
else
LATEST_BACKUP=""
fi

if [ -f "$LATEST_BACKUP" ]; then
echo "🗄️ Found latest backup: $LATEST_BACKUP"
echo "⏳ Restoring from backup..."
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < "$LATEST_BACKUP"
echo "✅ Backup restoration complete. Skipping schema and migrations."
exit 0
else
echo "ℹ️ No valid backup found. Proceeding with base schema and migrations."
fi

# --------------------------------------------
# 🏗️ STEP 2: Continue with base schema setup
# --------------------------------------------

# Create migrations table if it doesn't exist
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "
CREATE TABLE IF NOT EXISTS schema_migrations (
version TEXT PRIMARY KEY,
applied_at TIMESTAMP DEFAULT now()
);
"

# List of base schema files to execute in order
BASE_SQL_FILES=(
"0_extensions.sql"
"1_tables.sql"
"indexes.sql"
"4_functions.sql"
"triggers.sql"
"3_views.sql"
"2_dml.sql"
"5_database_user.sql"
)

echo "Running base schema SQL files in order..."

for file in "${BASE_SQL_FILES[@]}"; do
full_path="$SQL_DIR/$file"
if [ -f "$full_path" ]; then
echo "Executing $file..."
psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$full_path"
else
echo "WARNING: $file not found, skipping."
fi
done

echo "✅ Base schema SQL execution complete."

# --------------------------------------------
# 🚀 STEP 3: Apply SQL migrations
# --------------------------------------------

if [ -d "$MIGRATIONS_DIR" ] && compgen -G "$MIGRATIONS_DIR/*.sql" > /dev/null; then
echo "Applying migrations..."
for f in "$MIGRATIONS_DIR"/*.sql; do
version=$(basename "$f")
if ! psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT 1 FROM schema_migrations WHERE version = '$version'" | grep -q 1; then
echo "Applying migration: $version"
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -f "$f"
psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "INSERT INTO schema_migrations (version) VALUES ('$version');"
else
echo "Skipping already applied migration: $version"
fi
done
else
echo "No migration files found or directory is empty, skipping migrations."
fi

echo "🎉 Database initialization completed successfully."
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
-- Performance indexes for optimized tasks queries
-- Migration: 20250115000000-performance-indexes.sql

-- Composite index for main task filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_archived_parent
ON tasks(project_id, archived, parent_task_id)
WHERE archived = FALSE;

-- Index for status joins
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_status_project
ON tasks(status_id, project_id)
WHERE archived = FALSE;

-- Index for assignees lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_assignees_task_member
ON tasks_assignees(task_id, team_member_id);

-- Index for phase lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_phase_task_phase
ON task_phase(task_id, phase_id);

-- Index for subtask counting
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_archived
ON tasks(parent_task_id, archived)
WHERE parent_task_id IS NOT NULL AND archived = FALSE;

-- Index for labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_labels_task_label
ON task_labels(task_id, label_id);

-- Index for comments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_comments_task
ON task_comments(task_id);

-- Index for attachments count
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_attachments_task
ON task_attachments(task_id);

-- Index for work log aggregation
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_work_log_task
ON task_work_log(task_id);

-- Index for subscribers check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_subscribers_task
ON task_subscribers(task_id);

-- Index for dependencies check
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_dependencies_task
ON task_dependencies(task_id);

-- Index for timers lookup
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_task_user
ON task_timers(task_id, user_id);

-- Index for custom columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_cc_column_values_task
ON cc_column_values(task_id);

-- Index for team member info view optimization
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_team_user
ON team_members(team_id, user_id)
WHERE active = TRUE;

-- Index for notification settings
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_notification_settings_user_team
ON notification_settings(user_id, team_id);

-- Index for task status categories
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_category
ON task_statuses(category_id, project_id);

-- Index for project phases
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_project_phases_project_sort
ON project_phases(project_id, sort_index);

-- Index for task priorities
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_priorities_value
ON task_priorities(value);

-- Index for team labels
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_labels_team
ON team_labels(team_id);

-- NEW INDEXES FOR PERFORMANCE OPTIMIZATION --

-- Composite index for task main query optimization (covers most WHERE conditions)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_performance_main
ON tasks(project_id, archived, parent_task_id, status_id, priority_id)
WHERE archived = FALSE;

-- Index for sorting by sort_order with project filter
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_project_sort_order
ON tasks(project_id, sort_order)
WHERE archived = FALSE;

-- Index for email_invitations to optimize team_member_info_view
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_email_invitations_team_member
ON email_invitations(team_member_id);

-- Covering index for task status with category information
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_statuses_covering
ON task_statuses(id, category_id, project_id);

-- Index for task aggregation queries (parent task progress calculation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_parent_status_archived
ON tasks(parent_task_id, status_id, archived)
WHERE archived = FALSE;

-- Index for project team member filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_team_members_project_lookup
ON team_members(team_id, active, user_id)
WHERE active = TRUE;

-- Covering index for tasks with frequently accessed columns
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_covering_main
ON tasks(id, project_id, archived, parent_task_id, status_id, priority_id, sort_order, name)
WHERE archived = FALSE;

-- Index for task search functionality
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_name_search
ON tasks USING gin(to_tsvector('english', name))
WHERE archived = FALSE;

-- Index for date-based filtering (if used)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_tasks_dates
ON tasks(project_id, start_date, end_date)
WHERE archived = FALSE;

-- Index for task timers with user filtering
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_task_timers_user_task
ON task_timers(user_id, task_id);

-- Index for sys_task_status_categories lookups
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sys_task_status_categories_covering
ON sys_task_status_categories(id, color_code, color_code_dark, is_done, is_doing, is_todo);
2 changes: 1 addition & 1 deletion worklenz-backend/database/sql/1_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ CREATE TYPE DEPENDENCY_TYPE AS ENUM ('blocked_by');

CREATE TYPE SCHEDULE_TYPE AS ENUM ('daily', 'weekly', 'yearly', 'monthly', 'every_x_days', 'every_x_weeks', 'every_x_months');

CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt');
CREATE TYPE LANGUAGE_TYPE AS ENUM ('en', 'es', 'pt', 'alb', 'de', 'zh_cn');

-- START: Users
CREATE SEQUENCE IF NOT EXISTS users_user_no_seq START 1;
Expand Down
Loading
Loading