Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
- New `Multilang` module with pure-function helpers for multilang JSONB data
- Language tabs in entity form, data form, and data view (adaptive compact mode for >5 languages)
- Override-only storage for secondary languages with ghost-text placeholders
- Lazy re-keying when global primary language changes
- Lazy re-keying when global primary language changes (recomputes all secondary overrides)
- Translation convenience API: `Entities.set_entity_translation/3`, `EntityData.set_translation/3`, `EntityData.set_title_translation/3`, and related get/remove functions
- Multilang-aware category extraction in data navigator and entity data
- Non-translatable fields (slug, status) separated into their own card
- Required field indicators hidden on secondary language tabs
- Title translations stored as `_title` in JSONB data column (unified with other field translations)
- Slug generation disabled on secondary language tabs
- Validation error messages wrapped in gettext for i18n

## 1.7.42 - 2026-02-17
- Use PostgreSQL IF NOT EXISTS / IF EXISTS for UUID column operations
Expand Down
196 changes: 98 additions & 98 deletions lib/modules/entities/DEEP_DIVE.md

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions lib/modules/entities/OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ PhoenixKit's Entities layer is a dynamic content type engine. It lets administra
## High-level capabilities

- **Entity blueprints** – Define reusable content types (`phoenix_kit_entities`) with metadata, singular/plural labels, icon, status, JSON field schema, and optional custom settings.
- **Dynamic fields** – 11 built-in field types (text, textarea, number, boolean, date, email, URL, select, radio, checkbox, rich text). Field definitions live in JSONB and are validated at creation time. *(Note: image, file, and relation fields are defined but not yet fully implemented—UI shows "coming soon" placeholders.)*
- **Dynamic fields** – 12 built-in field types (text, textarea, number, boolean, date, email, URL, select, radio, checkbox, rich text, file). Field definitions live in JSONB and are validated at creation time. *(Note: image and relation fields are defined but not yet fully implemented—UI shows "coming soon" placeholders.)*
- **Entity data records** – Store instances of an entity (`phoenix_kit_entity_data`) with slug support, status workflow (draft/published/archived), JSONB data payload, metadata, creator tracking, and timestamps.
- **Admin UI** – LiveView dashboards for managing blueprints, browsing/creating data, filtering, and adjusting module settings.
- **Settings + security** – Feature toggle and max entities per user are enforced; additional settings (relation/file flags, auto slugging, etc.) are persisted in `phoenix_kit_settings` but reserved for future use. All surfaces are gated behind the admin scope.
Expand Down Expand Up @@ -80,7 +80,7 @@ Indexes cover `name`, `status`, `created_by`. A comment block documents JSON col
- `slug` – optional unique slug per entity
- `status` – `draft | published | archived`
- `data` – JSONB map keyed by field definition (or multilang structure, see below)
- `metadata` – optional JSONB extras (includes title translations when multilang enabled)
- `metadata` – optional JSONB extras (tags, categories, etc.)
- `created_by` – admin user id
- `date_created`, `date_updated`

Expand All @@ -96,7 +96,7 @@ Indexes cover `entity_id`, `slug`, `status`, `created_by`, `title`. FK cascades

## Core modules

### `PhoenixKit.Entities`
### `PhoenixKit.Modules.Entities`
Responsible for entity blueprints:
- Schema + changeset enforcing unique names, valid field definitions, timestamps, etc.
- CRUD helpers (`list_entities/0`, `get_entity!/1`, `get_entity/1`, `get_entity_by_name/1`, `create_entity/1`, `update_entity/2`, `delete_entity/1`, `change_entity/2`).
Expand All @@ -108,15 +108,15 @@ Note: `create_entity/1` auto-fills `created_by` with the first admin user if not

Field validation pipeline ensures every entry in `fields_definition` has `type/key/label` and uses a supported type. Note: the changeset validates but does not enrich field definitions—use `FieldTypes.new_field/4` to apply default properties.

### `PhoenixKit.Entities.EntityData`
### `PhoenixKit.Modules.Entities.EntityData`
Manages actual records:
- Schema + changeset verifying required fields, slug format, status, and cross-checking submitted JSON against the entity definition.
- CRUD and query helpers (`list_all/0`, `list_by_entity/1`, `get!/1`, `get/1`, `search_by_title/2`, `create/1`, `update/2`, `delete/1`, `change/2`).
- Field-level validation ensures required fields are present, numbers are numeric, booleans are booleans, options exist, etc.

