dbdoc_engine 0.1.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 +7 -0
- data/README.md +331 -0
- data/Rakefile +8 -0
- data/app/assets/builds/dbdoc_engine/application.css +5 -0
- data/app/assets/images/dbdoc_engine/arrowdown.svg +3 -0
- data/app/assets/images/dbdoc_engine/arrowhorizontal.svg +3 -0
- data/app/assets/images/dbdoc_engine/arrowleft.svg +3 -0
- data/app/assets/images/dbdoc_engine/changelog.svg +3 -0
- data/app/assets/images/dbdoc_engine/column_stats_dbdocs.svg +23 -0
- data/app/assets/images/dbdoc_engine/diagram.svg +3 -0
- data/app/assets/images/dbdoc_engine/double_arrow.svg +4 -0
- data/app/assets/images/dbdoc_engine/group_bu.svg +3 -0
- data/app/assets/images/dbdoc_engine/japan_circle.png +0 -0
- data/app/assets/images/dbdoc_engine/log_in_image.png +0 -0
- data/app/assets/images/dbdoc_engine/logo.svg +12 -0
- data/app/assets/images/dbdoc_engine/orange_changelog.svg +3 -0
- data/app/assets/images/dbdoc_engine/orange_fields.svg +23 -0
- data/app/assets/images/dbdoc_engine/orange_logo.svg +12 -0
- data/app/assets/images/dbdoc_engine/orange_table.svg +21 -0
- data/app/assets/images/dbdoc_engine/orange_updates.svg +43 -0
- data/app/assets/images/dbdoc_engine/orange_wiki.svg +3 -0
- data/app/assets/images/dbdoc_engine/search.svg +3 -0
- data/app/assets/images/dbdoc_engine/setting.svg +3 -0
- data/app/assets/images/dbdoc_engine/table_dbdocs.svg +21 -0
- data/app/assets/images/dbdoc_engine/uk_circle_transparent.png +0 -0
- data/app/assets/images/dbdoc_engine/update_stats_dbdocs.svg +43 -0
- data/app/assets/images/dbdoc_engine/wiki.svg +3 -0
- data/app/assets/stylesheets/dbdoc_engine/admin.css +176 -0
- data/app/assets/stylesheets/dbdoc_engine/admin_header.css +179 -0
- data/app/assets/stylesheets/dbdoc_engine/application.scss +1 -0
- data/app/assets/stylesheets/dbdoc_engine/changelog.css +173 -0
- data/app/assets/stylesheets/dbdoc_engine/dashboard.css +513 -0
- data/app/assets/stylesheets/dbdoc_engine/dbdoc_application.css +117 -0
- data/app/assets/stylesheets/dbdoc_engine/ecommerce.css +253 -0
- data/app/assets/stylesheets/dbdoc_engine/group_details.css +178 -0
- data/app/assets/stylesheets/dbdoc_engine/header.css +212 -0
- data/app/assets/stylesheets/dbdoc_engine/loading_spinner.css +127 -0
- data/app/assets/stylesheets/dbdoc_engine/login.css +213 -0
- data/app/assets/stylesheets/dbdoc_engine/schema_diagram.css +149 -0
- data/app/assets/stylesheets/dbdoc_engine/sidebar.css +296 -0
- data/app/assets/stylesheets/dbdoc_engine/table_details.css +417 -0
- data/app/controllers/dbdoc_engine/admin/base_controller.rb +23 -0
- data/app/controllers/dbdoc_engine/admin/dashboard_controller.rb +16 -0
- data/app/controllers/dbdoc_engine/admin/data_transfer_controller.rb +63 -0
- data/app/controllers/dbdoc_engine/admin/db_design_dynamic_tables_controller.rb +198 -0
- data/app/controllers/dbdoc_engine/admin/db_design_table_groups_controller.rb +107 -0
- data/app/controllers/dbdoc_engine/application_controller.rb +65 -0
- data/app/controllers/dbdoc_engine/concerns/internationalization.rb +57 -0
- data/app/controllers/dbdoc_engine/db_doc_sessions_controller.rb +33 -0
- data/app/controllers/dbdoc_engine/home_controller.rb +79 -0
- data/app/controllers/dbdoc_engine/schema_diagram_controller.rb +293 -0
- data/app/helper/dbdoc_engine/application_helper.rb +35 -0
- data/app/helpers/dbdoc_engine/application_helper.rb +4 -0
- data/app/helpers/dbdoc_engine/changelogs_helper.rb +27 -0
- data/app/helpers/dbdoc_engine/column_helper.rb +30 -0
- data/app/helpers/dbdoc_engine/db_design_dynamic_tables_helper.rb +15 -0
- data/app/helpers/dbdoc_engine/home_helper.rb +75 -0
- data/app/javascript/dbdoc_engine/application.js +12 -0
- data/app/javascript/dbdoc_engine/controllers/application.js +29 -0
- data/app/javascript/dbdoc_engine/controllers/auto_submit_controller.js +17 -0
- data/app/javascript/dbdoc_engine/controllers/chart_controller.js +58 -0
- data/app/javascript/dbdoc_engine/controllers/column-type_controller.js +149 -0
- data/app/javascript/dbdoc_engine/controllers/column_controller.js +362 -0
- data/app/javascript/dbdoc_engine/controllers/column_search_controller.js +42 -0
- data/app/javascript/dbdoc_engine/controllers/dbdoc_accordion_controller.js +42 -0
- data/app/javascript/dbdoc_engine/controllers/ecommerce_controller.js +73 -0
- data/app/javascript/dbdoc_engine/controllers/group_details_controller.js +88 -0
- data/app/javascript/dbdoc_engine/controllers/import_export_controller.js +200 -0
- data/app/javascript/dbdoc_engine/controllers/index.js +9 -0
- data/app/javascript/dbdoc_engine/controllers/language_controller.js +100 -0
- data/app/javascript/dbdoc_engine/controllers/loading_spinner_controller.js +48 -0
- data/app/javascript/dbdoc_engine/controllers/login_controller.js +75 -0
- data/app/javascript/dbdoc_engine/controllers/notification_controller.js +15 -0
- data/app/javascript/dbdoc_engine/controllers/schema_diagram_controller.js +1129 -0
- data/app/javascript/dbdoc_engine/controllers/select2_controller.js +67 -0
- data/app/javascript/dbdoc_engine/controllers/sidebar_controller.js +943 -0
- data/app/javascript/dbdoc_engine/controllers/table_details_controller.js +245 -0
- data/app/javascript/dbdoc_engine/controllers/table_group_validation_controller.js +148 -0
- data/app/javascript/dbdoc_engine/controllers/table_validation_controller.js +423 -0
- data/app/jobs/dbdoc_engine/application_job.rb +4 -0
- data/app/mailers/dbdoc_engine/application_mailer.rb +6 -0
- data/app/models/dbdoc_engine/application_record.rb +6 -0
- data/app/models/dbdoc_engine/concerns/soft_deletable.rb +30 -0
- data/app/models/dbdoc_engine/db_design_changelog.rb +44 -0
- data/app/models/dbdoc_engine/db_design_dynamic_column.rb +211 -0
- data/app/models/dbdoc_engine/db_design_dynamic_table.rb +124 -0
- data/app/models/dbdoc_engine/db_design_table_group.rb +88 -0
- data/app/models/dbdoc_engine/user.rb +21 -0
- data/app/queries/dbdoc_engine/admin_dashboard_queries.rb +71 -0
- data/app/queries/dbdoc_engine/db_design_changelog_queries.rb +68 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_column_queries.rb +37 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_table_commands.rb +106 -0
- data/app/queries/dbdoc_engine/db_design_dynamic_table_queries.rb +194 -0
- data/app/queries/dbdoc_engine/db_design_table_group_queries.rb +154 -0
- data/app/services/dbdoc_engine/db_design_dynamic_table_export_service.rb +38 -0
- data/app/services/dbdoc_engine/db_design_dynamic_table_handler_service.rb +49 -0
- data/app/services/dbdoc_engine/db_design_dynamic_tables_service.rb +21 -0
- data/app/services/dbdoc_engine/error_handler_service.rb +43 -0
- data/app/services/dbdoc_engine/schema_rb_import_service.rb +194 -0
- data/app/services/dbdoc_engine/schema_rb_parser_service.rb +339 -0
- data/app/services/dbdoc_engine/table_filter_service.rb +35 -0
- data/app/services/dbdoc_engine/table_groups_service.rb +199 -0
- data/app/services/dbdoc_engine/table_management_service.rb +192 -0
- data/app/views/dbdoc_engine/admin/dashboard/_action_badge.html.erb +11 -0
- data/app/views/dbdoc_engine/admin/dashboard/_changelog_rows.html.erb +22 -0
- data/app/views/dbdoc_engine/admin/dashboard/_changelog_table_headers.html.erb +8 -0
- data/app/views/dbdoc_engine/admin/dashboard/_filter_fields.html.erb +43 -0
- data/app/views/dbdoc_engine/admin/dashboard/index.html.erb +159 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_column_fields.html.erb +225 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_deleted_table_index.html.erb +110 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_foreign_key_fields.html.erb +51 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_form.html.erb +75 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_recent_activity.html.erb +39 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_columns.html.erb +127 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_index.html.erb +109 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_information.html.erb +99 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/deleted_tables.html.erb +95 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/edit.html.erb +23 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_all_to_excel.xlsx.axlsx +240 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_to_excel.xlsx.axlsx +135 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/index.html.erb +109 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/new.html.erb +25 -0
- data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/show_table_info.html.erb +125 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_deleted_table_groups_list.html.erb +75 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_form.html.erb +88 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/_table_groups_list.html.erb +82 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/deleted_groups.html.erb +60 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/edit.html.erb +25 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/index.html.erb +85 -0
- data/app/views/dbdoc_engine/admin/db_design_table_groups/new.html.erb +26 -0
- data/app/views/dbdoc_engine/db_doc_sessions/new.html.erb +59 -0
- data/app/views/dbdoc_engine/home/changelog_details.html.erb +80 -0
- data/app/views/dbdoc_engine/home/changelogs.html.erb +20 -0
- data/app/views/dbdoc_engine/home/group_details.html.erb +94 -0
- data/app/views/dbdoc_engine/home/index.html.erb +11 -0
- data/app/views/dbdoc_engine/home/partials/_action_badge.html.erb +11 -0
- data/app/views/dbdoc_engine/home/partials/_breadcrumb_navigation.html.erb +30 -0
- data/app/views/dbdoc_engine/home/partials/_changelog_rows.html.erb +35 -0
- data/app/views/dbdoc_engine/home/partials/_changelog_table_headers.html.erb +16 -0
- data/app/views/dbdoc_engine/home/partials/_column_headers.html.erb +23 -0
- data/app/views/dbdoc_engine/home/partials/_column_row.html.erb +157 -0
- data/app/views/dbdoc_engine/home/partials/_filter_form.html.erb +47 -0
- data/app/views/dbdoc_engine/home/partials/_group_section.html.erb +84 -0
- data/app/views/dbdoc_engine/home/partials/_pagination.html.erb +5 -0
- data/app/views/dbdoc_engine/home/partials/_stats_container.html.erb +46 -0
- data/app/views/dbdoc_engine/home/partials/_table_groups.html.erb +7 -0
- data/app/views/dbdoc_engine/home/partials/_table_information_section.html.erb +50 -0
- data/app/views/dbdoc_engine/home/partials/_table_section.html.erb +48 -0
- data/app/views/dbdoc_engine/home/table_details.html.erb +9 -0
- data/app/views/dbdoc_engine/schema_diagram/index.html.erb +102 -0
- data/app/views/dbdoc_engine/shared/_admin_header.html.erb +78 -0
- data/app/views/dbdoc_engine/shared/_header.html.erb +94 -0
- data/app/views/dbdoc_engine/shared/_js_translations.html.erb +3 -0
- data/app/views/dbdoc_engine/shared/_language_button.html.erb +14 -0
- data/app/views/dbdoc_engine/shared/_sidebar.html.erb +128 -0
- data/app/views/kaminari/dbdoc_engine/_first_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_gap.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_last_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_next_page.html.erb +3 -0
- data/app/views/kaminari/dbdoc_engine/_page.html.erb +9 -0
- data/app/views/kaminari/dbdoc_engine/_paginator.html.erb +17 -0
- data/app/views/kaminari/dbdoc_engine/_prev_page.html.erb +3 -0
- data/app/views/layouts/dbdoc_engine/application.html.erb +107 -0
- data/app/views/layouts/dbdoc_engine/header.html.erb +108 -0
- data/config/importmap.rb +11 -0
- data/config/locales/en.yml +307 -0
- data/config/locales/ja.yml +306 -0
- data/config/routes.rb +73 -0
- data/db/migrate/rails7/20250227060610_create_db_design_table_groups.rb +15 -0
- data/db/migrate/rails7/20250227094626_create_db_design_dynamic_tables.rb +19 -0
- data/db/migrate/rails7/20250228022732_create_db_design_dynamic_columns.rb +34 -0
- data/db/migrate/rails7/20250401051453_create_db_design_changelogs.rb +26 -0
- data/db/migrate/rails7/20250411040822_create_users.rb +14 -0
- data/db/migrate/rails7/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
- data/db/migrate/rails8/20250227060610_create_db_design_table_groups.rb +15 -0
- data/db/migrate/rails8/20250227094626_create_db_design_dynamic_tables.rb +19 -0
- data/db/migrate/rails8/20250228022732_create_db_design_dynamic_columns.rb +34 -0
- data/db/migrate/rails8/20250401051453_create_db_design_changelogs.rb +26 -0
- data/db/migrate/rails8/20250411040822_create_users.rb +14 -0
- data/db/migrate/rails8/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
- data/db/seeds.rb +28 -0
- data/lib/dbdoc_engine/engine.rb +57 -0
- data/lib/dbdoc_engine/version.rb +3 -0
- data/lib/dbdoc_engine.rb +9 -0
- data/lib/generators/dbdoc_engine/install/install_generator.rb +245 -0
- data/lib/generators/dbdoc_engine/uninstall/uninstall_generator.rb +196 -0
- data/lib/tasks/dbdoc_engine_tasks.rake +44 -0
- data/public/dbdoc_engine_assets/images/camel_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/dblogo.svg +4 -0
- data/public/dbdoc_engine_assets/images/japan_circle.png +0 -0
- data/public/dbdoc_engine_assets/images/king_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/login-bg.svg +44 -0
- data/public/dbdoc_engine_assets/images/logo.png +0 -0
- data/public/dbdoc_engine_assets/images/logo.svg +12 -0
- data/public/dbdoc_engine_assets/images/queen_chess_head.png +0 -0
- data/public/dbdoc_engine_assets/images/soldier_chess_headd.png +0 -0
- data/public/dbdoc_engine_assets/images/uk_circle_transparent.png +0 -0
- metadata +415 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stimulus controller that handles form validation for database table creation/editing
|
|
5
|
+
* Validates table information, columns, and checks for duplicate column names
|
|
6
|
+
*/
|
|
7
|
+
export default class extends Controller {
|
|
8
|
+
// Define target elements that can be referenced in the controller
|
|
9
|
+
static targets = ["tableName", "physicalTableName", "tableGroup", "createdBy", "updatedBy"]
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Connect lifecycle method - called when controller is connected to the DOM
|
|
13
|
+
* Sets up event listeners for real-time data syncing
|
|
14
|
+
*/
|
|
15
|
+
connect() {
|
|
16
|
+
// Check if we're in create mode (hidden updated_by field exists)
|
|
17
|
+
const hiddenUpdatedBy = this.element.querySelector('input[name$="[updated_by]"][type="hidden"]');
|
|
18
|
+
const createdBy = this.element.querySelector('input[name$="[created_by]"]');
|
|
19
|
+
|
|
20
|
+
// If both fields exist, set up the sync between created_by and updated_by
|
|
21
|
+
if (hiddenUpdatedBy && createdBy) {
|
|
22
|
+
// Sync on input changes
|
|
23
|
+
createdBy.addEventListener('input', () => {
|
|
24
|
+
hiddenUpdatedBy.value = createdBy.value;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Main validation method triggered on form submission
|
|
31
|
+
* Prevents default form submission, validates all required fields,
|
|
32
|
+
* and only submits if all validations pass
|
|
33
|
+
*
|
|
34
|
+
* @param {Event} event - The form submission event
|
|
35
|
+
*/
|
|
36
|
+
validate(event) {
|
|
37
|
+
event.preventDefault()
|
|
38
|
+
let isValid = true
|
|
39
|
+
|
|
40
|
+
// Reset previous error states
|
|
41
|
+
this.clearErrors()
|
|
42
|
+
|
|
43
|
+
// For new record: Make sure hidden updated_by is synced with created_by before validation
|
|
44
|
+
const hiddenUpdatedBy = this.element.querySelector('input[name$="[updated_by]"][type="hidden"]');
|
|
45
|
+
const createdBy = this.element.querySelector('input[name$="[created_by]"]');
|
|
46
|
+
if (hiddenUpdatedBy && createdBy) {
|
|
47
|
+
hiddenUpdatedBy.value = createdBy.value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Store all invalid elements for scrolling later
|
|
51
|
+
const invalidElements = []
|
|
52
|
+
|
|
53
|
+
// Run all validation checks and aggregate results
|
|
54
|
+
isValid = this.validateTableInformation(invalidElements) && isValid
|
|
55
|
+
isValid = this.validateColumns(invalidElements) && isValid
|
|
56
|
+
isValid = this.checkDuplicateColumnNames(invalidElements) && isValid
|
|
57
|
+
|
|
58
|
+
if (isValid) {
|
|
59
|
+
event.target.submit() // If everything is valid, submit the form
|
|
60
|
+
} else {
|
|
61
|
+
// If not valid, scroll to the first invalid element
|
|
62
|
+
if (invalidElements.length > 0) {
|
|
63
|
+
// Expand accordion section if the error is in a column
|
|
64
|
+
const accordionParent = this.findAccordionParent(invalidElements[0])
|
|
65
|
+
if (accordionParent) {
|
|
66
|
+
this.expandAccordion(accordionParent)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Scroll to the element with error
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
invalidElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
72
|
+
invalidElements[0].focus()
|
|
73
|
+
}, accordionParent ? 400 : 0) // Small delay when expanding accordion
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Validates the main table information fields
|
|
80
|
+
* Checks for required values in table name, physical name, group, created by fields,
|
|
81
|
+
* and updated_by when editing an existing table
|
|
82
|
+
*
|
|
83
|
+
* @param {Array} invalidElements - Array to collect invalid elements
|
|
84
|
+
* @returns {boolean} - True if all table information is valid, false otherwise
|
|
85
|
+
*/
|
|
86
|
+
validateTableInformation(invalidElements = []) {
|
|
87
|
+
let isValid = true;
|
|
88
|
+
|
|
89
|
+
// Select input fields safely using attribute selectors
|
|
90
|
+
const tableName = this.element.querySelector('[name$="[table_name]"]');
|
|
91
|
+
const physicalTableName = this.element.querySelector('[name$="[physical_table_name]"]');
|
|
92
|
+
const tableGroup = this.element.querySelector('[name$="[db_design_table_group_id]"]');
|
|
93
|
+
const createdBy = this.element.querySelector('[name$="[created_by]"]');
|
|
94
|
+
|
|
95
|
+
// Updated by field - first get the visible one (not hidden)
|
|
96
|
+
const visibleUpdatedBy = this.element.querySelector('input[name$="[updated_by]"]:not([type="hidden"])');
|
|
97
|
+
// Determines if we're in edit mode (has visible updated_by field)
|
|
98
|
+
const isEditing = !!visibleUpdatedBy;
|
|
99
|
+
|
|
100
|
+
// Validate each field only if it exists, showing appropriate errors
|
|
101
|
+
if (!tableName || !tableName.value.trim()) {
|
|
102
|
+
this.showError(tableName, "table_name_required");
|
|
103
|
+
invalidElements.push(tableName);
|
|
104
|
+
isValid = false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!physicalTableName || !physicalTableName.value.trim()) {
|
|
108
|
+
this.showError(physicalTableName, "physical_table_name_required");
|
|
109
|
+
if (!invalidElements.length) invalidElements.push(physicalTableName);
|
|
110
|
+
isValid = false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!tableGroup || !tableGroup.value.trim()) {
|
|
114
|
+
this.showError(tableGroup, "table_group_must_be_selected");
|
|
115
|
+
if (!invalidElements.length) invalidElements.push(tableGroup);
|
|
116
|
+
isValid = false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!createdBy || !createdBy.value.trim()) {
|
|
120
|
+
this.showError(createdBy, "created_by_field_required");
|
|
121
|
+
if (!invalidElements.length) invalidElements.push(createdBy);
|
|
122
|
+
isValid = false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only validate updated_by when editing (only if visible updated_by exists)
|
|
126
|
+
if (isEditing && (!visibleUpdatedBy.value.trim())) {
|
|
127
|
+
this.showError(visibleUpdatedBy, "updated_by_required");
|
|
128
|
+
if (!invalidElements.length) invalidElements.push(visibleUpdatedBy);
|
|
129
|
+
isValid = false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return isValid;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validates all column fields for each table column
|
|
137
|
+
* Skips columns marked for deletion and validates required fields
|
|
138
|
+
* Performs special validation for foreign keys and data types with length requirements
|
|
139
|
+
*
|
|
140
|
+
* @param {Array} invalidElements - Array to collect invalid elements
|
|
141
|
+
* @returns {boolean} - True if all columns are valid, false otherwise
|
|
142
|
+
*/
|
|
143
|
+
validateColumns(invalidElements = []) {
|
|
144
|
+
const columnContainers = document.querySelectorAll('.column-fields')
|
|
145
|
+
let allColumnsValid = true
|
|
146
|
+
|
|
147
|
+
columnContainers.forEach((container) => {
|
|
148
|
+
// Skip columns marked for deletion
|
|
149
|
+
const destroyInput = container.querySelector('[name$="[_destroy]"]')
|
|
150
|
+
if (destroyInput && destroyInput.value === '1') {
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Select column fields
|
|
155
|
+
const columnName = container.querySelector('[name$="[column_name]"]')
|
|
156
|
+
const physicalColumnName = container.querySelector('[name$="[physical_column_name]"]')
|
|
157
|
+
const dataType = container.querySelector('[name$="[data_type]"]')
|
|
158
|
+
const length = container.querySelector('[name$="[length]"]')
|
|
159
|
+
const createdBy = container.querySelector('[name$="[created_by]"]')
|
|
160
|
+
|
|
161
|
+
// Find foreign key related elements with more robust selection
|
|
162
|
+
const foreignKeyCheckbox = container.querySelector('[name$="[is_foreign_key]"]')
|
|
163
|
+
const foreignKeyFieldsContainer = container.querySelector('.foreign-key-fields')
|
|
164
|
+
const referencedTable = container.querySelector('[name$="[foreign_table_name]"]')
|
|
165
|
+
const referencedColumn = container.querySelector('[name$="[foreign_column_name]"]')
|
|
166
|
+
const relationshipType = container.querySelector('[name$="[relationship_type]"]')
|
|
167
|
+
|
|
168
|
+
// Multiple ways to determine if foreign key is active
|
|
169
|
+
const isForeignKeyChecked = foreignKeyCheckbox?.checked ||
|
|
170
|
+
foreignKeyCheckbox?.value === '1' ||
|
|
171
|
+
!foreignKeyFieldsContainer?.classList.contains('hidden')
|
|
172
|
+
|
|
173
|
+
let columnHasError = false;
|
|
174
|
+
|
|
175
|
+
// Validate required column fields
|
|
176
|
+
if (!columnName.value.trim()) {
|
|
177
|
+
this.showError(columnName, "column_name_required")
|
|
178
|
+
if (!columnHasError) {
|
|
179
|
+
invalidElements.push(columnName)
|
|
180
|
+
columnHasError = true
|
|
181
|
+
}
|
|
182
|
+
allColumnsValid = false
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!physicalColumnName.value.trim()) {
|
|
186
|
+
this.showError(physicalColumnName, "physical_column_name_required")
|
|
187
|
+
if (!columnHasError) {
|
|
188
|
+
invalidElements.push(physicalColumnName)
|
|
189
|
+
columnHasError = true
|
|
190
|
+
}
|
|
191
|
+
allColumnsValid = false
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!dataType.value) {
|
|
195
|
+
this.showError(dataType, "data_type_must_be_selected")
|
|
196
|
+
if (!columnHasError) {
|
|
197
|
+
invalidElements.push(dataType)
|
|
198
|
+
columnHasError = true
|
|
199
|
+
}
|
|
200
|
+
allColumnsValid = false
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Special validation for data types that require length specification
|
|
204
|
+
const lengthRequiredTypes = ['varchar', 'character', 'char']
|
|
205
|
+
if (lengthRequiredTypes.includes(dataType.value) && (!length.value || parseInt(length.value) <= 0)) {
|
|
206
|
+
this.showError(length, "length_is_required_for_this_data_type")
|
|
207
|
+
if (!columnHasError) {
|
|
208
|
+
invalidElements.push(length)
|
|
209
|
+
columnHasError = true
|
|
210
|
+
}
|
|
211
|
+
allColumnsValid = false
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!createdBy.value.trim()) {
|
|
215
|
+
this.showError(createdBy, "created_by_field_required")
|
|
216
|
+
if (!columnHasError) {
|
|
217
|
+
invalidElements.push(createdBy)
|
|
218
|
+
columnHasError = true
|
|
219
|
+
}
|
|
220
|
+
allColumnsValid = false
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Enhanced foreign key validation - only validate if it's marked as a foreign key
|
|
224
|
+
if (isForeignKeyChecked) {
|
|
225
|
+
// Check if referenced table is selected
|
|
226
|
+
if (!referencedTable || !referencedTable.value.trim()) {
|
|
227
|
+
this.showError(referencedTable, "referenced_table_is_required_for_foreign_key")
|
|
228
|
+
if (!columnHasError) {
|
|
229
|
+
invalidElements.push(referencedTable)
|
|
230
|
+
columnHasError = true
|
|
231
|
+
}
|
|
232
|
+
allColumnsValid = false
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check if referenced column is selected
|
|
236
|
+
if (!referencedColumn || !referencedColumn.value.trim()) {
|
|
237
|
+
this.showError(referencedColumn, "referenced_column_is_required_for_foreign_key")
|
|
238
|
+
if (!columnHasError) {
|
|
239
|
+
invalidElements.push(referencedColumn)
|
|
240
|
+
columnHasError = true
|
|
241
|
+
}
|
|
242
|
+
allColumnsValid = false
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!relationshipType || !relationshipType.value.trim()) {
|
|
246
|
+
this.showError(relationshipType, "relationship_type_is_required_for_foreign_key")
|
|
247
|
+
if (!columnHasError) {
|
|
248
|
+
invalidElements.push(relationshipType)
|
|
249
|
+
columnHasError = true
|
|
250
|
+
}
|
|
251
|
+
allColumnsValid = false
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return allColumnsValid
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Checks for duplicate column names within the table
|
|
261
|
+
* Collects all active column names and marks duplicates with errors
|
|
262
|
+
*
|
|
263
|
+
* @param {Array} invalidElements - Array to collect invalid elements
|
|
264
|
+
* @returns {boolean} - True if no duplicates found, false otherwise
|
|
265
|
+
*/
|
|
266
|
+
checkDuplicateColumnNames(invalidElements = []) {
|
|
267
|
+
const columnContainers = document.querySelectorAll('.column-fields')
|
|
268
|
+
const columnNames = new Map()
|
|
269
|
+
let hasDuplicates = false
|
|
270
|
+
|
|
271
|
+
// First pass: collect all active column names
|
|
272
|
+
columnContainers.forEach((container) => {
|
|
273
|
+
// Skip columns marked for deletion
|
|
274
|
+
const destroyInput = container.querySelector('[name$="[_destroy]"]')
|
|
275
|
+
if (destroyInput && destroyInput.value === '1') {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const columnNameInput = container.querySelector('[name$="[column_name]"]')
|
|
280
|
+
if (columnNameInput && columnNameInput.value.trim()) {
|
|
281
|
+
const columnName = columnNameInput.value.trim().toLowerCase()
|
|
282
|
+
|
|
283
|
+
if (columnNames.has(columnName)) {
|
|
284
|
+
// Store references to all inputs with this name for error marking
|
|
285
|
+
columnNames.set(columnName, [...columnNames.get(columnName), columnNameInput])
|
|
286
|
+
hasDuplicates = true
|
|
287
|
+
} else {
|
|
288
|
+
columnNames.set(columnName, [columnNameInput])
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
// Second pass: show errors for duplicates
|
|
294
|
+
if (hasDuplicates) {
|
|
295
|
+
columnNames.forEach((inputs, name) => {
|
|
296
|
+
if (inputs.length > 1) {
|
|
297
|
+
inputs.forEach((input, index) => {
|
|
298
|
+
this.showError(input, "duplicate_columns")
|
|
299
|
+
// Only add the first instance of each duplicate to the invalid elements
|
|
300
|
+
if (index === 0 && invalidElements.length === 0) {
|
|
301
|
+
invalidElements.push(input)
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
return false
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Displays an error message for an input element
|
|
314
|
+
* Adds is-invalid class and creates/updates an error message div
|
|
315
|
+
*
|
|
316
|
+
* @param {HTMLElement} element - The input element to mark as invalid
|
|
317
|
+
* @param {string} messageKey - The translation key for the error message
|
|
318
|
+
*/
|
|
319
|
+
showError(element, messageKey) {
|
|
320
|
+
if (!element) return
|
|
321
|
+
|
|
322
|
+
// Add 'is-invalid' class to input/select field
|
|
323
|
+
element.classList.add('is-invalid')
|
|
324
|
+
|
|
325
|
+
// Get translated message from the hidden translations div
|
|
326
|
+
const translatedMessage = this.getTranslation(messageKey)
|
|
327
|
+
|
|
328
|
+
// First, remove any existing error messages for this element
|
|
329
|
+
const existingErrors = element.parentNode.querySelectorAll('.invalid-feedback')
|
|
330
|
+
existingErrors.forEach(errorEl => errorEl.remove())
|
|
331
|
+
|
|
332
|
+
// Create a new error message element
|
|
333
|
+
const errorElement = document.createElement('div')
|
|
334
|
+
errorElement.classList.add('invalid-feedback', 'd-block')
|
|
335
|
+
errorElement.textContent = translatedMessage
|
|
336
|
+
|
|
337
|
+
// Append it after the input element
|
|
338
|
+
element.parentNode.appendChild(errorElement)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Retrieves a translated error message using a global translations object
|
|
343
|
+
* Falls back to the key itself if translation not found
|
|
344
|
+
*
|
|
345
|
+
* @param {string} key - The translation key
|
|
346
|
+
* @returns {string} - The translated message or the key if not found
|
|
347
|
+
*/
|
|
348
|
+
getTranslation(key) {
|
|
349
|
+
// Use the global Translations object if available
|
|
350
|
+
if (window.Translations && window.Translations[key]) {
|
|
351
|
+
return window.Translations[key];
|
|
352
|
+
}
|
|
353
|
+
return key;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Clears all validation errors from the form
|
|
358
|
+
* Removes is-invalid classes and error message elements
|
|
359
|
+
*/
|
|
360
|
+
clearErrors() {
|
|
361
|
+
// Remove 'is-invalid' class and error messages
|
|
362
|
+
this.element.querySelectorAll('.is-invalid').forEach(el => {
|
|
363
|
+
el.classList.remove('is-invalid')
|
|
364
|
+
const errorEl = el.nextElementSibling
|
|
365
|
+
if (errorEl && errorEl.classList.contains('invalid-feedback')) {
|
|
366
|
+
errorEl.remove()
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Finds the closest accordion parent element for an input
|
|
373
|
+
* Updated to work with custom dbdoc-accordion controller
|
|
374
|
+
*
|
|
375
|
+
* @param {HTMLElement} element - The input element
|
|
376
|
+
* @returns {HTMLElement|null} - The accordion item element or null
|
|
377
|
+
*/
|
|
378
|
+
findAccordionParent(element) {
|
|
379
|
+
if (!element) return null
|
|
380
|
+
|
|
381
|
+
// Look for the accordion item that contains the dbdoc-accordion controller
|
|
382
|
+
let parent = element.closest('[data-controller*="dbdoc-accordion"]')
|
|
383
|
+
|
|
384
|
+
return parent
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Expands an accordion section using the custom dbdoc-accordion controller
|
|
389
|
+
*
|
|
390
|
+
* @param {HTMLElement} accordionElement - The accordion item element with dbdoc-accordion controller
|
|
391
|
+
*/
|
|
392
|
+
expandAccordion(accordionElement) {
|
|
393
|
+
if (!accordionElement) return
|
|
394
|
+
|
|
395
|
+
// Get the Stimulus controller instance for the accordion
|
|
396
|
+
const accordionController = window.Stimulus.getControllerForElementAndIdentifier(accordionElement, 'dbdoc-accordion')
|
|
397
|
+
|
|
398
|
+
if (accordionController) {
|
|
399
|
+
// Check if it's already open by looking at the content target
|
|
400
|
+
const contentTarget = accordionController.contentTarget
|
|
401
|
+
const isOpen = contentTarget && contentTarget.classList.contains('show')
|
|
402
|
+
|
|
403
|
+
if (!isOpen) {
|
|
404
|
+
// Use the controller's open method to expand the accordion
|
|
405
|
+
accordionController.open()
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
// Fallback method if controller instance is not accessible
|
|
409
|
+
const contentElement = accordionElement.querySelector('[data-dbdoc-accordion-target="content"]')
|
|
410
|
+
const buttonElement = accordionElement.querySelector('[data-dbdoc-accordion-target="button"]')
|
|
411
|
+
|
|
412
|
+
if (contentElement && buttonElement) {
|
|
413
|
+
const isOpen = contentElement.classList.contains('show')
|
|
414
|
+
|
|
415
|
+
if (!isOpen) {
|
|
416
|
+
contentElement.classList.add('show')
|
|
417
|
+
buttonElement.setAttribute('aria-expanded', 'true')
|
|
418
|
+
buttonElement.classList.remove('collapsed')
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# app/models/concerns/soft_deletable.rb
|
|
2
|
+
module DbdocEngine::Concerns::SoftDeletable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
scope :without_deleted, -> { where(deleted_at: nil) }
|
|
7
|
+
scope :with_deleted, -> { unscope(where: :deleted_at) }
|
|
8
|
+
scope :only_deleted, -> { unscope(where: :deleted_at).where.not(deleted_at: nil) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def deleted?
|
|
12
|
+
deleted_at.present?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def soft_delete
|
|
16
|
+
update(deleted_at: Time.current)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def restore
|
|
20
|
+
update(deleted_at: nil)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.soft_delete_all
|
|
24
|
+
update_all(deleted_at: Time.current)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.restore_all
|
|
28
|
+
update_all(deleted_at: nil)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Model for logging changes made to tables, table groups, and columns.
|
|
2
|
+
# It tracks actions such as create, update, and delete, and stores related metadata like entity names,
|
|
3
|
+
# descriptions, and the user who made the changes.
|
|
4
|
+
module DbdocEngine
|
|
5
|
+
class DbDesignChangelog < ApplicationRecord
|
|
6
|
+
self.table_name = 'db_design_changelogs'
|
|
7
|
+
# Constants for action types
|
|
8
|
+
ACTION_CREATE = 'create'.freeze
|
|
9
|
+
ACTION_UPDATE = 'update'.freeze
|
|
10
|
+
ACTION_DELETE = 'delete'.freeze
|
|
11
|
+
|
|
12
|
+
# Constants for entity types
|
|
13
|
+
ENTITY_TABLE_GROUP = 'table_group'.freeze
|
|
14
|
+
ENTITY_TABLE = 'table'.freeze
|
|
15
|
+
ENTITY_COLUMN = 'column'.freeze
|
|
16
|
+
|
|
17
|
+
# Validations
|
|
18
|
+
validates :change_timestamp, presence: true
|
|
19
|
+
|
|
20
|
+
# Ensures action type is present and valid
|
|
21
|
+
validates :action_type, presence: true, inclusion: { in: [ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE] }
|
|
22
|
+
|
|
23
|
+
# Ensures entity type is present and valid
|
|
24
|
+
validates :entity_type, presence: true, inclusion: { in: [ENTITY_TABLE_GROUP, ENTITY_TABLE, ENTITY_COLUMN] }
|
|
25
|
+
|
|
26
|
+
# Ensures entity name is provided
|
|
27
|
+
validates :entity_name, presence: true
|
|
28
|
+
|
|
29
|
+
# Ensures a description of the change is provided
|
|
30
|
+
validates :description, presence: true
|
|
31
|
+
|
|
32
|
+
# Ensures the name of the user who made the change is provided
|
|
33
|
+
validates :changed_by, presence: true
|
|
34
|
+
|
|
35
|
+
# Helper method to create changelog entry
|
|
36
|
+
def self.log_change(*args)
|
|
37
|
+
DbdocEngine::DbDesignChangelogQueries.log_change(*args)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.filtered_changelogs(params)
|
|
41
|
+
DbdocEngine::DbDesignChangelogQueries.filtered_changelogs(params)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|