All checks were successful
deploy / deploy (push) Successful in 4m22s
Single activity_log table powering two views: chronological timeline on each order detail page (replacing the old fulfilment card) and a global feed at /admin/activity with tabs, category filters, search, and pagination. Real-time via PubSub — new entries appear instantly, nav badge updates across all admin pages. Instrumented across all event points: Stripe webhooks, order notifier, submission worker, fulfilment status worker, product sync worker, and Oban exhausted-job telemetry. Contextual action buttons (retry submission, retry sync, dismiss) with Oban unique constraints to prevent double-enqueue. 90-day pruning via cron. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
103 lines
3.2 KiB
Elixir
103 lines
3.2 KiB
Elixir
defmodule Berrypod.Repo.Migrations.CreateActivityLog do
|
|
use Ecto.Migration
|
|
|
|
def up do
|
|
create table(:activity_log, primary_key: false) do
|
|
add :id, :binary_id, primary_key: true
|
|
add :event_type, :string, null: false
|
|
add :level, :string, null: false, default: "info"
|
|
add :order_id, references(:orders, type: :binary_id, on_delete: :nilify_all)
|
|
add :payload, :map, default: %{}
|
|
add :message, :string, null: false
|
|
add :resolved_at, :utc_datetime
|
|
add :occurred_at, :utc_datetime, null: false
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
create index(:activity_log, [:order_id])
|
|
create index(:activity_log, [:occurred_at])
|
|
create index(:activity_log, [:level, :resolved_at])
|
|
|
|
# Backfill from existing orders
|
|
flush()
|
|
backfill_from_orders()
|
|
end
|
|
|
|
def down do
|
|
drop table(:activity_log)
|
|
end
|
|
|
|
defp backfill_from_orders do
|
|
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
|
|
|
|
# order.created for all paid orders
|
|
execute("""
|
|
INSERT INTO activity_log (id, event_type, level, order_id, payload, message, occurred_at, inserted_at, updated_at)
|
|
SELECT
|
|
lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))),
|
|
'order.created',
|
|
'info',
|
|
id,
|
|
'{}',
|
|
'Order placed',
|
|
inserted_at,
|
|
'#{now}',
|
|
'#{now}'
|
|
FROM orders
|
|
WHERE payment_status = 'paid'
|
|
""")
|
|
|
|
# order.submitted
|
|
execute("""
|
|
INSERT INTO activity_log (id, event_type, level, order_id, payload, message, occurred_at, inserted_at, updated_at)
|
|
SELECT
|
|
lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))),
|
|
'order.submitted',
|
|
'info',
|
|
id,
|
|
'{}',
|
|
'Submitted to provider',
|
|
submitted_at,
|
|
'#{now}',
|
|
'#{now}'
|
|
FROM orders
|
|
WHERE submitted_at IS NOT NULL
|
|
""")
|
|
|
|
# order.shipped
|
|
execute("""
|
|
INSERT INTO activity_log (id, event_type, level, order_id, payload, message, occurred_at, inserted_at, updated_at)
|
|
SELECT
|
|
lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))),
|
|
'order.shipped',
|
|
'info',
|
|
id,
|
|
'{}',
|
|
'Shipped',
|
|
shipped_at,
|
|
'#{now}',
|
|
'#{now}'
|
|
FROM orders
|
|
WHERE shipped_at IS NOT NULL
|
|
""")
|
|
|
|
# order.delivered
|
|
execute("""
|
|
INSERT INTO activity_log (id, event_type, level, order_id, payload, message, occurred_at, inserted_at, updated_at)
|
|
SELECT
|
|
lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)),2) || '-' || substr('89ab', abs(random()) % 4 + 1, 1) || substr(hex(randomblob(2)),2) || '-' || hex(randomblob(6))),
|
|
'order.delivered',
|
|
'info',
|
|
id,
|
|
'{}',
|
|
'Delivered',
|
|
delivered_at,
|
|
'#{now}',
|
|
'#{now}'
|
|
FROM orders
|
|
WHERE delivered_at IS NOT NULL
|
|
""")
|
|
end
|
|
end
|