diff --git a/CLAUDE.md b/CLAUDE.md index 247d8382..2f2573d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -278,6 +278,34 @@ PR review files go in `dev_docs/pull_requests/{year}/{pr_number}-{slug}/CLAUDE_R **Use inline icon buttons for table/list row actions**, not dropdown menus. Pattern: `flex gap-1` with `btn btn-xs btn-ghost` icon-only buttons and `title` tooltips. Dropdown menus are OK for selectors (status, format, version pickers) but not for CRUD actions. +### Structs Over Plain Maps + +**Prefer structs over plain maps when a struct type exists.** Use the struct or its `.new/1` constructor in application code rather than bare maps. + +**Key struct types:** +- `PhoenixKit.Dashboard.Tab` - Tab configuration (`Tab.new/1`) +- `PhoenixKit.Dashboard.Badge` - Badge configuration (`Badge.new/1`) +- `PhoenixKit.Dashboard.ContextSelector` - Context selector configuration + +**Exception:** In `config/config.exs`, plain maps are idiomatic and correct — `.new/1` constructors accept maps and convert them to structs at runtime. + +```elixir +# ✅ In application code, use structs +Tab.new(%{id: :orders, label: "Orders", path: "/dashboard/orders"}) + +# ✅ In config/config.exs, plain maps are fine (converted at runtime) +config :phoenix_kit, :user_dashboard_tabs, [ + %{id: :orders, label: "Orders", path: "/dashboard/orders"} +] + +# ❌ In application code, avoid plain maps when struct exists +%{id: :orders, label: "Orders", path: "/dashboard/orders"} +``` + +### DateTime: Always Use `DateTime.utc_now()` + +**Always use `:utc_datetime` and `DateTime.utc_now()` in new code.** Never use `NaiveDateTime` in new schemas or application code. See the [DateTime Convention](#datetime-convention) section for the full reference table and rationale. + ### URL Prefix and Navigation (IMPORTANT) **NEVER hardcode PhoenixKit paths.** PhoenixKit uses a configurable URL prefix (default: `/phoenix_kit`). Always use the provided helpers to ensure paths work correctly regardless of prefix configuration. @@ -584,6 +612,21 @@ belongs_to :user, User, foreign_key: :user_uuid, references: :uuid, type: UUIDv7 belongs_to :user, User, foreign_key: :user_uuid, type: UUIDv7 ``` +### DateTime Convention + +**Always use `:utc_datetime` and `DateTime.utc_now()` in new code.** Never use `NaiveDateTime` in new schemas or application code. + +| Context | Use | Never Use | +|---------|-----|-----------| +| Schema timestamps | `timestamps(type: :utc_datetime)` | `timestamps()` or `timestamps(type: :naive_datetime)` | +| Individual datetime fields | `field :name, :utc_datetime` | `field :name, :naive_datetime` | +| Application code | `DateTime.utc_now()` | `NaiveDateTime.utc_now()` | +| Bulk insert timestamps | `DateTime.utc_now()` | `NaiveDateTime.utc_now() \|> NaiveDateTime.truncate(:second)` | + +**Existing `:utc_datetime_usec` schemas are acceptable** — no need to downgrade them. `DateTime.utc_now()` works with both `:utc_datetime` and `:utc_datetime_usec` fields (Ecto handles precision automatically). + +**Why:** A production bug was caused by copying `NaiveDateTime.utc_now()` into a `:utc_datetime_usec` schema. Using `DateTime.utc_now()` everywhere prevents this class of bugs. See `dev_docs/2026-02-15-datetime-inconsistency-report.md` for full details. + ### Key Design Principles - **No Circular Dependencies** - Optional Phoenix deps prevent import cycles diff --git a/dev_docs/2026-02-15-datetime-inconsistency-report.md b/dev_docs/2026-02-15-datetime-inconsistency-report.md index d87f70b2..c0bc96ec 100644 --- a/dev_docs/2026-02-15-datetime-inconsistency-report.md +++ b/dev_docs/2026-02-15-datetime-inconsistency-report.md @@ -2,7 +2,7 @@ **Date:** 2026-02-15 **Severity:** Medium (already caused a production bug) -**Recommendation:** Standardize on `timestamptz` + `:utc_datetime_usec` everywhere +**Recommendation:** Standardize on `:utc_datetime` + `DateTime.utc_now()` everywhere (existing `:utc_datetime_usec` schemas left as-is) --- @@ -110,97 +110,64 @@ Code that receives values from different schemas needs to handle all three, or r --- -## Recommendation: Standardize on `timestamptz` + `:utc_datetime_usec` +## Recommendation: Standardize on `:utc_datetime` + `DateTime.utc_now()` ### Target State | Layer | Standard | Notes | |-------|----------|-------| -| **PostgreSQL** | `timestamptz` | Timezone-aware, auto-normalizes to UTC | -| **Ecto Schema** | `:utc_datetime_usec` | Returns `DateTime` with microsecond precision | -| **Application Code** | `DateTime.utc_now()` | Consistent everywhere | -| **Default timestamps** | `timestamps(type: :utc_datetime_usec)` | Override Ecto's default | +| **Ecto Schema** | `:utc_datetime` | Returns `DateTime` with second precision | +| **Application Code** | `DateTime.utc_now()` | Consistent everywhere, already returns second precision | +| **Default timestamps** | `timestamps(type: :utc_datetime)` | Override Ecto's default | +| **Existing `:utc_datetime_usec`** | Leave as-is | Already works correctly, no need to downgrade | -### Why `timestamptz` over `timestamp` +### Why `:utc_datetime` (not `:utc_datetime_usec`) -PostgreSQL's `timestamptz` stores the value in UTC internally (same storage cost) but provides timezone conversion on input/output. If a client connects with a non-UTC timezone, `timestamptz` handles the conversion correctly. With plain `timestamp`, that same value would be silently misinterpreted. +- Microsecond precision is not needed for this application +- Second precision matches the existing `timestamp(0)` database columns — no DB migration required +- `DateTime.utc_now()` returns second precision by default, so no `truncate/2` calls needed +- Simpler migration path: only schema + application code changes, no database column alterations -### Why `:utc_datetime_usec` over `:utc_datetime` +### Why not `:naive_datetime` -- Modern Ecto/Phoenix generators default to `_usec` variants -- No precision loss — you get the full resolution PostgreSQL offers -- One fewer type to think about -- The "usec" suffix is misleading — it doesn't cost extra storage, it just preserves what PostgreSQL already stores +- `NaiveDateTime` has no timezone information, making it easy to misuse +- `DateTime` with UTC timezone is explicit about the timezone context +- Copying `DateTime.utc_now()` between any modules (`:utc_datetime` or `:utc_datetime_usec`) works correctly +- Ecto automatically handles `DateTime` → `:utc_datetime_usec` promotion (adds zero microseconds) + +### Database Migration (Deferred) + +Converting `timestamp(0)` → `timestamptz` columns is deferred to a separate V58 migration. The schema-level change to `:utc_datetime` is safe with existing `timestamp(0)` columns because Ecto handles the conversion transparently. --- ## Migration Plan -### Phase 1: Prevent New Inconsistencies (Immediate) - -1. **Add project-wide convention to CLAUDE.md and CONTRIBUTING.md:** - - Always use `timestamps(type: :utc_datetime_usec)` in schemas - - Always use `DateTime.utc_now()` in application code - - Always use `timestamptz` in raw SQL migrations - -2. **Add a Credo check or compile-time warning** for `NaiveDateTime.utc_now()` in non-migration code. - -### Phase 2: Align Schemas with Database (Low Risk) - -Update all Ecto schemas to use `:utc_datetime_usec` for existing columns. Since the database columns with precision=6 already store microseconds, the schema change is backwards-compatible — Ecto will just return `DateTime` instead of `NaiveDateTime`. - -**Schemas to update:** -- `User` — `confirmed_at`, `timestamps()` -- `Role` — `timestamps()` -- `AdminNote` — `timestamps()` -- `RoleAssignment` — `assigned_at` -- All Connections schemas — `inserted_at`, `requested_at`, `responded_at` -- All Shop schemas — `timestamps()` -- `Subscription` — all 8 datetime fields -- `WebhookEvent` — `processed_at` -- Storage schemas — `last_verified_at`, `timestamps()` - -**Application code to update (use `DateTime.utc_now()` instead of `NaiveDateTime.utc_now()`):** -- `lib/phoenix_kit/users/auth/user.ex:322` -- `lib/phoenix_kit/users/sessions.ex:234` -- `lib/phoenix_kit/users/permissions.ex:504` -- `lib/phoenix_kit/users/magic_link_registration.ex:166` -- `lib/phoenix_kit/users/roles.ex:783` -- `lib/phoenix_kit/users/role_assignment.ex:113` -- `lib/modules/storage/storage.ex:238` -- `lib/modules/connections/follow.ex:99` -- `lib/modules/connections/block.ex:110` -- `lib/modules/connections/block_history.ex:51` -- `lib/modules/connections/connection.ex:153,165` -- `lib/modules/connections/connection_history.ex:83` -- `lib/modules/connections/follow_history.ex:50` -- `lib/modules/comments/comments.ex:302` - -### Phase 3: Database Migration (Requires Downtime Planning) - -A versioned migration (V57 or later) to alter all 64 precision-0 columns: - -```sql --- Convert timestamp(0) to timestamptz with microsecond precision --- This is a metadata-only change in PostgreSQL for columns that already store UTC -ALTER TABLE phoenix_kit_users - ALTER COLUMN confirmed_at TYPE timestamptz USING confirmed_at AT TIME ZONE 'UTC', - ALTER COLUMN inserted_at TYPE timestamptz USING inserted_at AT TIME ZONE 'UTC', - ALTER COLUMN updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC'; - --- Repeat for all 34 affected tables... -``` +### Phase 1: Standardize Schemas and Application Code (COMPLETED 2026-02-17) + +All schemas and application code have been updated: + +1. **Schema timestamps:** All `timestamps()` and `timestamps(type: :naive_datetime)` → `timestamps(type: :utc_datetime)` +2. **Individual fields:** All `field :name, :naive_datetime` → `field :name, :utc_datetime` +3. **Application code:** All `NaiveDateTime.utc_now()` → `DateTime.utc_now()` in non-display code +4. **Convention added to CLAUDE.md** to prevent future regressions -**Important notes:** -- `ALTER COLUMN ... TYPE` on large tables may require a lock. For tables with millions of rows, consider doing this during a maintenance window or using `pg_repack`. -- The `AT TIME ZONE 'UTC'` clause tells PostgreSQL that existing values are UTC (they are, since Ecto always writes UTC). -- Also convert the 135 precision-6 `timestamp` columns to `timestamptz` for timezone safety. +**Files updated:** ~38 schema files, 9 field type files, 14 application code files -### Phase 4: Verify and Clean Up +**What was NOT changed:** +- Schemas already on `:utc_datetime_usec` (left as-is, they work correctly) +- Schemas already on `:utc_datetime` (already correct) +- Display/formatter code in `time_display.ex` and `file_display.ex` (handles both DateTime and NaiveDateTime) -- Run full integration test suite against a parent application -- Remove any `NaiveDateTime` imports/aliases that are no longer needed -- Update any date formatting utilities that special-case `NaiveDateTime` +### Phase 2: Database Migration (Deferred) + +Converting `timestamp(0)` → `timestamptz` columns deferred to a separate V58 migration. The schema-level changes are safe with existing columns. + +### Phase 3: Verify and Clean Up + +- Compile with `--warnings-as-errors` — no type warnings +- Run `mix test`, `mix format`, `mix credo --strict` +- Verify no remaining `NaiveDateTime.utc_now()` in application code (only in display utilities) --- @@ -368,10 +335,11 @@ lib/modules/comments/comments.ex:302 ✅ ### Recommendation Priority Update -Based on verification findings: +**Status: Phase 1 COMPLETED (2026-02-17)** + +All schema and application code standardized on `:utc_datetime` + `DateTime.utc_now()`. Convention added to CLAUDE.md. -1. **Immediate (High Risk):** Add compile-time check or Credo rule for `NaiveDateTime.utc_now()` in non-migration code - this prevents new bugs being introduced -2. **Short-term:** Fix the 17 files using `NaiveDateTime.utc_now()` in application code (especially `permissions.ex`, `comments.ex`, `storage.ex`) -3. **Medium-term:** Align Ecto schemas to use `:utc_datetime_usec` (backwards-compatible change) -4. **Long-term:** Database migration to `timestamptz` (requires downtime planning) +**Remaining:** +1. **Database migration (V58):** Convert `timestamp(0)` → `timestamptz` columns (deferred, requires downtime planning) +2. **Optional:** Add Credo check or compile-time warning for `NaiveDateTime.utc_now()` to prevent regressions diff --git a/dev_docs/2026-02-17-datetime-standardization-plan.md b/dev_docs/2026-02-17-datetime-standardization-plan.md new file mode 100644 index 00000000..6463f0ea --- /dev/null +++ b/dev_docs/2026-02-17-datetime-standardization-plan.md @@ -0,0 +1,182 @@ +# DateTime Standardization Plan + +**Date:** 2026-02-17 +**Status:** Planned +**Related:** `dev_docs/2026-02-15-datetime-inconsistency-report.md` + +--- + +## Context + +A production bug (Entities crash from `NaiveDateTime` in a `:utc_datetime_usec` field) revealed that PhoenixKit uses 3 different datetime conventions. Copying code between modules causes runtime crashes. The audit at `dev_docs/2026-02-15-datetime-inconsistency-report.md` documents the full scope. + +**Goal:** Standardize **everything** on `:utc_datetime` and `DateTime.utc_now()`. Microsecond precision is not needed. Existing `:utc_datetime_usec` schemas will be downgraded to `:utc_datetime` — Ecto automatically truncates microseconds on read, so existing DB data is preserved (just trimmed to seconds). + +--- + +## What Needs Fixing: Summary + +| Problem | Count | Action | +|---------|-------|--------| +| Schemas using `:naive_datetime` (default or explicit) | **38 files** | Change to `:utc_datetime` | +| Schemas using `:utc_datetime_usec` (timestamps) | **~17 files** | Downgrade to `:utc_datetime` | +| Individual fields typed `:naive_datetime` | **11 fields in 9 files** | Change to `:utc_datetime` | +| Individual fields typed `:utc_datetime_usec` | **~30+ fields in ~17 files** | Downgrade to `:utc_datetime` | +| Application code using `NaiveDateTime.utc_now()` | **19 calls in 14 files** | Change to `DateTime.utc_now()` | +| Schemas already on `:utc_datetime` | **7 files** | No change needed | + +**Data safety:** Changing `:utc_datetime_usec` → `:utc_datetime` does NOT delete data. Ecto truncates microseconds on read. The DB column retains full precision — only the Elixir representation loses sub-second detail. + +--- + +## Step 1: Update Schema Timestamp Declarations (~55 files) + +### Group A — Default `timestamps()` → `timestamps(type: :utc_datetime)` (8 files) + +| File | Module | +|------|--------| +| `lib/phoenix_kit/users/auth/user.ex` | `PhoenixKit.Users.Auth.User` | +| `lib/phoenix_kit/users/admin_note.ex` | `PhoenixKit.Users.AdminNote` | +| `lib/phoenix_kit/users/role.ex` | `PhoenixKit.Users.Role` | +| `lib/modules/shop/schemas/product.ex` | `PhoenixKit.Modules.Shop.Product` | +| `lib/modules/shop/schemas/category.ex` | `PhoenixKit.Modules.Shop.Category` | +| `lib/modules/shop/schemas/import_config.ex` | `PhoenixKit.Modules.Shop.ImportConfig` | +| `lib/modules/shop/schemas/import_log.ex` | `PhoenixKit.Modules.Shop.ImportLog` | +| `lib/modules/shop/schemas/shipping_method.ex` | `PhoenixKit.Modules.Shop.ShippingMethod` | + +Note: `shop_config.ex` has `@timestamps_opts [type: :utc_datetime]` — already correct. + +### Group B — `timestamps(updated_at: false)` → add `type: :utc_datetime` (3 files) + +| File | Module | +|------|--------| +| `lib/phoenix_kit/users/role_assignment.ex` | `PhoenixKit.Users.RoleAssignment` | +| `lib/phoenix_kit/users/role_permission.ex` | `PhoenixKit.Users.RolePermission` | +| `lib/phoenix_kit/users/auth/user_token.ex` | `PhoenixKit.Users.Auth.UserToken` | + +### Group C — Explicit `timestamps(type: :naive_datetime)` → `:utc_datetime` (26 files) + +**Tickets (4):** `ticket.ex`, `ticket_comment.ex`, `ticket_attachment.ex`, `ticket_status_history.ex` +**Posts (13):** `post.ex`, `post_like.ex`, `post_dislike.ex`, `post_comment.ex`, `post_mention.ex`, `post_group.ex`, `post_group_assignment.ex`, `post_tag.ex`, `post_tag_assignment.ex`, `post_media.ex`, `post_view.ex`, `comment_like.ex`, `comment_dislike.ex` +**Comments (3):** `comment.ex`, `comment_like.ex`, `comment_dislike.ex` +**Storage (5):** `file.ex`, `dimension.ex`, `file_instance.ex`, `bucket.ex`, `file_location.ex` +**Connections (1):** `connection.ex` + +### Group D — `timestamps(type: :utc_datetime_usec)` → `:utc_datetime` (~17 files) + +| File | Module | +|------|--------| +| `lib/modules/ai/endpoint.ex` | `PhoenixKit.Modules.AI.Endpoint` | +| `lib/modules/ai/prompt.ex` | `PhoenixKit.Modules.AI.Prompt` | +| `lib/modules/ai/request.ex` | `PhoenixKit.Modules.AI.Request` | +| `lib/modules/emails/template.ex` | `PhoenixKit.Modules.Emails.Template` | +| `lib/modules/emails/log.ex` | `PhoenixKit.Modules.Emails.Log` | +| `lib/modules/emails/event.ex` | `PhoenixKit.Modules.Emails.Event` | +| `lib/modules/billing/schemas/billing_profile.ex` | `PhoenixKit.Modules.Billing.BillingProfile` | +| `lib/modules/billing/schemas/invoice.ex` | `PhoenixKit.Modules.Billing.Invoice` | +| `lib/modules/billing/schemas/currency.ex` | `PhoenixKit.Modules.Billing.Currency` | +| `lib/modules/billing/schemas/order.ex` | `PhoenixKit.Modules.Billing.Order` | +| `lib/modules/billing/schemas/transaction.ex` | `PhoenixKit.Modules.Billing.Transaction` | +| `lib/modules/sync/connection.ex` | `PhoenixKit.Modules.Sync.Connection` | +| `lib/modules/sync/transfer.ex` | `PhoenixKit.Modules.Sync.Transfer` | +| `lib/modules/legal/schemas/consent_log.ex` | `PhoenixKit.Modules.Legal.ConsentLog` | +| `lib/phoenix_kit/users/oauth_provider.ex` | `PhoenixKit.Users.OAuthProvider` | +| `lib/phoenix_kit/audit_log/entry.ex` | `PhoenixKit.AuditLog.Entry` | +| `lib/phoenix_kit/scheduled_jobs/scheduled_job.ex` | `PhoenixKit.ScheduledJobs.ScheduledJob` | + +--- + +## Step 2: Update Individual Field Type Declarations + +### `:naive_datetime` → `:utc_datetime` (9 files, 11 fields) + +| File | Field(s) | +|------|----------| +| `lib/phoenix_kit/users/auth/user.ex` | `field :confirmed_at` | +| `lib/phoenix_kit/users/role_assignment.ex` | `field :assigned_at` | +| `lib/modules/connections/connection.ex` | `field :requested_at`, `field :responded_at` | +| `lib/modules/connections/connection_history.ex` | `field :inserted_at` | +| `lib/modules/connections/follow.ex` | `field :inserted_at` | +| `lib/modules/connections/follow_history.ex` | `field :inserted_at` | +| `lib/modules/connections/block.ex` | `field :inserted_at` | +| `lib/modules/connections/block_history.ex` | `field :inserted_at` | +| `lib/modules/storage/schemas/file_location.ex` | `field :last_verified_at` | + +### `:utc_datetime_usec` → `:utc_datetime` (~17 files, ~30+ fields) + +| File | Field(s) | +|------|----------| +| `lib/modules/entities/entities.ex` | `date_created`, `date_updated` | +| `lib/modules/entities/entity_data.ex` | `date_created`, `date_updated` | +| `lib/modules/sync/transfer.ex` | `approved_at`, `denied_at`, `approval_expires_at`, `started_at`, `completed_at` | +| `lib/modules/sync/connection.ex` | `expires_at`, `approved_at`, `suspended_at`, `revoked_at`, `last_connected_at`, `last_transfer_at` | +| `lib/modules/referrals/schemas/referral_code_usage.ex` | `date_used` | +| `lib/modules/referrals/referrals.ex` | `date_created`, `expiration_date` | +| `lib/modules/billing/schemas/invoice.ex` | `receipt_generated_at`, `sent_at`, `paid_at`, `voided_at` | +| `lib/modules/billing/schemas/order.ex` | `confirmed_at`, `paid_at`, `cancelled_at` | +| `lib/modules/tickets/ticket.ex` | `resolved_at`, `closed_at` | +| `lib/modules/posts/schemas/post.ex` | `scheduled_at`, `published_at` | +| `lib/modules/posts/schemas/post_view.ex` | `viewed_at` | +| `lib/modules/ai/endpoint.ex` | `last_validated_at` | +| `lib/modules/ai/prompt.ex` | `last_used_at` | +| `lib/modules/emails/template.ex` | `last_used_at` | +| `lib/modules/emails/log.ex` | `queued_at`, `sent_at`, `delivered_at`, `bounced_at`, `complained_at`, `opened_at`, `clicked_at`, `rejected_at`, `failed_at`, `delayed_at` | +| `lib/modules/emails/event.ex` | `occurred_at` | +| `lib/modules/emails/rate_limiter.ex` | `expires_at`, `inserted_at`, `updated_at` (embedded schema) | +| `lib/phoenix_kit/settings/setting.ex` | `date_added`, `date_updated` | +| `lib/phoenix_kit/scheduled_jobs/scheduled_job.ex` | `scheduled_at`, `executed_at` | +| `lib/phoenix_kit/users/oauth_provider.ex` | `token_expires_at` | + +--- + +## Step 3: Update Application Code (14 files, 19 calls) + +Replace `NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)` → `DateTime.utc_now()` +Replace bare `NaiveDateTime.utc_now()` → `DateTime.utc_now()` + +| File | Line(s) | Context | +|------|---------|---------| +| `lib/phoenix_kit/users/auth/user.ex` | ~322 | `confirm_changeset` | +| `lib/phoenix_kit/users/permissions.ex` | ~738 | `set_permissions` bulk insert timestamps | +| `lib/phoenix_kit/users/role_assignment.ex` | ~102 | `put_assigned_at` changeset | +| `lib/phoenix_kit/users/magic_link_registration.ex` | ~166 | `do_complete_registration` confirmed_at | +| `lib/phoenix_kit/users/roles.ex` | ~802 | `maybe_add_confirmed_at` | +| `lib/phoenix_kit/users/sessions.ex` | ~234, ~290, ~294 | Query boundaries + age calculations | +| `lib/modules/storage/storage.ex` | ~238 | `reset_dimensions_to_defaults` bulk insert | +| `lib/modules/comments/comments.ex` | ~307 | `bulk_update_status` update_all | +| `lib/modules/connections/connection.ex` | ~170, ~182 | `put_requested_at`, `put_responded_at` | +| `lib/modules/connections/connection_history.ex` | ~98 | `put_timestamp` | +| `lib/modules/connections/follow.ex` | ~116 | `put_inserted_at` | +| `lib/modules/connections/follow_history.ex` | ~58 | `put_timestamp` | +| `lib/modules/connections/block.ex` | ~124 | `put_inserted_at` | +| `lib/modules/connections/block_history.ex` | ~59 | `put_timestamp` | + +--- + +## Step 4: Update Documentation + +- Update `dev_docs/2026-02-15-datetime-inconsistency-report.md` recommendation to target `:utc_datetime` +- Add DateTime Convention section to CLAUDE.md: + - Always use `timestamps(type: :utc_datetime)` in new schemas + - Always use `DateTime.utc_now()` in application code + - Never use `NaiveDateTime.utc_now()` or `:utc_datetime_usec` in new code + +--- + +## Already Correct (No Change Needed) + +- **7 schemas already on `:utc_datetime`** — billing/shop (webhook_event, payment_option, subscription, subscription_plan, payment_method, cart, cart_item) +- **Display/formatter code** — `date.ex`, `time_display.ex`, `file_display.ex` already handle both DateTime and NaiveDateTime (keep NaiveDateTime clauses for backward compat) +- **DB migration** — converting `timestamp(0)` → `timestamptz` columns deferred to separate V58 migration + +--- + +## Verification + +1. `mix compile --warnings-as-errors` — no type warnings +2. `mix test` — all tests pass +3. `mix format` — clean +4. `mix credo --strict` — no issues +5. `mix dialyzer` — no new warnings +6. `grep -r "NaiveDateTime.utc_now" lib/` — should only remain in display code (`time_display.ex`, `file_display.ex`) +7. `grep -r "utc_datetime_usec" lib/` — should return zero matches diff --git a/lib/modules/ai/endpoint.ex b/lib/modules/ai/endpoint.ex index 6458f289..3f28c91b 100644 --- a/lib/modules/ai/endpoint.ex +++ b/lib/modules/ai/endpoint.ex @@ -143,13 +143,13 @@ defmodule PhoenixKit.Modules.AI.Endpoint do # Status field :enabled, :boolean, default: true field :sort_order, :integer, default: 0 - field :last_validated_at, :utc_datetime_usec + field :last_validated_at, :utc_datetime has_many :requests, PhoenixKit.Modules.AI.Request, foreign_key: :endpoint_uuid, references: :uuid - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/ai/prompt.ex b/lib/modules/ai/prompt.ex index 47486a9c..61715d40 100644 --- a/lib/modules/ai/prompt.ex +++ b/lib/modules/ai/prompt.ex @@ -106,12 +106,12 @@ defmodule PhoenixKit.Modules.AI.Prompt do # Usage tracking field :usage_count, :integer, default: 0 - field :last_used_at, :utc_datetime_usec + field :last_used_at, :utc_datetime # Flexible metadata field :metadata, :map, default: %{} - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/ai/request.ex b/lib/modules/ai/request.ex index bc37c1f4..bdeac4ff 100644 --- a/lib/modules/ai/request.ex +++ b/lib/modules/ai/request.ex @@ -139,7 +139,7 @@ defmodule PhoenixKit.Modules.AI.Request do field :user_id, :integer belongs_to :user, User, foreign_key: :user_uuid, references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/billing/schemas/billing_profile.ex b/lib/modules/billing/schemas/billing_profile.ex index 6b3f0ebd..32cc23fb 100644 --- a/lib/modules/billing/schemas/billing_profile.ex +++ b/lib/modules/billing/schemas/billing_profile.ex @@ -97,7 +97,7 @@ defmodule PhoenixKit.Modules.Billing.BillingProfile do field :user_id, :integer belongs_to :user, User, foreign_key: :user_uuid, references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/billing/schemas/currency.ex b/lib/modules/billing/schemas/currency.ex index e40792ad..9dc157bf 100644 --- a/lib/modules/billing/schemas/currency.ex +++ b/lib/modules/billing/schemas/currency.ex @@ -45,7 +45,7 @@ defmodule PhoenixKit.Modules.Billing.Currency do field :exchange_rate, :decimal, default: Decimal.new("1.0") field :sort_order, :integer, default: 0 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/billing/schemas/invoice.ex b/lib/modules/billing/schemas/invoice.ex index 0686440f..12c6a5ca 100644 --- a/lib/modules/billing/schemas/invoice.ex +++ b/lib/modules/billing/schemas/invoice.ex @@ -90,13 +90,13 @@ defmodule PhoenixKit.Modules.Billing.Invoice do # Receipt (integrated) field :receipt_number, :string - field :receipt_generated_at, :utc_datetime_usec + field :receipt_generated_at, :utc_datetime field :receipt_data, :map, default: %{} # Timestamps - field :sent_at, :utc_datetime_usec - field :paid_at, :utc_datetime_usec - field :voided_at, :utc_datetime_usec + field :sent_at, :utc_datetime + field :paid_at, :utc_datetime + field :voided_at, :utc_datetime # legacy field :user_id, :integer @@ -106,7 +106,7 @@ defmodule PhoenixKit.Modules.Billing.Invoice do belongs_to :order, Order, foreign_key: :order_uuid, references: :uuid, type: UUIDv7 has_many :transactions, Transaction, foreign_key: :invoice_uuid, references: :uuid - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/billing/schemas/order.ex b/lib/modules/billing/schemas/order.ex index e5dcfb31..1a6681bd 100644 --- a/lib/modules/billing/schemas/order.ex +++ b/lib/modules/billing/schemas/order.ex @@ -116,9 +116,9 @@ defmodule PhoenixKit.Modules.Billing.Order do field :metadata, :map, default: %{} # Timestamps - field :confirmed_at, :utc_datetime_usec - field :paid_at, :utc_datetime_usec - field :cancelled_at, :utc_datetime_usec + field :confirmed_at, :utc_datetime + field :paid_at, :utc_datetime + field :cancelled_at, :utc_datetime # legacy field :user_id, :integer @@ -135,7 +135,7 @@ defmodule PhoenixKit.Modules.Billing.Order do foreign_key: :order_uuid, references: :uuid - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/billing/schemas/transaction.ex b/lib/modules/billing/schemas/transaction.ex index 303bb58b..6b72291e 100644 --- a/lib/modules/billing/schemas/transaction.ex +++ b/lib/modules/billing/schemas/transaction.ex @@ -44,7 +44,7 @@ defmodule PhoenixKit.Modules.Billing.Transaction do field :user_id, :integer belongs_to :user, User, foreign_key: :user_uuid, references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/comments/comments.ex b/lib/modules/comments/comments.ex index 8c27e412..28fc1ae9 100644 --- a/lib/modules/comments/comments.ex +++ b/lib/modules/comments/comments.ex @@ -304,7 +304,7 @@ defmodule PhoenixKit.Modules.Comments do def bulk_update_status(comment_ids, status) when is_list(comment_ids) and status in ["published", "hidden", "deleted", "pending"] do from(c in Comment, where: c.uuid in ^comment_ids) - |> repo().update_all(set: [status: status, updated_at: NaiveDateTime.utc_now()]) + |> repo().update_all(set: [status: status, updated_at: DateTime.utc_now()]) end @doc """ diff --git a/lib/modules/comments/schemas/comment.ex b/lib/modules/comments/schemas/comment.ex index d66224f5..e884da88 100644 --- a/lib/modules/comments/schemas/comment.ex +++ b/lib/modules/comments/schemas/comment.ex @@ -44,8 +44,8 @@ defmodule PhoenixKit.Modules.Comments.Comment do user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, parent: t() | Ecto.Association.NotLoaded.t() | nil, children: [t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_comments" do @@ -67,7 +67,7 @@ defmodule PhoenixKit.Modules.Comments.Comment do has_many :children, __MODULE__, foreign_key: :parent_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/comments/schemas/comment_dislike.ex b/lib/modules/comments/schemas/comment_dislike.ex index 33b3e343..85eec73e 100644 --- a/lib/modules/comments/schemas/comment_dislike.ex +++ b/lib/modules/comments/schemas/comment_dislike.ex @@ -16,8 +16,8 @@ defmodule PhoenixKit.Modules.Comments.CommentDislike do user_uuid: UUIDv7.t() | nil, comment: PhoenixKit.Modules.Comments.Comment.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_comments_dislikes" do @@ -30,7 +30,7 @@ defmodule PhoenixKit.Modules.Comments.CommentDislike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/comments/schemas/comment_like.ex b/lib/modules/comments/schemas/comment_like.ex index d8043ab8..7088999c 100644 --- a/lib/modules/comments/schemas/comment_like.ex +++ b/lib/modules/comments/schemas/comment_like.ex @@ -16,8 +16,8 @@ defmodule PhoenixKit.Modules.Comments.CommentLike do user_uuid: UUIDv7.t() | nil, comment: PhoenixKit.Modules.Comments.Comment.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_comments_likes" do @@ -30,7 +30,7 @@ defmodule PhoenixKit.Modules.Comments.CommentLike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/connections/block.ex b/lib/modules/connections/block.ex index 8f78a918..e267c4cf 100644 --- a/lib/modules/connections/block.ex +++ b/lib/modules/connections/block.ex @@ -47,7 +47,7 @@ defmodule PhoenixKit.Modules.Connections.Block do reason: String.t() | nil, blocker: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), blocked: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil } schema "phoenix_kit_user_blocks" do @@ -65,7 +65,7 @@ defmodule PhoenixKit.Modules.Connections.Block do field :blocked_id, :integer field :reason, :string - field :inserted_at, :naive_datetime + field :inserted_at, :utc_datetime end @doc """ @@ -121,7 +121,7 @@ defmodule PhoenixKit.Modules.Connections.Block do put_change( changeset, :inserted_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end diff --git a/lib/modules/connections/block_history.ex b/lib/modules/connections/block_history.ex index 227b343f..c7a06da7 100644 --- a/lib/modules/connections/block_history.ex +++ b/lib/modules/connections/block_history.ex @@ -34,7 +34,7 @@ defmodule PhoenixKit.Modules.Connections.BlockHistory do field :action, :string field :reason, :string - field :inserted_at, :naive_datetime + field :inserted_at, :utc_datetime end @actions ~w(block unblock) @@ -56,7 +56,7 @@ defmodule PhoenixKit.Modules.Connections.BlockHistory do put_change( changeset, :inserted_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end diff --git a/lib/modules/connections/connection.ex b/lib/modules/connections/connection.ex index 005071ea..8ef15a6b 100644 --- a/lib/modules/connections/connection.ex +++ b/lib/modules/connections/connection.ex @@ -66,12 +66,12 @@ defmodule PhoenixKit.Modules.Connections.Connection do requester_id: integer() | nil, recipient_id: integer() | nil, status: status(), - requested_at: NaiveDateTime.t(), - responded_at: NaiveDateTime.t() | nil, + requested_at: DateTime.t(), + responded_at: DateTime.t() | nil, requester: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), recipient: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_user_connections" do @@ -89,10 +89,10 @@ defmodule PhoenixKit.Modules.Connections.Connection do field :recipient_id, :integer field :status, :string, default: "pending" - field :requested_at, :naive_datetime - field :responded_at, :naive_datetime + field :requested_at, :utc_datetime + field :responded_at, :utc_datetime - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ @@ -167,7 +167,7 @@ defmodule PhoenixKit.Modules.Connections.Connection do put_change( changeset, :requested_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end @@ -179,7 +179,7 @@ defmodule PhoenixKit.Modules.Connections.Connection do put_change( changeset, :responded_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) else changeset diff --git a/lib/modules/connections/connection_history.ex b/lib/modules/connections/connection_history.ex index 520fd8ce..17c7026e 100644 --- a/lib/modules/connections/connection_history.ex +++ b/lib/modules/connections/connection_history.ex @@ -47,7 +47,7 @@ defmodule PhoenixKit.Modules.Connections.ConnectionHistory do field :actor_id, :integer field :action, :string - field :inserted_at, :naive_datetime + field :inserted_at, :utc_datetime end @actions ~w(requested accepted rejected removed) @@ -95,7 +95,7 @@ defmodule PhoenixKit.Modules.Connections.ConnectionHistory do put_change( changeset, :inserted_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end diff --git a/lib/modules/connections/follow.ex b/lib/modules/connections/follow.ex index b83dd9fe..c5f2a816 100644 --- a/lib/modules/connections/follow.ex +++ b/lib/modules/connections/follow.ex @@ -42,7 +42,7 @@ defmodule PhoenixKit.Modules.Connections.Follow do followed_id: integer() | nil, follower: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), followed: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil } schema "phoenix_kit_user_follows" do @@ -59,7 +59,7 @@ defmodule PhoenixKit.Modules.Connections.Follow do field :follower_id, :integer field :followed_id, :integer - field :inserted_at, :naive_datetime + field :inserted_at, :utc_datetime end @doc """ @@ -113,7 +113,7 @@ defmodule PhoenixKit.Modules.Connections.Follow do put_change( changeset, :inserted_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end diff --git a/lib/modules/connections/follow_history.ex b/lib/modules/connections/follow_history.ex index 69602cf5..7d7732a3 100644 --- a/lib/modules/connections/follow_history.ex +++ b/lib/modules/connections/follow_history.ex @@ -33,7 +33,7 @@ defmodule PhoenixKit.Modules.Connections.FollowHistory do field :followed_id, :integer field :action, :string - field :inserted_at, :naive_datetime + field :inserted_at, :utc_datetime end @actions ~w(follow unfollow) @@ -55,7 +55,7 @@ defmodule PhoenixKit.Modules.Connections.FollowHistory do put_change( changeset, :inserted_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) end end diff --git a/lib/modules/emails/event.ex b/lib/modules/emails/event.ex index 63d5963c..daa33782 100644 --- a/lib/modules/emails/event.ex +++ b/lib/modules/emails/event.ex @@ -71,7 +71,7 @@ defmodule PhoenixKit.Modules.Emails.Event do field :id, :integer, read_after_writes: true field :event_type, :string field :event_data, :map, default: %{} - field :occurred_at, :utc_datetime_usec + field :occurred_at, :utc_datetime field :ip_address, :string field :user_agent, :string field :geo_location, :map, default: %{} @@ -88,7 +88,7 @@ defmodule PhoenixKit.Modules.Emails.Event do field :email_log_id, :integer belongs_to :email_log, Log, foreign_key: :email_log_uuid, references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end ## --- Schema Functions --- diff --git a/lib/modules/emails/log.ex b/lib/modules/emails/log.ex index 81475b5e..7bca0ef1 100644 --- a/lib/modules/emails/log.ex +++ b/lib/modules/emails/log.ex @@ -190,16 +190,16 @@ defmodule PhoenixKit.Modules.Emails.Log do field :retry_count, :integer, default: 0 field :error_message, :string field :status, :string, default: "queued" - field :queued_at, :utc_datetime_usec - field :sent_at, :utc_datetime_usec - field :delivered_at, :utc_datetime_usec - field :bounced_at, :utc_datetime_usec - field :complained_at, :utc_datetime_usec - field :opened_at, :utc_datetime_usec - field :clicked_at, :utc_datetime_usec - field :rejected_at, :utc_datetime_usec - field :failed_at, :utc_datetime_usec - field :delayed_at, :utc_datetime_usec + field :queued_at, :utc_datetime + field :sent_at, :utc_datetime + field :delivered_at, :utc_datetime + field :bounced_at, :utc_datetime + field :complained_at, :utc_datetime + field :opened_at, :utc_datetime + field :clicked_at, :utc_datetime + field :rejected_at, :utc_datetime + field :failed_at, :utc_datetime + field :delayed_at, :utc_datetime field :configuration_set, :string field :message_tags, :map, default: %{} field :provider, :string, default: "unknown" @@ -219,7 +219,7 @@ defmodule PhoenixKit.Modules.Emails.Log do references: :uuid, on_delete: :delete_all - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end ## --- Schema Functions --- diff --git a/lib/modules/emails/rate_limiter.ex b/lib/modules/emails/rate_limiter.ex index 10e9d02f..ababefb0 100644 --- a/lib/modules/emails/rate_limiter.ex +++ b/lib/modules/emails/rate_limiter.ex @@ -15,12 +15,12 @@ defmodule PhoenixKit.Modules.Emails.EmailBlocklist do field :id, :integer, read_after_writes: true field :email, :string field :reason, :string - field :expires_at, :utc_datetime_usec + field :expires_at, :utc_datetime # legacy field :user_id, :integer field :user_uuid, UUIDv7 - field :inserted_at, :utc_datetime_usec - field :updated_at, :utc_datetime_usec + field :inserted_at, :utc_datetime + field :updated_at, :utc_datetime end def changeset(blocklist, attrs) do diff --git a/lib/modules/emails/template.ex b/lib/modules/emails/template.ex index 7f4450a6..0c2a9792 100644 --- a/lib/modules/emails/template.ex +++ b/lib/modules/emails/template.ex @@ -129,7 +129,7 @@ defmodule PhoenixKit.Modules.Emails.Template do field :variables, :map, default: %{} field :metadata, :map, default: %{} field :usage_count, :integer, default: 0 - field :last_used_at, :utc_datetime_usec + field :last_used_at, :utc_datetime field :version, :integer, default: 1 field :is_system, :boolean, default: false # legacy @@ -139,7 +139,7 @@ defmodule PhoenixKit.Modules.Emails.Template do field :updated_by_user_id, :integer field :updated_by_user_uuid, UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/entities/entities.ex b/lib/modules/entities/entities.ex index eb216657..e5ae5204 100644 --- a/lib/modules/entities/entities.ex +++ b/lib/modules/entities/entities.ex @@ -129,8 +129,8 @@ defmodule PhoenixKit.Modules.Entities do # legacy field :created_by, :integer field :created_by_uuid, UUIDv7 - field :date_created, :utc_datetime_usec - field :date_updated, :utc_datetime_usec + field :date_created, :utc_datetime + field :date_updated, :utc_datetime belongs_to :creator, User, foreign_key: :created_by_uuid, diff --git a/lib/modules/entities/entity_data.ex b/lib/modules/entities/entity_data.ex index 7c236c42..2e956c2d 100644 --- a/lib/modules/entities/entity_data.ex +++ b/lib/modules/entities/entity_data.ex @@ -103,8 +103,8 @@ defmodule PhoenixKit.Modules.Entities.EntityData do # legacy field :created_by, :integer field :created_by_uuid, UUIDv7 - field :date_created, :utc_datetime_usec - field :date_updated, :utc_datetime_usec + field :date_created, :utc_datetime + field :date_updated, :utc_datetime # legacy field :entity_id, :integer diff --git a/lib/modules/legal/schemas/consent_log.ex b/lib/modules/legal/schemas/consent_log.ex index d3f1cdb6..0e1ff8e1 100644 --- a/lib/modules/legal/schemas/consent_log.ex +++ b/lib/modules/legal/schemas/consent_log.ex @@ -83,7 +83,7 @@ defmodule PhoenixKit.Modules.Legal.ConsentLog do field :user_agent_hash, :string field :metadata, :map, default: %{} - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/comment_dislike.ex b/lib/modules/posts/schemas/comment_dislike.ex index d61e679e..bd5d6ea0 100644 --- a/lib/modules/posts/schemas/comment_dislike.ex +++ b/lib/modules/posts/schemas/comment_dislike.ex @@ -16,8 +16,8 @@ defmodule PhoenixKit.Modules.Posts.CommentDislike do user_uuid: UUIDv7.t() | nil, comment: PhoenixKit.Modules.Posts.PostComment.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_comment_dislikes" do @@ -30,7 +30,7 @@ defmodule PhoenixKit.Modules.Posts.CommentDislike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/comment_like.ex b/lib/modules/posts/schemas/comment_like.ex index 6ff9cc05..fa0e84b9 100644 --- a/lib/modules/posts/schemas/comment_like.ex +++ b/lib/modules/posts/schemas/comment_like.ex @@ -16,8 +16,8 @@ defmodule PhoenixKit.Modules.Posts.CommentLike do user_uuid: UUIDv7.t() | nil, comment: PhoenixKit.Modules.Posts.PostComment.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_comment_likes" do @@ -30,7 +30,7 @@ defmodule PhoenixKit.Modules.Posts.CommentLike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post.ex b/lib/modules/posts/schemas/post.ex index 183a3d17..32fbcb79 100644 --- a/lib/modules/posts/schemas/post.ex +++ b/lib/modules/posts/schemas/post.ex @@ -105,8 +105,8 @@ defmodule PhoenixKit.Modules.Posts.Post do mentions: [PhoenixKit.Modules.Posts.PostMention.t()] | Ecto.Association.NotLoaded.t(), tags: [PhoenixKit.Modules.Posts.PostTag.t()] | Ecto.Association.NotLoaded.t(), groups: [PhoenixKit.Modules.Posts.PostGroup.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_posts" do @@ -115,8 +115,8 @@ defmodule PhoenixKit.Modules.Posts.Post do field :content, :string field :type, :string, default: "post" field :status, :string, default: "draft" - field :scheduled_at, :utc_datetime_usec - field :published_at, :utc_datetime_usec + field :scheduled_at, :utc_datetime + field :published_at, :utc_datetime field :repost_url, :string field :slug, :string field :like_count, :integer, default: 0 @@ -146,7 +146,7 @@ defmodule PhoenixKit.Modules.Posts.Post do join_through: PhoenixKit.Modules.Posts.PostGroupAssignment, join_keys: [post_id: :uuid, group_id: :uuid] - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_comment.ex b/lib/modules/posts/schemas/post_comment.ex index e2f316ec..1153b830 100644 --- a/lib/modules/posts/schemas/post_comment.ex +++ b/lib/modules/posts/schemas/post_comment.ex @@ -28,8 +28,8 @@ defmodule PhoenixKit.Modules.Posts.PostComment do likes: [PhoenixKit.Modules.Posts.CommentLike.t()] | Ecto.Association.NotLoaded.t(), dislikes: [PhoenixKit.Modules.Posts.CommentDislike.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_comments" do @@ -53,7 +53,7 @@ defmodule PhoenixKit.Modules.Posts.PostComment do has_many :likes, PhoenixKit.Modules.Posts.CommentLike, foreign_key: :comment_id has_many :dislikes, PhoenixKit.Modules.Posts.CommentDislike, foreign_key: :comment_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_dislike.ex b/lib/modules/posts/schemas/post_dislike.ex index ad198afc..eab49251 100644 --- a/lib/modules/posts/schemas/post_dislike.ex +++ b/lib/modules/posts/schemas/post_dislike.ex @@ -29,8 +29,8 @@ defmodule PhoenixKit.Modules.Posts.PostDislike do user_uuid: UUIDv7.t() | nil, post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_dislikes" do @@ -43,7 +43,7 @@ defmodule PhoenixKit.Modules.Posts.PostDislike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_group.ex b/lib/modules/posts/schemas/post_group.ex index 29833163..7224d7ec 100644 --- a/lib/modules/posts/schemas/post_group.ex +++ b/lib/modules/posts/schemas/post_group.ex @@ -63,8 +63,8 @@ defmodule PhoenixKit.Modules.Posts.PostGroup do user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), cover_image: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t() | nil, posts: [PhoenixKit.Modules.Posts.Post.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_groups" do @@ -87,7 +87,7 @@ defmodule PhoenixKit.Modules.Posts.PostGroup do join_through: PhoenixKit.Modules.Posts.PostGroupAssignment, join_keys: [group_id: :uuid, post_id: :uuid] - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_group_assignment.ex b/lib/modules/posts/schemas/post_group_assignment.ex index d32742b0..cd5b71cd 100644 --- a/lib/modules/posts/schemas/post_group_assignment.ex +++ b/lib/modules/posts/schemas/post_group_assignment.ex @@ -38,8 +38,8 @@ defmodule PhoenixKit.Modules.Posts.PostGroupAssignment do position: integer(), post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), group: PhoenixKit.Modules.Posts.PostGroup.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_group_assignments" do @@ -55,7 +55,7 @@ defmodule PhoenixKit.Modules.Posts.PostGroupAssignment do type: UUIDv7, primary_key: true - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_like.ex b/lib/modules/posts/schemas/post_like.ex index e3521b2f..fee0a821 100644 --- a/lib/modules/posts/schemas/post_like.ex +++ b/lib/modules/posts/schemas/post_like.ex @@ -29,8 +29,8 @@ defmodule PhoenixKit.Modules.Posts.PostLike do user_uuid: UUIDv7.t() | nil, post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_likes" do @@ -43,7 +43,7 @@ defmodule PhoenixKit.Modules.Posts.PostLike do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_media.ex b/lib/modules/posts/schemas/post_media.ex index c35eacad..1caa6906 100644 --- a/lib/modules/posts/schemas/post_media.ex +++ b/lib/modules/posts/schemas/post_media.ex @@ -44,8 +44,8 @@ defmodule PhoenixKit.Modules.Posts.PostMedia do caption: String.t() | nil, post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), file: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_media" do @@ -55,7 +55,7 @@ defmodule PhoenixKit.Modules.Posts.PostMedia do belongs_to :post, PhoenixKit.Modules.Posts.Post, references: :uuid belongs_to :file, PhoenixKit.Modules.Storage.File, references: :uuid - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_mention.ex b/lib/modules/posts/schemas/post_mention.ex index daf2fe4a..e311ed4f 100644 --- a/lib/modules/posts/schemas/post_mention.ex +++ b/lib/modules/posts/schemas/post_mention.ex @@ -45,8 +45,8 @@ defmodule PhoenixKit.Modules.Posts.PostMention do mention_type: String.t(), post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_mentions" do @@ -61,7 +61,7 @@ defmodule PhoenixKit.Modules.Posts.PostMention do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_tag.ex b/lib/modules/posts/schemas/post_tag.ex index 3716da75..fdbbe69d 100644 --- a/lib/modules/posts/schemas/post_tag.ex +++ b/lib/modules/posts/schemas/post_tag.ex @@ -39,8 +39,8 @@ defmodule PhoenixKit.Modules.Posts.PostTag do name: String.t(), slug: String.t(), usage_count: integer(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_tags" do @@ -52,7 +52,7 @@ defmodule PhoenixKit.Modules.Posts.PostTag do join_through: PhoenixKit.Modules.Posts.PostTagAssignment, join_keys: [tag_id: :uuid, post_id: :uuid] - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_tag_assignment.ex b/lib/modules/posts/schemas/post_tag_assignment.ex index ece2bd80..7410a9cf 100644 --- a/lib/modules/posts/schemas/post_tag_assignment.ex +++ b/lib/modules/posts/schemas/post_tag_assignment.ex @@ -28,8 +28,8 @@ defmodule PhoenixKit.Modules.Posts.PostTagAssignment do tag_id: UUIDv7.t(), post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), tag: PhoenixKit.Modules.Posts.PostTag.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_tag_assignments" do @@ -43,7 +43,7 @@ defmodule PhoenixKit.Modules.Posts.PostTagAssignment do type: UUIDv7, primary_key: true - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/posts/schemas/post_view.ex b/lib/modules/posts/schemas/post_view.ex index d2de0722..88eb2a15 100644 --- a/lib/modules/posts/schemas/post_view.ex +++ b/lib/modules/posts/schemas/post_view.ex @@ -52,15 +52,15 @@ defmodule PhoenixKit.Modules.Posts.PostView do viewed_at: DateTime.t(), post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(), user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil, - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_post_views" do field :ip_address, :string field :user_agent_hash, :string field :session_id, :string - field :viewed_at, :utc_datetime_usec + field :viewed_at, :utc_datetime belongs_to :post, PhoenixKit.Modules.Posts.Post, references: :uuid, type: UUIDv7 @@ -71,7 +71,7 @@ defmodule PhoenixKit.Modules.Posts.PostView do field :user_id, :integer - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/referrals/referrals.ex b/lib/modules/referrals/referrals.ex index ebb8496d..8c7d6413 100644 --- a/lib/modules/referrals/referrals.ex +++ b/lib/modules/referrals/referrals.ex @@ -87,8 +87,8 @@ defmodule PhoenixKit.Modules.Referrals do # legacy field :beneficiary, :integer field :beneficiary_uuid, UUIDv7 - field :date_created, :utc_datetime_usec - field :expiration_date, :utc_datetime_usec + field :date_created, :utc_datetime + field :expiration_date, :utc_datetime belongs_to :creator, PhoenixKit.Users.Auth.User, foreign_key: :created_by_uuid, diff --git a/lib/modules/referrals/schemas/referral_code_usage.ex b/lib/modules/referrals/schemas/referral_code_usage.ex index d46d1398..822ee424 100644 --- a/lib/modules/referrals/schemas/referral_code_usage.ex +++ b/lib/modules/referrals/schemas/referral_code_usage.ex @@ -42,7 +42,7 @@ defmodule PhoenixKit.Modules.Referrals.ReferralCodeUsage do # legacy field :used_by, :integer field :used_by_uuid, UUIDv7 - field :date_used, :utc_datetime_usec + field :date_used, :utc_datetime # legacy field :code_id, :integer diff --git a/lib/modules/shop/schemas/category.ex b/lib/modules/shop/schemas/category.ex index 1e943805..b8ce8933 100644 --- a/lib/modules/shop/schemas/category.ex +++ b/lib/modules/shop/schemas/category.ex @@ -69,7 +69,7 @@ defmodule PhoenixKit.Modules.Shop.Category do references: :uuid, type: UUIDv7 - timestamps() + timestamps(type: :utc_datetime) end @doc "Returns list of valid category statuses" diff --git a/lib/modules/shop/schemas/import_config.ex b/lib/modules/shop/schemas/import_config.ex index 573a2ac1..46af3bab 100644 --- a/lib/modules/shop/schemas/import_config.ex +++ b/lib/modules/shop/schemas/import_config.ex @@ -82,7 +82,7 @@ defmodule PhoenixKit.Modules.Shop.ImportConfig do # Option mappings for CSV import (JSONB) field :option_mappings, {:array, :map}, default: [] - timestamps() + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/shop/schemas/import_log.ex b/lib/modules/shop/schemas/import_log.ex index 32b54c31..51500d6b 100644 --- a/lib/modules/shop/schemas/import_log.ex +++ b/lib/modules/shop/schemas/import_log.ex @@ -57,7 +57,7 @@ defmodule PhoenixKit.Modules.Shop.ImportLog do field :user_id, :integer belongs_to :user, User, foreign_key: :user_uuid, references: :uuid, type: UUIDv7 - timestamps() + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/shop/schemas/product.ex b/lib/modules/shop/schemas/product.ex index 3fcb46c4..8023771e 100644 --- a/lib/modules/shop/schemas/product.ex +++ b/lib/modules/shop/schemas/product.ex @@ -110,7 +110,7 @@ defmodule PhoenixKit.Modules.Shop.Product do references: :uuid, type: UUIDv7 - timestamps() + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/shop/schemas/shipping_method.ex b/lib/modules/shop/schemas/shipping_method.ex index e9f1286c..44606a0d 100644 --- a/lib/modules/shop/schemas/shipping_method.ex +++ b/lib/modules/shop/schemas/shipping_method.ex @@ -59,7 +59,7 @@ defmodule PhoenixKit.Modules.Shop.ShippingMethod do field :metadata, :map, default: %{} - timestamps() + timestamps(type: :utc_datetime) end @required_fields [:name, :price] diff --git a/lib/modules/storage/schemas/bucket.ex b/lib/modules/storage/schemas/bucket.ex index 4a337388..e4af14b8 100644 --- a/lib/modules/storage/schemas/bucket.ex +++ b/lib/modules/storage/schemas/bucket.ex @@ -92,8 +92,8 @@ defmodule PhoenixKit.Modules.Storage.Bucket do max_size_mb: integer() | nil, file_locations: [PhoenixKit.Modules.Storage.FileLocation.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_buckets" do @@ -112,7 +112,7 @@ defmodule PhoenixKit.Modules.Storage.Bucket do has_many :file_locations, PhoenixKit.Modules.Storage.FileLocation, foreign_key: :bucket_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/storage/schemas/dimension.ex b/lib/modules/storage/schemas/dimension.ex index 12314c8c..be717b01 100644 --- a/lib/modules/storage/schemas/dimension.ex +++ b/lib/modules/storage/schemas/dimension.ex @@ -86,7 +86,7 @@ defmodule PhoenixKit.Modules.Storage.Dimension do field :maintain_aspect_ratio, :boolean, default: true field :order, :integer, default: 0 - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/storage/schemas/file.ex b/lib/modules/storage/schemas/file.ex index 2bbf2ad6..5556f8dc 100644 --- a/lib/modules/storage/schemas/file.ex +++ b/lib/modules/storage/schemas/file.ex @@ -112,8 +112,8 @@ defmodule PhoenixKit.Modules.Storage.File do user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), instances: [PhoenixKit.Modules.Storage.FileInstance.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_files" do @@ -140,7 +140,7 @@ defmodule PhoenixKit.Modules.Storage.File do field :user_id, :integer has_many :instances, PhoenixKit.Modules.Storage.FileInstance, foreign_key: :file_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/storage/schemas/file_instance.ex b/lib/modules/storage/schemas/file_instance.ex index 60a31a99..98874779 100644 --- a/lib/modules/storage/schemas/file_instance.ex +++ b/lib/modules/storage/schemas/file_instance.ex @@ -98,8 +98,8 @@ defmodule PhoenixKit.Modules.Storage.FileInstance do file: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t(), locations: [PhoenixKit.Modules.Storage.FileLocation.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_file_instances" do @@ -116,7 +116,7 @@ defmodule PhoenixKit.Modules.Storage.FileInstance do belongs_to :file, PhoenixKit.Modules.Storage.File, references: :uuid has_many :locations, PhoenixKit.Modules.Storage.FileLocation, foreign_key: :file_instance_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/storage/schemas/file_location.ex b/lib/modules/storage/schemas/file_location.ex index 814c0ae2..9189aa11 100644 --- a/lib/modules/storage/schemas/file_location.ex +++ b/lib/modules/storage/schemas/file_location.ex @@ -75,26 +75,26 @@ defmodule PhoenixKit.Modules.Storage.FileLocation do path: String.t(), status: String.t(), priority: integer(), - last_verified_at: NaiveDateTime.t() | nil, + last_verified_at: DateTime.t() | nil, file_instance_id: UUIDv7.t() | nil, bucket_id: UUIDv7.t() | nil, file_instance: PhoenixKit.Modules.Storage.FileInstance.t() | Ecto.Association.NotLoaded.t(), bucket: PhoenixKit.Modules.Storage.Bucket.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_file_locations" do field :path, :string field :status, :string, default: "active" field :priority, :integer, default: 0 - field :last_verified_at, :naive_datetime + field :last_verified_at, :utc_datetime belongs_to :file_instance, PhoenixKit.Modules.Storage.FileInstance, references: :uuid belongs_to :bucket, PhoenixKit.Modules.Storage.Bucket, references: :uuid - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/storage/storage.ex b/lib/modules/storage/storage.ex index ff8139ea..f57e2443 100644 --- a/lib/modules/storage/storage.ex +++ b/lib/modules/storage/storage.ex @@ -235,7 +235,7 @@ defmodule PhoenixKit.Modules.Storage do repo().delete_all(Dimension) # Insert default dimensions - now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + now = DateTime.utc_now() default_dimensions = [ # Image dimensions diff --git a/lib/modules/sync/connection.ex b/lib/modules/sync/connection.ex index 7d8c7e81..859fb042 100644 --- a/lib/modules/sync/connection.ex +++ b/lib/modules/sync/connection.ex @@ -81,7 +81,7 @@ defmodule PhoenixKit.Modules.Sync.Connection do field :auto_approve_tables, {:array, :string}, default: [] # Expiration & limits - field :expires_at, :utc_datetime_usec + field :expires_at, :utc_datetime field :max_downloads, :integer field :downloads_used, :integer, default: 0 field :max_records_total, :integer @@ -105,15 +105,15 @@ defmodule PhoenixKit.Modules.Sync.Connection do field :auto_sync_interval_minutes, :integer, default: 60 # Approval & status tracking - field :approved_at, :utc_datetime_usec - field :suspended_at, :utc_datetime_usec + field :approved_at, :utc_datetime + field :suspended_at, :utc_datetime field :suspended_reason, :string - field :revoked_at, :utc_datetime_usec + field :revoked_at, :utc_datetime field :revoked_reason, :string # Audit & statistics - field :last_connected_at, :utc_datetime_usec - field :last_transfer_at, :utc_datetime_usec + field :last_connected_at, :utc_datetime + field :last_transfer_at, :utc_datetime field :total_transfers, :integer, default: 0 field :total_records_transferred, :integer, default: 0 field :total_bytes_transferred, :integer, default: 0 @@ -152,7 +152,7 @@ defmodule PhoenixKit.Modules.Sync.Connection do references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/sync/transfer.ex b/lib/modules/sync/transfer.ex index 0448fe32..e3c4c63c 100644 --- a/lib/modules/sync/transfer.ex +++ b/lib/modules/sync/transfer.ex @@ -89,18 +89,18 @@ defmodule PhoenixKit.Modules.Sync.Transfer do # Status and approval field :status, :string, default: "pending" field :requires_approval, :boolean, default: false - field :approved_at, :utc_datetime_usec - field :denied_at, :utc_datetime_usec + field :approved_at, :utc_datetime + field :denied_at, :utc_datetime field :denial_reason, :string - field :approval_expires_at, :utc_datetime_usec + field :approval_expires_at, :utc_datetime # Request context field :requester_ip, :string field :requester_user_agent, :string field :error_message, :string - field :started_at, :utc_datetime_usec - field :completed_at, :utc_datetime_usec + field :started_at, :utc_datetime + field :completed_at, :utc_datetime field :metadata, :map, default: %{} # legacy @@ -135,7 +135,7 @@ defmodule PhoenixKit.Modules.Sync.Transfer do references: :uuid, type: UUIDv7 - timestamps(type: :utc_datetime_usec, updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ diff --git a/lib/modules/tickets/ticket.ex b/lib/modules/tickets/ticket.ex index c6dc5289..b25d4805 100644 --- a/lib/modules/tickets/ticket.ex +++ b/lib/modules/tickets/ticket.ex @@ -99,8 +99,8 @@ defmodule PhoenixKit.Modules.Tickets.Ticket do [PhoenixKit.Modules.Tickets.TicketAttachment.t()] | Ecto.Association.NotLoaded.t(), status_history: [PhoenixKit.Modules.Tickets.TicketStatusHistory.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_tickets" do @@ -110,8 +110,8 @@ defmodule PhoenixKit.Modules.Tickets.Ticket do field :slug, :string field :comment_count, :integer, default: 0 field :metadata, :map, default: %{} - field :resolved_at, :utc_datetime_usec - field :closed_at, :utc_datetime_usec + field :resolved_at, :utc_datetime + field :closed_at, :utc_datetime belongs_to :user, PhoenixKit.Users.Auth.User, foreign_key: :user_uuid, @@ -132,7 +132,7 @@ defmodule PhoenixKit.Modules.Tickets.Ticket do has_many :status_history, PhoenixKit.Modules.Tickets.TicketStatusHistory, foreign_key: :ticket_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/tickets/ticket_attachment.ex b/lib/modules/tickets/ticket_attachment.ex index 862b3b86..7be9ffc7 100644 --- a/lib/modules/tickets/ticket_attachment.ex +++ b/lib/modules/tickets/ticket_attachment.ex @@ -53,8 +53,8 @@ defmodule PhoenixKit.Modules.Tickets.TicketAttachment do comment: PhoenixKit.Modules.Tickets.TicketComment.t() | Ecto.Association.NotLoaded.t() | nil, file: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_ticket_attachments" do @@ -65,7 +65,7 @@ defmodule PhoenixKit.Modules.Tickets.TicketAttachment do belongs_to :comment, PhoenixKit.Modules.Tickets.TicketComment, references: :uuid belongs_to :file, PhoenixKit.Modules.Storage.File, references: :uuid - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/tickets/ticket_comment.ex b/lib/modules/tickets/ticket_comment.ex index 3e3d0510..a8e83f34 100644 --- a/lib/modules/tickets/ticket_comment.ex +++ b/lib/modules/tickets/ticket_comment.ex @@ -72,8 +72,8 @@ defmodule PhoenixKit.Modules.Tickets.TicketComment do children: [t()] | Ecto.Association.NotLoaded.t(), attachments: [PhoenixKit.Modules.Tickets.TicketAttachment.t()] | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil, - updated_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil, + updated_at: DateTime.t() | nil } schema "phoenix_kit_ticket_comments" do @@ -94,7 +94,7 @@ defmodule PhoenixKit.Modules.Tickets.TicketComment do has_many :children, __MODULE__, foreign_key: :parent_id has_many :attachments, PhoenixKit.Modules.Tickets.TicketAttachment, foreign_key: :comment_id - timestamps(type: :naive_datetime) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/modules/tickets/ticket_status_history.ex b/lib/modules/tickets/ticket_status_history.ex index e829b6f5..bff9c514 100644 --- a/lib/modules/tickets/ticket_status_history.ex +++ b/lib/modules/tickets/ticket_status_history.ex @@ -67,7 +67,7 @@ defmodule PhoenixKit.Modules.Tickets.TicketStatusHistory do reason: String.t() | nil, ticket: PhoenixKit.Modules.Tickets.Ticket.t() | Ecto.Association.NotLoaded.t(), changed_by: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(), - inserted_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil } schema "phoenix_kit_ticket_status_history" do @@ -83,7 +83,7 @@ defmodule PhoenixKit.Modules.Tickets.TicketStatusHistory do field :changed_by_id, :integer - timestamps(type: :naive_datetime, updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ diff --git a/lib/phoenix_kit/audit_log/entry.ex b/lib/phoenix_kit/audit_log/entry.ex index ce062e6f..85928048 100644 --- a/lib/phoenix_kit/audit_log/entry.ex +++ b/lib/phoenix_kit/audit_log/entry.ex @@ -57,7 +57,7 @@ defmodule PhoenixKit.AuditLog.Entry do field :user_agent, :string field :metadata, :map - timestamps(type: :utc_datetime_usec, updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ diff --git a/lib/phoenix_kit/migrations/postgres.ex b/lib/phoenix_kit/migrations/postgres.ex index 4fad99a0..759cc923 100644 --- a/lib/phoenix_kit/migrations/postgres.ex +++ b/lib/phoenix_kit/migrations/postgres.ex @@ -442,12 +442,19 @@ defmodule PhoenixKit.Migrations.Postgres do - All operations idempotent — safe on fresh installs and all upgrade paths - Existing non-NULL UUID values unchanged - ### V57 - UUID FK Column Repair ⚡ LATEST + ### V57 - UUID FK Column Repair - Re-runs idempotent UUID FK column operations from V56 - Fixes missing role_uuid and granted_by_uuid on phoenix_kit_role_permissions - Catches any other UUID FK columns missed when V56 was applied with earlier code - Safe no-op on databases where V56 already created everything correctly + ### V58 - Timestamp Column Type Standardization ⚡ LATEST + - Converts ALL timestamp columns across 68 tables from `timestamp` to `timestamptz` + - Completes DateTime standardization (Elixir `:utc_datetime` + PostgreSQL `timestamptz`) + - No USING clause needed for up (PostgreSQL treats timestamp as UTC implicitly) + - Down uses `USING col AT TIME ZONE 'UTC'` for safe revert to `timestamp(0)` + - Fully idempotent: checks table/column existence and current type before altering + ## Migration Paths ### Fresh Installation (0 → Current) @@ -506,7 +513,7 @@ defmodule PhoenixKit.Migrations.Postgres do use Ecto.Migration @initial_version 1 - @current_version 57 + @current_version 58 @default_prefix "public" @doc false diff --git a/lib/phoenix_kit/migrations/postgres/v58.ex b/lib/phoenix_kit/migrations/postgres/v58.ex new file mode 100644 index 00000000..31fcdb2f --- /dev/null +++ b/lib/phoenix_kit/migrations/postgres/v58.ex @@ -0,0 +1,334 @@ +defmodule PhoenixKit.Migrations.Postgres.V58 do + @moduledoc """ + V58: Timestamp Column Type Standardization (timestamptz) + + Converts ALL timestamp columns across all 68 PhoenixKit tables from + `timestamp without time zone` (aka `timestamp(0)`) to + `timestamp with time zone` (aka `timestamptz`). + + This completes the DateTime standardization: + - Steps 1-4 (prior): Elixir schemas → `:utc_datetime` / `DateTime.utc_now()` + - V58: PostgreSQL columns → `timestamptz` + + ## Why timestamptz? + + PostgreSQL `timestamp` stores values without timezone context. While PhoenixKit + always stores UTC, `timestamptz` makes this explicit at the database level and + is the PostgreSQL-recommended type for timestamps. Ecto's `:utc_datetime` type + maps to `timestamptz` natively. + + ## Conversion Safety + + The `up` direction needs no `USING` clause — PostgreSQL implicitly treats + existing `timestamp` values as UTC when casting to `timestamptz`. + + The `down` direction requires `USING col AT TIME ZONE 'UTC'` to explicitly + convert `timestamptz` back to `timestamp(0)`. + + ## Idempotency + + All operations check: + - `table_exists?/2` — skip if table doesn't exist (optional modules) + - `column_exists?/3` — skip if column doesn't exist + - `column_is_timestamptz?/3` — skip if already converted (up) or already reverted (down) + """ + + use Ecto.Migration + + # All PhoenixKit tables and their timestamp columns (68 tables, ~193 columns) + @timestamp_columns [ + # V01 — Core Auth + {"phoenix_kit", ["migrated_at"]}, + {"phoenix_kit_users", ["confirmed_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_users_tokens", ["inserted_at"]}, + {"phoenix_kit_user_roles", ["inserted_at", "updated_at"]}, + {"phoenix_kit_user_role_assignments", ["assigned_at", "inserted_at"]}, + + # V03 — Settings + {"phoenix_kit_settings", ["date_added", "date_updated"]}, + + # V04 — Referrals + {"phoenix_kit_referral_codes", ["date_created", "expiration_date"]}, + {"phoenix_kit_referral_code_usage", ["date_used"]}, + + # V07+V13+V19 — Email Logs (merged) + {"phoenix_kit_email_logs", + [ + "sent_at", + "delivered_at", + "bounced_at", + "complained_at", + "opened_at", + "clicked_at", + "queued_at", + "rejected_at", + "failed_at", + "delayed_at", + "inserted_at", + "updated_at" + ]}, + {"phoenix_kit_email_events", ["occurred_at", "inserted_at", "updated_at"]}, + + # V09 — Email Blocklist + {"phoenix_kit_email_blocklist", ["expires_at", "inserted_at", "updated_at"]}, + + # V15 — Email Templates + {"phoenix_kit_email_templates", ["last_used_at", "inserted_at", "updated_at"]}, + + # V16 — OAuth + {"phoenix_kit_user_oauth_providers", ["token_expires_at", "inserted_at", "updated_at"]}, + + # V17 — Entities + {"phoenix_kit_entities", ["date_created", "date_updated"]}, + {"phoenix_kit_entity_data", ["date_created", "date_updated"]}, + + # V20 — Storage + {"phoenix_kit_buckets", ["inserted_at", "updated_at"]}, + {"phoenix_kit_files", ["inserted_at", "updated_at"]}, + {"phoenix_kit_file_instances", ["inserted_at", "updated_at"]}, + {"phoenix_kit_file_locations", ["last_verified_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_storage_dimensions", ["inserted_at", "updated_at"]}, + + # V22 — Email Extensions + {"phoenix_kit_email_orphaned_events", + ["received_at", "matched_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_email_metrics", ["inserted_at", "updated_at"]}, + {"phoenix_kit_audit_logs", ["inserted_at"]}, + + # V29 — Posts + {"phoenix_kit_posts", ["scheduled_at", "published_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_post_media", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_likes", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_comments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_mentions", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_tags", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_tag_assignments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_groups", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_group_assignments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_post_views", ["viewed_at", "inserted_at", "updated_at"]}, + + # V31 — Billing Core + {"phoenix_kit_currencies", ["inserted_at", "updated_at"]}, + {"phoenix_kit_billing_profiles", ["inserted_at", "updated_at"]}, + {"phoenix_kit_orders", + [ + "confirmed_at", + "paid_at", + "cancelled_at", + "checkout_expires_at", + "inserted_at", + "updated_at" + ]}, + {"phoenix_kit_invoices", + [ + "receipt_generated_at", + "sent_at", + "paid_at", + "voided_at", + "inserted_at", + "updated_at" + ]}, + {"phoenix_kit_transactions", ["inserted_at", "updated_at"]}, + + # V32 — AI Core + {"phoenix_kit_ai_accounts", ["last_validated_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_ai_requests", ["inserted_at", "updated_at"]}, + + # V33 — Billing Extended + {"phoenix_kit_payment_methods", ["inserted_at", "updated_at"]}, + {"phoenix_kit_subscription_plans", ["inserted_at", "updated_at"]}, + {"phoenix_kit_subscriptions", + [ + "current_period_start", + "current_period_end", + "cancelled_at", + "trial_start", + "trial_end", + "grace_period_end", + "last_renewal_attempt_at", + "inserted_at", + "updated_at" + ]}, + {"phoenix_kit_payment_provider_configs", ["last_verified_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_webhook_events", ["processed_at", "inserted_at", "updated_at"]}, + + # V34 — AI Endpoints + {"phoenix_kit_ai_endpoints", ["last_validated_at", "inserted_at", "updated_at"]}, + + # V35 — Tickets + {"phoenix_kit_tickets", ["resolved_at", "closed_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_ticket_comments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_ticket_attachments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_ticket_status_history", ["inserted_at"]}, + + # V36 — Social/Connections + {"phoenix_kit_user_follows", ["inserted_at"]}, + {"phoenix_kit_user_connections", + ["requested_at", "responded_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_user_blocks", ["inserted_at"]}, + {"phoenix_kit_user_follows_history", ["inserted_at"]}, + {"phoenix_kit_user_connections_history", ["inserted_at"]}, + {"phoenix_kit_user_blocks_history", ["inserted_at"]}, + + # V37 — Sync (renamed in V44) + {"phoenix_kit_sync_connections", + [ + "expires_at", + "approved_at", + "suspended_at", + "revoked_at", + "last_connected_at", + "last_transfer_at", + "inserted_at", + "updated_at" + ]}, + {"phoenix_kit_sync_transfers", + [ + "approved_at", + "denied_at", + "approval_expires_at", + "started_at", + "completed_at", + "inserted_at" + ]}, + + # V38 — AI Prompts + {"phoenix_kit_ai_prompts", ["last_used_at", "inserted_at", "updated_at"]}, + + # V39 — Admin Notes + {"phoenix_kit_admin_notes", ["inserted_at", "updated_at"]}, + + # V42 — Scheduled Jobs + {"phoenix_kit_scheduled_jobs", ["scheduled_at", "executed_at", "inserted_at", "updated_at"]}, + + # V43 — Legal/Consent + {"phoenix_kit_consent_logs", ["inserted_at", "updated_at"]}, + + # V45 — Shop + {"phoenix_kit_shop_categories", ["inserted_at", "updated_at"]}, + {"phoenix_kit_shop_products", ["inserted_at", "updated_at"]}, + {"phoenix_kit_shop_shipping_methods", ["inserted_at", "updated_at"]}, + {"phoenix_kit_shop_carts", ["expires_at", "converted_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_shop_cart_items", ["inserted_at", "updated_at"]}, + {"phoenix_kit_payment_options", ["inserted_at", "updated_at"]}, + + # V46 — Shop Config/Import + {"phoenix_kit_shop_config", ["inserted_at", "updated_at"]}, + {"phoenix_kit_shop_import_logs", ["started_at", "completed_at", "inserted_at", "updated_at"]}, + {"phoenix_kit_shop_import_configs", ["inserted_at", "updated_at"]}, + + # V48 — Post Reactions + {"phoenix_kit_post_dislikes", ["inserted_at", "updated_at"]}, + {"phoenix_kit_comment_likes", ["inserted_at", "updated_at"]}, + {"phoenix_kit_comment_dislikes", ["inserted_at", "updated_at"]}, + + # V53 — Role Permissions + {"phoenix_kit_role_permissions", ["inserted_at"]}, + + # V55 — Standalone Comments + {"phoenix_kit_comments", ["inserted_at", "updated_at"]}, + {"phoenix_kit_comments_likes", ["inserted_at", "updated_at"]}, + {"phoenix_kit_comments_dislikes", ["inserted_at", "updated_at"]} + ] + + def up(%{prefix: prefix} = opts) do + escaped_prefix = Map.get(opts, :escaped_prefix, prefix) + + Enum.each(@timestamp_columns, fn {table, columns} -> + if table_exists?(table, escaped_prefix) do + convert_columns_to_timestamptz(table, columns, prefix, escaped_prefix) + end + end) + + execute("COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '58'") + end + + def down(%{prefix: prefix} = opts) do + escaped_prefix = Map.get(opts, :escaped_prefix, prefix) + + Enum.each(@timestamp_columns, fn {table, columns} -> + if table_exists?(table, escaped_prefix) do + revert_columns_from_timestamptz(table, columns, prefix, escaped_prefix) + end + end) + + execute("COMMENT ON TABLE #{prefix_table_name("phoenix_kit", prefix)} IS '57'") + end + + defp convert_columns_to_timestamptz(table, columns, prefix, escaped_prefix) do + full_table = prefix_table_name(table, prefix) + + Enum.each(columns, fn col -> + if column_exists?(table, col, escaped_prefix) and + not column_is_timestamptz?(table, col, escaped_prefix) do + execute("ALTER TABLE #{full_table} ALTER COLUMN #{col} TYPE timestamptz") + end + end) + end + + defp revert_columns_from_timestamptz(table, columns, prefix, escaped_prefix) do + full_table = prefix_table_name(table, prefix) + + Enum.each(columns, fn col -> + if column_exists?(table, col, escaped_prefix) and + column_is_timestamptz?(table, col, escaped_prefix) do + execute( + "ALTER TABLE #{full_table} ALTER COLUMN #{col} " <> + "TYPE timestamp(0) USING #{col} AT TIME ZONE 'UTC'" + ) + end + end) + end + + # Helpers + + defp table_exists?(table, escaped_prefix) do + query = """ + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_name = '#{table}' + AND table_schema = '#{escaped_prefix}' + ) + """ + + case repo().query(query, [], log: false) do + {:ok, %{rows: [[true]]}} -> true + _ -> false + end + end + + defp column_exists?(table, column, escaped_prefix) do + query = """ + SELECT EXISTS ( + SELECT FROM information_schema.columns + WHERE table_name = '#{table}' + AND column_name = '#{column}' + AND table_schema = '#{escaped_prefix}' + ) + """ + + case repo().query(query, [], log: false) do + {:ok, %{rows: [[true]]}} -> true + _ -> false + end + end + + defp column_is_timestamptz?(table, column, escaped_prefix) do + query = """ + SELECT data_type = 'timestamp with time zone' + FROM information_schema.columns + WHERE table_name = '#{table}' + AND column_name = '#{column}' + AND table_schema = '#{escaped_prefix}' + """ + + case repo().query(query, [], log: false) do + {:ok, %{rows: [[true]]}} -> true + _ -> false + end + end + + defp prefix_table_name(table_name, nil), do: table_name + defp prefix_table_name(table_name, "public"), do: "public.#{table_name}" + defp prefix_table_name(table_name, prefix), do: "#{prefix}.#{table_name}" +end diff --git a/lib/phoenix_kit/migrations/uuid_fk_columns.ex b/lib/phoenix_kit/migrations/uuid_fk_columns.ex index 8772d9ad..e57bf7fd 100644 --- a/lib/phoenix_kit/migrations/uuid_fk_columns.ex +++ b/lib/phoenix_kit/migrations/uuid_fk_columns.ex @@ -631,7 +631,14 @@ defmodule PhoenixKit.Migrations.UUIDFKColumns do column_exists?(table_str, uuid_fk, escaped_prefix) do table_name = prefix_table_name(table_str, prefix) - # SET NOT NULL is idempotent in PostgreSQL (no-op if already NOT NULL) + # Backfill any remaining NULLs — handles orphaned integer FK references + # (e.g. created_by references a deleted user with no CASCADE constraint) + execute(""" + UPDATE #{table_name} + SET #{uuid_fk} = uuid_generate_v7() + WHERE #{uuid_fk} IS NULL + """) + execute(""" ALTER TABLE #{table_name} ALTER COLUMN #{uuid_fk} SET NOT NULL diff --git a/lib/phoenix_kit/scheduled_jobs/scheduled_job.ex b/lib/phoenix_kit/scheduled_jobs/scheduled_job.ex index e22cc85b..ef6e77ff 100644 --- a/lib/phoenix_kit/scheduled_jobs/scheduled_job.ex +++ b/lib/phoenix_kit/scheduled_jobs/scheduled_job.ex @@ -35,8 +35,8 @@ defmodule PhoenixKit.ScheduledJobs.ScheduledJob do field :handler_module, :string field :resource_type, :string field :resource_id, :binary_id - field :scheduled_at, :utc_datetime_usec - field :executed_at, :utc_datetime_usec + field :scheduled_at, :utc_datetime + field :executed_at, :utc_datetime field :status, :string, default: "pending" field :attempts, :integer, default: 0 field :max_attempts, :integer, default: 3 @@ -45,7 +45,7 @@ defmodule PhoenixKit.ScheduledJobs.ScheduledJob do field :priority, :integer, default: 0 field :created_by_id, :integer - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/phoenix_kit/settings/setting.ex b/lib/phoenix_kit/settings/setting.ex index ea3704fb..f9f69d3c 100644 --- a/lib/phoenix_kit/settings/setting.ex +++ b/lib/phoenix_kit/settings/setting.ex @@ -88,8 +88,8 @@ defmodule PhoenixKit.Settings.Setting do field :value, :string field :value_json, :map field :module, :string - field :date_added, :utc_datetime_usec - field :date_updated, :utc_datetime_usec + field :date_added, :utc_datetime + field :date_updated, :utc_datetime end @doc """ diff --git a/lib/phoenix_kit/users/admin_note.ex b/lib/phoenix_kit/users/admin_note.ex index b44a1552..0f04dbd1 100644 --- a/lib/phoenix_kit/users/admin_note.ex +++ b/lib/phoenix_kit/users/admin_note.ex @@ -31,8 +31,8 @@ defmodule PhoenixKit.Users.AdminNote do author_id: integer(), author_uuid: UUIDv7.t() | nil, content: String.t(), - inserted_at: NaiveDateTime.t(), - updated_at: NaiveDateTime.t() + inserted_at: DateTime.t(), + updated_at: DateTime.t() } @primary_key {:uuid, UUIDv7, autogenerate: true} @@ -55,7 +55,7 @@ defmodule PhoenixKit.Users.AdminNote do field :content, :string - timestamps() + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/phoenix_kit/users/auth/user.ex b/lib/phoenix_kit/users/auth/user.ex index 1752c16b..9bbb2714 100644 --- a/lib/phoenix_kit/users/auth/user.ex +++ b/lib/phoenix_kit/users/auth/user.ex @@ -40,15 +40,15 @@ defmodule PhoenixKit.Users.Auth.User do first_name: String.t() | nil, last_name: String.t() | nil, is_active: boolean(), - confirmed_at: NaiveDateTime.t() | nil, + confirmed_at: DateTime.t() | nil, user_timezone: String.t() | nil, registration_ip: String.t() | nil, registration_country: String.t() | nil, registration_region: String.t() | nil, registration_city: String.t() | nil, custom_fields: map() | nil, - inserted_at: NaiveDateTime.t(), - updated_at: NaiveDateTime.t() + inserted_at: DateTime.t(), + updated_at: DateTime.t() } @primary_key {:uuid, UUIDv7, autogenerate: true} @@ -63,7 +63,7 @@ defmodule PhoenixKit.Users.Auth.User do field :first_name, :string field :last_name, :string field :is_active, :boolean, default: true - field :confirmed_at, :naive_datetime + field :confirmed_at, :utc_datetime field :user_timezone, :string field :registration_ip, :string field :registration_country, :string @@ -79,7 +79,7 @@ defmodule PhoenixKit.Users.Auth.User do join_through: PhoenixKit.Users.RoleAssignment, join_keys: [user_uuid: :uuid, role_uuid: :uuid] - timestamps() + timestamps(type: :utc_datetime) end @doc """ @@ -319,7 +319,7 @@ defmodule PhoenixKit.Users.Auth.User do Confirms the account by setting `confirmed_at`. """ def confirm_changeset(user) do - now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + now = DateTime.utc_now() change(user, confirmed_at: now) end diff --git a/lib/phoenix_kit/users/auth/user_token.ex b/lib/phoenix_kit/users/auth/user_token.ex index e1ec1db8..15031573 100644 --- a/lib/phoenix_kit/users/auth/user_token.ex +++ b/lib/phoenix_kit/users/auth/user_token.ex @@ -54,7 +54,7 @@ defmodule PhoenixKit.Users.Auth.UserToken do references: :uuid, type: UUIDv7 - timestamps(updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ diff --git a/lib/phoenix_kit/users/magic_link_registration.ex b/lib/phoenix_kit/users/magic_link_registration.ex index b401a1c2..5c697d17 100644 --- a/lib/phoenix_kit/users/magic_link_registration.ex +++ b/lib/phoenix_kit/users/magic_link_registration.ex @@ -163,7 +163,7 @@ defmodule PhoenixKit.Users.MagicLinkRegistration do attrs = attrs |> Map.put("email", email) - |> Map.put("confirmed_at", NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)) + |> Map.put("confirmed_at", DateTime.utc_now()) track_geolocation = Settings.get_boolean_setting("track_registration_geolocation", false) diff --git a/lib/phoenix_kit/users/oauth_provider.ex b/lib/phoenix_kit/users/oauth_provider.ex index 4e099f6c..73b7a309 100644 --- a/lib/phoenix_kit/users/oauth_provider.ex +++ b/lib/phoenix_kit/users/oauth_provider.ex @@ -25,10 +25,10 @@ defmodule PhoenixKit.Users.OAuthProvider do field :provider_email, :string field :access_token, :string field :refresh_token, :string - field :token_expires_at, :utc_datetime_usec + field :token_expires_at, :utc_datetime field :raw_data, :map, default: %{} - timestamps(type: :utc_datetime_usec) + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/phoenix_kit/users/permissions.ex b/lib/phoenix_kit/users/permissions.ex index acdd4aaa..e0d0e627 100644 --- a/lib/phoenix_kit/users/permissions.ex +++ b/lib/phoenix_kit/users/permissions.ex @@ -735,7 +735,7 @@ defmodule PhoenixKit.Users.Permissions do # Bulk insert new permissions if MapSet.size(to_add) > 0 do - now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + now = DateTime.utc_now() granted_by_int = resolve_user_id(granted_by_id) granted_by_uuid = resolve_user_uuid(granted_by_id) diff --git a/lib/phoenix_kit/users/role.ex b/lib/phoenix_kit/users/role.ex index 45f30adb..c6a3a77b 100644 --- a/lib/phoenix_kit/users/role.ex +++ b/lib/phoenix_kit/users/role.ex @@ -34,8 +34,8 @@ defmodule PhoenixKit.Users.Role do name: String.t(), description: String.t() | nil, is_system_role: boolean(), - inserted_at: NaiveDateTime.t(), - updated_at: NaiveDateTime.t() + inserted_at: DateTime.t(), + updated_at: DateTime.t() } @roles %{ @@ -60,7 +60,7 @@ defmodule PhoenixKit.Users.Role do join_through: PhoenixKit.Users.RoleAssignment, join_keys: [role_uuid: :uuid, user_uuid: :uuid] - timestamps() + timestamps(type: :utc_datetime) end @doc """ diff --git a/lib/phoenix_kit/users/role_assignment.ex b/lib/phoenix_kit/users/role_assignment.ex index 49f75eea..3ba07e9d 100644 --- a/lib/phoenix_kit/users/role_assignment.ex +++ b/lib/phoenix_kit/users/role_assignment.ex @@ -28,8 +28,8 @@ defmodule PhoenixKit.Users.RoleAssignment do user_uuid: UUIDv7.t() | nil, role_uuid: UUIDv7.t() | nil, assigned_by_uuid: UUIDv7.t() | nil, - assigned_at: NaiveDateTime.t(), - inserted_at: NaiveDateTime.t() + assigned_at: DateTime.t(), + inserted_at: DateTime.t() } @primary_key {:uuid, UUIDv7, autogenerate: true} @@ -52,9 +52,9 @@ defmodule PhoenixKit.Users.RoleAssignment do references: :uuid, type: UUIDv7 - field :assigned_at, :naive_datetime + field :assigned_at, :utc_datetime - timestamps(updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ @@ -99,7 +99,7 @@ defmodule PhoenixKit.Users.RoleAssignment do put_change( changeset, :assigned_at, - NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) + DateTime.utc_now() ) _ -> diff --git a/lib/phoenix_kit/users/role_permission.ex b/lib/phoenix_kit/users/role_permission.ex index b8cf7ccb..2f2440b6 100644 --- a/lib/phoenix_kit/users/role_permission.ex +++ b/lib/phoenix_kit/users/role_permission.ex @@ -27,7 +27,7 @@ defmodule PhoenixKit.Users.RolePermission do module_key: String.t(), granted_by: integer() | nil, granted_by_uuid: UUIDv7.t() | nil, - inserted_at: NaiveDateTime.t() | nil + inserted_at: DateTime.t() | nil } @primary_key {:uuid, UUIDv7, autogenerate: true} @@ -41,7 +41,7 @@ defmodule PhoenixKit.Users.RolePermission do field :role_id, :integer belongs_to :role, Role, foreign_key: :role_uuid, references: :uuid, type: UUIDv7 - timestamps(updated_at: false) + timestamps(type: :utc_datetime, updated_at: false) end @doc """ diff --git a/lib/phoenix_kit/users/roles.ex b/lib/phoenix_kit/users/roles.ex index 8d3c4f51..3953cce4 100644 --- a/lib/phoenix_kit/users/roles.ex +++ b/lib/phoenix_kit/users/roles.ex @@ -799,7 +799,7 @@ defmodule PhoenixKit.Users.Roles do # Add confirmed_at timestamp if user email is not confirmed defp maybe_add_confirmed_at(changes, nil) do - Map.put(changes, :confirmed_at, NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)) + Map.put(changes, :confirmed_at, DateTime.utc_now()) end defp maybe_add_confirmed_at(changes, _confirmed_at), do: changes diff --git a/lib/phoenix_kit/users/sessions.ex b/lib/phoenix_kit/users/sessions.ex index be808c89..16ddfe99 100644 --- a/lib/phoenix_kit/users/sessions.ex +++ b/lib/phoenix_kit/users/sessions.ex @@ -231,7 +231,7 @@ defmodule PhoenixKit.Users.Sessions do """ def get_session_stats do - now = NaiveDateTime.utc_now() + now = DateTime.utc_now() today_start = %{now | hour: 0, minute: 0, second: 0, microsecond: {0, 0}} # Active sessions @@ -287,10 +287,10 @@ defmodule PhoenixKit.Users.Sessions do end defp calculate_age_in_days(created_at) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), created_at, :day) + DateTime.diff(DateTime.utc_now(), created_at, :day) end defp session_expired?(created_at) do - NaiveDateTime.diff(NaiveDateTime.utc_now(), created_at, :day) >= @session_validity_in_days + DateTime.diff(DateTime.utc_now(), created_at, :day) >= @session_validity_in_days end end