]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
daily work: sqlExportBackend() works, improvements all models
authorHamatoma <author@hamatoma.de>
Fri, 16 Oct 2020 11:21:47 +0000 (13:21 +0200)
committerHamatoma <author@hamatoma.de>
Fri, 16 Oct 2020 23:40:07 +0000 (01:40 +0200)
22 files changed:
lib/db_tool.dart [new file with mode: 0644]
lib/main.dart
lib/src/helper/string_helper.dart
lib/src/model/column_model.dart
lib/src/model/field_model.dart
lib/src/model/model_base.dart
lib/src/model/model_types.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/standard/role_model.dart
lib/src/model/standard/user_model.dart
lib/src/model/table_model.dart
lib/src/model/text_field_model.dart
lib/src/model/text_model.dart
lib/src/model/widget_model.dart
lib/src/tool/db_tool.dart [deleted file]
lib/src/widget/filters.dart
pubspec.yaml
test/helpers/string_helper_test.dart
test/model/db_model_test.dart
test/model/model_test.dart
test/tool/tool_test.dart

diff --git a/lib/db_tool.dart b/lib/db_tool.dart
new file mode 100644 (file)
index 0000000..7c4c080
--- /dev/null
@@ -0,0 +1,89 @@
+import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter_bones/flutter_bones.dart';
+
+import 'src/model/standard/role_model.dart';
+import 'src/model/standard/user_model.dart';
+
+void main(List<String> argv) async {
+  final logger = MemoryLogger(LEVEL_FINE);
+  final dbHelper = DbHelper(logger);
+  if (argv.isEmpty) {
+    argv = ['export-sql'];
+  }
+  final mode = argv[0];
+  argv.removeAt(0);
+  final options = <String>[];
+  argv = StringHelper.splitArgv(argv, options);
+  switch (mode) {
+    case 'export-sql':
+      dbHelper.exportSql(argv, options);
+      break;
+    default:
+      logger.error('unknown mode: $mode');
+      break;
+  }
+}
+
+class DbHelper {
+  final BaseLogger logger;
+
+  DbHelper(this.logger);
+
+  void exportSql(List<String> args, List<String> options) {
+    String directory;
+    String value;
+    for (var opt in options) {
+      value = StringUtils.stringOption('directory', 'd', opt);
+      if (value != null) {
+        directory = value;
+      }
+    }
+    directory ??= 'data';
+    final dirDDL = FileSync.joinPaths(directory, 'ddl');
+    final dirREST = FileSync.joinPaths(directory, 'rest');
+    FileSync.ensureDirectory(dirDDL);
+    FileSync.ensureDirectory(dirREST);
+    if (args.isEmpty) {
+      args = ['user', 'role'];
+    }
+    while (args.isNotEmpty) {
+      final name = args[0];
+      args.removeAt(0);
+      ModuleModel module;
+      switch (name) {
+        case 'user':
+          module = UserModel(logger);
+          break;
+        case 'role':
+          module = RoleModel(logger);
+          break;
+        default:
+          logger.error('unknown table');
+          break;
+      }
+      if (module != null) {
+        module.parse();
+        var filename = FileSync.joinPaths(dirDDL, '$name.sql');
+        FileSync.toFile(filename, module.exportSqlCreateTable());
+        print('exported: $filename');
+        filename = FileSync.joinPaths(dirREST, '$name.yaml');
+        FileSync.toFile(filename, module.exportSqlBackend());
+        print('exported: $filename');
+      }
+    }
+  }
+
+  void usage(String error) {
+    print('''usage: dbhelper <mode> [<args>]
+<mode>:
+  create-sql [<module>] [<opt>]
+    <module>: 'role', 'user' ...
+    <opt>:
+      -d<dir> or --directory=<dir>:
+        the base directory used for the exported files.
+Examples:
+dbhelper create-sql role role.sql -d/tmp
+dbhelper create-sql --directory=/opt/sql-data
+''');
+  }
+}
index 7f5769714465f021fbbb2f5cd384a4a80e22d294..29a9d9e607e1f6a2f7410c05531ee75a19d9d672 100644 (file)
@@ -1,7 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_bones/app.dart';
 
