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,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
module BusinessRules
|
|
3
3
|
# The typed action vocabulary — a FIXED, safe set the engine dispatches. No
|
|
4
4
|
# arbitrary code: an action is a json descriptor with a `type` and known
|
|
@@ -9,7 +9,7 @@ module Loam
|
|
|
9
9
|
|
|
10
10
|
# Run one action against the record that triggered the rule. Returns a
|
|
11
11
|
# short label of what it did (for the execution log). `:veto` is the
|
|
12
|
-
# block_transition signal (see
|
|
12
|
+
# block_transition signal (see OpenLoam::BusinessRules.veto?).
|
|
13
13
|
def self.run(action, record)
|
|
14
14
|
case action["type"].to_s
|
|
15
15
|
when "notify" then run_notify(action, record)
|
|
@@ -24,10 +24,10 @@ module Loam
|
|
|
24
24
|
title = action["title"].to_s
|
|
25
25
|
body = action["body"]
|
|
26
26
|
if action["role"].present?
|
|
27
|
-
|
|
27
|
+
OpenLoam::Notifications.notify_role(action["role"], title: title, body: body)
|
|
28
28
|
elsif action["user_id"].present?
|
|
29
29
|
user = User.find_by(id: action["user_id"])
|
|
30
|
-
|
|
30
|
+
OpenLoam::Notifications.notify(user, title: title, body: body) if user
|
|
31
31
|
end
|
|
32
32
|
"notify"
|
|
33
33
|
end
|
|
@@ -35,7 +35,7 @@ module Loam
|
|
|
35
35
|
# Publish validates the name format (raising InvalidEventNameError), so a
|
|
36
36
|
# malformed event name is caught by the engine's per-rule isolation.
|
|
37
37
|
def self.run_emit_event(action)
|
|
38
|
-
|
|
38
|
+
OpenLoam::Events.publish(action["name"], (action["payload"] || {}).symbolize_keys)
|
|
39
39
|
"emit_event(#{action["name"]})"
|
|
40
40
|
end
|
|
41
41
|
|
|
@@ -65,7 +65,7 @@ module Loam
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def self.refuse_workflow_column!(record, field)
|
|
68
|
-
return unless record.class.respond_to?(:
|
|
68
|
+
return unless record.class.respond_to?(:open_loam_workflow) && record.class.open_loam_workflow&.column == field
|
|
69
69
|
|
|
70
70
|
raise ArgumentError, "set_field must not write the workflow column #{field.inspect} — use a transition"
|
|
71
71
|
end
|
|
@@ -83,7 +83,7 @@ module Loam
|
|
|
83
83
|
# but never write one whose tenant is not the tenant in context.
|
|
84
84
|
def self.refuse_cross_tenant!(record)
|
|
85
85
|
return unless record.respond_to?(:tenant_id)
|
|
86
|
-
return if record.tenant_id ==
|
|
86
|
+
return if record.tenant_id == OpenLoam.tenant!.id
|
|
87
87
|
|
|
88
88
|
raise ArgumentError, "set_field refuses a record outside the current tenant"
|
|
89
89
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
module BusinessRules
|
|
3
3
|
# The SAFE condition evaluator — the critical security boundary of the rules
|
|
4
4
|
# engine. A rule is DATA an admin edits, so this must NEVER run arbitrary
|
|
@@ -8,7 +8,7 @@ module Loam
|
|
|
8
8
|
# else is refused — no `send` of arbitrary methods, no SQL, no eval.
|
|
9
9
|
module Condition
|
|
10
10
|
OPS = %w[eq neq gt gte lt lte in contains present blank].freeze
|
|
11
|
-
REFUSED = :
|
|
11
|
+
REFUSED = :__open_loam_field_refused__
|
|
12
12
|
|
|
13
13
|
def self.matches?(node, record)
|
|
14
14
|
node = node.is_a?(Hash) ? node : {}
|
|
@@ -48,7 +48,7 @@ module Loam
|
|
|
48
48
|
elsif custom_field?(record, field)
|
|
49
49
|
begin
|
|
50
50
|
record.custom_field(field)
|
|
51
|
-
rescue
|
|
51
|
+
rescue OpenLoam::UnknownCustomFieldError
|
|
52
52
|
REFUSED
|
|
53
53
|
end
|
|
54
54
|
else
|
|
@@ -59,8 +59,8 @@ module Loam
|
|
|
59
59
|
def self.refused_column?(klass, field)
|
|
60
60
|
return true if field == "tenant_id"
|
|
61
61
|
|
|
62
|
-
klass.respond_to?(:
|
|
63
|
-
klass.
|
|
62
|
+
klass.respond_to?(:open_loam_encrypted_attributes) &&
|
|
63
|
+
klass.open_loam_encrypted_attributes.map(&:to_s).include?(field)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def self.custom_field?(record, field)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
require "
|
|
2
|
-
require "
|
|
1
|
+
require "open_loam/business_rules/condition"
|
|
2
|
+
require "open_loam/business_rules/actions"
|
|
3
3
|
|
|
4
|
-
module
|
|
5
|
-
# The engine that evaluates
|
|
4
|
+
module OpenLoam
|
|
5
|
+
# The engine that evaluates OpenLoam::BusinessRule rows: WHEN a trigger fires and
|
|
6
6
|
# the condition holds, THEN run the actions — in the event's tenant context,
|
|
7
7
|
# in priority order, each rule isolated so one failure never breaks dispatch.
|
|
8
8
|
#
|
|
9
|
-
# It listens to every
|
|
9
|
+
# It listens to every OpenLoam event (like OpenLoam::Webhooks), so a tenant can add a
|
|
10
10
|
# rule from the admin without a reboot. Evaluation runs INLINE in whatever
|
|
11
11
|
# published the event — cheap for the prototype; a job-based dispatcher is the
|
|
12
12
|
# scaling path.
|
|
@@ -15,19 +15,19 @@ module Loam
|
|
|
15
15
|
# rules, but only so deep, so a self-triggering rule terminates.
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
|
-
# Wired once from
|
|
19
|
-
# every rule twice (see
|
|
18
|
+
# Wired once from OpenLoam::Engine. Idempotent — subscribing twice would run
|
|
19
|
+
# every rule twice (see OpenLoam::Webhooks.subscribe!).
|
|
20
20
|
def subscribe!
|
|
21
|
-
@subscription ||=
|
|
21
|
+
@subscription ||= OpenLoam::Events.subscribe_all { |event_name, payload| on_event(event_name, payload) }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def on_event(event_name, payload)
|
|
25
|
-
tenant =
|
|
25
|
+
tenant = OpenLoam::Tenant.find_by(id: payload[:tenant_id])
|
|
26
26
|
return if tenant.nil?
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
OpenLoam.as_tenant(tenant) do
|
|
29
29
|
within_depth_guard do
|
|
30
|
-
|
|
30
|
+
OpenLoam::BusinessRule.active.by_priority.each do |rule|
|
|
31
31
|
next unless rule.matches_trigger?(event_name)
|
|
32
32
|
|
|
33
33
|
run_rule(rule, subject_for(rule, payload), event_name)
|
|
@@ -40,7 +40,7 @@ module Loam
|
|
|
40
40
|
# active rules for the record's entity type (optionally filtered to a
|
|
41
41
|
# trigger) in priority order.
|
|
42
42
|
def evaluate(record, trigger: nil)
|
|
43
|
-
|
|
43
|
+
OpenLoam::BusinessRule.active.for_entity(record.class.base_class.name).by_priority.each do |rule|
|
|
44
44
|
next if trigger && !rule.matches_trigger?(trigger)
|
|
45
45
|
|
|
46
46
|
run_rule(rule, record, trigger)
|
|
@@ -51,7 +51,7 @@ module Loam
|
|
|
51
51
|
# holds, veto this transition? Consulted by a workflow BEFORE it moves (see
|
|
52
52
|
# the demo's DamageReport). Evaluate-only — no actions run, nothing logged.
|
|
53
53
|
def veto?(record, trigger)
|
|
54
|
-
|
|
54
|
+
OpenLoam::BusinessRule.active.for_entity(record.class.base_class.name).by_priority.any? do |rule|
|
|
55
55
|
rule.matches_trigger?(trigger) &&
|
|
56
56
|
rule.action_list.any? { |action| action["type"].to_s == "block_transition" } &&
|
|
57
57
|
Condition.matches?(rule.condition_tree, record)
|
|
@@ -81,7 +81,7 @@ module Loam
|
|
|
81
81
|
# deterministic "condition didn't match" ones (readable from the rule).
|
|
82
82
|
return if !matched && error.nil?
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
OpenLoam::BusinessRuleRun.create!(
|
|
85
85
|
business_rule: rule,
|
|
86
86
|
subject_type: record&.class&.base_class&.name,
|
|
87
87
|
subject_id: record&.id,
|
|
@@ -102,21 +102,21 @@ module Loam
|
|
|
102
102
|
# A rule may ONLY target a tenant-scoped model. This is the security
|
|
103
103
|
# boundary: a global model like `User` has no tenant default scope, so a
|
|
104
104
|
# find_by(id:) would reach across tenants — refusing anything that is not
|
|
105
|
-
# a
|
|
105
|
+
# a OpenLoam::TenantRecord closes that, and a TenantRecord.find_by can only
|
|
106
106
|
# ever return a record in the CURRENT tenant even if an id is injected.
|
|
107
|
-
return nil unless klass.is_a?(Class) && klass <
|
|
107
|
+
return nil unless klass.is_a?(Class) && klass < OpenLoam::TenantRecord
|
|
108
108
|
|
|
109
109
|
klass.find_by(id: payload[:id])
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def within_depth_guard
|
|
113
|
-
depth = Thread.current[:
|
|
113
|
+
depth = Thread.current[:open_loam_rule_depth] ||= 0
|
|
114
114
|
return if depth >= MAX_DEPTH
|
|
115
115
|
|
|
116
|
-
Thread.current[:
|
|
116
|
+
Thread.current[:open_loam_rule_depth] = depth + 1
|
|
117
117
|
yield
|
|
118
118
|
ensure
|
|
119
|
-
Thread.current[:
|
|
119
|
+
Thread.current[:open_loam_rule_depth] = depth if depth
|
|
120
120
|
end
|
|
121
121
|
end
|
|
122
122
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Discussion on a record:
|
|
3
3
|
#
|
|
4
|
-
# equipment.comment!("Hydraulics serviced") # as
|
|
5
|
-
# equipment.
|
|
4
|
+
# equipment.comment!("Hydraulics serviced") # as OpenLoam.actor
|
|
5
|
+
# equipment.open_loam_comments.oldest_first
|
|
6
6
|
#
|
|
7
7
|
# Included by every generated entity. Comments are tenant-scoped like their
|
|
8
8
|
# subject, so a discussion can never be read from another tenant.
|
|
@@ -10,13 +10,13 @@ module Loam
|
|
|
10
10
|
extend ActiveSupport::Concern
|
|
11
11
|
|
|
12
12
|
included do
|
|
13
|
-
has_many :
|
|
13
|
+
has_many :open_loam_comments, as: :commentable, class_name: "OpenLoam::Comment", dependent: :destroy
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Author defaults to whoever is acting — the same person the audit trail
|
|
17
17
|
# and the event payload would name.
|
|
18
|
-
def comment!(body, author:
|
|
19
|
-
|
|
18
|
+
def comment!(body, author: OpenLoam::Current.actor)
|
|
19
|
+
open_loam_comments.create!(body: body, author: author)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# The one way to read and write per-tenant settings.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
4
|
+
# OpenLoam::Configs.get("rental.late_fee_per_day") # resolved for the current tenant
|
|
5
|
+
# OpenLoam::Configs.set("rental.late_fee_per_day", 45) # this tenant's override
|
|
6
|
+
# OpenLoam::Configs.set("rental.currency", "PLN", scope: :global)
|
|
7
|
+
# OpenLoam::Configs.reset("rental.late_fee_per_day") # drop the override → fall back
|
|
8
8
|
#
|
|
9
9
|
# Resolution order, most specific first:
|
|
10
10
|
# 1. the current tenant's OVERRIDE row (tenant_id = current tenant)
|
|
11
11
|
# 2. the GLOBAL row (tenant_id NULL)
|
|
12
|
-
# 3. the declared default in
|
|
12
|
+
# 3. the declared default in OpenLoam.config_defaults[key]
|
|
13
13
|
# 4. the `default:` argument (nil)
|
|
14
14
|
#
|
|
15
|
-
# Reads are memoized per request in
|
|
15
|
+
# Reads are memoized per request in OpenLoam::Current.config_cache, keyed by
|
|
16
16
|
# [key, tenant_id], and the whole cache is dropped on any write. That is the
|
|
17
17
|
# deliberately-simple prototype cache; a Rails.cache-backed layer shared across
|
|
18
18
|
# requests is the scaling path, and it would slot in behind this same API.
|
|
@@ -36,7 +36,7 @@ module Loam
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Write a setting. scope: :tenant writes the current tenant's override and
|
|
39
|
-
# requires a tenant in context (a missing one raises, like every
|
|
39
|
+
# requires a tenant in context (a missing one raises, like every OpenLoam
|
|
40
40
|
# write); scope: :global writes the app-wide row (tenant_id NULL). Upsert.
|
|
41
41
|
def set(key, value, scope: :tenant)
|
|
42
42
|
key = key.to_s
|
|
@@ -66,8 +66,8 @@ module Loam
|
|
|
66
66
|
# defaults, plus any global or current-tenant rows already written. Sorted
|
|
67
67
|
# and de-duplicated, for the settings screen.
|
|
68
68
|
def defined_keys
|
|
69
|
-
rows =
|
|
70
|
-
(
|
|
69
|
+
rows = OpenLoam::Config.where(tenant_id: [nil, OpenLoam.tenant&.id]).distinct.pluck(:key)
|
|
70
|
+
(OpenLoam.config_defaults.keys + rows).map(&:to_s).uniq.sort
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
private
|
|
@@ -76,39 +76,39 @@ module Loam
|
|
|
76
76
|
# the caller then applies its own `default:` without caching it.
|
|
77
77
|
def resolve(key)
|
|
78
78
|
if (tenant_id = current_tenant_id)
|
|
79
|
-
override =
|
|
79
|
+
override = OpenLoam::Config.find_by(key: key, tenant_id: tenant_id)
|
|
80
80
|
return [true, override.value_json] if override
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
global =
|
|
83
|
+
global = OpenLoam::Config.find_by(key: key, tenant_id: nil)
|
|
84
84
|
return [true, global.value_json] if global
|
|
85
85
|
|
|
86
|
-
return [true,
|
|
86
|
+
return [true, OpenLoam.config_defaults[key]] if OpenLoam.config_defaults.key?(key)
|
|
87
87
|
|
|
88
88
|
[false, nil]
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# The row(s) a write/read targets. :global is the tenant_id NULL row;
|
|
92
92
|
# :tenant is the current tenant's — which requires a tenant in context,
|
|
93
|
-
# so
|
|
93
|
+
# so OpenLoam.tenant! raises MissingTenantError when there is none.
|
|
94
94
|
def row_for(key, scope)
|
|
95
95
|
case scope
|
|
96
|
-
when :global then
|
|
97
|
-
when :tenant then
|
|
96
|
+
when :global then OpenLoam::Config.where(key: key, tenant_id: nil)
|
|
97
|
+
when :tenant then OpenLoam::Config.where(key: key, tenant_id: OpenLoam.tenant!.id)
|
|
98
98
|
else raise ArgumentError, "unknown config scope #{scope.inspect} (use :tenant or :global)"
|
|
99
99
|
end
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def current_tenant_id
|
|
103
|
-
|
|
103
|
+
OpenLoam.tenant&.id
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
def cache
|
|
107
|
-
|
|
107
|
+
OpenLoam::Current.config_cache ||= {}
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def clear_cache
|
|
111
|
-
|
|
111
|
+
OpenLoam::Current.config_cache = {}
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
end
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require "set"
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module OpenLoam
|
|
4
4
|
# A small, dependency-free "next run" calculator for a standard 5-field cron
|
|
5
5
|
# expression ("min hour dom month dow") plus an "interval:N" (every N seconds)
|
|
6
|
-
# form — same stdlib-only spirit as
|
|
6
|
+
# form — same stdlib-only spirit as OpenLoam::Totp / OpenLoam::Base32. Timezone-aware;
|
|
7
7
|
# always returns UTC. Supports `*`, lists (`1,15`), ranges (`1-5`), and steps
|
|
8
8
|
# (`*/15`, `0-30/10`), and the classic cron day-of-month / day-of-week OR quirk
|
|
9
9
|
# (when BOTH are restricted, a match on either fires).
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# CSV formula-injection defense. A cell whose value starts with =, +, -, @, or
|
|
3
3
|
# a tab/CR is treated as a FORMULA by Excel/Google Sheets when the file is
|
|
4
4
|
# opened — so `=cmd|...` or `=HYPERLINK(...)` in imported/exported data can run.
|
|
5
5
|
# Prefixing such a value with a single quote neutralizes it while leaving the
|
|
6
|
-
# visible text intact. Applied on every CSV cell
|
|
6
|
+
# visible text intact. Applied on every CSV cell OpenLoam writes (export + the
|
|
7
7
|
# import error file).
|
|
8
8
|
module Csv
|
|
9
9
|
DANGEROUS = /\A[=+\-@\t\r]/
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
require "active_support/current_attributes"
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module OpenLoam
|
|
4
4
|
# Per-request (and per-job) execution context. Every tenant-scoped query,
|
|
5
5
|
# audit record, and published event reads from here.
|
|
6
6
|
class Current < ActiveSupport::CurrentAttributes
|
|
7
|
-
# config_cache memoizes resolved
|
|
7
|
+
# config_cache memoizes resolved OpenLoam::Configs values for the duration of
|
|
8
8
|
# one request/job, keyed by [key, tenant_id]. It resets with everything else
|
|
9
9
|
# here, so a value can never be read stale across requests.
|
|
10
10
|
#
|
|
11
|
-
# mutation_mode is the approval-gate seam (
|
|
11
|
+
# mutation_mode is the approval-gate seam (OpenLoam::PendingActions): nil/:direct
|
|
12
12
|
# means writes commit normally; :confirm means a caller (an MCP tool acting
|
|
13
13
|
# for an AI agent) stages writes for human approval instead. It lives here so
|
|
14
14
|
# it is per-request and resets automatically.
|
|
15
|
-
# dictionary_cache memoizes resolved
|
|
15
|
+
# dictionary_cache memoizes resolved OpenLoam::Dictionaries lookups for one
|
|
16
16
|
# request/job, keyed by [kind, key, tenant_id] — same posture as config_cache.
|
|
17
|
-
# locale is the current content-translation language (
|
|
17
|
+
# locale is the current content-translation language (OpenLoam::Translatable) —
|
|
18
18
|
# request state like tenant/actor, set in a before_action and reset here.
|
|
19
19
|
attribute :tenant, :actor, :config_cache, :mutation_mode, :dictionary_cache, :locale
|
|
20
20
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module
|
|
2
|
-
# The read-model index for custom fields (
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# The read-model index for custom fields (OpenLoam::CustomFieldValue) — a typed-EAV
|
|
3
3
|
# projection that makes custom-field filter/sort/search index-backed.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# OpenLoam::CustomFieldIndex.filter(DamageReport, "severity", "eq", "critical") # a relation
|
|
6
|
+
# OpenLoam::CustomFieldIndex.order(DamageReport, "severity", :asc)
|
|
7
|
+
# OpenLoam::CustomFieldIndex.reindex(DamageReport) # rebuild
|
|
8
8
|
#
|
|
9
|
-
# SAFETY: `field_key` must name a real
|
|
9
|
+
# SAFETY: `field_key` must name a real OpenLoam::FieldDefinition (no arbitrary
|
|
10
10
|
# keys); values are cast to the declared type; queries are tenant-scoped
|
|
11
11
|
# throughout (index rows carry tenant_id, the model relation is default-scoped),
|
|
12
12
|
# so a filter can't reach across tenants. Encrypted data lives in real columns,
|
|
@@ -16,7 +16,7 @@ module Loam
|
|
|
16
16
|
|
|
17
17
|
module_function
|
|
18
18
|
|
|
19
|
-
# --- maintenance (called from
|
|
19
|
+
# --- maintenance (called from OpenLoam::CustomFields) ---
|
|
20
20
|
|
|
21
21
|
# Re-project one record's custom fields (delete + insert). Soft-delete keeps
|
|
22
22
|
# the rows (the filter's base scope hides the record anyway — the same
|
|
@@ -26,18 +26,18 @@ module Loam
|
|
|
26
26
|
return unless model.respond_to?(:custom_field_definitions)
|
|
27
27
|
|
|
28
28
|
type = index_type(model)
|
|
29
|
-
|
|
29
|
+
OpenLoam::CustomFieldValue.where(indexable_type: type, indexable_id: record.id).delete_all
|
|
30
30
|
|
|
31
31
|
rows = model.custom_field_definitions.filter_map { |definition| row_for(record, definition, type) }
|
|
32
|
-
|
|
32
|
+
OpenLoam::CustomFieldValue.insert_all(rows) if rows.any?
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def remove(record)
|
|
36
|
-
|
|
36
|
+
OpenLoam::CustomFieldValue.where(indexable_type: index_type(record.class), indexable_id: record.id).delete_all
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def reindex(model)
|
|
40
|
-
|
|
40
|
+
OpenLoam::CustomFieldValue.where(indexable_type: index_type(model)).delete_all
|
|
41
41
|
model.find_each { |record| project(record) }
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ module Loam
|
|
|
47
47
|
field_key = field_key.to_s
|
|
48
48
|
refuse_unknown_field!(model, field_key)
|
|
49
49
|
refuse_unreadable_field!(model, field_key)
|
|
50
|
-
raise
|
|
50
|
+
raise OpenLoam::Error, "unknown custom-field op #{op.inspect}" unless OPS.include?(op.to_s)
|
|
51
51
|
|
|
52
52
|
if covered?(model, field_key)
|
|
53
53
|
self.partial = false
|
|
@@ -89,12 +89,12 @@ module Loam
|
|
|
89
89
|
self.partial = true
|
|
90
90
|
schedule_reindex(model)
|
|
91
91
|
end
|
|
92
|
-
table =
|
|
92
|
+
table = OpenLoam::CustomFieldValue.table_name
|
|
93
93
|
conn = model.connection
|
|
94
94
|
join = "LEFT JOIN #{table} ON #{table}.indexable_type = #{conn.quote(index_type(model))} " \
|
|
95
95
|
"AND #{table}.indexable_id = #{model.table_name}.id " \
|
|
96
96
|
"AND #{table}.field_key = #{conn.quote(field_key.to_s)} " \
|
|
97
|
-
"AND #{table}.tenant_id = #{
|
|
97
|
+
"AND #{table}.tenant_id = #{OpenLoam.tenant!.id}"
|
|
98
98
|
model.joins(join).order(Arel.sql("#{table}.value_text #{dir.to_s.casecmp('desc').zero? ? 'DESC' : 'ASC'}"))
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -132,22 +132,22 @@ module Loam
|
|
|
132
132
|
# Was the last filter/order served over an incomplete index? The admin surfaces
|
|
133
133
|
# this as an honest "results may be incomplete, reindexing…".
|
|
134
134
|
def partial?
|
|
135
|
-
Thread.current[:
|
|
135
|
+
Thread.current[:open_loam_index_partial] == true
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def partial=(value)
|
|
139
|
-
Thread.current[:
|
|
139
|
+
Thread.current[:open_loam_index_partial] = value
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
# Enqueue a background reindex to heal a gap — DEDUPED so a hot gappy field
|
|
143
143
|
# doesn't enqueue one per request. In-process dedup here (single-process
|
|
144
144
|
# prototype); a DB/cache marker is the multi-process path.
|
|
145
145
|
def schedule_reindex(model)
|
|
146
|
-
key = [
|
|
146
|
+
key = [ OpenLoam.tenant!.id, model.base_class.name ]
|
|
147
147
|
return if pending_reindex.include?(key)
|
|
148
148
|
|
|
149
149
|
pending_reindex << key
|
|
150
|
-
|
|
150
|
+
OpenLoam::CustomFieldReindexJob.perform_later(OpenLoam.tenant!.id, model.base_class.name)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
def clear_pending(tenant_id, model_name)
|
|
@@ -188,7 +188,7 @@ module Loam
|
|
|
188
188
|
# --- internals ---
|
|
189
189
|
|
|
190
190
|
def base(model)
|
|
191
|
-
|
|
191
|
+
OpenLoam::CustomFieldValue.where(indexable_type: index_type(model)) # tenant-scoped by default_scope
|
|
192
192
|
end
|
|
193
193
|
|
|
194
194
|
def index_type(model_or_class)
|
|
@@ -235,7 +235,7 @@ module Loam
|
|
|
235
235
|
|
|
236
236
|
def refuse_unknown_field!(model, field_key)
|
|
237
237
|
known = model.respond_to?(:custom_field_definitions) && model.custom_field_definitions.exists?(name: field_key)
|
|
238
|
-
raise
|
|
238
|
+
raise OpenLoam::Error, "no custom field #{field_key.inspect} on #{model.name}" unless known
|
|
239
239
|
end
|
|
240
240
|
|
|
241
241
|
# THE oracle guard: filtering or sorting on a custom field the current role
|
|
@@ -243,16 +243,16 @@ module Loam
|
|
|
243
243
|
# records match. Enforced against the current actor's role; a system/background
|
|
244
244
|
# context (no actor — reindex, a business rule) is trusted and not gated.
|
|
245
245
|
def refuse_unreadable_field!(model, field_key)
|
|
246
|
-
actor =
|
|
246
|
+
actor = OpenLoam::Current.actor
|
|
247
247
|
return if actor.nil?
|
|
248
248
|
|
|
249
249
|
definition = model.custom_field_definitions.find_by(name: field_key.to_s)
|
|
250
250
|
return if definition.nil? # unknown field is refuse_unknown_field!'s job
|
|
251
251
|
|
|
252
|
-
role =
|
|
252
|
+
role = OpenLoam::Membership.find_by(user_id: actor.id)&.role
|
|
253
253
|
return if definition.readable_by?(role)
|
|
254
254
|
|
|
255
|
-
raise
|
|
255
|
+
raise OpenLoam::FieldAccessError, "custom field #{field_key.inspect} is not readable by #{role.inspect}"
|
|
256
256
|
end
|
|
257
257
|
|
|
258
258
|
def sanitize_like(value)
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Runtime, migration-free fields — stored in the `custom_fields` json
|
|
3
3
|
# column every generated entity table carries. Included automatically by
|
|
4
|
-
# `rails g
|
|
5
|
-
#
|
|
4
|
+
# `rails g open_loam:entity`. Shape is declared at runtime via a
|
|
5
|
+
# OpenLoam::FieldDefinition (typically through the admin "Field definitions"
|
|
6
6
|
# screen), not in code — that's what makes it migration-free.
|
|
7
7
|
#
|
|
8
8
|
# equipment.custom_field(:serial_number) # => "SN-123" (cast)
|
|
9
9
|
# equipment.set_custom_field(:serial_number, "SN-123")
|
|
10
10
|
#
|
|
11
|
-
# Reading/writing a name with no matching
|
|
12
|
-
# tenant + entity raises
|
|
13
|
-
# same "fail loudly at the access site" philosophy as
|
|
11
|
+
# Reading/writing a name with no matching OpenLoam::FieldDefinition for this
|
|
12
|
+
# tenant + entity raises OpenLoam::UnknownCustomFieldError immediately — the
|
|
13
|
+
# same "fail loudly at the access site" philosophy as OpenLoam.tenant!.
|
|
14
14
|
module CustomFields
|
|
15
15
|
extend ActiveSupport::Concern
|
|
16
16
|
|
|
17
17
|
included do
|
|
18
|
-
# Keep the read-model index (
|
|
18
|
+
# Keep the read-model index (OpenLoam::CustomFieldIndex) current so custom-field
|
|
19
19
|
# filter/sort is index-backed. Re-project only when the custom_fields column
|
|
20
20
|
# actually changed (a soft-delete touches only deleted_at, so the rows stay
|
|
21
21
|
# and the base scope hides the record — the L-912 posture). A no-op for a
|
|
22
|
-
# model with no custom_fields column. Guarded by respond_to? so
|
|
22
|
+
# model with no custom_fields column. Guarded by respond_to? so OpenLoam::
|
|
23
23
|
# CustomFields stays includable in a plain class for a DSL-only test.
|
|
24
24
|
if respond_to?(:after_save)
|
|
25
|
-
after_save :
|
|
26
|
-
after_destroy :
|
|
25
|
+
after_save :open_loam_reindex_custom_fields, if: :open_loam_custom_fields_dirty?
|
|
26
|
+
after_destroy :open_loam_deindex_custom_fields
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
class_methods do
|
|
31
31
|
def custom_field_definitions
|
|
32
|
-
|
|
32
|
+
OpenLoam::FieldDefinition.where(entity_type: name)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -53,15 +53,15 @@ module Loam
|
|
|
53
53
|
value = custom_field(name)
|
|
54
54
|
return value unless definition.field_type == "dictionary"
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
OpenLoam::Dictionaries.label_for(definition.dictionary_key, value)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
-
def
|
|
62
|
-
def
|
|
61
|
+
def open_loam_reindex_custom_fields = OpenLoam::CustomFieldIndex.project(self)
|
|
62
|
+
def open_loam_deindex_custom_fields = OpenLoam::CustomFieldIndex.remove(self)
|
|
63
63
|
|
|
64
|
-
def
|
|
64
|
+
def open_loam_custom_fields_dirty?
|
|
65
65
|
return false unless self.class.respond_to?(:custom_field_definitions)
|
|
66
66
|
|
|
67
67
|
# On create every column counts as changed; on update, only re-project when
|
|
@@ -71,8 +71,8 @@ module Loam
|
|
|
71
71
|
|
|
72
72
|
def custom_field_definition!(name)
|
|
73
73
|
custom_field_definitions_by_name.fetch(name.to_s) do
|
|
74
|
-
raise UnknownCustomFieldError, "No
|
|
75
|
-
"(tenant #{
|
|
74
|
+
raise UnknownCustomFieldError, "No OpenLoam::FieldDefinition named #{name.inspect} for #{self.class.name} " \
|
|
75
|
+
"(tenant #{OpenLoam.tenant&.id.inspect})"
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
module
|
|
1
|
+
module OpenLoam
|
|
2
2
|
# Resolves the admin dashboard for the current tenant/actor: the ordered,
|
|
3
3
|
# role-visible, computed widgets to show. Uses the tenant's configured
|
|
4
|
-
#
|
|
4
|
+
# OpenLoam::DashboardWidget rows (a manager arranges them); falls back to every
|
|
5
5
|
# registered widget in registration order when the tenant has configured none,
|
|
6
6
|
# so the dashboard is useful out of the box.
|
|
7
7
|
module Dashboard
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
10
|
def for(actor:, role:)
|
|
11
|
-
configured =
|
|
12
|
-
keys = configured.presence ||
|
|
11
|
+
configured = OpenLoam::DashboardWidget.active.ordered.pluck(:widget_key)
|
|
12
|
+
keys = configured.presence || OpenLoam::Widgets.keys
|
|
13
13
|
|
|
14
|
-
keys.filter_map { |key|
|
|
14
|
+
keys.filter_map { |key| OpenLoam::Widgets.resolve(key, actor: actor, role: role) }
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
module
|
|
2
|
-
# Read API for per-tenant managed lookup lists (
|
|
3
|
-
#
|
|
4
|
-
# memoized per request in
|
|
1
|
+
module OpenLoam
|
|
2
|
+
# Read API for per-tenant managed lookup lists (OpenLoam::Dictionary /
|
|
3
|
+
# OpenLoam::DictionaryEntry). Everything is tenant-scoped automatically and
|
|
4
|
+
# memoized per request in OpenLoam::Current.dictionary_cache (keyed with the tenant
|
|
5
5
|
# id, so an as_tenant switch mid-request can't read another tenant's list) —
|
|
6
|
-
# the same simple prototype cache as
|
|
6
|
+
# the same simple prototype cache as OpenLoam::Configs.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# OpenLoam::Dictionaries.entries("damage_severity") # active, ordered
|
|
9
|
+
# OpenLoam::Dictionaries.default("damage_severity") # the default entry
|
|
10
|
+
# OpenLoam::Dictionaries.label_for("damage_severity", "critical") # => "Critical"
|
|
11
11
|
module Dictionaries
|
|
12
12
|
class << self
|
|
13
13
|
# The dictionary for the current tenant, or nil.
|
|
14
14
|
def get(key)
|
|
15
|
-
memo([:dict, key.to_s]) {
|
|
15
|
+
memo([:dict, key.to_s]) { OpenLoam::Dictionary.find_by(key: key.to_s) }
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
# Active entries, ordered by position. Empty for an unknown key.
|
|
@@ -38,14 +38,14 @@ module Loam
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def clear_cache
|
|
41
|
-
|
|
41
|
+
OpenLoam::Current.dictionary_cache = {}
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def memo(subkey)
|
|
47
|
-
store = (
|
|
48
|
-
cache_key = subkey + [
|
|
47
|
+
store = (OpenLoam::Current.dictionary_cache ||= {})
|
|
48
|
+
cache_key = subkey + [ OpenLoam.tenant&.id ]
|
|
49
49
|
return store[cache_key] if store.key?(cache_key)
|
|
50
50
|
|
|
51
51
|
store[cache_key] = yield
|