Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ for port in "${ports[@]}"; do
fi
done

# Fix line endings (convert CRLF to LF) and set executable permission for 00_init.sh
dos2unix worklenz-backend/database/00_init.sh 2>/dev/null || true
chmod +x worklenz-backend/database/00_init.sh


# Start the containers
echo -e "${BLUE}Starting Worklenz services...${NC}"
$DOCKER_COMPOSE_CMD down
Expand All @@ -121,6 +126,28 @@ echo "This may take a minute or two depending on your system..."
check_service "Database" "worklenz_db" ""
DB_STATUS=$?


# Wait for database to be healthy before running migrations
echo -e "${BLUE}Waiting for database to be healthy...${NC}"
until [ "$(docker inspect --format='{{.State.Health.Status}}' worklenz_db 2>/dev/null)" == "healthy" ]; do
sleep 2
echo -n "."
done
echo -e "${GREEN}✓${NC} Database is healthy"

# Run node-pg-migrate migrations if DB is up
if [ $DB_STATUS -eq 0 ]; then
echo -e "${BLUE}Running database migrations...${NC}"
$DOCKER_COMPOSE_CMD run --rm backend npm run migrate:up
MIGRATE_STATUS=$?
if [ $MIGRATE_STATUS -eq 0 ]; then
echo -e "${GREEN}✓${NC} Database migrations applied successfully"
else
echo -e "${RED}✗${NC} Database migrations failed"
exit 1
fi
fi

check_service "MinIO" "worklenz_minio" "http://localhost:9000/minio/health/live"
MINIO_STATUS=$?

Expand Down
1 change: 1 addition & 0 deletions update-docker-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ DB_PASSWORD=password
DB_NAME=worklenz_db
DB_MAX_CLIENTS=50
USE_PG_NATIVE=true
DATABASE_URL=postgres://postgres:password@db:5432/worklenz_db

# Storage Configuration
STORAGE_PROVIDER=s3
Expand Down
3 changes: 3 additions & 0 deletions worklenz-backend/.pgmrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"config-file": "migrate.json"
}
2 changes: 2 additions & 0 deletions worklenz-backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ COPY --from=builder /usr/src/app/build ./build
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/release ./release
COPY --from=builder /usr/src/app/worklenz-email-templates ./worklenz-email-templates
COPY --from=builder /usr/src/app/database/pg-migrations ./pg-migrations
COPY --from=builder /usr/src/app/database/pg-migrations ./migrations


EXPOSE 3000
Expand Down
18 changes: 0 additions & 18 deletions worklenz-backend/database/00_init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,6 @@ 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,161 @@
/* eslint-disable camelcase */

exports.shorthands = undefined;
exports.noTransaction = true;

exports.up = pgm => {
// Since CONCURRENTLY cannot run in transactions, we'll create regular indexes
// For production, run the concurrent version separately

console.log('Creating performance indexes (non-concurrent for migration compatibility)...');

// Composite index for main task filtering
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_project_archived_parent
ON tasks(project_id, archived, parent_task_id)
WHERE archived = FALSE
`);

// Index for status joins
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_status_project
ON tasks(status_id, project_id)
WHERE archived = FALSE
`);

// Index for assignees lookup
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_assignees_task_member
ON tasks_assignees(task_id, team_member_id)
`);

// Index for phase lookup
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_phase_task_phase
ON task_phase(task_id, phase_id)
`);

// Index for subtask counting
pgm.sql(`
CREATE INDEX 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
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_labels_task_label
ON task_labels(task_id, label_id)
`);

// Index for comments count
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_comments_task
ON task_comments(task_id)
`);

// Index for attachments count
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_attachments_task
ON task_attachments(task_id)
`);

// Index for work log aggregation
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_work_log_task
ON task_work_log(task_id)
`);

// Index for subscribers check
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_subscribers_task
ON task_subscribers(task_id)
`);

// Index for dependencies check
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_dependencies_task
ON task_dependencies(task_id)
`);

// Additional performance indexes
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_dependencies_related
ON task_dependencies(related_task_id)
`);

// Index for custom column values
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_cc_column_values_task
ON cc_column_values(task_id)
`);

// Index for project members lookup
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_project_members_team_project
ON project_members(team_member_id, project_id)
`);

// Index for sorting
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_project_sort
ON tasks(project_id, sort_order)
WHERE archived = FALSE
`);

// Index for roadmap sorting
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_project_roadmap_sort
ON tasks(project_id, roadmap_sort_order)
WHERE archived = FALSE
`);

// Index for user lookup in team members
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_team_members_user_team
ON team_members(user_id, team_id)
WHERE active = TRUE
`);

// Index for task statuses lookup
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_task_statuses_project_category
ON task_statuses(project_id, category_id)
`);

// Index for task priorities lookup
pgm.sql(`
CREATE INDEX IF NOT EXISTS idx_tasks_priority
ON tasks(priority_id)
WHERE archived = FALSE
`);

console.log('Performance indexes created successfully!');
};

exports.down = pgm => {
// Drop indexes in reverse order
pgm.sql('DROP INDEX IF EXISTS idx_tasks_priority');
pgm.sql('DROP INDEX IF EXISTS idx_task_statuses_project_category');
pgm.sql('DROP INDEX IF EXISTS idx_team_members_user_team');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_project_roadmap_sort');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_project_sort');
pgm.sql('DROP INDEX IF EXISTS idx_project_members_team_project');
pgm.sql('DROP INDEX IF EXISTS idx_cc_column_values_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_dependencies_related');
pgm.sql('DROP INDEX IF EXISTS idx_task_dependencies_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_subscribers_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_work_log_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_attachments_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_comments_task');
pgm.sql('DROP INDEX IF EXISTS idx_task_labels_task_label');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_parent_archived');
pgm.sql('DROP INDEX IF EXISTS idx_task_phase_task_phase');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_assignees_task_member');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_status_project');
pgm.sql('DROP INDEX IF EXISTS idx_tasks_project_archived_parent');
};

exports.__migration = {
transaction: false,
};
Loading