From 680cc90b2c1109302c05efa3b1c3db828df1ca08 Mon Sep 17 00:00:00 2001 From: Danny Atthaya Date: Fri, 9 Jul 2021 11:03:16 +0700 Subject: [PATCH] Add foreign key and soft delete --- src/Badaso.php | 1 - src/ContentManager/FileGenerator.php | 27 +- src/Controllers/BadasoDatabaseController.php | 22 +- src/Database/Schema/SchemaManager.php | 5 + src/Helpers/MigrationParser.php | 260 ++++++++++++-- .../js/assets/scss/layout/_custom.scss | 4 + .../js/assets/scss/module/_alert.scss | 3 +- .../scss/page/_database-management.scss | 20 ++ src/resources/js/lang/modules/en.js | 2 +- .../js/pages/database-management/add.vue | 221 +++++++++--- .../js/pages/database-management/edit.vue | 332 ++++++++++++++---- src/resources/js/utils/database-helper.js | 26 ++ 12 files changed, 761 insertions(+), 162 deletions(-) diff --git a/src/Badaso.php b/src/Badaso.php index 7d87bd607..78ed26e85 100644 --- a/src/Badaso.php +++ b/src/Badaso.php @@ -77,7 +77,6 @@ class Badaso 'password_resets', 'menus', 'menu_items', - 'users', 'roles', 'permissions', 'configurations', diff --git a/src/ContentManager/FileGenerator.php b/src/ContentManager/FileGenerator.php index a614438dc..01b91d25e 100644 --- a/src/ContentManager/FileGenerator.php +++ b/src/ContentManager/FileGenerator.php @@ -287,7 +287,7 @@ public function repackSeedData($data): array /** * Generate Badaso Migration File. */ - public function generateBDOMigrationFile(string $table_name, string $prefix, array $rows): string + public function generateBDOMigrationFile(string $table_name, string $prefix, array $rows, array $relations = []): string { $migration_class_name = $this->file_system->generateMigrationClassName($table_name, $prefix); @@ -301,8 +301,15 @@ public function generateBDOMigrationFile(string $table_name, string $prefix, arr $migration_file = $this->file_system->getMigrationFile($migration_file_name, $migration_folder_path); - $schema_up = $this->migration_parser->getMigrationSchemaUp($table_name, $rows, $prefix); - $schema_down = $this->migration_parser->getMigrationSchemaDown($table_name, $rows, $prefix); + $schema_up = ''; + $schema_down = ''; + + $schema_up .= $this->migration_parser->getMigrationSchemaUp($table_name, $rows, $prefix); + if (! empty($relations)) { + $schema_up .= PHP_EOL.PHP_EOL.$this->migration_parser->getMigrationRelationshipSchemaUp($table_name, $relations); + $schema_down .= $this->migration_parser->getMigrationRelationshipSchemaDown($table_name, $relations).PHP_EOL.PHP_EOL; + } + $schema_down .= $this->migration_parser->getMigrationSchemaDown($table_name, $rows, $prefix); $stub = $this->content_manager->replaceString('{{class}}', $migration_class_name, $stub); $stub = $this->content_manager->replaceString('{{schema_up}}', $schema_up, $stub); @@ -324,7 +331,7 @@ public function deleteMigrationFiles(string $file_name) /** * Generate Badaso Alter Migration File. */ - public function generateBDOAlterMigrationFile(array $table, array $rows = null, string $prefix): string + public function generateBDOAlterMigrationFile(array $table, array $rows = null, string $prefix, array $relations = []): string { $migration_class_name = $this->file_system->generateAlterMigrationClassName($table, $prefix); @@ -337,8 +344,16 @@ public function generateBDOAlterMigrationFile(array $table, array $rows = null, $migration_folder_path = $this->file_system->getMigrationFolderPath(); $migration_file = $this->file_system->getMigrationFile($migration_file_name, $migration_folder_path); - $schema_up = $this->migration_parser->getAlterMigrationSchemaUp($table, $rows, $prefix); - $schema_down = $this->migration_parser->getAlterMigrationSchemaDown($table, $rows, $prefix); + + $schema_up = ''; + $schema_down = ''; + + $schema_up .= $this->migration_parser->getAlterMigrationSchemaUp($table, $rows, $prefix, $relations); + if (array_key_exists('current_relations', $relations) && count($relations['current_relations']) > 0) { + $schema_up .= $this->migration_parser->getAlterMigrationRelationshipSchemaUp($table, $relations); + $schema_down .= $this->migration_parser->getAlterMigrationRelationshipSchemaDown($table, $relations).PHP_EOL; + } + $schema_down .= $this->migration_parser->getAlterMigrationSchemaDown($table, $rows, $prefix, $relations); $stub = $this->content_manager->replaceString('{{class}}', $migration_class_name, $stub); $stub = $this->content_manager->replaceString('{{schema_up}}', $schema_up, $stub); diff --git a/src/Controllers/BadasoDatabaseController.php b/src/Controllers/BadasoDatabaseController.php index 0ab7a54ba..504936e7f 100644 --- a/src/Controllers/BadasoDatabaseController.php +++ b/src/Controllers/BadasoDatabaseController.php @@ -45,7 +45,7 @@ function ($attribute, $value, $fail) { 'rows.*.field_type' => 'required|string', ]); - $this->file_name = $this->file_generator->generateBDOMigrationFile($request->table, 'create', $request->rows); + $this->file_name = $this->file_generator->generateBDOMigrationFile($request->table, 'create', $request->rows, $request->relations); $exitCode = Artisan::call('migrate', [ '--path' => 'database/migrations/badaso/', @@ -89,8 +89,19 @@ function ($attribute, $value, $fail) { ]); $columns = SchemaManager::describeTable($request->table)->toArray(); + $columnsFK = SchemaManager::getDoctrineForeignKeys($request->table); + $fKConstraints = []; + foreach ($columnsFK as $columnFK) { + $fKConstraints[$columnFK->getUnquotedLocalColumns()[0]] = [ + 'source_field' => $columnFK->getUnquotedLocalColumns()[0], + 'target_table' => $columnFK->getForeignTableName(), + 'target_field' => $columnFK->getForeignColumns()[0], + 'on_delete' => strtolower($columnFK->getOption('onDelete')), + 'on_update' => strtolower($columnFK->getOption('onUpdate')), + ]; + } - return ApiResponse::success(['columns' => $columns]); + return ApiResponse::success(['columns' => $columns, 'columnsFK' => $fKConstraints]); } catch (Exception $e) { return APIResponse::failed($e); } @@ -114,15 +125,16 @@ function ($attribute, $value, $fail) { Rule::notIn(Badaso::getProtectedTables()), ], 'fields.current_fields' => 'required|array', - 'fields.modified_fields' => 'nullable|array', + 'fields.modified_fields' => 'required|array', ]); $data = $request->all(); $fields = $data['fields']; $table = $data['table']; + $relations = $data['relations']; - if (count($fields['current_fields']) > 0) { - $this->file_name[] = $this->file_generator->generateBDOAlterMigrationFile($table, $fields, 'alter'); + if (count($fields['modified_fields']) > 0) { + $this->file_name[] = $this->file_generator->generateBDOAlterMigrationFile($table, $fields, 'alter', $relations); } if ($table['current_name'] !== $table['modified_name']) { diff --git a/src/Database/Schema/SchemaManager.php b/src/Database/Schema/SchemaManager.php index ee8552723..209c9c421 100644 --- a/src/Database/Schema/SchemaManager.php +++ b/src/Database/Schema/SchemaManager.php @@ -140,4 +140,9 @@ public static function getDoctrineColumn($table, $column) { return static::getDoctrineTable($table)->getColumn($column); } + + public static function getDoctrineForeignKeys($table) + { + return static::manager()->listTableForeignKeys($table); + } } diff --git a/src/Helpers/MigrationParser.php b/src/Helpers/MigrationParser.php index 2817946ca..1c8de475a 100644 --- a/src/Helpers/MigrationParser.php +++ b/src/Helpers/MigrationParser.php @@ -146,6 +146,22 @@ class MigrationParser DB::statement('ALTER TABLE %s ALTER COLUMN %s DROP DEFAULT'); TXT; + const ADD_FOREIGN_KEY = <<<'TXT' + $table->foreign('%s')->references('%s')->on('%s') + TXT; + + const DROP_FOREIGN_KEY = <<<'TXT' + $table->dropForeign([%s]); + TXT; + + const ADD_FOREIGN_KEY_ON_DELETE = <<<'TXT' + ->onDelete('%s') + TXT; + + const ADD_FOREIGN_KEY_ON_UPDATE = <<<'TXT' + ->onUpdate('%s') + TXT; + public static function getMigrationSchemaUp($name, $rows, $prefix = null) { if ($prefix == 'drop') { @@ -158,6 +174,36 @@ public static function getMigrationSchemaUp($name, $rows, $prefix = null) return sprintf(self::MIGRATION_UP_WRAPPER, $name, implode(PHP_EOL.chr(9).chr(9).chr(9), self::getMigrationFields($name, $rows))); } + public static function getMigrationRelationshipSchemaUp($name, $relations) + { + return sprintf(self::ALTER_WRAPPER, $name, implode(PHP_EOL.chr(9).chr(9).chr(9), self::getMigrationRelationshipUp($name, $relations))); + } + + public static function getAlterMigrationRelationshipSchemaUp($name, $relations) + { + if (implode(PHP_EOL.chr(9).chr(9).chr(9), self::getAlterMigrationRelationshipUp($relations)) !== '') { + return sprintf(self::ALTER_WRAPPER, + $name['current_name'], + implode(PHP_EOL.chr(9).chr(9).chr(9), self::getAlterMigrationRelationshipUp($relations)) + ); + } + } + + public static function getMigrationRelationshipSchemaDown($name, $relations) + { + return sprintf(self::ALTER_WRAPPER, $name, self::getMigrationRelationshipDown($relations)); + } + + public static function getAlterMigrationRelationshipSchemaDown($name, $relations) + { + if (implode(PHP_EOL.chr(9).chr(9).chr(9), self::getAlterMigrationRelationshipDown($relations)) !== '') { + return sprintf(self::ALTER_WRAPPER, + $name['current_name'], + implode(PHP_EOL.chr(9).chr(9).chr(9), self::getAlterMigrationRelationshipDown($relations)) + ); + } + } + public static function getMigrationSchemaDown($name, $rows = [], $prefix = null, bool $timestamp = true) { if ($prefix == 'drop') { @@ -170,11 +216,13 @@ public static function getMigrationSchemaDown($name, $rows = [], $prefix = null, ); } - public static function getAlterMigrationSchemaUp($name, $rows, $prefix = null) + public static function getAlterMigrationSchemaUp($name, $rows, $prefix = null, $relations = []) { $stub = ''; + $dropped_fk_field = ''; + if ($prefix == 'rename') { - $stub .= sprintf( + $stub .= chr(9).chr(9).chr(9).sprintf( self::RENAME_TABLE_WRAPPER, $name['current_name'], $name['modified_name'], @@ -187,12 +235,23 @@ public static function getAlterMigrationSchemaUp($name, $rows, $prefix = null) $dropped_field = 0; $altered_field = 0; $added_field = 0; + $dropped_fk = 0; $fields = []; $modified = []; $alter = []; + $fk = []; $rows = self::formatRows($rows); + if (! empty($relations)) { + foreach ($relations['modified_relations'] as $key => $relation) { + if (array_key_exists('modify_type', $relation) && $relation['modify_type'] === 'DROP_FOREIGN_KEY') { + $dropped_fk++; + $fk[] = $relation; + } + } + } + foreach ($rows['current_fields'] as $key => $value) { if (! array_key_exists($key, $rows['modified_fields'])) { $dropped_field++; @@ -239,6 +298,14 @@ public static function getAlterMigrationSchemaUp($name, $rows, $prefix = null) } } + if ($dropped_fk > 0) { + $fk_fields = []; + foreach ($fk as $key => $value) { + $fk_fields[] = sprintf(self::DROP_FOREIGN_KEY, "'".$value['source_field']."'"); + } + $dropped_fk_field = sprintf(self::ALTER_WRAPPER, $name['current_name'], implode(PHP_EOL.chr(9).chr(9).chr(9), $fk_fields)); + } + if ($altered_field > 0) { $fields = self::getAlterMigrationUpFields($rows, $name); /** @@ -309,14 +376,15 @@ public static function getAlterMigrationSchemaUp($name, $rows, $prefix = null) } } - return $stub; + return ($dropped_fk_field ? $dropped_fk_field.PHP_EOL.PHP_EOL : null).$stub; } - public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix = null) + public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix = null, $relations = []) { $stub = ''; + $dropped_fk_field = ''; if ($prefix == 'rename') { - $stub .= sprintf( + $stub .= chr(9).chr(9).chr(9).sprintf( self::RENAME_TABLE_WRAPPER, $name['modified_name'], $name['current_name'], @@ -326,9 +394,20 @@ public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix $dropped_field = 0; $altered_field = 0; $added_field = 0; + $dropped_fk = 0; $fields = []; $modified = []; $alter = []; + $fk = []; + + if (! empty($relations)) { + foreach ($relations['modified_relations'] as $key => $relation) { + if (array_key_exists('modify_type', $relation) && $relation['modify_type'] === 'DROP_FOREIGN_KEY') { + $dropped_fk++; + $fk[] = $relation; + } + } + } $rows = self::formatRows($rows); @@ -378,6 +457,23 @@ public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix } } + if ($dropped_fk > 0) { + $fk_fields = []; + + foreach ($fk as $key => $value) { + $temp_field = sprintf(self::ADD_FOREIGN_KEY, $value['source_field'], $value['target_field'], $value['target_table']); + if (! empty($value['on_delete'])) { + $temp_field .= sprintf(self::ADD_FOREIGN_KEY_ON_DELETE, $value['on_delete']); + } + if (! empty($value['on_update'])) { + $temp_field .= sprintf(self::ADD_FOREIGN_KEY_ON_UPDATE, $value['on_update']); + } + $fk_fields[] = $temp_field.';'; + } + + $dropped_fk_field = sprintf(self::ALTER_WRAPPER, $name['current_name'], implode(PHP_EOL.chr(9).chr(9).chr(9), $fk_fields)); + } + if ($altered_field > 0) { $fields = self::getAlterMigrationDownFields($rows, $name); @@ -409,17 +505,21 @@ public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix if ($dropped_field > 0) { foreach ($rows['modified_fields'] as $key => $value) { if (in_array('DROP_FIELD', $value['modify_type'])) { - $modified[] = sprintf( - self::FIELD_STUB, - self::getMigrationTypeField($value['field_type']), - $value['field_name'], - self::getMigrationLengthField($value['field_length'], $value['field_type']), - $value['field_default'], - self::getMigrationNullField($value['field_null']), - self::getMigrationIndexField($value['field_index'], null, $value['field_name']), - self::getMigrationAttributeField($value['field_attribute'] ?? null), - self::getMigrationIncrementField($value['field_increment']) - ); + if (in_array($value['field_type'], ['timestamp']) && in_array($value['field_name'], ['deleted_at'])) { + $modified[] = sprintf(self::SOFT_DELETE); + } else { + $modified[] = sprintf( + self::FIELD_STUB, + self::getMigrationTypeField($value['field_type']), + $value['field_name'], + self::getMigrationLengthField($value['field_length'], $value['field_type']), + $value['field_default'], + self::getMigrationNullField($value['field_null']), + self::getMigrationIndexField($value['field_index'], null, $value['field_name']), + self::getMigrationAttributeField($value['field_attribute'] ?? null), + self::getMigrationIncrementField($value['field_increment']) + ); + } } } } @@ -441,7 +541,107 @@ public static function getAlterMigrationSchemaDown($name, $rows = null, $prefix } } - return $stub; + return $stub.($dropped_fk_field ? $dropped_fk_field.PHP_EOL.PHP_EOL : null); + } + + public static function getMigrationRelationshipUp($name, $relations) + { + $fields = []; + + foreach ($relations as $relation) { + $field = sprintf(self::ADD_FOREIGN_KEY, $relation['source_field'], $relation['target_field'], $relation['target_table']); + + if (! empty($relation['on_delete'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_DELETE, $relation['on_delete']); + } + + if (! empty($relation['on_update'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_UPDATE, $relation['on_update']); + } + + $fields[] = $field.';'; + } + + return $fields; + } + + public static function getAlterMigrationRelationshipUp($relations) + { + $fields = []; + + foreach ($relations['modified_relations'] as $relation) { + $type = $relation['modify_type'] ?? null; + if (! empty($type)) { + if ($type === 'ADD_FOREIGN_KEY') { + $field = sprintf(self::ADD_FOREIGN_KEY, $relation['source_field'], $relation['target_field'], $relation['target_table']); + + if (! empty($relation['on_delete'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_DELETE, $relation['on_delete']); + } + + if (! empty($relation['on_update'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_UPDATE, $relation['on_update']); + } + + $fields[] = $field.';'; + } + + if ($type === 'CHANGE_FOREIGN_KEY') { + $filteredRelation = "'".$relation['source_field']."'"; + $fields[] = sprintf(self::DROP_FOREIGN_KEY, $filteredRelation); + + $field = sprintf(self::ADD_FOREIGN_KEY, $relation['source_field'], $relation['target_field'], $relation['target_table']); + if (! empty($relation['on_delete'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_DELETE, $relation['on_delete']); + } + if (! empty($relation['on_update'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_UPDATE, $relation['on_update']); + } + $fields[] = $field.';'; + } + } + } + + return $fields; + } + + public static function getMigrationRelationshipDown($relations) + { + $filteredRelation = collect(array_values($relations))->pluck('source_field')->toArray(); + $relation = "'".implode("', '", $filteredRelation)."'"; + + return sprintf(self::DROP_FOREIGN_KEY, $relation); + } + + public static function getAlterMigrationRelationshipDown($relations) + { + $fields = []; + + foreach ($relations['modified_relations'] as $key => $relation) { + $type = $relation['modify_type'] ?? null; + if (! empty($type)) { + if ($type === 'ADD_FOREIGN_KEY') { + $filteredRelation = "'".$relation['source_field']."'"; + $fields[] = sprintf(self::DROP_FOREIGN_KEY, $filteredRelation); + } + + if ($type === 'CHANGE_FOREIGN_KEY') { + $filteredRelation = "'".$relation['source_field']."'"; + $fields[] = sprintf(self::DROP_FOREIGN_KEY, $filteredRelation); + + $field = sprintf(self::ADD_FOREIGN_KEY, $relation['source_field'], $relations['current_relations'][$key]['target_field'], $relations['current_relations'][$key]['target_table']); + if (! empty($relations['current_relations'][$key]['on_delete'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_DELETE, $relations['current_relations'][$key]['on_delete']); + } + if (! empty($relations['current_relations'][$key]['on_update'])) { + $field .= sprintf(self::ADD_FOREIGN_KEY_ON_UPDATE, $relations['current_relations'][$key]['on_update']); + } + $fields[] = $field.';'; + } + } + } + + return $fields; } public static function getMigrationFields($name, $rows) @@ -454,7 +654,7 @@ public static function getMigrationFields($name, $rows) } elseif (in_array($row['field_type'], ['timestamp']) && in_array($row['field_name'], ['deleted_at'])) { $fields[] = sprintf(self::SOFT_DELETE); } else { - if ($row['field_index'] !== null) { + if (! empty($row['field_index']) && $row['field_index'] !== 'foreign') { $index = '->'.self::getMigrationIndexField($row['field_index'], null, $row['field_name']); } else { $index = null; @@ -569,13 +769,21 @@ public static function getAlterMigrationUpFields(array $rows, $table = null) self::getMigrationIndexField($row['field_index'], $current_fields['field_index'], $row['field_name']), ); } elseif ($current_fields['field_index'] !== null && $row['field_index'] === null) { - if ($current_fields['field_index'] !== 'primary') { + if ($current_fields['field_index'] !== 'primary' && $current_fields['field_index'] !== 'foreign') { $stub[] = sprintf( self::DROP_INDEX, ucfirst($current_fields['field_index']), $row['field_name'] ); } + + if ($current_fields['field_index'] === 'foreign') { + $stub[] = sprintf( + self::DROP_INDEX, + 'Index', + $table['current_name'].'_'.$row['field_name'].'_foreign' + ); + } } } elseif (in_array('UPDATE_INDEX', $row['modify_type'])) { $stub[] = sprintf( @@ -695,7 +903,7 @@ public static function getAlterMigrationDownFields(array $rows, $table = null) if (! empty(array_intersect($row['modify_type'], ['UPDATE_INDEX', 'UPDATE_INCREMENT']))) { if ($current_fields['field_index'] !== null && $row['field_index'] === null) { - if ($current_fields['field_index'] !== 'primary') { + if ($current_fields['field_index'] !== 'primary' && $current_fields['field_index'] !== 'foreign') { $indexes[] = sprintf( self::CREATE_INDEX, self::getMigrationIndexField($row['field_index'], $current_fields['field_index'], $row['field_name']), @@ -820,7 +1028,7 @@ public static function getMigrationLengthField($field, $fieldType) public static function getMigrationDefaultField($fieldType, $field) { - if ($field !== null) { + if (! empty($field)) { if (in_array($fieldType, ['integer', 'float', 'double', 'decimal'])) { return sprintf(self::FIELD_DEFAULT_DECIMAL, $field); } else { @@ -848,10 +1056,12 @@ public static function getMigrationNullField($field, $oldField = null) public static function getMigrationIndexField($field, $current = null, $name) { - if ($field === null && $current !== null) { - return sprintf(self::FIELD_INDEX, $current, $name); - } elseif ($field !== null && $current === null) { - return sprintf(self::FIELD_INDEX, $field, $name); + if ($field !== 'foreign') { + if ($field === null && $current !== null) { + return sprintf(self::FIELD_INDEX, $current, $name); + } elseif ($field !== null && $current === null) { + return sprintf(self::FIELD_INDEX, $field, $name); + } } } diff --git a/src/resources/js/assets/scss/layout/_custom.scss b/src/resources/js/assets/scss/layout/_custom.scss index a5c2abb7b..8885690b0 100644 --- a/src/resources/js/assets/scss/layout/_custom.scss +++ b/src/resources/js/assets/scss/layout/_custom.scss @@ -220,4 +220,8 @@ a { .vs-avatar--con-img img { object-fit: contain; height: 100%; +} + +.vs-select--options { + z-index: 99999 !important; } \ No newline at end of file diff --git a/src/resources/js/assets/scss/module/_alert.scss b/src/resources/js/assets/scss/module/_alert.scss index 2d3aaf8c4..005241391 100644 --- a/src/resources/js/assets/scss/module/_alert.scss +++ b/src/resources/js/assets/scss/module/_alert.scss @@ -1,7 +1,7 @@ .badaso-alert { &__body { background-color: #f3f5f7; - border-color: var(--danger); + border-color: var($danger); border-left-width: .2rem; border-left-style: solid; margin: -15px -15px 0 -15px; @@ -10,6 +10,7 @@ &__title { font-weight: bold; margin-bottom: -.4rem; + color: $danger; } &__description, &__title { diff --git a/src/resources/js/assets/scss/page/_database-management.scss b/src/resources/js/assets/scss/page/_database-management.scss index df1bed694..3243e36c4 100644 --- a/src/resources/js/assets/scss/page/_database-management.scss +++ b/src/resources/js/assets/scss/page/_database-management.scss @@ -27,4 +27,24 @@ &__popup-sync { float: right; } + + &__button-group { + display: flex; + + & > button { + margin: 2px; + } + } + + &__relationship-dialog { + row-gap: 8px; + } + + &__relationship-prompt { + z-index: 29999; + + & .vs-dialog-accept-button { + margin-right: 8px; + } + } } \ No newline at end of file diff --git a/src/resources/js/lang/modules/en.js b/src/resources/js/lang/modules/en.js index b2534e6de..5b7909b61 100644 --- a/src/resources/js/lang/modules/en.js +++ b/src/resources/js/lang/modules/en.js @@ -985,7 +985,7 @@ export default { warning: { title: "IMPORTANT", content: - 'Only the following column types can be "changed": Big Integer, BLOB, Boolean, Date, Datetime, Decimal, Float, Integer, JSON, Long Text, Medium Text, Set, Small Integer, Varchar, Text and Time.', + 'Only the following column types can be "changed": Big Integer, BLOB, Boolean, Date, Datetime, Decimal, Float, Integer, JSON, Long Text, Medium Text, Set, Small Integer, Varchar, Text and Time. Also, every field that you change, it\'ll be recorded when you submit the alter table. If you make some mistakes, you can refresh this page to reset your changes.', crud: "Make sure the table has not been generated with CRUD Management if you want to edit or drop it.", notAllowed: "You're not allowed to edit.", diff --git a/src/resources/js/pages/database-management/add.vue b/src/resources/js/pages/database-management/add.vue index 34e24aa3a..72a0b6ea9 100644 --- a/src/resources/js/pages/database-management/add.vue +++ b/src/resources/js/pages/database-management/add.vue @@ -95,6 +95,7 @@ required v-model="tr.fieldName" :disabled="tr.undeletable" + @input="renameForeignkey(tr)" /> @@ -149,6 +150,7 @@ class="database-management__field-index" v-model="tr.fieldIndex" :disabled="tr.undeletable" + @change="setFieldIndex(tr)" > - - - +
+ + + + + + +
@@ -228,6 +240,42 @@ + + + +

Source Table

+
+ + + + +

Target Table

+
+ + + + + + + + + + + +

Type

+
+ + + + + + + + + + +
+
- + @@ -317,12 +365,30 @@ export default { databaseData: { table: "", rows: [], + relations: {} }, fieldTypeList: [], + relationDialog: false, + tables: [], + fields: [], + selectedField: "" }), validations() { return { databaseData: { + relations: { + $each: { + sourceField: { + required + }, + targetTable: { + required + }, + targetField: { + required + }, + } + }, table: { required, maxLength: maxLength(64), @@ -356,12 +422,98 @@ export default { return this.$databaseHelper.getMigrationIndexList(); }, }, + relationType() { + return this.$databaseHelper.getForeignConstraint(); + } }, mounted() { this.getDbmsFieldType(); this.insertIdToRows(); }, methods: { + renameForeignkey(item) { + if (this.databaseData.relations[item.id]) { + let newVal = item.fieldName + let oldVal = this.databaseData.relations[item.id].sourceField || null + if (newVal !== oldVal) { + this.databaseData.relations[item.id].sourceField = newVal + } + } + }, + setFieldIndex(item) { + if (item.fieldIndex === 'foreign') { + this.$set(this.databaseData.relations, item.id, { + sourceField: item.fieldName, + targetTable: "", + targetField: "", + onDelete: null, + onUpdate: null, + }) + } else { + this.$delete(this.databaseData.relations, item.id) + } + }, + setRelation() { + this.$v.databaseData.relations.$touch(); + if (!this.$v.databaseData.relations.$invalid) { + this.relationDialog = false + } + }, + fetchTableFields() { + this.$openLoader(); + this.$api.badasoTable + .read({ + table: this.databaseData.relations[this.selectedField].targetTable, + }) + .then((response) => { + this.$closeLoader(); + this.fields = response.data.tableFields; + }) + .catch((error) => { + this.$closeLoader(); + this.$vs.notify({ + title: this.$t("alert.danger"), + text: error.message, + color: "danger", + }); + }); + }, + openRelationDialog(item) { + this.selectedField = item.id + this.relationDialog = true + this.getTableList() + }, + cancelRelationDialog() { + this.$v.databaseData.relations.$touch(); + if (this.$v.databaseData.relations.$invalid) { + this.relationDialog = false + this.databaseData.relations[this.selectedField].targetTable = "" + this.databaseData.relations[this.selectedField].targetField = "" + this.databaseData.relations[this.selectedField].onDelete = "" + this.databaseData.relations[this.selectedField].onUpdate = "" + } + }, + getTableList() { + this.$openLoader(); + this.$api.badasoCrud + .browse() + .then((response) => { + this.$closeLoader(); + this.tables = response.data.tablesWithCrudData.map(table => { + return { + value: table.tableName + } + }); + }) + .catch((error) => { + this.$closeLoader(); + this.$vs.notify({ + title: this.$t("alert.danger"), + text: error.message, + color: "danger", + }); + }); + }, getDbmsFieldType() { this.$openLoader(); this.$api.badasoDatabase @@ -431,6 +583,7 @@ export default { addField() { let index = this.databaseData.rows.map(row => row.undeletable).indexOf(true) this.databaseData.rows.splice(index, 0, { + id: this.$helper.uuid(), fieldName: "", fieldType: "", fieldLength: null, @@ -456,46 +609,6 @@ export default { return found; }, - // addTimestamps() { - // if (this.findFieldOnRows("created_at")) { - // this.$vs.notify({ - // title: this.$t("alert.danger"), - // text: this.$t("database.warning.exists", { 0: "created_at" }), - // color: "danger", - // }); - // } else { - // this.databaseData.rows.push({ - // fieldName: "created_at", - // fieldType: "timestamp", - // fieldLength: null, - // fieldNull: true, - // fieldAttribute: false, - // fieldIncrement: false, - // fieldIndex: null, - // fieldDefault: null, - // }); - // } - - // if (this.findFieldOnRows("updated_at")) { - // this.$vs.notify({ - // title: this.$t("alert.danger"), - // text: this.$t("database.warning.exists", { 0: "updated_at" }), - // color: "danger", - // }); - // } else { - // this.databaseData.rows.push({ - // fieldName: "updated_at", - // fieldType: "timestamp", - // fieldLength: null, - // fieldNull: true, - // fieldAttribute: false, - // fieldIncrement: false, - // fieldIndex: null, - // fieldDefault: null, - // }); - // } - // }, - addSoftDeletes() { if (this.findFieldOnRows("deleted_at")) { this.$vs.notify({ @@ -517,13 +630,16 @@ export default { } }, - dropField(index) { + dropField(index, index) { this.$vs.dialog({ type: "confirm", color: "danger", title: this.$t("action.delete.title"), text: this.$t("action.delete.text"), - accept: () => this.databaseData.rows.splice(index, 1), + accept: () => { + this.databaseData.rows.splice(index, 1); + this.$delete(this.databaseData.relations, item.fieldName) + }, acceptText: this.$t("action.delete.accept"), cancelText: this.$t("action.delete.cancel"), }); @@ -531,6 +647,7 @@ export default { insertIdToRows() { this.databaseData.rows.push({ + id: "id", fieldName: "id", fieldType: "bigint", fieldLength: null, diff --git a/src/resources/js/pages/database-management/edit.vue b/src/resources/js/pages/database-management/edit.vue index f2cccd412..f30a9739c 100644 --- a/src/resources/js/pages/database-management/edit.vue +++ b/src/resources/js/pages/database-management/edit.vue @@ -91,7 +91,7 @@ required :value="tr.fieldName" class="inputx" - @change="alterFieldProperty(tr, $event, 'RENAME', 'fieldName', indextr)" + @change="alterFieldProperty(tr, $event, 'RENAME', 'fieldName', indextr); renameForeignkey(tr)" :disabled="tr.undeletable" /> @@ -173,14 +173,24 @@ - - - +
+ + + + + + +
@@ -263,6 +273,42 @@ + + + +

Source Table

+
+ + + + +

Target Table

+
+ + + + + + + + + + + +

Type

+
+ + + + + + + + + + +
+
- + @@ -331,6 +377,8 @@ import { helpers, } from "vuelidate/lib/validators"; +import _ from 'lodash'; + const alphaNumAndUnderscoreValidator = helpers.regex( "alphaNumAndDot", /^[a-zA-Z\d_]*$/i @@ -369,9 +417,17 @@ export default { currentFields: [], modifiedFields: [], }, + relations: { + currentRelations: {}, + modifiedRelations: {} + } }, isCanEdit: false, fieldTypeList: [], + relationDialog: false, + tables: [], + fields: [], + selectedField: "" }), validations() { return { @@ -416,6 +472,34 @@ export default { required } }, + relations: { + currentRelations: { + $each: { + sourceField: { + required + }, + targetTable: { + required + }, + targetField: { + required + }, + } + }, + modifiedRelations: { + $each: { + sourceField: { + required + }, + targetTable: { + required + }, + targetField: { + required + }, + } + } + } }, }; }, @@ -425,6 +509,9 @@ export default { return this.$databaseHelper.getMigrationIndexList(); }, }, + relationType() { + return this.$databaseHelper.getForeignConstraint(); + } }, mounted() { this.getInfoTable(); @@ -432,6 +519,85 @@ export default { this.getIsCanEdit(); }, methods: { + renameForeignkey(item) { + if (this.databaseData.relations.modifiedRelations[item.id]) { + let newVal = item.fieldName + let oldVal = this.databaseData.relations.modifiedRelations[item.id].sourceField || null + if (newVal !== oldVal) { + this.databaseData.relations.modifiedRelations[item.id].sourceField = newVal + } + } + }, + setRelation() { + let modified = Object.values(this.databaseData.relations.modifiedRelations[this.selectedField]); + let current = Object.values(this.databaseData.relations.currentRelations[this.selectedField]); + this.$v.databaseData.relations.$touch(); + if (!this.$v.databaseData.relations.$invalid) { + this.relationDialog = false + if (modified.length !== current.length) { + this.$set(this.databaseData.relations.modifiedRelations[this.selectedField], 'modifyType', 'ADD_FOREIGN_KEY') + } else { + this.$set(this.databaseData.relations.modifiedRelations[this.selectedField], 'modifyType', 'CHANGE_FOREIGN_KEY') + } + } + }, + fetchTableFields() { + this.$openLoader(); + this.$api.badasoTable + .read({ + table: this.databaseData.relations.modifiedRelations[this.selectedField].targetTable, + }) + .then((response) => { + this.$closeLoader(); + this.fields = response.data.tableFields; + }) + .catch((error) => { + this.$closeLoader(); + this.$vs.notify({ + title: this.$t("alert.danger"), + text: error.message, + color: "danger", + }); + }); + }, + openRelationDialog(item) { + this.selectedField = item.id + this.relationDialog = true + this.getTableList() + + if (this.databaseData.relations.modifiedRelations[this.selectedField].targetTable) { + this.fetchTableFields() + } + }, + cancelRelationDialog() { + this.relationDialog = false + if (this.databaseData.relations.currentRelations.hasOwnProperty(this.selectedField)) { + this.databaseData.relations.modifiedRelations[this.selectedField] = { + ...this.databaseData.relations.currentRelations[this.selectedField] + } + } + }, + getTableList() { + this.$openLoader(); + this.$api.badasoCrud + .browse() + .then((response) => { + this.$closeLoader(); + this.tables = response.data.tablesWithCrudData.map(table => { + return { + value: table.tableName + } + }); + }) + .catch((error) => { + this.$closeLoader(); + this.$vs.notify({ + title: this.$t("alert.danger"), + text: error.message, + color: "danger", + }); + }); + }, getDbmsFieldType() { this.$api.badasoDatabase .getType() @@ -486,6 +652,7 @@ export default { }) .then((response) => { let data = response.data.columns; + let dataFK = response.data.columnsFK; for (const [key, column] of Object.entries(data)) { let id = this.$helper.uuid(); this.databaseData.fields.modifiedFields.push({ @@ -496,12 +663,7 @@ export default { fieldNull: column.notnull ? false : true, fieldAttribute: column.unsigned, fieldIncrement: column.autoincrement, - fieldIndex: - Object.keys(column.indexes).length > 0 - ? Object.values(column.indexes)[0] - .type.toString() - .toLowerCase() - : null, + fieldIndex: this.getFieldIndexes(column.indexes), fieldDefault: column.default, modifyType: [], undeletable: column.name === 'created_at' || column.name === 'updated_at' ? true : false @@ -515,18 +677,20 @@ export default { fieldNull: column.notnull ? false : true, fieldAttribute: column.unsigned, fieldIncrement: column.autoincrement, - fieldIndex: - Object.keys(column.indexes).length > 0 - ? Object.values(column.indexes)[0] - .type.toString() - .toLowerCase() - : null, + fieldIndex: this.getFieldIndexes(column.indexes), fieldDefault: column.default }); + + if (dataFK.hasOwnProperty(key)) { + this.$set(this.databaseData.relations.currentRelations, id, JSON.parse(JSON.stringify(dataFK[key]))) + this.$set(this.databaseData.relations.modifiedRelations, id, JSON.parse(JSON.stringify(dataFK[key]))) + } } this.databaseData.table.currentName = this.$route.params.tableName; this.databaseData.table.modifiedName = this.$route.params.tableName; + // this.databaseData.relations.currentRelations = JSON.parse(JSON.stringify(response.data.columnsFK)) + // this.databaseData.relations.modifiedRelations = JSON.parse(JSON.stringify(response.data.columnsFK)) this.$closeLoader(); }) .catch((error) => { @@ -539,6 +703,16 @@ export default { }); }, + getFieldIndexes(indexes) { + if (!_.isEmpty(indexes)) { + if (Object.values(indexes)[0].name.includes('foreign')) { + return 'foreign' + } + return Object.values(indexes)[0].type.toString().toLowerCase() + } + return null + }, + submitForm() { this.$v.databaseData.$touch(); if (!this.$v.databaseData.$invalid) { @@ -621,53 +795,30 @@ export default { return found; }, - // addTimestamps() { - // if (this.findFieldOnRows("created_at")) { - // this.$vs.notify({ - // title: this.$t("alert.danger"), - // text: this.$t("database.warning.exists", { 0: "created_at" }), - // color: "danger", - // }); - // } else { - // this.databaseData.fields.modifiedFields.push({ - // id: this.$helper.uuid(), - // fieldName: "created_at", - // fieldType: "timestamp", - // fieldLength: null, - // fieldNull: true, - // fieldAttribute: false, - // fieldIncrement: false, - // fieldIndex: null, - // fieldDefault: null, - // modifyType: [ - // 'CREATE' - // ] - // }); - // } - - // if (this.findFieldOnRows("updated_at")) { - // this.$vs.notify({ - // title: this.$t("alert.danger"), - // text: this.$t("database.warning.exists", { 0: "updated_at" }), - // color: "danger", - // }); - // } else { - // this.databaseData.fields.modifiedFields.push({ - // id: this.$helper.uuid(), - // fieldName: "updated_at", - // fieldType: "timestamp", - // fieldLength: null, - // fieldNull: true, - // fieldAttribute: false, - // fieldIncrement: false, - // fieldIndex: null, - // fieldDefault: null, - // modifyType: [ - // 'CREATE' - // ] - // }); - // } - // }, + addSoftDeletes() { + if (this.findFieldOnRows("deleted_at")) { + this.$vs.notify({ + title: this.$t("alert.danger"), + text: this.$t("database.warning.exists", { 0: "deleted_at" }), + color: "danger", + }); + } else { + this.databaseData.fields.modifiedFields.push({ + id: this.$helper.uuid(), + fieldName: "deleted_at", + fieldType: "timestamp", + fieldLength: null, + fieldNull: true, + fieldAttribute: false, + fieldIncrement: false, + fieldIndex: null, + fieldDefault: "", + modifyType: [ + 'CREATE' + ] + }); + } + }, dropField(item, index) { this.$vs.dialog({ @@ -683,11 +834,50 @@ export default { saveDrop(item, index) { this.databaseData.fields.modifiedFields.splice(index, 1) + if (item.fieldIndex === 'foreign') { + this.databaseData.relations.modifiedRelations[item.id].modifyType = 'DROP_FOREIGN_KEY' + + if (this.databaseData.relations.modifiedRelations[item.id].hasOwnProperty('new')) { + this.$delete(this.databaseData.relations.modifiedRelations, item.id) + } + } }, alterFieldProperty(item, event, eventType, field, indexRow) { let oldValue = item[field] let newValue = null + let modifiedRelation = this.databaseData.relations.modifiedRelations[item.id] || null + + if (field === 'fieldIndex') { + if (modifiedRelation === null) { + if (event === 'foreign') { + this.$set(this.databaseData.relations.modifiedRelations, item.id, { + modifyType: "ADD_FOREIGN_KEY", + sourceField: item.fieldName, + targetTable: "", + targetField: "", + onDelete: null, + onUpdate: null, + new: true + }) + eventType = null + } + } else { + if (this.databaseData.relations.modifiedRelations[item.id].hasOwnProperty('new')) { + if (newValue !== 'foreign' && oldValue === 'foreign') { + this.$delete(this.databaseData.relations.modifiedRelations, item.id) + } + } else { + if (newValue === 'foreign' && oldValue === null) { + this.$delete(this.databaseData.relations.modifiedRelations[item.id], 'modifyType') + } else if (newValue === null && oldValue === null) { + this.$delete(this.databaseData.relations.modifiedRelations[item.id], 'modifyType') + } else { + this.$set(this.databaseData.relations.modifiedRelations[item.id], 'modifyType', 'DROP_FOREIGN_KEY') + } + } + } + } if (field === 'fieldType' || field === 'fieldIndex') { newValue = event @@ -714,7 +904,7 @@ export default { if (item.id === value.id) { if (newValue == value[field]) { let itemIndex = item.modifyType.indexOf(eventType); - + if (itemIndex > -1) { item.modifyType.splice(itemIndex, 1); } diff --git a/src/resources/js/utils/database-helper.js b/src/resources/js/utils/database-helper.js index b27a33ce7..fe4b744af 100644 --- a/src/resources/js/utils/database-helper.js +++ b/src/resources/js/utils/database-helper.js @@ -16,6 +16,32 @@ export default { value: "primary", default: false, }, + { + label: "Foreign", + value: "foreign", + default: false, + } ]; }, + + getForeignConstraint() { + return [ + { + label: "CASCADE", + value: "cascade" + }, + { + label: "SET NULL", + value: "set null" + }, + { + label: "NO ACTION", + value: "no action" + }, + { + label: "RESTRICT", + value: "restrict" + }, + ] + } };