Note: `create/1` auto-fills `created_by` with the first admin user if not provided.

### `PhoenixKit.Entities.FieldTypes`
### `PhoenixKit.Modules.Entities.FieldTypes`
Registry of supported field types with metadata:
- `all/0`, `list_types/0`, `for_picker/0` – introspection for UI builders.
- Category helpers, default properties, and `validate_field/1` to ensure field definitions are complete.
Expand All @@ -126,15 +126,15 @@ Registry of supported field types with metadata:
- `text_field/3`, `textarea_field/3`, `email_field/3`, `number_field/3`, `boolean_field/3`, `rich_text_field/3` – Common field types
- Used both when saving entity definitions and when rendering forms.

### `PhoenixKit.Entities.Multilang`
### `PhoenixKit.Modules.Entities.Multilang`
Pure-function module for multi-language data transformations. No database calls — used by LiveViews and the convenience API.
- Global helpers: `enabled?/0`, `primary_language/0`, `enabled_languages/0`.
- Data reading: `get_language_data/2`, `get_primary_data/1`, `get_raw_language_data/2`, `multilang_data?/1`.
- Data writing: `put_language_data/3`, `migrate_to_multilang/2`, `flatten_to_primary/1`.
- Re-keying: `rekey_primary/2`, `maybe_rekey_data/1` — handles primary language changes.
- UI: `build_language_tabs/0` — builds tab data for language switcher UI.

### `PhoenixKit.Entities.FormBuilder`
### `PhoenixKit.Modules.Entities.FormBuilder`
- Renders form inputs dynamically based on field definitions (`build_fields/3`, `build_field/3`).
- Provides `validate_data/2` and lower-level helpers to check payloads before they reach `EntityData.changeset/2`.
- Language-aware: accepts `lang_code` option to render fields for a specific language, with ghost-text placeholders showing primary language values on secondary tabs.
Expand Down Expand Up @@ -204,7 +204,7 @@ Each field definition is a map like:

> **Note**: Settings marked "Not yet enforced" are persisted in the database and visible in the admin UI, but the underlying functionality is not yet implemented. They are placeholders for future features.