-void main() {
+void main(List<String> args) {
+  //args.length > 1 && args[0].startsWith('--') &&
   runApp(BoneApp());
 }
-
index 35ce393accf3c4a5cd6de2ed4bd9925f2c99605e..85597595f362e384621fcf9568c8fb61fc146245 100644 (file)
@@ -1,9 +1,45 @@
 import 'package:dart_bones/dart_bones.dart';
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:intl/intl.dart';
 
 class StringHelper {
   static final regExpTrue = RegExp(r'^(true|yes)$', caseSensitive: false);
   static final regExpFalse = RegExp(r'^(false|no)$', caseSensitive: false);
+  static const locale = 'de_DE';
+
+  /// Converts a [dateTime] into a string.
+  /// If [dateTime] is null the current date and time is used.
+  /// If [sortable] is true the result is sortable (year.month.day ...).
+  /// [separator] is the part between date and time.
+  /// If [withSeconds] is true the result contains the seconds otherwise not.
+  static dateTimeToString(DateTime dateTime,
+      {String separator = ' ', withSeconds: true, sortable = false}) {
+    dateTime ??= DateTime.now();
+    String rc;
+    if (sortable) {
+      rc = DateFormat(withSeconds
+              ? 'yyyy.MM.dd${separator}HH:mm:ss'
+              : 'yyyy.MM.dd${separator}HH:mm')
+          .format(dateTime);
+    } else {
+      rc = dateToString(dateTime) +
+          separator +
+          (withSeconds
+              ? DateFormat.Hms().format(dateTime)
+              : DateFormat.Hm(locale).format(dateTime));
+    }
+    return rc;
+  }
+
+  /// Converts a [date] into a string.
+  /// If [date] is null the current date is used.
+  /// If [sortable] is true the result is sortable (year.month.day).
+  static dateToString(DateTime date, {sortable = false}) {
+    date ??= DateTime.now();
+    final formatter =
+        sortable ? DateFormat('yyyy.MM.dd') : DateFormat.yMd(locale);
+    return formatter.format(date);
+  }
 
   /// Converts a string to a given [dataType].
   static dynamic fromString(String value, DataType dataType) {
@@ -43,4 +79,20 @@ class StringHelper {
     }
     return rc;
   }
+
+  /// Splits a argument list into an [option] list and a true arguments list.
+  /// [args]: the argument list to split.
+  /// [options]: OUT: the options list
+  /// Returns the true argument list (arguments without options).
+  static List<String> splitArgv(List<String> args, List<String> options) {
+    final rc = <String>[];
+    for (var arg in args) {
+      if (arg.startsWith('-')) {
+        options.add(arg);
+      } else {
+        rc.add(arg);
+      }
+    }
+    return rc;
+  }
 }
index f9a3467bed01bfcaf6ae30e199d9d9e6f2c5c4c8..63ee2f839ad4ac0b6cf75828f5b8c232853b9eb8 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:dart_bones/dart_bones.dart';
+import 'package:flutter/cupertino.dart';
 
 import 'model_base.dart';
 import 'model_types.dart';
@@ -8,7 +9,7 @@ import 'widget_model.dart';
 /// Describes a column of a database table.
 class ColumnModel extends WidgetModel {
   static final regExprOptions = RegExp(
-      r'^(undef|readonly|disabled|hidden|null|notnull|primary|required|unique)$');
+      r'^(undef|readonly|disabled|doStore|hidden|null|notnull|password|primary|required|unique)$');
   String name;
   String label;
   String toolTip;
@@ -20,9 +21,22 @@ class ColumnModel extends WidgetModel {
   final Map<String, dynamic> map;
   String foreignKey;
 
+  /// A constructor used in the parser.
   ColumnModel(this.table, this.map, BaseLogger logger)
       : super(null, null, WidgetModelType.column, logger);
 
+  /// A constructor for columns created by the program.
+  ColumnModel.raw(
+      {@required this.name,
+      @required this.table,
+      @required this.label,
+      @required this.dataType,
+      @required this.options,
+      this.size,
+      this.map,
+      BaseLogger logger})
+      : super(null, null, WidgetModelType.column, logger);
+
   /// Dumps the instance into a [StringBuffer]
   StringBuffer dump(StringBuffer stringBuffer) {
     stringBuffer.write(
@@ -42,20 +56,28 @@ class ColumnModel extends WidgetModel {
 
   /// Parses the map and stores the data in the instance.
   void parse() {
-    name = parseString('name', map, required: true);
+    name = parseString('column', map, required: true);
     checkSuperfluousAttributes(
         map,
-        'dataType foreignKey label name options rows size tooTip widgetType'
+        'column dataType foreignKey label options rows size tooTip widgetType'
             .split(' '));
     dataType =
         parseEnum<DataType>('dataType', map, DataType.values, required: true);
     label = parseString('label', map, required: false);
     toolTip = parseString('toolTip', map, required: false);
     size = parseInt('size', map, required: dataType == DataType.string);
-    rows = parseInt('size', map);
+    rows = parseInt('rows', map);
 
     options = parseOptions('options', map);
     checkOptionsByRegExpr(options, regExprOptions);
+    if (options.contains('primary')) {
+      if (!options.contains('notnull')) {
+        options.add('notnull');
+      }
+      if (!options.contains('unique')) {
+        options.add('unique');
+      }
+    }
   }
 
   @override
index 4ddc9e0dc069704636fc9fbb44270e57f45890ac..d9185fbf4dcfeef4e084e8f027968b3924cfdf6f 100644 (file)
@@ -17,6 +17,7 @@ abstract class FieldModel extends WidgetModel {
   DataType dataType;
   FormFieldSetter onSaved;
   List<String> options;
+  FilterType filterType;
 
   final Map<String, dynamic> map;
   var _value;
@@ -52,6 +53,7 @@ abstract class FieldModel extends WidgetModel {
     name = parseString('name', map, required: true);
     label = parseString('label', map, required: false);
     toolTip = parseString('toolTip', map, required: false);
+    filterType = parseEnum<FilterType>('filterType', map, FilterType.values);
 
     dataType = parseEnum<DataType>('dataType', map, DataType.values);
     if (dataType == null) {
index abccce1ab5ca119a733d59cae73e78c9e22f0cb4..189ccfeb1d7e643ff2d7e52b60704cbb8eb65f68 100644 (file)
@@ -74,20 +74,20 @@ abstract class ModelBase {
     if (!map.containsKey(key)) {
       if (required) {
         logger.error('missing int attribute "$key" in ${fullName()}');
-      } else {
-        final value = map[key];
-        if (value != null) {
-          if (value.runtimeType == int) {
-            rc = map[key];
-          } else if (value.runtimeType == String) {
-            if (Validation.isInt(value)) {
-              rc = StringUtils.asInt(value);
-            } else {
-              logger.error('not an integer: $value map[$key] in {fullName()}');
-            }
+      }
+    } else {
+      final value = map[key];
+      if (value != null) {
+        if (value.runtimeType == int) {
+          rc = map[key];
+        } else if (value.runtimeType == String) {
+          if (Validation.isInt(value)) {
+            rc = StringUtils.asInt(value);
           } else {
             logger.error('not an integer: $value map[$key] in {fullName()}');
           }
+        } else {
+          logger.error('not an integer: $value map[$key] in {fullName()}');
         }
       }
     }
@@ -149,7 +149,7 @@ abstract class ModelBase {
   }
 
   /// Fetches an entry from a map addressed by a [key].
-  /// This entry is splitted by the delimiter given at index 0.
+  /// This entry is split by the delimiter given at index 0.
   /// Example: ";a;b" returns ['a', 'b'].
   /// An error is logged if [required] is true and the map does not contain the key.
   List<dynamic> parseValueList(
@@ -162,19 +162,21 @@ abstract class ModelBase {
     if (ModelBase.isList(map[key])) {
       rc = map[key];
     } else {
-      final strings = parseStringList(key, map, required: required);
-      rc = strings.map((item) {
-        var rc2;
-        switch (dataType) {
-          case DataType.int:
-            rc2 = StringUtils.asInt(item);
-            break;
-          default:
-            logger.error('unknown dataType in parseValueList()');
-            rc2 = item;
-        }
-        return rc2;
-      });
+      final strings = parseStringList(key, map, required: required).toList();
+      if (strings.isNotEmpty) {
+        rc = strings.map((item) {
+          var rc2;
+          switch (dataType) {
+            case DataType.int:
+              rc2 = StringUtils.asInt(item);
+              break;
+            default:
+              logger.error('unknown dataType in parseValueList()');
+              rc2 = item;
+          }
+          return rc2;
+        });
+      }
     }
     return rc;
   }
index 292a9ecdc9451291ee69aa826533a8ea641947b5..4874a31af3b11f9f47a21ad6c939a18328a6a97e 100644 (file)
@@ -14,5 +14,6 @@ enum FilterType {
   dateTil,
   dateTimeFrom,
   dateTimeTil,
+  equals,
   pattern,
 }
index 7763206ea2c22c4ccdd6ce557c32010a0fd79d37..2d781e2dd9f386879c3842ac95410b6283f85cd3 100644 (file)
@@ -23,6 +23,21 @@ class ModuleModel extends ModelBase {
 
   ModuleModel(this.map, BaseLogger logger) : super(logger);
 
+  /// Adds the column [name] from [source] to [target] if that column is not member of [target].
+  void addColumnIfMissing(
+      List<ColumnModel> target, List<ColumnModel> source, String name) {
+    final isEmpty = target.isEmpty;
+    final first = isEmpty
+        ? null
+        : target.firstWhere((col) => col.name == name, orElse: () => null);
+    final first2 = source.isEmpty
+        ? null
+        : source.firstWhere((col) => col.name == name, orElse: () => null);
+    if (first == null && first2 != null) {
+      target.add(first2);
+    }
+  }
+
   /// Appends a [page] to the instance.
   void addPage(PageModel page) {
     if (pages.where((element) => element.name == page.name).isNotEmpty) {
@@ -48,62 +63,188 @@ class ModuleModel extends ModelBase {
     return stringBuffer;
   }
 
+  /// Writes the insert SQL part into the [buffer].
+  void exportInsert(StringBuffer buffer, TableModel table) {
+    buffer.write('''        sql: "INSERT INTO ${table.name}(''');
+    final columns = table.columns
+        .where((col) =>
+    col.hasOption('doStore') ||
+        (!col.hasOption('primary') && (!col.hasOption('hidden'))))
+        .toList();
+    addColumnIfMissing(columns, table.columns, '${table.name}_createdat');
+    addColumnIfMissing(columns, table.columns, '${table.name}_createdby');
+    buffer.write(columns.fold(
+        '',
+            (prev, col) =>
+        (prev as String) +
+            ((prev as String).isEmpty ? col.name : ',' + col.name)));
+    buffer.write(')\n');
+    buffer.write('          VALUES(');
+    buffer.write(columns.fold(
+        '',
+            (prev, col) =>
+        (prev as String) +
+            ((prev as String).isEmpty ? '' : ',') +
+            (col.name.endsWith('createdat') ? 'NOW()' : (':' + col.name))));
+    buffer.write(');"\n');
+  }
+
+  /// Writes the list SQL part into the [buffer].
+  void exportList(StringBuffer buffer, TableModel table) {
+    final page = pages
+        .firstWhere((element) => element.pageModelType == PageModelType.list,
+        orElse: () => null);
+    if (page != null) {
+      buffer.write('''      - name: list
+        type: list
+        sql: "SELECT * from ${table.name}\n''');
+      // @ToDo: joins
+      final fields = page.fields.values
+          .where((item) => item is FieldModel && item.filterType != null);
+      if (fields.isEmpty) {
+        buffer.write('          WHERE 1;"');
+      } else {
+        buffer.write('          WHERE ');
+        var first = true;
+        for (var field in fields) {
+          if (!first) {
+            buffer.write(' AND ');
+          }
+          switch (field.filterType) {
+            case FilterType.dateFrom:
+            case FilterType.dateTimeFrom:
+              buffer.write('${field.name}>=:${field.name}');
+              break;
+            case FilterType.dateTil:
+            case FilterType.dateTimeTil:
+              buffer.write('${field.name}<=:${field.name}');
+              break;
+            case FilterType.pattern:
+              buffer.write('${field.name} like :${field.name}');
+              break;
+          }
+          first = false;
+        }
+        buffer.write(';"\n');
+      }
+    }
+  }
+
+  /// Exports a YAML file (as String) describing the SQL statements for insert, update ...
+  /// testDate: the fix date 2020.01.01 will be used (for unit tests)
+  String exportSqlBackend({bool testDate: false}) {
+    StringBuffer buffer = StringBuffer();
+    final date = StringHelper.dateTimeToString(
+        testDate ? DateTime(2020, 1, 1) : null,
+        sortable: true);
+    final table = mainTable();
+    if (table != null) {
+      buffer.write('''---
+# configuration of the bones backend for ${table.name}:
+created: $date
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+  - module: ${table.name}
+    list:
+      - name: insert
+        type: insert
+''');
+      exportInsert(buffer, table);
+      buffer.write('''      - name: update
+        type: update
+''');
+      exportUpdate(buffer, table);
+      buffer.write('''      - name: delete
+        type: delete
+        sql: "DELETE from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
+      - name: record
+        type: record
+        sql: "SELECT * from ${table.name} WHERE ${table.name}_id=:${table.name}_id;"
+''');
+      exportList(buffer, table);
+    }
+    return buffer.toString();
+  }
+
   /// Exports the SQL statement to create the module tables.
-  String exportSql() {
+  String exportSqlCreateTable() {
     StringBuffer buffer = StringBuffer();
     for (var table in tables) {
-      buffer.write('drop table if exists ${table.name};\n');
-      buffer.write('create table ${table.name} (\n');
+      buffer.write('DROP TABLE IF EXISTS ${table.name};\n');
+      buffer.write('CREATE TABLE ${table.name} (\n');
       for (var column in table.columns) {
         String type;
         switch (column.dataType) {
           case DataType.bool:
-            type = 'char(1)';
+            type = 'CHAR(1)';
             break;
           case DataType.currency:
-            type = 'int(10)';
+            type = 'INT(10)';
             break;
           case DataType.date:
-            type = 'date';
+            type = 'DATE';
             break;
           case DataType.dateTime:
-            type = 'timestamp';
+            type = 'TIMESTAMP';
             break;
           case DataType.float:
-            type = 'double';
+            type = 'DOUBLE';
             break;
           case DataType.int:
-            type = 'int(10)';
+            type = 'INT(10)';
             break;
           case DataType.reference:
-            type = 'int(10)';
+            type = 'INT(10) UNSIGNED';
             break;
           case DataType.string:
             if (column.size == null) {
-              type = 'varchar(255)';
+              type = 'VARCHAR(255)';
             } else if (column.size <= 255) {
-              type = 'varchar($column.size})';
+              type = 'VARCHAR(${column.size})';
             } else if (column.size <= 0xffff) {
-              type = 'text';
+              type = 'TEXT';
             } else {
-              type = 'mediumtext';
+              type = 'MEDIUMTEXT';
             }
             break;
         }
         String options = '';
         for (var option in column.options) {
-          if ('notnull null primary unique'.contains(option)) {
+          if ('notnull null unique'.contains(option)) {
             options += ' ' + option;
           }
         }
-        buffer.write('  ${column.name} $type$options;\n');
+        buffer.write('  ${column.name} $type$options,\n');
       }
+      buffer.write('  PRIMARY KEY(${table.name}_id)\n');
       buffer.write(');\n');
     }
     final rc = buffer.toString();
     return rc;
   }
 
+  /// Writes the insert SQL part into the [buffer].
+  void exportUpdate(StringBuffer buffer, TableModel table) {
+    buffer.write('        sql: "UPDATE ${table.name} SET\n          ');
+    final columns = table.columns
+        .where((col) =>
+    col.hasOption('doStore') ||
+        (!col.hasOption('primary') && !col.hasOption('hidden')))
+        .toList();
+    addColumnIfMissing(columns, table.columns, '${table.name}_changedat');
+    addColumnIfMissing(columns, table.columns, '${table.name}_changedby');
+    buffer.write(columns.fold(
+        '',
+            (prev, col) =>
+        (prev as String) +
+            ((prev as String).isEmpty ? '' : ',') +
+            col.name +
+            '=' +
+            ((col.name.endsWith('changedat') ? 'NOW()' : ':' + col.name))));
+    buffer.write('\n          WHERE ${table.name}_id=:${table.name}_id";\n');
+  }
+
   /// Returns the name including the names of the parent
   @override
   String fullName() => name;
@@ -115,7 +256,8 @@ class ModuleModel extends ModelBase {
     TableModel table = tables[0];
     if (columnName.contains('.')) {
       final parts = columnName.split('.');
-      table = tables.firstWhere((element) => element.name == parts[0]);
+      table = tables.firstWhere((element) => element.name == parts[0],
+          orElse: () => null);
       if (table == null) {
         logger.error(
             'unknown reference (table) "$columnName" in ${caller.fullName()}');
@@ -131,13 +273,14 @@ class ModuleModel extends ModelBase {
   /// Returns the main table of the module.
   /// This is the first defined table.
   TableModel mainTable() {
-    TableModel rc = tables.length == 0 ? null : tables[0];
+    final rc = tables.isEmpty ? null : tables[0];
     return rc;
   }
 
   /// Returns a child page given by [name], null otherwise.
   PageModel pageByName(String name) {
-    final found = pages.firstWhere((element) => element.name == name);
+    final found = pages.firstWhere((element) => element.name == name,
+        orElse: () => null);
     return found;
   }
 
@@ -165,7 +308,8 @@ class ModuleModel extends ModelBase {
 
   /// Returns a child table given by [name], null otherwise.
   TableModel tableByName(String name) {
-    final found = tables.firstWhere((element) => element.name == name);
+    final found = tables.firstWhere((element) => element.name == name,
+        orElse: () => null);
     return found;
   }
 
index 01d5f6ecbe8afde5b3646a9c4bce84184629fb0a..eca7fdb185ce0da0e8cfe4842de37f48fa019c24 100644 (file)
@@ -107,9 +107,9 @@ class PageModel extends ModelBase {
 
   /// Parses the map and stores the data in the instance.
   void parse() {
-    name = parseString('name', map, required: true);
+    name = parseString('page', map, required: true);
     checkSuperfluousAttributes(
-        map, 'name options pageType sections'.split(' '));
+        map, 'options page pageType sections'.split(' '));
     pageModelType = parseEnum<PageModelType>(
         'pageType', map, PageModelType.values,
         required: true);
index bc6fec5499467327934b3b55ad94e8526af0fba2..a1c6b1c419516b14384954ded5df6fcc57f070df 100644 (file)
@@ -6,44 +6,37 @@ class RoleModel extends ModuleModel {
     "module": "role",
     "tables": [
       {
-        'name': 'role',
+        'table': 'role',
         'columns': [
           {
-            'name': 'role_id',
+            'column': 'role_id',
             'dataType': 'int',
             'label': 'Id',
             'options': 'primary',
           },
           {
-            'name': 'role_name',
+            'column': 'role_name',
             'dataType': 'string',
             'label': 'Rolle',
             'size': 32,
             'options': 'unique;notnull',
           },
           {
-            'name': 'role_priority',
+            'column': 'role_priority',
             'dataType': 'int',
             'label': 'Priorität',
           },
           {
-            'name': 'role_created',
-            'dataType': 'dateTime',
-            'label': 'Erzeugt',
-            'options': 'hidden;null',
-          },
-          {
-            'name': 'role_changed',
-            'dataType': 'dateTime',
-            'label': 'Geändert',
-            'options': 'hidden;null',
+            'column': 'role_active',
+            'dataType': 'bool',
+            'label': 'Aktiv',
           },
         ]
       },
     ],
     'pages': [
       {
-        "name": "create",
+        "page": "create",
         "pageType": "create",
         "sections": [
           {
@@ -57,7 +50,7 @@ class RoleModel extends ModuleModel {
         ]
       },
       {
-        "name": "change",
+        "page": "change",
         "pageType": "change",
         "sections": [
           {
@@ -71,7 +64,7 @@ class RoleModel extends ModuleModel {
         ]
       },
       {
-        "name": "list",
+        "page": "list",
         "pageType": "list",
         "sections": [
           {
index 3411bffbec0fe3adfafcd391371e0e5587364dcf..84d18a992d1673bea8ae126f75b872232bb86a62 100644 (file)
@@ -6,44 +6,44 @@ class UserModel extends ModuleModel {
     "module": "user",
     "tables": [
       {
-        'name': 'users',
+        'table': 'user',
         'columns': [
           {
-            'name': 'user_id',
+            'column': 'user_id',
             'dataType': 'int',
             'label': 'Id',
             'options': 'primary',
           },
           {
-            'name': 'user_name',
+            'column': 'user_name',
             'dataType': 'string',
             'label': 'User',
             'size': 64,
-            'options': 'unique',
+            'options': 'unique;notnull',
           },
           {
-            'name': 'user_displayname',
+            'column': 'user_displayname',
             'dataType': 'string',
             'label': 'Anzeigename',
             'size': 32,
             'options': 'unique',
           },
           {
-            'name': 'user_email',
+            'column': 'user_email',
             'dataType': 'string',
             'label': 'EMail',
             'size': 128,
             'options': 'unique',
           },
           {
-            'name': 'user_password',
+            'column': 'user_password',
             'dataType': 'string',
             'label': 'User',
             'size': 128,
             'options': 'password',
           },
           {
-            'name': 'user_role',
+            'column': 'user_role',
             'dataType': 'reference',
             'label': 'Role',
             'foreignKey': 'role.role_id',
@@ -54,45 +54,55 @@ class UserModel extends ModuleModel {
     ],
     'pages': [
       {
-        "name": "create",
+        "page": "create",
         "pageType": "create",
         "sections": [
           {
             "sectionType": "simpleForm",
             "children": [
               {
-                "widgetType": "text",
-                "text": "*_Erfassung eines neuen Benutzers:_*",
-                "options": "rich",
-              },
-              {
-                "widgetType": "emptyLine",
-              },
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "page": "change",
+        "pageType": "change",
+        "sections": [
+          {
+            "sectionType": "simpleForm",
+            "children": [
               {
-                "widgetType": "textField",
-                "name": "user",
-                "label": "Benutzer",
-                "options": "required;unique",
-              },
+                "widgetType": "allDbFields",
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "page": "list",
+        "pageType": "list",
+        "sections": [
+          {
+            "sectionType": "filterPanel",
+            "children": [
               {
                 "widgetType": "textField",
-                "name": "displayname",
-                "label": "Anzeigename",
-                "fieldType": "text",
-                "options": "required",
+                "filterType": "pattern",
+                "name": "user_name",
               },
               {
                 "widgetType": "combobox",
-                "name": "role",
-                "label": "Rolle",
-                "dataType": "reference",
-                "options": "required;undef",
-              },
+                "filterType": "equals",
+                "name": "user_role",
+              }
             ]
           }
         ]
       },
-    ],
+    ]
   };
 
   UserModel(BaseLogger logger) : super(model, logger);
index f1d56f830adefcaec2a0d42873332dd78c0280ad..3deafec37edbef8c59af2f78be8dc9e6a954e002 100644 (file)
@@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
 
 import 'column_model.dart';
 import 'model_base.dart';
+import 'model_types.dart';
 import 'module_model.dart';
 
 /// Represents a database table describing the data model of the model.
@@ -59,8 +60,8 @@ class TableModel extends ModelBase {
 
   /// Parses the map and stores the data in the instance.
   void parse() {
-    name = parseString('name', map, required: true);
-    checkSuperfluousAttributes(map, 'name options columns'.split(' '));
+    name = parseString('table', map, required: true);
+    checkSuperfluousAttributes(map, 'columns options table'.split(' '));
     options = parseOptions('options', map);
     if (!map.containsKey('columns')) {
       logger.error('missing columns in table ${fullName()}');
@@ -74,11 +75,35 @@ class TableModel extends ModelBase {
       }
     }
     checkOptionsByRegExpr(options, regExprOptions);
+    _addIfMissing('${name}_createdat', 'Erzeugt', DataType.dateTime,
+        ['hidden', 'null', 'doStore']);
+    _addIfMissing('${name}_createdby', 'Erzeugt von', DataType.string,
+        ['hidden', 'doStore'], 16);
+    _addIfMissing('${name}_changedat', 'Geändert', DataType.dateTime,
+        ['hidden', 'null', 'doStore']);
+    _addIfMissing('${name}_changedby', 'Geändert von', DataType.string,
+        ['hidden', 'doStore'], 16);
   }
 
   @override
   widgetName() => name;
 
+  /// Adds a column [name] if it does not exist with [label] and [dataType].
+  void _addIfMissing(
+      String name, String label, DataType dataType, List<String> options,
+      [int size]) {
+    if (getColumn(name, required: false) == null) {
+      addColumn(ColumnModel.raw(
+          name: name,
+          table: this,
+          label: label,
+          dataType: dataType,
+          options: options ?? [],
+          size: size,
+          logger: logger));
+    }
+  }
+
   /// Returns a list of tables constructed by the Json like [list].
   static void parseList(
       ModuleModel module, List<dynamic> list, BaseLogger logger) {
index 2360c7958bd429c557fde17fdb325565d9ddcf20..f3df8eacfa6f15e0c38c49f8aa6eb84b2dbdf41c 100644 (file)
@@ -20,7 +20,6 @@ class TextFieldModel extends FieldModel {
   FormFieldSetter onSaved;
 
   final Map<String, dynamic> map;
-  FilterType filterType;
 
   TextFieldModel(
       SectionModel section, PageModel page, this.map, BaseLogger logger)
@@ -42,7 +41,6 @@ class TextFieldModel extends FieldModel {
             .split(' '));
     maxSize = parseInt('maxSize', map, required: false);
     rows = parseInt('rows', map, required: false);
-    filterType = parseEnum<FilterType>('filterType', map, FilterType.values);
     switch (dataType) {
       case DataType.int:
       case DataType.reference:
index 454b1f9ff771a5661b07669eb87b9b0ad5c4461d..f16195d03a5ebfc88f2823086576afc3ab5b1925 100644 (file)
@@ -6,7 +6,7 @@ import 'widget_model.dart';
 
 /// Describes a text widget without user interaction.
 class TextModel extends WidgetModel {
-  static final regExprOptions = RegExp(r'^(richtext)$');
+  static final regExprOptions = RegExp(r'^(rich)$');
   List<String> options;
   String text;
   bool isRichText;
index 269964b068607dec5e1ef0f64e58cb1cf11315c4..e7f75cc1ca412f8c369661a5e087ffa11e543cf7 100644 (file)
@@ -16,7 +16,6 @@ abstract class WidgetModel extends ModelBase {
       : super(logger) {
     this.id = ++lastId;
   }
-
   /// Dumps the internal structure into a [stringBuffer]
   StringBuffer dump(StringBuffer stringBuffer);
 
diff --git a/lib/src/tool/db_tool.dart b/lib/src/tool/db_tool.dart
deleted file mode 100644 (file)
index 984ac4b..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-import 'package:dart_bones/dart_bones.dart';
-import 'package:flutter/widgets.dart';
-import 'package:flutter_bones/flutter_bones.dart';
-import 'package:test/test.dart';
-
-import '../model/standard/role_model.dart';
-import '../model/standard/user_model.dart';
-
-void dummy() {
-  group('xxx', () {
-    test('y', () {
-      Widget text = Text('');
-      expect(text, isNotNull);
-    });
-  });
-}
-
-void main(List<String> argv) async {
-  final logger = MemoryLogger(LEVEL_FINE);
-  if (argv.length > 0xffff) {
-    dummy();
-  }
-  final dbHelper = DbHelper(logger);
-  if (argv.length == 0) {
-    logger.error("missing arguments");
-  } else {
-    final mode = argv[0];
-    argv.removeAt(0);
-    switch (mode) {
-      case 'create-sql':
-        dbHelper.exportSql(argv);
-        break;
-      default:
-        logger.error('unknown mode: ${argv[0]}');
-        break;
-    }
-  }
-  //await tool.main();
-}
-
-class DbHelper {
-  final BaseLogger logger;
-
-  DbHelper(this.logger);
-
-  void exportSql(List<String> argv) {
-    if (argv.length == 0) {
-      logger.error('missing table');
-    } else {
-      final table = argv[0];
-      argv.removeAt(0);
-      ModuleModel module;
-      switch (table) {
-        case 'user':
-          module = UserModel(logger);
-          break;
-        case 'role':
-          module = RoleModel(logger);
-          break;
-        default:
-          logger.error('unknown table');
-          break;
-      }
-      final filename = argv.length == 0 ? '$table.sql' : argv[0];
-      FileSync.toFile(filename, module.exportSql());
-      print('exported: $filename');
-    }
-  }
-
-  void usage(String error) {
-    print('''usage: dbhelper <mode> [<args>]
-<mode>:
-  create-sql <module> [<output-file>]
-    <module>: 'role', 'user' ...
-Examples:
-dbhelper create-sql role role.sql
-''');
-  }
-}
index fbdb7dd9a3b4d176efab79a43e2d0c11ee301e78..99e290fb1cc9e9a0349ae2fee6b17a0c5209211b 100644 (file)
@@ -77,7 +77,7 @@ class Filters
   void add(FilterItem item) => filters.add(item);
 
   FilterItem byName(String name) =>
-      filters.firstWhere((element) => element.name == name);
+      filters.firstWhere((element) => element.name == name, orElse: () => null);
 
   @override
   getOnChanged(String customString, TextCallbackController controller) {
index e1d0a53f9a283007d2d77ade540224b4544ac103..ffc1db6bb71db12b04f685ff2e72219e1b411b40 100644 (file)
@@ -25,7 +25,7 @@ dependencies:
     sdk: flutter
   flutter_localizations:
     sdk: flutter
-  dart_bones: "^0.2.2"
+  dart_bones: "^0.4.3"
 
 
   # The following adds the Cupertino Icons font to your application.
index 8de58918992317f4ee85d9b84dc93c6034c80fa5..7330334e5b170496e8b7c4cb0e6d28025b4442c0 100644 (file)
@@ -1,7 +1,13 @@
 import 'package:flutter_bones/flutter_bones.dart';
+import 'package:intl/date_symbol_data_local.dart';
+import 'package:intl/intl.dart';
 import 'package:test/test.dart';
 
 void main() {
+  setUpAll(() {
+    Intl.defaultLocale = "de_DE";
+    initializeDateFormatting();
+  });
   group('fromString', () {
     test('fromString-string', () {
       expect(StringHelper.fromString('abc', DataType.string), equals('abc'));
@@ -40,4 +46,57 @@ void main() {
           startsWith('<StringHelper.fromString(): unknown datatype'));
     });
   });
+  group('dateXToString', () {
+    test('dateToString', () {
+      expect(StringHelper.dateToString(DateTime(2020, 10, 7), sortable: true),
+          equals('2020.10.07'));
+      expect(StringHelper.dateToString(DateTime(2020, 10, 7), sortable: false),
+          equals('7.10.2020'));
+      expect(StringHelper.dateToString(null, sortable: true).contains('.'),
+          isTrue);
+    });
+    test('dateTimeToString', () {
+      expect(
+          StringHelper.dateTimeToString(DateTime(2020, 10, 7, 4, 33, 2),
+              sortable: true, separator: '-', withSeconds: true),
+          equals('2020.10.07-04:33:02'));
+      expect(
+          StringHelper.dateTimeToString(DateTime(2020, 10, 7, 4, 33, 2),
+              sortable: true, separator: '-', withSeconds: false),
+          equals('2020.10.07-04:33'));
+      expect(
+          StringHelper.dateTimeToString(DateTime(2020, 10, 7, 4, 33, 2),
+              sortable: false),
+          equals('7.10.2020 04:33:02'));
+      expect(
+          StringHelper.dateTimeToString(DateTime(2020, 10, 7, 4, 33, 2),
+              sortable: false, withSeconds: false),
+          equals('7.10.2020 04:33'));
+      expect(
+          StringHelper.dateTimeToString(null,
+                  sortable: false, withSeconds: false)
+              .contains(' '),
+          isTrue);
+    });
+  });
+  group("diverse", () {
+    test('splitArgs', () {
+      var args = ['a', '-a', 'b'];
+      var options = <String>[];
+      expect(StringHelper.splitArgv(args, options), equals(['a', 'b']));
+      expect(options, equals(['-a']));
+      options.clear();
+      args = ['-a', '--b=c'];
+      expect(StringHelper.splitArgv(args, options), equals([]));
+      expect(options, equals(args));
+      options.clear();
+      args = ['a'];
+      expect(StringHelper.splitArgv(args, options), equals(args));
+      expect(options, equals([]));
+      options.clear();
+      args = [];
+      expect(StringHelper.splitArgv(args, options), equals(args));
+      expect(options, equals([]));
+    });
+  });
 }
index 8001a31b856ed075f9ae5574a3930a2288274da1..76ad5e60bbc2ab4d752db66b1b341a391edbbcbc 100644 (file)
@@ -16,18 +16,26 @@ void main() {
       final page = module.pageByName('create');
       expect(page?.fullName(), equals('demo1.create'));
       expect(module.fullName(), equals('demo1'));
-      final table = module.tableByName('users');
-      expect(table.fullName(), equals('demo1.users'));
-      expect(table.widgetName(), equals('users'));
+      final table = module.tableByName('user');
+      expect(table.fullName(), equals('demo1.user'));
+      expect(table.widgetName(), equals('user'));
       final dump = module.dump(StringBuffer()).toString();
       expect(dump, '''= module demo1: options: 
-== table users: options: 
-    column user_id: DataType.int "Id" options: primary
+== table user: options: 
+    column user_id: DataType.int "Id" options: primary notnull unique
     column user_name: DataType.string "User" options: unique
     column user_role: DataType.reference "Role" options: 
-== table roles: options: 
-    column role_id: DataType.int "Id" options: primary
+    column user_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+    column user_createdby: DataType.string "Erzeugt von" options: hidden doStore
+    column user_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+    column user_changedby: DataType.string "Geändert von" options: hidden doStore
+== table role: options: 
+    column role_id: DataType.int "Id" options: primary notnull unique
     column role_name: DataType.string "Role" options: unique
+    column role_createdat: DataType.dateTime "Erzeugt" options: hidden null doStore
+    column role_createdby: DataType.string "Erzeugt von" options: hidden doStore
+    column role_changedat: DataType.dateTime "Geändert" options: hidden null doStore
+    column role_changedby: DataType.string "Geändert von" options: hidden doStore
 == page create: PageModelType.create options: 
     = section simpleForm1: SectionModelType.simpleForm options:  [
     textField user: options: required unique
@@ -38,7 +46,7 @@ void main() {
       final userField = table.getColumn('user_id');
       expect(userField, isNotNull);
       expect(userField.widgetName(), 'user_id');
-      expect(userField.fullName(), equals('users.user_id'));
+      expect(userField.fullName(), equals('user.user_id'));
       final userRef = page.getField('user');
       expect(userRef, isNotNull);
       expect(userRef.fullName(), equals('create.user'));
@@ -51,23 +59,23 @@ final userModel = <String, dynamic>{
   'module': 'demo1',
   'tables': [
     {
-      'name': 'users',
+      'table': 'user',
       'columns': [
         {
-          'name': 'user_id',
+          'column': 'user_id',
           'dataType': 'int',
           'label': 'Id',
           'options': 'primary',
         },
         {
-          'name': 'user_name',
+          'column': 'user_name',
           'dataType': 'string',
           'label': 'User',
           'size': 32,
           'options': 'unique',
         },
         {
-          'name': 'user_role',
+          'column': 'user_role',
           'dataType': 'reference',
           'label': 'Role',
           'foreignKey': 'role.role_id',
@@ -76,16 +84,16 @@ final userModel = <String, dynamic>{
       ]
     },
     {
-      'name': 'roles',
+      'table': 'role',
       'columns': [
         {
-          'name': 'role_id',
+          'column': 'role_id',
           'dataType': 'int',
           'label': 'Id',
           'options': 'primary',
         },
         {
-          'name': 'role_name',
+          'column': 'role_name',
           'dataType': 'string',
           'label': 'Role',
           'size': 32,
@@ -96,7 +104,7 @@ final userModel = <String, dynamic>{
   ],
   'pages': [
     {
-      'name': 'create',
+      'page': 'create',
       'pageType': 'create',
       'sections': [
         {
@@ -112,7 +120,7 @@ final userModel = <String, dynamic>{
             {
               'widgetType': 'dbReference',
               'name': 'role',
-              'column': 'roles.role_id',
+              'column': 'role.role_id',
             },
             {
               'widgetType': 'button',
index 60e97b8e95652c78a596172fdf3e707e8f9d9d86..9e85c68a20dad909ffe65ea9142090f8772d5e11 100644 (file)
@@ -166,7 +166,7 @@ void main() {
       var module = Demo1(map, logger);
       module.parse();
       var errors = logger.errors;
-      expect(errors.length, equals(3));
+      expect(errors.length, equals(2));
       final dump = module.dump(StringBuffer()).toString();
       expect(dump, equals('''= module demo1: options: 
 == page create: PageModelType.create options: 
@@ -190,13 +190,19 @@ void main() {
       expect(nonFieldWidgets.length, equals(2));
     });
   });
+  group('standard-models', () {
+    test('user', () {
+      final model = UserModel(logger);
+      model.parse();
+    });
+  });
 }
 
 final userModel = <String, dynamic>{
   'module': 'demo1',
   'pages': [
     {
-      'name': 'create',
+      'page': 'create',
       'pageType': 'create',
       'sections': [
         {
index 506f74a5e3f1da3517b8b46f83b64a3734a8a472..023c4664090e4d2e7e8a1aa5af504d6118fcb821 100644 (file)
@@ -5,21 +5,61 @@ import 'package:test/test.dart';
 void main() {
   final logger = MemoryLogger(LEVEL_FINE);
   group('sql', () {
-    test('export-sql', () {
+    test('exportSqlCreateTablel', () {
       logger.clear();
       final module = RoleModel(logger);
       module.parse();
-      final content = module.exportSql();
+      final content = module.exportSqlCreateTable();
       final errors = logger.errors;
       expect(errors.length, equals(0));
-      expect(content, equals('''drop table if exists role;
-create table role (
-  role_id int(10) primary;
-  role_name varchar(255) unique notnull;
-  role_priority int(10);
-  role_created timestamp null;
-  role_changed timestamp null;
+      expect(content, equals('''DROP TABLE IF EXISTS role;
+CREATE TABLE role (
+  role_id INT(10) notnull unique,
+  role_name VARCHAR(32) unique notnull,
+  role_priority INT(10),
+  role_active CHAR(1),
+  role_createdat TIMESTAMP null,
+  role_createdby VARCHAR(16),
+  role_changedat TIMESTAMP null,
+  role_changedby VARCHAR(16),
+  PRIMARY KEY(role_id)
 );
+'''));
+    });
+    test('exportSqlBackend', () {
+      logger.clear();
+      final module = RoleModel(logger);
+      module.parse();
+      final content = module.exportSqlBackend(testDate: true);
+      final errors = logger.errors;
+      expect(errors.length, equals(0));
+      expect(content, equals('''---
+# configuration of the bones backend for role:
+created: 2020.01.01 00:00:00
+author: flutter_bones.module_model.exportSqlBackend()
+version: 1.0.0
+modules:
+  - module: role
+    list:
+      - name: insert
+        type: insert
+        sql: "INSERT INTO role(role_name,role_priority,role_active,role_createdat,role_createdby,role_changedat,role_changedby)
+          VALUES(:role_name,:role_priority,:role_active,NOW(),:role_createdby,:role_changedat,:role_changedby);"
+      - name: update
+        type: update
+        sql: "UPDATE role SET
+          role_name=:role_name,role_priority=:role_priority,role_active=:role_active,role_createdat=:role_createdat,role_createdby=:role_createdby,role_changedat=NOW(),role_changedby=:role_changedby
+          WHERE role_id=:role_id";
+      - name: delete
+        type: delete
+        sql: "DELETE from role WHERE role_id=:role_id;"
+      - name: record
+        type: record
+        sql: "SELECT * from role WHERE role_id=:role_id;"
+      - name: list
+        type: list
+        sql: "SELECT * from role
+          WHERE role_name like :role_name;"
 '''));
     });
   });