Packages
phoenix_kit
1.7.90
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/migrations/postgres/v42.ex
defmodule PhoenixKit.Migrations.Postgres.V42 do
@moduledoc """
PhoenixKit V42 Migration: Universal Scheduled Jobs System
This migration introduces a centralized scheduled jobs system that can handle
any type of scheduled task (posts, emails, notifications, etc.) using a
polymorphic design with behaviour-based handlers.
## Changes
### Scheduled Jobs Table (phoenix_kit_scheduled_jobs)
- Universal job scheduling with polymorphic resource references
- Handler module storage for dynamic dispatch
- Status tracking (pending, executed, failed, cancelled)
- Retry support with attempt tracking
- Priority-based execution ordering
- Flexible metadata via JSONB args field
## Design
The system uses a behaviour pattern where handler modules implement:
- `job_type/0` - Returns the job type string
- `resource_type/0` - Returns the resource type string
- `execute/2` - Executes the job with resource_id and args
## PostgreSQL Support
- Uses binary_id primary key
- Optimized indexes for status + scheduled_at queries
- Supports prefix for schema isolation
"""
use Ecto.Migration
@doc """
Run the V42 migration to add the scheduled jobs system.
"""
def up(%{prefix: prefix} = _opts) do
# ===========================================
# 1. SCHEDULED JOBS TABLE
# ===========================================
create_if_not_exists table(:phoenix_kit_scheduled_jobs, primary_key: false, prefix: prefix) do
add :id, :binary_id, primary_key: true
# Job type & handler
add :job_type, :string, null: false
add :handler_module, :string, null: false
# Polymorphic resource reference
add :resource_type, :string, null: false
add :resource_id, :binary_id, null: false
# Scheduling
add :scheduled_at, :utc_datetime_usec, null: false
add :executed_at, :utc_datetime_usec
# Status tracking
add :status, :string, null: false, default: "pending"
add :attempts, :integer, null: false, default: 0
add :max_attempts, :integer, null: false, default: 3
add :last_error, :text
# Metadata
add :args, :map, null: false, default: %{}
add :priority, :integer, null: false, default: 0
# Audit - optional user reference (bigint to match phoenix_kit_users.id)
add :created_by_id, :bigint
timestamps(type: :utc_datetime_usec)
end
# Primary query index: find pending jobs due for execution
create_if_not_exists index(:phoenix_kit_scheduled_jobs, [:status, :scheduled_at],
name: :phoenix_kit_scheduled_jobs_status_scheduled_idx,
prefix: prefix
)
# Find jobs for a specific resource
create_if_not_exists index(:phoenix_kit_scheduled_jobs, [:resource_type, :resource_id],
name: :phoenix_kit_scheduled_jobs_resource_idx,
prefix: prefix
)
# Filter by job type
create_if_not_exists index(:phoenix_kit_scheduled_jobs, [:job_type],
name: :phoenix_kit_scheduled_jobs_job_type_idx,
prefix: prefix
)
# Priority ordering within status
create_if_not_exists index(:phoenix_kit_scheduled_jobs, [:status, :priority, :scheduled_at],
name: :phoenix_kit_scheduled_jobs_priority_idx,
prefix: prefix
)
# Add foreign key to users (optional - created_by_id can be null)
execute """
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_scheduled_jobs_created_by_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}
ADD CONSTRAINT phoenix_kit_scheduled_jobs_created_by_id_fkey
FOREIGN KEY (created_by_id)
REFERENCES #{prefix_table_name("phoenix_kit_users", prefix)}(id)
ON DELETE SET NULL;
END IF;
EXCEPTION
WHEN undefined_table THEN
-- phoenix_kit_users doesn't exist yet, skip FK
NULL;
END $$;
"""
# ===========================================
# 2. TABLE COMMENTS
# ===========================================
execute """
COMMENT ON TABLE #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)} IS
'Universal scheduled jobs system for posts, emails, notifications, etc.'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.job_type IS
'Type of job: publish_post, send_email, send_notification, etc.'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.handler_module IS
'Elixir module that implements PhoenixKit.ScheduledJobs.Handler behaviour'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.resource_type IS
'Type of resource: post, email, notification, etc.'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.resource_id IS
'UUID of the target resource in its respective table'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.status IS
'Job status: pending, executed, failed, cancelled'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.args IS
'JSONB map of additional arguments passed to the handler'
"""
execute """
COMMENT ON COLUMN #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}.priority IS
'Higher values = higher priority. Jobs with same scheduled_at are ordered by priority DESC'
"""
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '42'"
end
@doc """
Rollback the V42 migration.
"""
def down(%{prefix: prefix} = _opts) do
# Drop foreign key
execute """
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'phoenix_kit_scheduled_jobs_created_by_id_fkey'
AND conrelid = '#{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}'::regclass
) THEN
ALTER TABLE #{prefix_table_name("phoenix_kit_scheduled_jobs", prefix)}
DROP CONSTRAINT phoenix_kit_scheduled_jobs_created_by_id_fkey;
END IF;
EXCEPTION
WHEN undefined_table THEN
NULL;
END $$;
"""
# Drop indexes
drop_if_exists index(:phoenix_kit_scheduled_jobs, [:status, :priority, :scheduled_at],
name: :phoenix_kit_scheduled_jobs_priority_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_scheduled_jobs, [:job_type],
name: :phoenix_kit_scheduled_jobs_job_type_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_scheduled_jobs, [:resource_type, :resource_id],
name: :phoenix_kit_scheduled_jobs_resource_idx,
prefix: prefix
)
drop_if_exists index(:phoenix_kit_scheduled_jobs, [:status, :scheduled_at],
name: :phoenix_kit_scheduled_jobs_status_scheduled_idx,
prefix: prefix
)
# Drop table
drop_if_exists table(:phoenix_kit_scheduled_jobs, prefix: prefix)
# Update version
execute "COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '41'"
end
defp prefix_table_name(table_name, nil), do: table_name
defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}"
end