`PhoenixKit.Entities.get_config/0` returns a map:
`PhoenixKit.Modules.Entities.get_config/0` returns a map:
```elixir
%{
enabled: boolean,
Expand All @@ -222,16 +222,16 @@ Each field definition is a map like:

### Enabling the module
```elixir
{:ok, _setting} = PhoenixKit.Entities.enable_system()
PhoenixKit.Entities.enabled?()
{:ok, _setting} = PhoenixKit.Modules.Entities.enable_system()
PhoenixKit.Modules.Entities.enabled?()
# => true/false
```

### Creating an entity blueprint
```elixir
# Note: created_by is optional - auto-fills with first admin user if omitted
{:ok, blog_entity} =
PhoenixKit.Entities.create_entity(%{
PhoenixKit.Modules.Entities.create_entity(%{
name: "blog_post",
display_name: "Blog Post",
display_name_plural: "Blog Posts",
Expand All @@ -246,7 +246,7 @@ PhoenixKit.Entities.enabled?()

### Creating fields with builder helpers
```elixir
alias PhoenixKit.Entities.FieldTypes
alias PhoenixKit.Modules.Entities.FieldTypes

# Build fields programmatically
fields = [
Expand All @@ -257,7 +257,7 @@ fields = [
FieldTypes.boolean_field("featured", "Featured Post", default: false)
]

{:ok, entity} = PhoenixKit.Entities.create_entity(%{
{:ok, entity} = PhoenixKit.Modules.Entities.create_entity(%{
name: "article",
display_name: "Article",
fields_definition: fields
Expand All @@ -268,7 +268,7 @@ fields = [
```elixir
# Note: created_by is optional - auto-fills with first admin user if omitted
{:ok, _record} =
PhoenixKit.Entities.EntityData.create(%{
PhoenixKit.Modules.Entities.EntityData.create(%{
entity_id: blog_entity.id,
title: "My First Post",
status: "published",
Expand All @@ -279,13 +279,13 @@ fields = [

### Counting statistics
```elixir
PhoenixKit.Entities.get_system_stats()
PhoenixKit.Modules.Entities.get_system_stats()
# => %{total_entities: 5, active_entities: 4, total_data_records: 23}
```

### Enforcing limits
```elixir
PhoenixKit.Entities.validate_user_entity_limit(admin.id)
PhoenixKit.Modules.Entities.validate_user_entity_limit(admin.id)
# {:ok, :valid} or {:error, "You have reached the maximum limit of 100 entities"}
```

Expand Down Expand Up @@ -315,15 +315,15 @@ When the **Languages module** is enabled with 2+ languages, all entities automat
- Primary language stores ALL fields
- Secondary languages store ONLY overrides (fields that differ from primary)
- Display merges: `Map.merge(primary_data, language_overrides)`
- `title` and `slug` DB columns remain primary-language-only; secondary title translations are in `metadata["translations"]`
- `title` and `slug` DB columns remain primary-language-only; secondary title translations are stored as `_title` overrides in the JSONB `data` column alongside other fields
- Entity definition translations (display_name, etc.) are in `entity.settings["translations"]`

### Translation Storage Summary

| What | Primary language | Secondary languages |
|------|-----------------|---------------------|
| Entity data (custom fields) | `data["en-US"]` | `data["es-ES"]` (overrides only) |
| Record title | `title` column | `metadata["translations"]["es-ES"]["title"]` |
| Record title | `title` column + `data[primary]["_title"]` | `data["es-ES"]["_title"]` (overrides) |
| Entity display_name | `display_name` column | `settings["translations"]["es-ES"]["display_name"]` |

### Enabling Multilang
Expand Down Expand Up @@ -389,7 +389,7 @@ When the global primary language changes (via Languages admin), existing records
1. User opens an existing record for editing
2. System detects embedded `_primary_language` differs from global primary
3. The new primary is promoted to have all fields (missing fields filled from old primary)
4. Title is swapped between the column and metadata translations
4. All secondary languages are recomputed against the new primary; `_title` is re-keyed with other fields
5. Changes persist when the user saves

Records that are never edited continue to work — read paths use the embedded primary for correct display.
Expand Down
10 changes: 5 additions & 5 deletions lib/modules/entities/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The Entities module delivers PhoenixKit's dynamic content type system. It allows administrators
to design structured content types with custom fields without writing migrations or code. This README gives a quick orientation for contributors working on the LiveView
layer; the business logic lives in the `PhoenixKit.Entities` context.
layer; the business logic lives in the `PhoenixKit.Modules.Entities` context.

## LiveViews & Components

Expand All @@ -23,13 +23,13 @@ All templates follow Phoenix 1.8 layout conventions (`<Layouts.app ...>` with `@
- **Data Navigator** – Browse, search, and filter entity data with status filters and archive/restore workflow.
- **Collaborative Editing** – Presence helpers in entity_form and data_form prevent overwrites when multiple admins edit the same record.
- **Settings Guardrails** – Module can be toggled on/off via PhoenixKit Settings (`entities_enabled`).
- **Event Broadcasting** – Hooks integrate with `PhoenixKit.Entities.Events` for lifecycle tracking.
- **Event Broadcasting** – Hooks integrate with `PhoenixKit.Modules.Entities.Events` for lifecycle tracking.

## Integration Points

- Context modules: `PhoenixKit.Entities`, `PhoenixKit.Entities.EntityData`, `PhoenixKit.Entities.FieldTypes`.
- Multilang module: `PhoenixKit.Entities.Multilang` – pure-function helpers for multilang JSONB.
- Supporting modules: `PhoenixKit.Entities.Events`, `PhoenixKit.Entities.PresenceHelpers`.
- Context modules: `PhoenixKit.Modules.Entities`, `PhoenixKit.Modules.Entities.EntityData`, `PhoenixKit.Modules.Entities.FieldTypes`.
- Multilang module: `PhoenixKit.Modules.Entities.Multilang` – pure-function helpers for multilang JSONB.
- Supporting modules: `PhoenixKit.Modules.Entities.Events`, `PhoenixKit.Modules.Entities.PresenceHelpers`.
- Languages integration: multilang is auto-enabled when `PhoenixKit.Modules.Languages` has 2+ enabled languages.
- Enabling flag: `PhoenixKit.Settings.get_setting("entities_enabled", "false")`.
- Router: available under `{prefix}/admin/entities/*` via `phoenix_kit_routes()`.
Expand Down
Loading
Loading