open-loam 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -8
- data/LICENSE +1 -1
- data/README.md +24 -24
- data/app/jobs/{loam → open_loam}/custom_field_reindex_job.rb +6 -6
- data/app/jobs/{loam → open_loam}/event_delivery_job.rb +8 -8
- data/app/jobs/{loam → open_loam}/event_redelivery_sweep_job.rb +4 -4
- data/app/jobs/{loam → open_loam}/webhook_delivery_job.rb +7 -7
- data/app/models/{loam → open_loam}/api_token.rb +7 -7
- data/app/models/{loam → open_loam}/audit_record.rb +3 -3
- data/app/models/{loam → open_loam}/auth_attempt.rb +5 -5
- data/app/models/{loam → open_loam}/business_rule.rb +10 -10
- data/app/models/{loam → open_loam}/business_rule_run.rb +5 -5
- data/app/models/{loam → open_loam}/comment.rb +9 -9
- data/app/models/{loam → open_loam}/config.rb +7 -7
- data/app/models/{loam → open_loam}/custom_field_value.rb +5 -5
- data/app/models/{loam → open_loam}/dashboard_widget.rb +5 -5
- data/app/models/open_loam/dictionary.rb +20 -0
- data/app/models/{loam → open_loam}/dictionary_entry.rb +7 -7
- data/app/models/{loam → open_loam}/event_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/field_definition.rb +8 -8
- data/app/models/{loam → open_loam}/inbound_webhook_delivery.rb +4 -4
- data/app/models/{loam → open_loam}/inbound_webhook_source.rb +13 -13
- data/app/models/{loam → open_loam}/membership.rb +6 -6
- data/app/models/{loam → open_loam}/mfa_credential.rb +8 -8
- data/app/models/{loam → open_loam}/notification.rb +10 -10
- data/app/models/{loam → open_loam}/pending_action.rb +23 -23
- data/app/models/{loam → open_loam}/perspective.rb +5 -5
- data/app/models/{loam → open_loam}/progress_job.rb +7 -7
- data/app/models/{loam → open_loam}/record_lock.rb +5 -5
- data/app/models/{loam → open_loam}/scheduled_job.rb +7 -7
- data/app/models/open_loam/search_token.rb +9 -0
- data/app/models/{loam → open_loam}/sso_identity.rb +4 -4
- data/app/models/{loam → open_loam}/sso_provider.rb +8 -8
- data/app/models/open_loam/tenant.rb +20 -0
- data/app/models/{loam → open_loam}/translation.rb +5 -5
- data/app/models/{loam → open_loam}/webhook_endpoint.rb +6 -6
- data/app/views/{loam → open_loam}/attachments/_attachments.html.erb +1 -1
- data/app/views/{loam → open_loam}/comments/_comments.html.erb +1 -1
- data/app/views/{loam → open_loam}/custom_fields/_fields.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/entity/entity_generator.rb +9 -9
- data/lib/generators/{loam → open_loam}/entity/templates/api_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/controller.rb +21 -21
- data/lib/generators/{loam → open_loam}/entity/templates/entity_test.rb +12 -12
- data/lib/generators/{loam → open_loam}/entity/templates/migration.rb +5 -5
- data/lib/generators/{loam → open_loam}/entity/templates/model.rb +10 -10
- data/lib/generators/{loam → open_loam}/entity/templates/policy.rb +1 -1
- data/lib/generators/{loam → open_loam}/entity/templates/views/_form.html.erb +4 -4
- data/lib/generators/{loam → open_loam}/entity/templates/views/deleted.html.erb +7 -7
- data/lib/generators/open_loam/entity/templates/views/edit.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/index.html.erb +90 -0
- data/lib/generators/open_loam/entity/templates/views/new.html.erb +3 -0
- data/lib/generators/open_loam/entity/templates/views/show.html.erb +36 -0
- data/lib/generators/{loam → open_loam}/install/install_generator.rb +47 -47
- data/lib/generators/open_loam/install/templates/AGENTS.md +341 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_docs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_controller.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/base_controller.rb +38 -38
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/comments_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/configs_controller.rb +9 -9
- data/lib/generators/open_loam/install/templates/admin/dashboard_controller.rb +10 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/dictionary_entries_controller.rb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/event_deliveries_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/events_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/features_controller.rb +9 -9
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_new.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/history_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/imports_controller.rb +12 -12
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_new.html.erb +2 -2
- data/lib/generators/open_loam/install/templates/admin/layout.html.erb +76 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/overrides_index.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_controller.rb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/perspectives_index.html.erb +1 -1
- data/lib/generators/open_loam/install/templates/admin/progress_jobs_controller.rb +19 -0
- data/lib/generators/{loam → open_loam}/install/templates/admin/progress_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/record_locks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_form.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/search_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_controller.rb +32 -32
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_controller.rb +6 -6
- data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_form.html.erb +2 -2
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_controller.rb +5 -5
- data/lib/generators/{loam → open_loam}/install/templates/admin/sudo_new.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_controller.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/admin/translations_index.html.erb +3 -3
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_index.html.erb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/admin.css +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/api_base_controller.rb +15 -15
- data/lib/generators/{loam → open_loam}/install/templates/guardrails_test.rb +17 -17
- data/lib/generators/{loam → open_loam}/install/templates/import_job.rb +7 -7
- data/lib/generators/{loam → open_loam}/install/templates/inbound_webhooks_controller.rb +4 -4
- data/lib/generators/{loam → open_loam}/install/templates/initializer.rb +74 -74
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_api_tokens.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_audit_records.rb +14 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_auth_attempts.rb → open_loam/install/templates/migrations/create_open_loam_auth_attempts.rb} +3 -3
- data/lib/generators/{loam/install/templates/migrations/create_loam_business_rules.rb → open_loam/install/templates/migrations/create_open_loam_business_rules.rb} +10 -10
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_comments.rb +13 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_configs.rb → open_loam/install/templates/migrations/create_open_loam_configs.rb} +7 -7
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_custom_field_values.rb +19 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dashboard_widgets.rb +12 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_dictionaries.rb +11 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_dictionary_entries.rb → open_loam/install/templates/migrations/create_open_loam_dictionary_entries.rb} +6 -6
- data/lib/generators/{loam/install/templates/migrations/create_loam_event_deliveries.rb → open_loam/install/templates/migrations/create_open_loam_event_deliveries.rb} +5 -5
- data/lib/generators/{loam/install/templates/migrations/create_loam_field_definitions.rb → open_loam/install/templates/migrations/create_open_loam_field_definitions.rb} +4 -4
- data/lib/generators/{loam/install/templates/migrations/create_loam_inbound_webhooks.rb → open_loam/install/templates/migrations/create_open_loam_inbound_webhooks.rb} +9 -9
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_memberships.rb +11 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_mfa_credentials.rb → open_loam/install/templates/migrations/create_open_loam_mfa_credentials.rb} +4 -4
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_notifications.rb +15 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_pending_actions.rb → open_loam/install/templates/migrations/create_open_loam_pending_actions.rb} +9 -9
- data/lib/generators/{loam/install/templates/migrations/create_loam_perspectives.rb → open_loam/install/templates/migrations/create_open_loam_perspectives.rb} +5 -5
- data/lib/generators/{loam/install/templates/migrations/create_loam_progress_jobs.rb → open_loam/install/templates/migrations/create_open_loam_progress_jobs.rb} +5 -5
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_record_locks.rb +16 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_scheduled_jobs.rb → open_loam/install/templates/migrations/create_open_loam_scheduled_jobs.rb} +5 -5
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_search_tokens.rb +18 -0
- data/lib/generators/{loam/install/templates/migrations/create_loam_sso_providers.rb → open_loam/install/templates/migrations/create_open_loam_sso_providers.rb} +12 -12
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_tenants.rb +9 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_translations.rb +15 -0
- data/lib/generators/open_loam/install/templates/migrations/create_open_loam_webhook_endpoints.rb +13 -0
- data/lib/generators/{loam → open_loam}/install/templates/migrations/create_users.rb +1 -1
- data/lib/generators/{loam → open_loam}/install/templates/user.rb +5 -5
- data/lib/generators/{loam → open_loam}/primary_key_options.rb +23 -23
- data/lib/open-loam.rb +2 -2
- data/lib/{loam → open_loam}/attachable.rb +4 -4
- data/lib/{loam → open_loam}/auditable.rb +23 -23
- data/lib/{loam → open_loam}/auth_throttle.rb +10 -10
- data/lib/{loam → open_loam}/base32.rb +1 -1
- data/lib/{loam → open_loam}/bulk.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/actions.rb +7 -7
- data/lib/{loam → open_loam}/business_rules/condition.rb +5 -5
- data/lib/{loam → open_loam}/business_rules.rb +19 -19
- data/lib/{loam → open_loam}/commentable.rb +6 -6
- data/lib/{loam → open_loam}/configs.rb +19 -19
- data/lib/{loam → open_loam}/cron.rb +2 -2
- data/lib/{loam → open_loam}/csv.rb +2 -2
- data/lib/{loam → open_loam}/current.rb +5 -5
- data/lib/{loam → open_loam}/custom_field_index.rb +23 -23
- data/lib/{loam → open_loam}/custom_fields.rb +17 -17
- data/lib/{loam → open_loam}/dashboard.rb +5 -5
- data/lib/{loam → open_loam}/dictionaries.rb +12 -12
- data/lib/{loam → open_loam}/durable_events.rb +19 -19
- data/lib/{loam → open_loam}/encryptable.rb +33 -33
- data/lib/{loam → open_loam}/encryption/cipher.rb +3 -3
- data/lib/{loam → open_loam}/encryption/key_provider.rb +3 -3
- data/lib/{loam → open_loam}/encryption.rb +11 -11
- data/lib/open_loam/engine.rb +57 -0
- data/lib/{loam → open_loam}/enrichers.rb +5 -5
- data/lib/{loam → open_loam}/errors.rb +10 -10
- data/lib/{loam → open_loam}/eval.rb +2 -2
- data/lib/{loam → open_loam}/event_stream.rb +14 -14
- data/lib/{loam → open_loam}/eventful.rb +12 -12
- data/lib/{loam → open_loam}/events.rb +5 -5
- data/lib/{loam → open_loam}/export.rb +8 -8
- data/lib/{loam → open_loam}/features.rb +15 -15
- data/lib/{loam → open_loam}/generated_key.rb +6 -6
- data/lib/{loam → open_loam}/import.rb +12 -12
- data/lib/{loam → open_loam}/inbound_webhooks.rb +7 -7
- data/lib/{loam → open_loam}/lifecycle.rb +25 -25
- data/lib/{loam/locales/loam.en.yml → open_loam/locales/open_loam.en.yml} +5 -5
- data/lib/{loam → open_loam}/mcp/server.rb +9 -9
- data/lib/{loam → open_loam}/mcp.rb +21 -21
- data/lib/{loam → open_loam}/notifications.rb +5 -5
- data/lib/{loam → open_loam}/open_api.rb +16 -16
- data/lib/{loam → open_loam}/overrides.rb +10 -10
- data/lib/{loam → open_loam}/pending_actions.rb +9 -9
- data/lib/{loam → open_loam}/permissions.rb +8 -8
- data/lib/{loam → open_loam}/perspectives.rb +9 -9
- data/lib/{loam → open_loam}/policy.rb +6 -6
- data/lib/{loam → open_loam}/progress.rb +6 -6
- data/lib/{loam → open_loam}/record_locks.rb +9 -9
- data/lib/{loam → open_loam}/scheduler.rb +20 -20
- data/lib/{loam → open_loam}/search/driver.rb +3 -3
- data/lib/{loam → open_loam}/search/like_driver.rb +5 -5
- data/lib/{loam → open_loam}/search/token_driver.rb +15 -15
- data/lib/{loam → open_loam}/search.rb +5 -5
- data/lib/{loam → open_loam}/searchable.rb +22 -22
- data/lib/{loam → open_loam}/soft_deletable.rb +10 -10
- data/lib/{loam → open_loam}/sso/claims.rb +1 -1
- data/lib/{loam → open_loam}/sso/fake_provider.rb +1 -1
- data/lib/{loam → open_loam}/sso/http_client.rb +3 -3
- data/lib/{loam → open_loam}/sso/oidc_provider.rb +2 -2
- data/lib/{loam → open_loam}/sso.rb +17 -17
- data/lib/{loam → open_loam}/telemetry.rb +6 -6
- data/lib/{loam → open_loam}/tenant_record.rb +10 -10
- data/lib/{loam → open_loam}/test_helpers.rb +4 -4
- data/lib/{loam → open_loam}/totp.rb +3 -3
- data/lib/{loam → open_loam}/translatable.rb +24 -24
- data/lib/{loam → open_loam}/undo.rb +11 -11
- data/lib/open_loam/version.rb +3 -0
- data/lib/{loam → open_loam}/webhooks.rb +4 -4
- data/lib/{loam → open_loam}/widgets.rb +9 -9
- data/lib/{loam → open_loam}/workflow.rb +45 -45
- data/lib/open_loam.rb +122 -0
- data/lib/tasks/{loam.rake → open_loam.rake} +43 -43
- data/lib/tasks/{loam_eval.rake → open_loam_eval.rake} +7 -7
- data/lib/tasks/open_loam_mcp.rake +20 -0
- metadata +242 -242
- data/app/models/loam/dictionary.rb +0 -20
- data/app/models/loam/search_token.rb +0 -9
- data/app/models/loam/tenant.rb +0 -20
- data/lib/generators/loam/entity/templates/views/edit.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/index.html.erb +0 -90
- data/lib/generators/loam/entity/templates/views/new.html.erb +0 -3
- data/lib/generators/loam/entity/templates/views/show.html.erb +0 -36
- data/lib/generators/loam/install/templates/AGENTS.md +0 -341
- data/lib/generators/loam/install/templates/admin/dashboard_controller.rb +0 -10
- data/lib/generators/loam/install/templates/admin/layout.html.erb +0 -76
- data/lib/generators/loam/install/templates/admin/progress_jobs_controller.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_api_tokens.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_audit_records.rb +0 -14
- data/lib/generators/loam/install/templates/migrations/create_loam_comments.rb +0 -13
- data/lib/generators/loam/install/templates/migrations/create_loam_custom_field_values.rb +0 -19
- data/lib/generators/loam/install/templates/migrations/create_loam_dashboard_widgets.rb +0 -12
- data/lib/generators/loam/install/templates/migrations/create_loam_dictionaries.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_memberships.rb +0 -11
- data/lib/generators/loam/install/templates/migrations/create_loam_notifications.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_record_locks.rb +0 -16
- data/lib/generators/loam/install/templates/migrations/create_loam_search_tokens.rb +0 -18
- data/lib/generators/loam/install/templates/migrations/create_loam_tenants.rb +0 -9
- data/lib/generators/loam/install/templates/migrations/create_loam_translations.rb +0 -15
- data/lib/generators/loam/install/templates/migrations/create_loam_webhook_endpoints.rb +0 -13
- data/lib/loam/engine.rb +0 -57
- data/lib/loam/version.rb +0 -3
- data/lib/loam.rb +0 -122
- data/lib/tasks/loam_mcp.rake +0 -20
- /data/lib/generators/{loam → open_loam}/install/templates/admin/api_tokens_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/business_rules_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/configs_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dashboard_widgets_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/dictionaries_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/features_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/field_definitions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_preview.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/imports_summary.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/inbound_webhook_sources_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_activated.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/mfa_show.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/notifications_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pagination.rb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/pending_actions_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/scheduled_jobs_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/search_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_mfa_challenge.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sessions_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_edit.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_index.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/sso_providers_new.html.erb +0 -0
- /data/lib/generators/{loam → open_loam}/install/templates/admin/webhook_endpoints_new.html.erb +0 -0
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A tenant's connection to an external identity provider (OIDC). Per-tenant
|
|
3
3
|
# config: each tenant/org wires its OWN IdP, so the client_secret is encrypted
|
|
4
4
|
# under the tenant key (the default Encryptable scope) — unlike MFA, which is
|
|
5
5
|
# user-keyed. Home-realm discovery matches an email `domain` to the owning
|
|
6
|
-
# provider at the sign-in page (see
|
|
7
|
-
class SsoProvider <
|
|
8
|
-
self.table_name = "
|
|
6
|
+
# provider at the sign-in page (see OpenLoam::Sso.provider_for).
|
|
7
|
+
class SsoProvider < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_sso_providers"
|
|
9
9
|
|
|
10
|
-
include
|
|
11
|
-
include
|
|
10
|
+
include OpenLoam::Auditable # config changes are audited; the secret is redacted
|
|
11
|
+
include OpenLoam::Encryptable
|
|
12
12
|
|
|
13
13
|
# Tenant-scoped key (default): SSO config belongs to the tenant, and the
|
|
14
14
|
# secret is only ever read inside that tenant's context on callback.
|
|
15
15
|
encrypts :client_secret
|
|
16
16
|
|
|
17
|
-
has_many :identities, class_name: "
|
|
17
|
+
has_many :identities, class_name: "OpenLoam::SsoIdentity",
|
|
18
18
|
foreign_key: :sso_provider_id, dependent: :delete_all
|
|
19
19
|
|
|
20
20
|
validates :name, :protocol, :domain, :jit_role, presence: true
|
|
@@ -27,7 +27,7 @@ module Loam
|
|
|
27
27
|
|
|
28
28
|
scope :active, -> { where(active: true) }
|
|
29
29
|
|
|
30
|
-
# IdP group ->
|
|
30
|
+
# IdP group -> OpenLoam role, first match wins; falls back to jit_role.
|
|
31
31
|
def group_roles
|
|
32
32
|
group_role_map.is_a?(Hash) ? group_role_map : {}
|
|
33
33
|
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# The tenant itself is deliberately NOT tenant-scoped — it is the axis the
|
|
3
|
+
# rest of the system is scoped by.
|
|
4
|
+
class Tenant < ActiveRecord::Base
|
|
5
|
+
include OpenLoam::GeneratedKey
|
|
6
|
+
self.table_name = "open_loam_tenants"
|
|
7
|
+
|
|
8
|
+
validates :name, presence: true
|
|
9
|
+
validates :slug, presence: true, uniqueness: true
|
|
10
|
+
|
|
11
|
+
has_many :memberships, class_name: "OpenLoam::Membership", dependent: :delete_all
|
|
12
|
+
|
|
13
|
+
# A new tenant is never empty: whatever the app registered via
|
|
14
|
+
# OpenLoam.on_tenant_created runs here, inside this tenant's context. After
|
|
15
|
+
# commit, so callbacks see a persisted tenant and may enqueue jobs.
|
|
16
|
+
# `bin/rails open_loam:sync` re-runs the same callbacks for existing tenants —
|
|
17
|
+
# which is why they must be idempotent (see OpenLoam::Lifecycle).
|
|
18
|
+
after_create_commit { OpenLoam::Lifecycle.run_tenant_created(self) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# A per-locale override for ONE field of ONE record — the storage behind
|
|
3
|
-
#
|
|
3
|
+
# OpenLoam::Translatable. The record's own column holds the base (default-locale)
|
|
4
4
|
# value; a translation row overrides it for a specific locale. Additive:
|
|
5
5
|
# writing a translation never touches the base column, so the base value is
|
|
6
6
|
# never lost. Tenant-scoped and audited.
|
|
7
|
-
class Translation <
|
|
8
|
-
self.table_name = "
|
|
7
|
+
class Translation < OpenLoam::TenantRecord
|
|
8
|
+
self.table_name = "open_loam_translations"
|
|
9
9
|
|
|
10
|
-
include
|
|
10
|
+
include OpenLoam::Auditable
|
|
11
11
|
|
|
12
12
|
belongs_to :translatable, polymorphic: true
|
|
13
13
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Where one tenant wants its domain events delivered. `event_pattern` uses
|
|
3
|
-
# the same rule as
|
|
3
|
+
# the same rule as OpenLoam::Events.subscribe — a trailing dot is a domain prefix
|
|
4
4
|
# ("rental."), anything else is an exact event name.
|
|
5
5
|
#
|
|
6
|
-
# The secret signs every delivery (X-
|
|
6
|
+
# The secret signs every delivery (X-OpenLoam-Signature), so a receiver can tell
|
|
7
7
|
# a real call from a forged one.
|
|
8
|
-
class WebhookEndpoint <
|
|
9
|
-
self.table_name = "
|
|
8
|
+
class WebhookEndpoint < OpenLoam::TenantRecord
|
|
9
|
+
self.table_name = "open_loam_webhook_endpoints"
|
|
10
10
|
|
|
11
11
|
validates :url, presence: true, format: { with: %r{\Ahttps?://}, message: "must start with http:// or https://" }
|
|
12
12
|
validates :event_pattern, presence: true
|
|
@@ -20,7 +20,7 @@ module Loam
|
|
|
20
20
|
# One matcher, shared with the event bus, so "rental." means the same thing
|
|
21
21
|
# to a subscriber and to a webhook.
|
|
22
22
|
def matches?(event_name)
|
|
23
|
-
|
|
23
|
+
OpenLoam::Events.pattern_matches?(event_pattern, event_name)
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Uploading happens through the entity's own form (it is an update, and the
|
|
3
3
|
entity's policy decides who may do it); this partial only lists what is
|
|
4
4
|
already attached. Note that these links are signed URLs, not tenant-checked
|
|
5
|
-
addresses — see
|
|
5
|
+
addresses — see OpenLoam::Attachable. %>
|
|
6
6
|
<h2>Files</h2>
|
|
7
7
|
|
|
8
8
|
<% if record.files.attached? %>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<p style="color:#b00"><%= flash[:alert] %></p>
|
|
9
9
|
<% end %>
|
|
10
10
|
|
|
11
|
-
<% record.
|
|
11
|
+
<% record.open_loam_comments.oldest_first.each do |comment| %>
|
|
12
12
|
<p>
|
|
13
13
|
<strong><%= User.find_by(id: comment.author_id)&.name || "—" %></strong>
|
|
14
14
|
<em><%= comment.created_at.to_fs(:short) %></em><br>
|
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
<% when "integer", "decimal" %>
|
|
21
21
|
<%= number_field_tag param_name, value %>
|
|
22
22
|
<% when "dictionary" %>
|
|
23
|
-
<% options =
|
|
23
|
+
<% options = OpenLoam::Dictionaries.entries(definition.dictionary_key).map { |e| [e.label, e.value] } %>
|
|
24
24
|
<%= select_tag param_name, options_for_select(options, value), include_blank: true, id: param_name %>
|
|
25
25
|
<% else %>
|
|
26
26
|
<%= text_field_tag param_name, value %>
|
|
27
27
|
<% end %>
|
|
28
28
|
<% else %>
|
|
29
29
|
<%# A dictionary field shows its entry label, not the raw stored code. %>
|
|
30
|
-
<% display = definition.field_type == "dictionary" ?
|
|
30
|
+
<% display = definition.field_type == "dictionary" ? OpenLoam::Dictionaries.label_for(definition.dictionary_key, value) : value %>
|
|
31
31
|
<%= definition.name.humanize %>: <%= display %>
|
|
32
32
|
<% if mode == :edit %><em>(read-only for your role)</em><% end %>
|
|
33
33
|
<% end %>
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
require "rails/generators"
|
|
2
2
|
require "rails/generators/active_record"
|
|
3
|
-
require "generators/
|
|
3
|
+
require "generators/open_loam/primary_key_options"
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module OpenLoam
|
|
6
6
|
module Generators
|
|
7
|
-
# `rails g
|
|
7
|
+
# `rails g open_loam:entity Equipment name:string daily_rate:decimal --domain rental`
|
|
8
8
|
#
|
|
9
9
|
# THE interface for adding a business entity — for humans and AI agents
|
|
10
10
|
# alike. One command produces a tenant-scoped, audited, evented model, its
|
|
11
11
|
# policy, an admin screen, and the isolation tests that prove the
|
|
12
|
-
# guardrails hold. No decisions about how tenancy/permissions work:
|
|
12
|
+
# guardrails hold. No decisions about how tenancy/permissions work: OpenLoam
|
|
13
13
|
# already decided.
|
|
14
14
|
class EntityGenerator < Rails::Generators::NamedBase
|
|
15
15
|
include ActiveRecord::Generators::Migration
|
|
16
|
-
include
|
|
16
|
+
include OpenLoam::Generators::PrimaryKeyOptions
|
|
17
17
|
|
|
18
18
|
source_root File.expand_path("templates", __dir__)
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ module Loam
|
|
|
21
21
|
class_option :domain, type: :string, default: "app",
|
|
22
22
|
desc: "Event domain prefix (-> domain.entity.created)"
|
|
23
23
|
class_option :encrypt, type: :array, default: [], banner: "field field",
|
|
24
|
-
desc: "Encrypt these fields at rest, per tenant (
|
|
24
|
+
desc: "Encrypt these fields at rest, per tenant (OpenLoam::Encryptable)"
|
|
25
25
|
class_option :encrypt_searchable, type: :array, default: [], banner: "field field",
|
|
26
26
|
desc: "Encrypt + add a blind index for exact-match lookup"
|
|
27
27
|
|
|
@@ -59,7 +59,7 @@ module Loam
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# `namespace:` makes Rails inject the route INTO the existing
|
|
62
|
-
# `namespace :admin do` block (the one
|
|
62
|
+
# `namespace :admin do` block (the one open_loam:install wrote) instead of
|
|
63
63
|
# stacking one admin block per entity, and falls back to creating the
|
|
64
64
|
# block when there is none. Re-running the generator is a no-op: the
|
|
65
65
|
# injection is `force: false`, so identical routing code is skipped.
|
|
@@ -70,7 +70,7 @@ module Loam
|
|
|
70
70
|
# identical `resources :gadgets` lines would leave the second one out.
|
|
71
71
|
#
|
|
72
72
|
# The admin resource carries the soft-delete recycle bin: `deleted` lists
|
|
73
|
-
# `only_deleted`, `restore` brings one back (see
|
|
73
|
+
# `only_deleted`, `restore` brings one back (see OpenLoam::SoftDeletable).
|
|
74
74
|
def add_route
|
|
75
75
|
route <<~RUBY.strip, namespace: :admin
|
|
76
76
|
resources :#{plural_file_name} do
|
|
@@ -101,7 +101,7 @@ module Loam
|
|
|
101
101
|
attributes.map(&:name)
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
# Only text-ish columns are worth a LIKE (see
|
|
104
|
+
# Only text-ish columns are worth a LIKE (see OpenLoam::Searchable), so an
|
|
105
105
|
# entity with none gets no `searchable_by` declaration and stays out of
|
|
106
106
|
# the global search. Encrypted columns are excluded: ciphertext is
|
|
107
107
|
# meaningless to LIKE, and declaring both would raise at class load.
|
|
@@ -9,7 +9,7 @@ module Api
|
|
|
9
9
|
|
|
10
10
|
def index
|
|
11
11
|
records = <%= class_name %>.order(created_at: :desc).to_a
|
|
12
|
-
enriched =
|
|
12
|
+
enriched = OpenLoam::Enrichers.enrich_many(records) # one batched pass, no N+1
|
|
13
13
|
render json: records.map { |record| entity_json(record, enrichments: enriched[record.id]) }
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -15,34 +15,34 @@ module Admin
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def index
|
|
18
|
-
@perspective =
|
|
19
|
-
@perspectives =
|
|
18
|
+
@perspective = OpenLoam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
|
|
19
|
+
@perspectives = OpenLoam::Perspectives.visible_to("<%= class_name %>", user: current_actor)
|
|
20
20
|
@records, @page, @has_next = paginate(index_scope)
|
|
21
|
-
@index_partial = params[:cf_field].present? &&
|
|
21
|
+
@index_partial = params[:cf_field].present? && OpenLoam::CustomFieldIndex.partial? # incomplete-index warning (L-919)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# CSV of the CURRENT filtered/perspective view — manager-only, policy- and
|
|
25
|
-
# encryption-aware (
|
|
25
|
+
# encryption-aware (OpenLoam::Export).
|
|
26
26
|
def export
|
|
27
27
|
require_role!(:manager)
|
|
28
|
-
send_data
|
|
28
|
+
send_data OpenLoam::Export.csv(index_scope, actor: current_actor),
|
|
29
29
|
filename: "<%= plural_name %>-#{Date.current}.csv", type: "text/csv"
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Datatable bulk actions on the selected ids — each is policy-checked per
|
|
33
|
-
# record and tenant-scoped (
|
|
33
|
+
# record and tenant-scoped (OpenLoam::Bulk). Zero selection is a no-op.
|
|
34
34
|
def bulk
|
|
35
35
|
ids = Array(params[:ids])
|
|
36
36
|
case params[:bulk_action]
|
|
37
37
|
when "soft_delete"
|
|
38
|
-
count =
|
|
38
|
+
count = OpenLoam::Bulk.soft_delete(<%= class_name %>, ids)
|
|
39
39
|
redirect_to polymorphic_path([:admin, <%= class_name %>]), notice: "Deleted #{count} record(s)."
|
|
40
40
|
when "set_field"
|
|
41
|
-
count =
|
|
41
|
+
count = OpenLoam::Bulk.set_field(<%= class_name %>, ids, field: params[:field], value: params[:value])
|
|
42
42
|
redirect_to polymorphic_path([:admin, <%= class_name %>]), notice: "Updated #{count} record(s)."
|
|
43
43
|
when "export"
|
|
44
44
|
require_role!(:manager) # same gate as the dedicated export action
|
|
45
|
-
send_data
|
|
45
|
+
send_data OpenLoam::Export.csv(OpenLoam::Bulk.selected(<%= class_name %>, ids), actor: current_actor),
|
|
46
46
|
filename: "<%= plural_name %>-selected.csv", type: "text/csv"
|
|
47
47
|
else
|
|
48
48
|
redirect_to polymorphic_path([:admin, <%= class_name %>]), alert: "Unknown bulk action."
|
|
@@ -75,7 +75,7 @@ module Admin
|
|
|
75
75
|
attach_files!(@record, policy)
|
|
76
76
|
|
|
77
77
|
if @record.save
|
|
78
|
-
redirect_to [:admin, @record], notice: t("
|
|
78
|
+
redirect_to [:admin, @record], notice: t("open_loam.flash.created", name: <%= class_name %>.model_name.human)
|
|
79
79
|
else
|
|
80
80
|
render :new, status: :unprocessable_entity
|
|
81
81
|
end
|
|
@@ -84,7 +84,7 @@ module Admin
|
|
|
84
84
|
def edit
|
|
85
85
|
authorize!(policy_for(@record), :update?)
|
|
86
86
|
# Take the advisory lock (courtesy) or learn who holds it, for the banner.
|
|
87
|
-
@lock =
|
|
87
|
+
@lock = OpenLoam::RecordLocks.acquire(@record, by: current_actor) || OpenLoam::RecordLocks.active_lock(@record)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def update
|
|
@@ -94,15 +94,15 @@ module Admin
|
|
|
94
94
|
attach_files!(@record, policy)
|
|
95
95
|
|
|
96
96
|
if @record.update(permitted_params(policy))
|
|
97
|
-
|
|
98
|
-
redirect_to [:admin, @record], notice: t("
|
|
97
|
+
OpenLoam::RecordLocks.release(@record, by: current_actor)
|
|
98
|
+
redirect_to [:admin, @record], notice: t("open_loam.flash.updated", name: <%= class_name %>.model_name.human)
|
|
99
99
|
else
|
|
100
100
|
render :edit, status: :unprocessable_entity
|
|
101
101
|
end
|
|
102
102
|
rescue ActiveRecord::StaleObjectError
|
|
103
103
|
# Someone saved between open and submit — show the diff, reload fresh, retry.
|
|
104
104
|
stale_conflict!(@record, FIELDS)
|
|
105
|
-
@lock =
|
|
105
|
+
@lock = OpenLoam::RecordLocks.acquire(@record, by: current_actor) || OpenLoam::RecordLocks.active_lock(@record)
|
|
106
106
|
render :edit, status: :conflict
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -111,8 +111,8 @@ module Admin
|
|
|
111
111
|
def destroy
|
|
112
112
|
authorize!(policy_for(@record), :destroy?)
|
|
113
113
|
@record.soft_delete!
|
|
114
|
-
|
|
115
|
-
redirect_to [:admin, <%= class_name %>], notice: t("
|
|
114
|
+
OpenLoam::RecordLocks.release(@record, by: current_actor)
|
|
115
|
+
redirect_to [:admin, <%= class_name %>], notice: t("open_loam.flash.destroyed", name: <%= class_name %>.model_name.human)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
# Restore looks through the deleted rows — the default scope hides them, so a
|
|
@@ -121,7 +121,7 @@ module Admin
|
|
|
121
121
|
@record = <%= class_name %>.with_deleted.find(params[:id])
|
|
122
122
|
authorize!(policy_for(@record), :update?)
|
|
123
123
|
@record.restore!
|
|
124
|
-
redirect_to [:deleted, :admin, <%= class_name %>], notice: t("
|
|
124
|
+
redirect_to [:deleted, :admin, <%= class_name %>], notice: t("open_loam.flash.restored", name: <%= class_name %>.model_name.human)
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
private
|
|
@@ -134,7 +134,7 @@ module Admin
|
|
|
134
134
|
# export so the CSV matches what the manager is looking at.
|
|
135
135
|
def index_scope
|
|
136
136
|
scope = <%= class_name %>.all
|
|
137
|
-
perspective =
|
|
137
|
+
perspective = OpenLoam::Perspectives.resolve("<%= class_name %>", user: current_actor, id: params[:perspective_id])
|
|
138
138
|
scope = perspective.apply(scope) if perspective
|
|
139
139
|
<% if searchable_attributes.any? -%>
|
|
140
140
|
scope = scope.search(params[:q])
|
|
@@ -160,13 +160,13 @@ module Admin
|
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
# A custom-field filter routed through the read-model index
|
|
163
|
-
# (
|
|
163
|
+
# (OpenLoam::CustomFieldIndex) — index-backed, not a JSON scan. An unknown field
|
|
164
164
|
# is ignored rather than raising.
|
|
165
165
|
def apply_custom_field_filter(scope)
|
|
166
166
|
return scope if params[:cf_field].blank?
|
|
167
167
|
|
|
168
|
-
scope.merge(
|
|
169
|
-
rescue
|
|
168
|
+
scope.merge(OpenLoam::CustomFieldIndex.filter(<%= class_name %>, params[:cf_field], params[:cf_op].presence || "eq", params[:cf_value]))
|
|
169
|
+
rescue OpenLoam::Error
|
|
170
170
|
scope
|
|
171
171
|
end
|
|
172
172
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
require "test_helper"
|
|
2
2
|
|
|
3
|
-
# Generated guardrail tests for <%= class_name %>. They prove the
|
|
3
|
+
# Generated guardrail tests for <%= class_name %>. They prove the OpenLoam
|
|
4
4
|
# invariants hold for THIS entity: tenant isolation, loud failure without
|
|
5
5
|
# context, audit-by-default, lifecycle events, membership-gated policies.
|
|
6
6
|
# Extend freely; never delete.
|
|
7
|
-
class <%= class_name %>
|
|
7
|
+
class <%= class_name %>OpenLoamTest < ActiveSupport::TestCase
|
|
8
8
|
setup do
|
|
9
|
-
@tenant_a =
|
|
10
|
-
@tenant_b =
|
|
9
|
+
@tenant_a = OpenLoam::Tenant.create!(name: "Tenant A", slug: "a-<%= file_name %>")
|
|
10
|
+
@tenant_b = OpenLoam::Tenant.create!(name: "Tenant B", slug: "b-<%= file_name %>")
|
|
11
11
|
@manager = User.create!(name: "Manager", email: "manager@example.test", password: "password")
|
|
12
12
|
@employee = User.create!(name: "Employee", email: "employee@example.test", password: "password")
|
|
13
13
|
|
|
14
14
|
with_tenant(@tenant_a) do
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
OpenLoam::Membership.create!(user: @manager, role: "manager")
|
|
16
|
+
OpenLoam::Membership.create!(user: @employee, role: "employee")
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -29,15 +29,15 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
test "guardrail: touching the model with no tenant context raises" do
|
|
32
|
-
assert_raises(
|
|
33
|
-
assert_raises(
|
|
32
|
+
assert_raises(OpenLoam::MissingTenantError) { <%= class_name %>.count }
|
|
33
|
+
assert_raises(OpenLoam::MissingTenantError) { <%= class_name %>.new }
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
test "guardrail: a record cannot be written into a foreign tenant" do
|
|
37
37
|
record = with_tenant(@tenant_a) { <%= class_name %>.create!(<%= sample_attributes %>) }
|
|
38
38
|
|
|
39
39
|
with_tenant(@tenant_b) do
|
|
40
|
-
assert_raises(
|
|
40
|
+
assert_raises(OpenLoam::MissingTenantError) { record.update!(<%= sample_attributes(1) %>) }
|
|
41
41
|
end
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
|
|
|
45
45
|
with_tenant(@tenant_a, actor: @manager) do
|
|
46
46
|
record = <%= class_name %>.create!(<%= sample_attributes %>)
|
|
47
47
|
|
|
48
|
-
audit =
|
|
48
|
+
audit = OpenLoam::AuditRecord.find_by(
|
|
49
49
|
auditable_type: "<%= class_name %>", auditable_id: record.id, action: "create"
|
|
50
50
|
)
|
|
51
51
|
assert audit, "expected an audit record for the create"
|
|
@@ -56,7 +56,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
|
|
|
56
56
|
|
|
57
57
|
test "lifecycle events are published with the tenant stamped" do
|
|
58
58
|
received = []
|
|
59
|
-
subscription =
|
|
59
|
+
subscription = OpenLoam::Events.subscribe("<%= domain %>.<%= singular_name %>.created") do |_name, payload|
|
|
60
60
|
received << payload
|
|
61
61
|
end
|
|
62
62
|
|
|
@@ -78,7 +78,7 @@ class <%= class_name %>LoamTest < ActiveSupport::TestCase
|
|
|
78
78
|
with_tenant(@tenant_a) do
|
|
79
79
|
assert_equal 0, <%= class_name %>.count, "a soft-deleted record is gone from ordinary queries"
|
|
80
80
|
assert_equal 1, <%= class_name %>.with_deleted.count, "but the row still exists"
|
|
81
|
-
assert
|
|
81
|
+
assert OpenLoam::AuditRecord.exists?(auditable_type: "<%= class_name %>", auditable_id: record_id, action: "soft_delete")
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# with_deleted lifts the deleted_at filter but NOT the tenant filter.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
class Create<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::VERSION::STRING.to_f %>]
|
|
2
2
|
def change
|
|
3
|
-
create_table :<%= table_name %><%=
|
|
4
|
-
t.references :tenant, null: false, foreign_key: { to_table: :
|
|
3
|
+
create_table :<%= table_name %><%= open_loam_id_option %> do |t|
|
|
4
|
+
t.references :tenant, null: false, foreign_key: { to_table: :open_loam_tenants }<%= open_loam_type_option %>
|
|
5
5
|
<% attributes.each do |attribute| -%>
|
|
6
6
|
<% if encrypted?(attribute) -%>
|
|
7
|
-
t.text :<%= attribute.name %> # encrypted at rest (
|
|
7
|
+
t.text :<%= attribute.name %> # encrypted at rest (OpenLoam::Encryptable) — text, since ciphertext outgrows varchar
|
|
8
8
|
<% else -%>
|
|
9
9
|
t.<%= attribute.type %> :<%= attribute.name %>
|
|
10
10
|
<% end -%>
|
|
@@ -14,10 +14,10 @@ class Create<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecor
|
|
|
14
14
|
<% end -%>
|
|
15
15
|
t.json :custom_fields, null: false, default: {}
|
|
16
16
|
t.datetime :deleted_at
|
|
17
|
-
t.integer :lock_version, null: false, default: 0 # optimistic locking (
|
|
17
|
+
t.integer :lock_version, null: false, default: 0 # optimistic locking (OpenLoam::RecordLock)
|
|
18
18
|
t.timestamps
|
|
19
19
|
end
|
|
20
|
-
#
|
|
20
|
+
# OpenLoam::SoftDeletable filters `deleted_at IS NULL` on every query.
|
|
21
21
|
add_index :<%= table_name %>, :deleted_at
|
|
22
22
|
<% encrypt_searchable_names.each do |field| -%>
|
|
23
23
|
add_index :<%= table_name %>, :<%= field %>_hash
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# Tenant-scoped, audited, evented — by inheritance, not by remembering.
|
|
2
|
-
class <%= class_name %> <
|
|
3
|
-
include
|
|
4
|
-
include
|
|
5
|
-
include
|
|
6
|
-
include
|
|
7
|
-
include
|
|
8
|
-
include
|
|
9
|
-
include
|
|
2
|
+
class <%= class_name %> < OpenLoam::TenantRecord
|
|
3
|
+
include OpenLoam::Auditable
|
|
4
|
+
include OpenLoam::Eventful
|
|
5
|
+
include OpenLoam::CustomFields
|
|
6
|
+
include OpenLoam::Commentable
|
|
7
|
+
include OpenLoam::Attachable
|
|
8
|
+
include OpenLoam::Searchable
|
|
9
|
+
include OpenLoam::SoftDeletable
|
|
10
10
|
<% if encrypted_field_names.any? -%>
|
|
11
|
-
include
|
|
11
|
+
include OpenLoam::Encryptable
|
|
12
12
|
<% end -%>
|
|
13
13
|
|
|
14
14
|
event_domain :<%= domain %>
|
|
@@ -23,5 +23,5 @@ class <%= class_name %> < Loam::TenantRecord
|
|
|
23
23
|
<% end -%>
|
|
24
24
|
|
|
25
25
|
# Business logic goes here. Publish business events explicitly:
|
|
26
|
-
#
|
|
26
|
+
# OpenLoam::Events.publish("<%= domain %>.something.happened", id: id)
|
|
27
27
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%% policy =
|
|
1
|
+
<%% policy = OpenLoam::Policy.for(@record) %>
|
|
2
2
|
|
|
3
3
|
<%%# Optimistic-locking conflict: what the user typed vs what is now saved. %>
|
|
4
4
|
<%% if @conflict.present? %>
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
<%% else %>
|
|
46
46
|
<p>
|
|
47
47
|
<%%= f.label :<%= attribute.name %> %>: <%%= @record.<%= attribute.name %> %>
|
|
48
|
-
<em><%%= t("
|
|
48
|
+
<em><%%= t("open_loam.form.read_only") %></em>
|
|
49
49
|
</p>
|
|
50
50
|
<%% end %>
|
|
51
51
|
<% end -%>
|
|
52
52
|
|
|
53
|
-
<%%= render "
|
|
53
|
+
<%%= render "open_loam/custom_fields/fields", record: @record, policy: policy, mode: :edit %>
|
|
54
54
|
|
|
55
55
|
<%% if policy.update? %>
|
|
56
56
|
<p>
|
|
57
|
-
<%%= f.label :files, t("
|
|
57
|
+
<%%= f.label :files, t("open_loam.form.attach_files") %><br>
|
|
58
58
|
<%%= f.file_field :files, multiple: true %>
|
|
59
59
|
</p>
|
|
60
60
|
<%% end %>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<h1><%%= t("
|
|
1
|
+
<h1><%%= t("open_loam.actions.deleted_title", name: <%= class_name %>.model_name.human(count: 2)) %></h1>
|
|
2
2
|
|
|
3
|
-
<p><%%= link_to t("
|
|
3
|
+
<p><%%= link_to t("open_loam.actions.back"), [:admin, <%= class_name %>] %></p>
|
|
4
4
|
|
|
5
5
|
<%%# No Show/Edit here: these rows are hidden by the default scope, so a plain
|
|
6
6
|
find would 404. The one action a deleted record offers is Restore. %>
|
|
7
7
|
<table>
|
|
8
8
|
<thead>
|
|
9
9
|
<tr>
|
|
10
|
-
<th><%%= t("
|
|
10
|
+
<th><%%= t("open_loam.labels.id") %></th>
|
|
11
11
|
<% attributes.each do |attribute| -%>
|
|
12
12
|
<th><%%= <%= class_name %>.human_attribute_name("<%= attribute.name %>") %></th>
|
|
13
13
|
<% end -%>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<td><%%= record.<%= attribute.name %> %></td>
|
|
24
24
|
<% end -%>
|
|
25
25
|
<td><%%= record.deleted_at %></td>
|
|
26
|
-
<td><%%= button_to t("
|
|
26
|
+
<td><%%= button_to t("open_loam.actions.restore"), [:restore, :admin, record], method: :patch %></td>
|
|
27
27
|
</tr>
|
|
28
28
|
<%% end %>
|
|
29
29
|
</tbody>
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
<%%# Page links carry nothing but the page: the recycle bin has no filter box. %>
|
|
33
33
|
<p>
|
|
34
34
|
<%% if @page > 1 %>
|
|
35
|
-
<%%= link_to t("
|
|
35
|
+
<%%= link_to t("open_loam.pagination.previous"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page - 1) %>
|
|
36
36
|
<%% end %>
|
|
37
|
-
<%%= t("
|
|
37
|
+
<%%= t("open_loam.pagination.page", page: @page) %>
|
|
38
38
|
<%% if @has_next %>
|
|
39
|
-
<%%= link_to t("
|
|
39
|
+
<%%= link_to t("open_loam.pagination.next"), polymorphic_path([:deleted, :admin, <%= class_name %>], page: @page + 1) %>
|
|
40
40
|
<%% end %>
|
|
41
41
|
</p>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<h1><%%= <%= class_name %>.model_name.human(count: 2) %></h1>
|
|
2
|
+
|
|
3
|
+
<%% if @index_partial %>
|
|
4
|
+
<p style="color:#b00"><%%= t("open_loam.index.incomplete") %></p>
|
|
5
|
+
<%% end %>
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<%%= link_to t("open_loam.actions.new", name: <%= class_name %>.model_name.human), new_polymorphic_path([:admin, <%= class_name %>]) %>
|
|
9
|
+
<%%= link_to t("open_loam.actions.deleted", name: <%= class_name %>.model_name.human(count: 2)), polymorphic_path([:deleted, :admin, <%= class_name %>]) %>
|
|
10
|
+
<%% if current_role == :manager %>
|
|
11
|
+
<%%= link_to t("open_loam.actions.export_csv"), url_for(action: :export, **current_index_params) %>
|
|
12
|
+
<%%= link_to t("open_loam.actions.import_csv"), new_admin_import_path(entity_type: "<%= class_name %>") %>
|
|
13
|
+
<%% end %>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<%%# Saved views (OpenLoam::Perspectives): pick one to apply, or save the current one. %>
|
|
17
|
+
<%% if @perspectives.any? %>
|
|
18
|
+
<%%= form_with url: [:admin, <%= class_name %>], method: :get do %>
|
|
19
|
+
<%%= select_tag :perspective_id,
|
|
20
|
+
options_for_select([[t("open_loam.views.all", name: <%= class_name %>.model_name.human(count: 2)), "none"]] + @perspectives.map { |p| [p.name, p.id] }, @perspective&.id),
|
|
21
|
+
onchange: "this.form.submit()" %>
|
|
22
|
+
<%%= link_to t("open_loam.views.manage"), admin_perspectives_path(entity_type: "<%= class_name %>") %>
|
|
23
|
+
<%% end %>
|
|
24
|
+
<%% else %>
|
|
25
|
+
<p><%%= link_to t("open_loam.views.manage_saved"), admin_perspectives_path(entity_type: "<%= class_name %>") %></p>
|
|
26
|
+
<%% end %>
|
|
27
|
+
|
|
28
|
+
<%%= form_with url: admin_perspectives_path, method: :post do %>
|
|
29
|
+
<%%= hidden_field_tag :entity_type, "<%= class_name %>" %>
|
|
30
|
+
<%%= hidden_field_tag :q, params[:q] %>
|
|
31
|
+
<%%= text_field_tag :name, nil, placeholder: t("open_loam.views.name_placeholder") %>
|
|
32
|
+
<%%= submit_tag t("open_loam.views.save_current") %>
|
|
33
|
+
<%% end %>
|
|
34
|
+
<% if searchable_attributes.any? -%>
|
|
35
|
+
|
|
36
|
+
<%%= form_with url: [:admin, <%= class_name %>], method: :get do |f| %>
|
|
37
|
+
<%%= text_field_tag :q, params[:q], placeholder: t("open_loam.views.filter_placeholder", name: <%= class_name %>.model_name.human(count: 2)) %>
|
|
38
|
+
<%%= f.submit t("open_loam.actions.filter") %>
|
|
39
|
+
<%% end %>
|
|
40
|
+
<% end -%>
|
|
41
|
+
|
|
42
|
+
<%%# Bulk actions on selected rows (OpenLoam::Bulk). Zero selection = no-op; each
|
|
43
|
+
action is policy-checked per record server-side. %>
|
|
44
|
+
<%%= form_with url: url_for(action: :bulk), method: :post do %>
|
|
45
|
+
<p>
|
|
46
|
+
<%%= select_tag :bulk_action, options_for_select([[t("open_loam.bulk.delete_selected"), "soft_delete"], [t("open_loam.bulk.set_field"), "set_field"], [t("open_loam.bulk.export_selected"), "export"]]) %>
|
|
47
|
+
<%%= select_tag :field, options_for_select(%i[<%= field_names.join(" ") %>]) %>
|
|
48
|
+
<%%= text_field_tag :value, nil, placeholder: t("open_loam.bulk.new_value") %>
|
|
49
|
+
<%%= submit_tag t("open_loam.bulk.apply"), data: { turbo_confirm: t("open_loam.bulk.confirm") } %>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<table>
|
|
53
|
+
<thead>
|
|
54
|
+
<tr>
|
|
55
|
+
<th><input type="checkbox" onclick="document.querySelectorAll('.bulk-select').forEach(function(c){c.checked=this.checked}.bind(this))"></th>
|
|
56
|
+
<th><%%= t("open_loam.labels.id") %></th>
|
|
57
|
+
<% attributes.each do |attribute| -%>
|
|
58
|
+
<th><%%= sort_link("<%= attribute.name %>", <%= class_name %>.human_attribute_name("<%= attribute.name %>")) %></th>
|
|
59
|
+
<% end -%>
|
|
60
|
+
<th></th>
|
|
61
|
+
</tr>
|
|
62
|
+
</thead>
|
|
63
|
+
<tbody>
|
|
64
|
+
<%% @records.each do |record| %>
|
|
65
|
+
<tr>
|
|
66
|
+
<td><%%= check_box_tag "ids[]", record.id, false, class: "bulk-select" %></td>
|
|
67
|
+
<td><%%= record.id %></td>
|
|
68
|
+
<% attributes.each do |attribute| -%>
|
|
69
|
+
<td><%%= record.<%= attribute.name %> %></td>
|
|
70
|
+
<% end -%>
|
|
71
|
+
<td>
|
|
72
|
+
<%%= link_to t("open_loam.actions.show"), [:admin, record] %>
|
|
73
|
+
<%%= link_to t("open_loam.actions.edit"), edit_polymorphic_path([:admin, record]) %>
|
|
74
|
+
</td>
|
|
75
|
+
</tr>
|
|
76
|
+
<%% end %>
|
|
77
|
+
</tbody>
|
|
78
|
+
</table>
|
|
79
|
+
<%% end %>
|
|
80
|
+
|
|
81
|
+
<%%# Page links carry the full filter state, or page 2 would quietly drop it. %>
|
|
82
|
+
<p>
|
|
83
|
+
<%% if @page > 1 %>
|
|
84
|
+
<%%= link_to t("open_loam.pagination.previous"), polymorphic_path([:admin, <%= class_name %>], **current_index_params, page: @page - 1) %>
|
|
85
|
+
<%% end %>
|
|
86
|
+
<%%= t("open_loam.pagination.page", page: @page) %>
|
|
87
|
+
<%% if @has_next %>
|
|
88
|
+
<%%= link_to t("open_loam.pagination.next"), polymorphic_path([:admin, <%= class_name %>], **current_index_params, page: @page + 1) %>
|
|
89
|
+
<%% end %>
|
|
90
|
+
</p>
|