From: Hamatoma Date: Wed, 6 Oct 2021 21:20:26 +0000 (+0200) Subject: Generator RestServer GlobalData edit/delete-routing X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=0c5831cc6b55b3308b16b6404bfe05e8496f8fa1;p=exhibition.git Generator RestServer GlobalData edit/delete-routing * Generator: ** Refactoring: PageGenerator und SqlGenerator ** Sql: Generation of foreign keys * RestServer: ** Wertet Offset/Size (Paginierung) bei List aus ** Automatische generierung des SELECT count(*) ...-Statements * GlobalData: ** Refactoring: the singleton instance is fetched later/deeper in class hierarchy * edit/delete-routing: the routing contains at least the primary key * persistence: Refactoring: Result of query() is now DbData --- diff --git a/lib/exhibition_app.dart b/lib/exhibition_app.dart index bc485e8..148a4f2 100644 --- a/lib/exhibition_app.dart +++ b/lib/exhibition_app.dart @@ -12,13 +12,13 @@ Route? _getRoute(RouteSettings settings) { StatefulWidget? page; switch (settings.name) { case '/start': - page = StartPage(globalLogger); + page = StartPage(GlobalData().logger); break; case '/info': - page = InfoPage(GlobalData()); + page = InfoPage(); break; case '/log': - page = LogPage(GlobalData()); + page = LogPage(); break; default: page = PageCollection().newPageByRoute(settings.name ?? ''); diff --git a/lib/meta/module_meta_data.dart b/lib/meta/module_meta_data.dart index 6c4f4f8..b7c507c 100644 --- a/lib/meta/module_meta_data.dart +++ b/lib/meta/module_meta_data.dart @@ -279,12 +279,12 @@ class PropertyMetaData { String columnName; /// The foreign key if dataType is DataType.reference, e.g. 'users.user_id' - String? reference; + String? foreignKey; PropertyMetaData(this.name, this.label, this.dataType, this.options, {this.displayType = DisplayType.text, this.columnName = '', this.size = 0, - this.reference, + this.foreignKey, int weight = 6}) { this.weight = weight > 12 ? 12 : weight; } diff --git a/lib/meta/modules.dart b/lib/meta/modules.dart index 2ee2891..57e2fe3 100644 --- a/lib/meta/modules.dart +++ b/lib/meta/modules.dart @@ -2,7 +2,6 @@ import 'module_meta_data.dart'; import 'roles_meta.dart'; import 'users_meta.dart'; - /// Returns the meta data of the module given by [name]. /// Returns null if not found. ModuleMetaData? moduleByName(String name) { @@ -19,9 +18,8 @@ ModuleMetaData? moduleByName(String name) { } return rc; } - /// Returns the module names as string list. -List moduleNames() { +List moduleNames(){ return [ 'Roles', 'Users', diff --git a/lib/meta/users_meta.dart b/lib/meta/users_meta.dart index af9ba00..53b9a2b 100644 --- a/lib/meta/users_meta.dart +++ b/lib/meta/users_meta.dart @@ -25,7 +25,7 @@ class UserMeta extends ModuleMetaData { size: 255), PropertyMetaData( 'role', i18n.tr('Role'), DataType.reference, ':notnull:', - displayType: DisplayType.combobox, reference: 'roles.role_id'), + displayType: DisplayType.combobox, foreignKey: 'roles.role_id;role_name;role'), PropertyMetaData( 'created', i18n.tr('Created'), DataType.datetime, ':hidden:'), PropertyMetaData( diff --git a/lib/page/info_page.dart b/lib/page/info_page.dart index ea8b7bc..9ae2ed8 100644 --- a/lib/page/info_page.dart +++ b/lib/page/info_page.dart @@ -6,19 +6,14 @@ import 'package:url_launcher/url_launcher.dart'; import '../setting/global_data.dart'; class InfoPage extends StatefulWidget { - final GlobalData globalData; - - const InfoPage(this.globalData, {Key? key}) : super(key: key); - @override - InfoPageState createState() { - return InfoPageState(); + _InfoPageState createState() { + return _InfoPageState(GlobalData()); } } -class InfoPageState extends State { - final GlobalData globalData = GlobalData(); - +class _InfoPageState extends State { + final GlobalData globalData; final GlobalKey _formKey = GlobalKey(debugLabel: 'Log'); final markdownData = '''# Pollector @@ -49,7 +44,7 @@ Zur Entwicklung wurden einige **Opensource-Pakete** verwendet: '''; int clickCounter = 0; - InfoPageState(); + _InfoPageState(this.globalData); @override Widget build(BuildContext context) { diff --git a/lib/page/log_page.dart b/lib/page/log_page.dart index 3e7d363..522b9c7 100644 --- a/lib/page/log_page.dart +++ b/lib/page/log_page.dart @@ -5,24 +5,20 @@ import 'package:flutter/material.dart'; import '../setting/global_data.dart'; class LogPage extends StatefulWidget { - final GlobalData globalData; - - const LogPage(this.globalData, {Key? key}) : super(key: key); - @override LogPageState createState() { - return LogPageState(); + return LogPageState(GlobalData()); } } class LogPageState extends State { - final GlobalData globalData = GlobalData(); + final GlobalData globalData; final GlobalKey _formKey = GlobalKey(debugLabel: 'Log'); String logName = ''; - LogPageState(); + LogPageState(this.globalData); @override Widget build(BuildContext context) { diff --git a/lib/page/page_collection.dart b/lib/page/page_collection.dart index 675ce07..eca445f 100644 --- a/lib/page/page_collection.dart +++ b/lib/page/page_collection.dart @@ -1,6 +1,5 @@ // DO NOT CHANGE. This file is created by the meta_tool! import 'package:flutter/material.dart'; -import '../setting/global_data.dart'; import 'page_collection_custom.dart'; import 'roles/create_role_page.dart'; @@ -11,6 +10,7 @@ import 'users/edit_user_page.dart'; import 'users/delete_user_page.dart'; import 'users/list_user_page.dart'; + /// Manages all meta data driven pages of the program. class PageCollection { static PageCollection? _instance; @@ -24,31 +24,32 @@ class PageCollection { /// Creates a page defined by a [route]. StatefulWidget? newPageByRoute(String route) { StatefulWidget? rc; - final globalData = GlobalData(); - switch (route) { + final parts = route.split(';'); + final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { case '/roles/create': - rc = CreateRolePage(globalData); + rc = CreateRolePage(); break; case '/roles/edit': - rc = EditRolePage(globalData); + rc = EditRolePage(arg1); break; case '/roles/list': - rc = ListRolePage(globalData); + rc = ListRolePage(); break; case '/users/create': - rc = CreateUserPage(globalData); + rc = CreateUserPage(); break; case '/users/edit': - rc = EditUserPage(globalData); + rc = EditUserPage(arg1); break; case '/users/delete': - rc = DeleteUserPage(globalData); + rc = DeleteUserPage(arg1); break; case '/users/list': - rc = ListUserPage(globalData); + rc = ListUserPage(); break; default: - rc = customPageByRoute(route, globalData); + rc = customPageByRoute(route); break; } if (rc != null) { diff --git a/lib/page/page_collection_custom.dart b/lib/page/page_collection_custom.dart index c61ba97..d9c8dc9 100644 --- a/lib/page/page_collection_custom.dart +++ b/lib/page/page_collection_custom.dart @@ -1,21 +1,22 @@ // This file is created by the meta_tool. But it can be customized. // It will never overridden by the meta_tool. import 'package:flutter/material.dart'; -import '../setting/global_data.dart'; import 'info_page.dart'; import 'log_page.dart'; /// Creates a page defined by a [route]. /// /// Note: only pages without meta data definitions should be handled here. -StatefulWidget? customPageByRoute(String route, GlobalData globalData) { +StatefulWidget? customPageByRoute(String route) { StatefulWidget? rc; - switch (route) { + final parts = route.split(';'); + //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { case '/info': - rc = InfoPage(globalData); + rc = InfoPage(); break; case '/log': - rc = LogPage(globalData); + rc = LogPage(); break; default: break; diff --git a/lib/page/roles/create_role_custom.dart b/lib/page/roles/create_role_custom.dart index 7d338e8..90fe008 100644 --- a/lib/page/roles/create_role_custom.dart +++ b/lib/page/roles/create_role_custom.dart @@ -14,6 +14,7 @@ class CreateRoleCustom extends State { final globalData = GlobalData(); AttendedPage? attendedPage; + CreateRoleCustom(); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +27,6 @@ class CreateRoleCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/roles/create_role_page.dart b/lib/page/roles/create_role_page.dart index 6018fb8..4ec6c88 100644 --- a/lib/page/roles/create_role_page.dart +++ b/lib/page/roles/create_role_page.dart @@ -7,20 +7,20 @@ import '../../widget/attended_page.dart'; import 'create_role_custom.dart'; class CreateRolePage extends StatefulWidget { - final GlobalData globalData; final PageStates pageStates = PageStates(); - CreateRolePage(this.globalData) : super(); + CreateRolePage() : super(); @override _CreateRolePageState createState() { final rc = _CreateRolePageState(); rc.attendedPage = - AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _CreateRolePageState extends CreateRoleCustom { + _CreateRolePageState(): super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/roles/edit_role_custom.dart b/lib/page/roles/edit_role_custom.dart index b66d65b..99b664b 100644 --- a/lib/page/roles/edit_role_custom.dart +++ b/lib/page/roles/edit_role_custom.dart @@ -11,9 +11,11 @@ import 'edit_role_page.dart'; final i18n = I18N(); class EditRoleCustom extends State { + final int primaryKey; final globalData = GlobalData(); AttendedPage? attendedPage; + EditRoleCustom(this.primaryKey); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +28,6 @@ class EditRoleCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/roles/edit_role_page.dart b/lib/page/roles/edit_role_page.dart index 309976c..ef15ac4 100644 --- a/lib/page/roles/edit_role_page.dart +++ b/lib/page/roles/edit_role_page.dart @@ -7,20 +7,21 @@ import '../../widget/attended_page.dart'; import 'edit_role_custom.dart'; class EditRolePage extends StatefulWidget { - final GlobalData globalData; + final int primaryKey; final PageStates pageStates = PageStates(); - EditRolePage(this.globalData) : super(); + EditRolePage(this.primaryKey) : super(); @override _EditRolePageState createState() { - final rc = _EditRolePageState(); + final rc = _EditRolePageState(this.primaryKey); rc.attendedPage = - AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _EditRolePageState extends EditRoleCustom { + _EditRolePageState(int primaryKey): super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/roles/list_role_custom.dart b/lib/page/roles/list_role_custom.dart index f367ee2..d85fc58 100644 --- a/lib/page/roles/list_role_custom.dart +++ b/lib/page/roles/list_role_custom.dart @@ -14,6 +14,7 @@ class ListRoleCustom extends State { final globalData = GlobalData(); AttendedPage? attendedPage; + ListRoleCustom(); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +27,6 @@ class ListRoleCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/roles/list_role_page.dart b/lib/page/roles/list_role_page.dart index b2f01d4..b42f052 100644 --- a/lib/page/roles/list_role_page.dart +++ b/lib/page/roles/list_role_page.dart @@ -7,20 +7,20 @@ import '../../widget/attended_page.dart'; import 'list_role_custom.dart'; class ListRolePage extends StatefulWidget { - final GlobalData globalData; final PageStates pageStates = PageStates(); - ListRolePage(this.globalData) : super(); + ListRolePage() : super(); @override _ListRolePageState createState() { final rc = _ListRolePageState(); rc.attendedPage = - AttendedPage(this, rc, globalData, RoleMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), RoleMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _ListRolePageState extends ListRoleCustom { + _ListRolePageState(): super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/create_user_custom.dart b/lib/page/users/create_user_custom.dart index 309f2e0..f6f70af 100644 --- a/lib/page/users/create_user_custom.dart +++ b/lib/page/users/create_user_custom.dart @@ -14,6 +14,7 @@ class CreateUserCustom extends State { final globalData = GlobalData(); AttendedPage? attendedPage; + CreateUserCustom(); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +27,6 @@ class CreateUserCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/users/create_user_page.dart b/lib/page/users/create_user_page.dart index dec4186..14e69e2 100644 --- a/lib/page/users/create_user_page.dart +++ b/lib/page/users/create_user_page.dart @@ -7,20 +7,20 @@ import '../../widget/attended_page.dart'; import 'create_user_custom.dart'; class CreateUserPage extends StatefulWidget { - final GlobalData globalData; final PageStates pageStates = PageStates(); - CreateUserPage(this.globalData) : super(); + CreateUserPage() : super(); @override _CreateUserPageState createState() { final rc = _CreateUserPageState(); rc.attendedPage = - AttendedPage(this, rc, globalData, UserMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _CreateUserPageState extends CreateUserCustom { + _CreateUserPageState(): super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/delete_user_custom.dart b/lib/page/users/delete_user_custom.dart index 6a3dfd1..d36681a 100644 --- a/lib/page/users/delete_user_custom.dart +++ b/lib/page/users/delete_user_custom.dart @@ -11,9 +11,11 @@ import 'delete_user_page.dart'; final i18n = I18N(); class DeleteUserCustom extends State { + final int primaryKey; final globalData = GlobalData(); AttendedPage? attendedPage; + DeleteUserCustom(this.primaryKey); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +28,6 @@ class DeleteUserCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/users/delete_user_page.dart b/lib/page/users/delete_user_page.dart index e5ac100..85cbfc9 100644 --- a/lib/page/users/delete_user_page.dart +++ b/lib/page/users/delete_user_page.dart @@ -7,20 +7,21 @@ import '../../widget/attended_page.dart'; import 'delete_user_custom.dart'; class DeleteUserPage extends StatefulWidget { - final GlobalData globalData; + final int primaryKey; final PageStates pageStates = PageStates(); - DeleteUserPage(this.globalData) : super(); + DeleteUserPage(this.primaryKey) : super(); @override _DeleteUserPageState createState() { - final rc = _DeleteUserPageState(); + final rc = _DeleteUserPageState(this.primaryKey); rc.attendedPage = - AttendedPage(this, rc, globalData, UserMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _DeleteUserPageState extends DeleteUserCustom { + _DeleteUserPageState(int primaryKey): super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/edit_user_custom.dart b/lib/page/users/edit_user_custom.dart index 3c20745..3e5f5a3 100644 --- a/lib/page/users/edit_user_custom.dart +++ b/lib/page/users/edit_user_custom.dart @@ -11,9 +11,11 @@ import 'edit_user_page.dart'; final i18n = I18N(); class EditUserCustom extends State { + final int primaryKey; final globalData = GlobalData(); AttendedPage? attendedPage; + EditUserCustom(this.primaryKey); @override Widget build(BuildContext context) { final padding = 16.0; @@ -26,7 +28,6 @@ class EditUserCustom extends State { padding: padding))); return rc; } - @override void initState() { super.initState(); diff --git a/lib/page/users/edit_user_page.dart b/lib/page/users/edit_user_page.dart index 4c6bcf3..1179663 100644 --- a/lib/page/users/edit_user_page.dart +++ b/lib/page/users/edit_user_page.dart @@ -7,20 +7,21 @@ import '../../widget/attended_page.dart'; import 'edit_user_custom.dart'; class EditUserPage extends StatefulWidget { - final GlobalData globalData; + final int primaryKey; final PageStates pageStates = PageStates(); - EditUserPage(this.globalData) : super(); + EditUserPage(this.primaryKey) : super(); @override _EditUserPageState createState() { - final rc = _EditUserPageState(); + final rc = _EditUserPageState(this.primaryKey); rc.attendedPage = - AttendedPage(this, rc, globalData, UserMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _EditUserPageState extends EditUserCustom { + _EditUserPageState(int primaryKey): super(primaryKey); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/page/users/list_user_custom.dart b/lib/page/users/list_user_custom.dart index e2b1db5..b3e40b8 100644 --- a/lib/page/users/list_user_custom.dart +++ b/lib/page/users/list_user_custom.dart @@ -14,16 +14,45 @@ class ListUserCustom extends State { final globalData = GlobalData(); AttendedPage? attendedPage; + ListUserCustom(); @override Widget build(BuildContext context) { final padding = 16.0; + final filters = WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), + screenWidth: attendedPage!.pageStates.screenWidth, padding: padding); + final rows = attendedPage!.getRows( + 'user_id;user_displayname;user_email;role', + '/users/list', + {}, + () => setState(() => 1), + '/users/edit', + context); + final table = DataTable( + columns: [ + DataColumn( + label: Text(i18n.tr('Id')), + ), + DataColumn( + label: Text(i18n.tr('Name')), + ), + DataColumn( + label: Text(i18n.tr('EMail')), + ), + DataColumn( + label: Text(i18n.tr('Role')), + ), + ], + rows: rows, + ); + final frameWidget = Column(children: [ + filters, + SizedBox(height: padding), + SizedBox(width: double.infinity, child: table), + ]); final rc = Scaffold( appBar: globalData.appBarBuilder(i18n.tr('Change User data')), drawer: globalData.drawerBuilder(context), - body: SafeArea( - child: WidgetForm.flexibleGrid(attendedPage!.attendedWidgets(), - screenWidth: attendedPage!.pageStates.screenWidth, - padding: padding))); + body: SafeArea(child: frameWidget)); return rc; } diff --git a/lib/page/users/list_user_page.dart b/lib/page/users/list_user_page.dart index b05bbf3..1938d3d 100644 --- a/lib/page/users/list_user_page.dart +++ b/lib/page/users/list_user_page.dart @@ -7,20 +7,20 @@ import '../../widget/attended_page.dart'; import 'list_user_custom.dart'; class ListUserPage extends StatefulWidget { - final GlobalData globalData; final PageStates pageStates = PageStates(); - ListUserPage(this.globalData) : super(); + ListUserPage() : super(); @override _ListUserPageState createState() { final rc = _ListUserPageState(); rc.attendedPage = - AttendedPage(this, rc, globalData, UserMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _ListUserPageState extends ListUserCustom { + _ListUserPageState(): super(); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; diff --git a/lib/persistence/file_persistence.dart b/lib/persistence/file_persistence.dart index c1bee7d..9637e75 100644 --- a/lib/persistence/file_persistence.dart +++ b/lib/persistence/file_persistence.dart @@ -6,6 +6,7 @@ import 'package:path/path.dart' as path; import 'persistence.dart'; import 'data_record.dart'; +import '../base/defines.dart'; /// Implements a persistence layer storing data in Json files. class FilePersistence extends Persistence { @@ -30,7 +31,7 @@ class FilePersistence extends Persistence { logger: logger); @override - Future query({required String what, DataMap? data}) async { + Future query({required String what, DataMap? data}) async { var rc; final fn = path.join(dataDirectory, '$what.json'); final file = File(fn); @@ -38,9 +39,17 @@ class FilePersistence extends Persistence { logger.error('missing $fn'); } else { final content = await file.readAsString(); - rc = convert.jsonDecode(content); + final data = convert.jsonDecode(content); + if (data is Map){ + rc = DbData.single(data as JsonMap); + } else if (data is List){ + rc = DbData.list(data as JsonList, -1); + } else if (data is String){ + rc = DbData.message(data); + } else { + rc = DbData.message('unknown data type: ${data.runtimeType}'); + } } - return rc; } diff --git a/lib/persistence/persistence.dart b/lib/persistence/persistence.dart index 1ef20ca..39949ef 100644 --- a/lib/persistence/persistence.dart +++ b/lib/persistence/persistence.dart @@ -1,11 +1,23 @@ +import '../base/defines.dart'; + import 'data_record.dart'; +class DbData { + final JsonMap? singleRecord; + final JsonList? recordList; + final int? count; + final String? message; + DbData(this.singleRecord, this.recordList, this.count, this.message); + DbData.single(JsonMap record): this(record, null, null, null); + DbData.list(JsonList records, int count): this(null, records, count, null); + DbData.message(String message): this(null, null, null, message); +} abstract class Persistence { /// Fetches data specified by [what] and some [data]. /// /// Returns null or a record (as a Map instance) /// or a list of records (as a List instance). - Future query({required String what, DataMap? data}); + Future query({required String what, DataMap? data}); /// Stores a map or a data container in the persistence layer. /// diff --git a/lib/persistence/rest_persistence.dart b/lib/persistence/rest_persistence.dart index a825562..a00a6b7 100644 --- a/lib/persistence/rest_persistence.dart +++ b/lib/persistence/rest_persistence.dart @@ -1,5 +1,6 @@ import 'dart:convert' as convert; import 'dart:io'; +import 'dart:math'; import 'package:dart_bones/dart_bones.dart'; import 'package:http/http.dart' as http; @@ -93,16 +94,29 @@ class RestPersistence extends Persistence { } @override - Future query( + Future query( {required String what, Map? data}) async { - String rc; + DbData rc; final params2 = data == null ? '{}' : convert.jsonEncode(data); final answer = await runRequest(what, body: params2, headers: jsonHeader); - if (answer.isNotEmpty && - (answer.startsWith('{') || answer.startsWith('['))) { - rc = convert.jsonDecode(answer); + if (answer.isEmpty){ + rc = DbData.message('query(): empty result'); + } else if (answer.startsWith('{')){ + rc = DbData.single(convert.jsonDecode(answer)); + } else if (answer.startsWith('[')) { + rc = DbData.list(convert.jsonDecode(answer), -1); + } else if (answer.startsWith('#')) { + int ix = answer.indexOf('['); + if (ix > 2 && ix < 8){ + final count = int.tryParse(answer.substring(1, ix)); + rc = DbData.list(convert.jsonDecode(answer.substring(ix)), count ?? -1); + } else { + rc = DbData.message( + 'query(): unexpected answer prefix: ' + '${answer.substring(0, min(answer.length, 8))}'); + } } else { - rc = answer; + rc = DbData.message(answer.substring(0, max(answer.length, 120))); } if (logger.logLevel >= LEVEL_FINE) { logger.log('answer: ${limitString(answer, sqlTraceLimit)}'); diff --git a/lib/setting/drawer_exhibition.dart b/lib/setting/drawer_exhibition.dart index 70b8847..49822ef 100644 --- a/lib/setting/drawer_exhibition.dart +++ b/lib/setting/drawer_exhibition.dart @@ -1,8 +1,7 @@ import 'package:dart_bones/dart_bones.dart'; +import 'package:exhibition/page/page_collection.dart'; import 'package:flutter/material.dart'; -import '../page/info_page.dart'; -import '../page/log_page.dart'; import 'global_data.dart'; class DrawerExhibition extends Drawer { @@ -74,6 +73,9 @@ class MenuConverter { IconData iconByName(String name, BaseLogger logger) { IconData? rc; switch (name) { + case 'people_outlined': + rc = Icons.people_outline; + break; case 'addchart_outlined': rc = Icons.addchart_outlined; break; @@ -98,24 +100,6 @@ class MenuConverter { } return rc ?? Icons.access_alarm; } - - /// Returns the page given by [name]. - StatefulWidget? pageByName(String name, GlobalData globalData) { - StatefulWidget? rc; - switch (name) { - case 'log': - rc = LogPage(globalData); - break; - case 'info': - rc = InfoPage(globalData); - break; - default: - globalData.logger - .error('MenuConverter.pageByName(): unknown page $name'); - break; - } - return rc; - } } class MenuItem { @@ -128,14 +112,17 @@ class MenuItem { static List menuItems(MenuConverter converter) { final globalData = GlobalData(); final logger = globalData.logger; + final collection = PageCollection(); return [ - MenuItem('Protokoll', () => converter.pageByName('log', globalData), + MenuItem('Users', () => collection.newPageByRoute('/users/list'), + converter.iconByName('people_outlined', logger)), + MenuItem('Protokoll', () => collection.newPageByRoute('/log'), converter.iconByName('line_weight_outlined', logger)), - MenuItem('Information', () => converter.pageByName('info', globalData), + MenuItem('Information', () => collection.newPageByRoute('/info'), converter.iconByName('info_outline', logger)), MenuItem( 'Konfiguration', - () => converter.pageByName('configuration', globalData), + () => collection.newPageByRoute('/configuration'), converter.iconByName('settings_applications_outlined', logger)), ]; } diff --git a/lib/widget/attended_page.dart b/lib/widget/attended_page.dart index 7dae3c0..de14e2a 100644 --- a/lib/widget/attended_page.dart +++ b/lib/widget/attended_page.dart @@ -1,3 +1,4 @@ +import 'package:exhibition/persistence/persistence.dart'; import 'package:exhibition/widget/attended_widget.dart'; import 'package:flutter/material.dart'; @@ -46,6 +47,40 @@ class AttendedPage { return rc; } + /// Returns a list of DataRow instances to show the content of some + /// database records in a [DataTable]. + /// + /// [columnList]: a ';' delimited list of column names, e.g. 'user_id;user_name' + List getRows(String columnList, String what, + Map parameters, Function() onDone, String routeEdit, BuildContext context) { + List? rc; + if (!pageStates.openQuery) { + pageStates.openQuery = true; + globalData.restPersistence! + .query(what: what, data: parameters) + .then((value) { + pageStates.dbData = value; + pageStates.openQuery = false; + onDone(); + }); + } else if (pageStates.dbData != null) { + if (pageStates.dbData?.recordList != null) { + rc = []; + pageStates.dbData?.recordList!.map((record) { + final cells = []; + for (var column in columnList.split(';')){ + final primaryKey = moduleMetaData.primaryOf(); + cells.add(DataCell(Text(record[column].toString()), + onTap: () => globalData.navigate(context, routeEdit + ';${record[primaryKey]}'))); + } + final rc3 = DataRow(cells: cells); + return rc3; + }); + } + } + return rc ?? []; + } + /// Validates the text [value] of the [textAttended]. /// /// Returns null on success or the error message on error. @@ -68,4 +103,6 @@ class PageStates { double screenWidth = 0; double screenHeight = 0; AttendedPage? attendedPage; + DbData? dbData; + bool openQuery = false; } diff --git a/metatool/bin/page_generator.dart b/metatool/bin/page_generator.dart index 6c13ce6..34ba5f2 100644 --- a/metatool/bin/page_generator.dart +++ b/metatool/bin/page_generator.dart @@ -5,7 +5,6 @@ import 'package:dart_bones/dart_bones.dart'; import '../lib/meta/module_meta_data.dart'; import '../lib/meta/modules.dart'; import 'generator.dart'; -import 'generator_base.dart'; import 'sql_generator.dart'; /// Handles the page generation. Is a base class of [Generator]. @@ -20,20 +19,20 @@ import '../../widget/attended_page.dart'; import 'edit_user_custom.dart'; class EditUserPage extends StatefulWidget { - final GlobalData globalData; - final PageStates pageStates = PageStates(); - EditUserPage(this.globalData) : super(); +DEF_PRIMARY final PageStates pageStates = PageStates(); + EditUserPage(THIS_PRIMARY) : super(); @override _EditUserPageState createState() { - final rc = _EditUserPageState(); + final rc = _EditUserPageState(THIS_PRIMARY); rc.attendedPage = - AttendedPage(this, rc, globalData, UserMeta.instance, pageStates); + AttendedPage(this, rc, GlobalData(), UserMeta.instance, pageStates); pageStates.attendedPage = rc.attendedPage; return rc; } } class _EditUserPageState extends EditUserCustom { + _EditUserPageState(PARAM_PRIMARY): super(VAL_PRIMARY); @override void didChangeDependencies() { final size = MediaQuery.of(context).size; @@ -57,10 +56,10 @@ import 'edit_user_page.dart'; final i18n = I18N(); class EditUserCustom extends State { - final globalData = GlobalData(); +DEF_PRIMARY final globalData = GlobalData(); AttendedPage? attendedPage; - + EditUserCustom(THIS_PRIMARY); @override Widget build(BuildContext context) { final padding = 16.0; @@ -81,7 +80,6 @@ class EditUserCustom extends State { '''; static final templateCollection = '''// DO NOT CHANGE. This file is created by the meta_tool! import 'package:flutter/material.dart'; -import '../setting/global_data.dart'; import 'page_collection_custom.dart'; #IMPORTS# @@ -99,10 +97,11 @@ class PageCollection { /// Creates a page defined by a [route]. StatefulWidget? newPageByRoute(String route) { StatefulWidget? rc; - final globalData = GlobalData(); - switch (route) { + final parts = route.split(';'); + final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { #CASES# default: - rc = customPageByRoute(route, globalData); + rc = customPageByRoute(route); break; } if (rc != null) { @@ -120,21 +119,22 @@ class PageCollection { static final templateCollectionCustom = '''// This file is created by the meta_tool. But it can be customized. // It will never overridden by the meta_tool. import 'package:flutter/material.dart'; -import '../setting/global_data.dart'; import 'info_page.dart'; import 'log_page.dart'; /// Creates a page defined by a [route]. /// /// Note: only pages without meta data definitions should be handled here. -StatefulWidget? customPageByRoute(String route, GlobalData globalData) { +StatefulWidget? customPageByRoute(String route) { StatefulWidget? rc; - switch (route) { + final parts = route.split(';'); + //final arg1 = parts.length < 2 ? 0 : int.parse(parts[1]); + switch (parts[0]) { case '/info': - rc = InfoPage(globalData); + rc = InfoPage(); break; case '/log': - rc = LogPage(globalData); + rc = LogPage(); break; default: break; @@ -143,7 +143,7 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) { } '''; static final templateCase = ''' case '/users/edit': - rc = EditUserPage(globalData); + rc = EditUserPage(ARG1); break; '''; PageGenerator(BaseLogger logger) : super(logger); @@ -164,12 +164,30 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) { /// Returns a Dart class definition of the [page] that should not be modified. String createCustomized(PageMetaData page) { var rc = replaceVariables(templateCustom, page); + if (page.pageType == PageType.edit || page.pageType == PageType.delete) { + rc = rc.replaceAll('DEF_PRIMARY', ' final int primaryKey;\n') + .replaceAll('THIS_PRIMARY', 'this.primaryKey'); + } else { + rc = rc.replaceAll('DEF_PRIMARY', '') + .replaceAll('THIS_PRIMARY', ''); + } return rc; } /// Returns a Dart class definition of the [page] that should not be modified. String createPage(PageMetaData page) { var rc = replaceVariables(templatePage, page); + if (page.pageType == PageType.edit || page.pageType == PageType.delete){ + rc = rc.replaceAll('DEF_PRIMARY', ' final int primaryKey;\n') + .replaceAll('THIS_PRIMARY', 'this.primaryKey') + .replaceAll('PARAM_PRIMARY', 'int primaryKey') + .replaceAll('VAL_PRIMARY', 'primaryKey'); + } else { + rc = rc.replaceAll('DEF_PRIMARY', '') + .replaceAll('THIS_PRIMARY', '') + .replaceAll('PARAM_PRIMARY', '') + .replaceAll('VAL_PRIMARY', ''); + } return rc; } @@ -184,7 +202,13 @@ StatefulWidget? customPageByRoute(String route, GlobalData globalData) { for (var page in module!.pageList) { imports.writeln("import '${name.toLowerCase()}/${page.name}_" "${module.moduleNameSingular.toLowerCase()}_page.dart';"); - cases.write(replaceVariables(templateCase, page)); + var case1 = replaceVariables(templateCase, page); + if (page.pageType == PageType.edit || page.pageType == PageType.delete){ + case1 = case1.replaceFirst('ARG1', 'arg1'); + } else { + case1 = case1.replaceFirst('ARG1', ''); + } + cases.write(case1); } } final content = templateCollection.replaceFirst('#IMPORTS#', imports.toString()) diff --git a/metatool/bin/sql_generator.dart b/metatool/bin/sql_generator.dart index 8efbb1d..bce0547 100644 --- a/metatool/bin/sql_generator.dart +++ b/metatool/bin/sql_generator.dart @@ -42,23 +42,29 @@ class SqlGenerator extends GeneratorBase { final buffer = StringBuffer(); buffer.write('---\n'); buffer.write('# DO NOT CHANGE. This file is created by the meta_tool\n'); - buffer.write('''# SQL statements of the module "$moduleName":\n + var sqlText = '''# SQL statements of the module "$moduleName":\n module: $moduleName list: type: list parameters: [] - sql: "select * from $tableName;" + sql: "SELECT + t0.*SELECTS + FROM $tableName t0 +JOINS + ;" byId: type: record parameters: [ "${list[0].name}" ] - sql: "select * from $tableName where ${list[0].columnName}=:${list[0].name};" + sql: "SELECT * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};" delete: type: delete parameters: [ "${list[0].name}" ] - sql: "delete * from $tableName where ${list[0].columnName}=:${list[0].name};" + sql: "DELETE * FROM $tableName WHERE ${list[0].columnName}=:${list[0].name};" update: type: update -'''); +'''; + sqlText = handleReferences(sqlText, module); + buffer.write(sqlText); var items = module.standardColumns('changedBy'); var parameters = addToBuffer(' parameters: [', maxLength: 80); var assignments = addToBuffer(' ', maxLength: 80); @@ -135,9 +141,40 @@ update: logger.error('+++ unknown module: $name'); } else { logger.log('current directory: ${Directory.current.path}'); - String filename = 'rest_server/data/sql/${name.toLowerCase()}.sql.yaml'; + String filename = '../rest_server/data/sql/${name.toLowerCase()}.sql.yaml'; writeFile(filename, generator.createSqlStatements(module)); } } } + /// Handles the foreign keys in the [sqlText] of the given [module]. + /// + /// Replaces the placeholders SELECTS and JOINS in [sqlText]. + /// + /// Returns the modified SQL text. + String handleReferences(String sqlText, ModuleMetaData module) { + var joins = ''; + var selects = ''; + var referenceNo = 0; + for (var property in module.propertyList){ + if (property.foreignKey != null){ + // foreignKey: 'roles.role_id;role_name;role' + final parts = property.foreignKey!.split(';'); + if (parts.length != 3){ + logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}'); + } else { + final keyParts = parts[0].split('.'); + if (keyParts.length != 2){ + logger.error('wrong foreignKey format: ${property.foreignKey} in ${module.moduleName}'); + } else { + ++referenceNo; + joins += ' JOIN ${keyParts[0]} t$referenceNo ON ' + 't$referenceNo.${keyParts[1]}=t0.${property.columnName}'; + selects += ',t$referenceNo.${parts[1]} AS ${parts[2]}'; + } + } + } + } + final rc = sqlText.replaceFirst('JOINS', joins).replaceFirst('SELECTS', selects); + return rc; + } } diff --git a/metatool/lib/base b/metatool/lib/base new file mode 120000 index 0000000..b23c242 --- /dev/null +++ b/metatool/lib/base @@ -0,0 +1 @@ +../../lib/base \ No newline at end of file diff --git a/metatool/lib/meta b/metatool/lib/meta new file mode 120000 index 0000000..09814ba --- /dev/null +++ b/metatool/lib/meta @@ -0,0 +1 @@ +../../lib/meta/ \ No newline at end of file diff --git a/metatool/rest_server/data/sql/roles.sql.yaml b/metatool/rest_server/data/sql/roles.sql.yaml new file mode 100644 index 0000000..3f68681 --- /dev/null +++ b/metatool/rest_server/data/sql/roles.sql.yaml @@ -0,0 +1,32 @@ +--- +# DO NOT CHANGE. This file is created by the meta_tool +# SQL statements of the module "Roles": + +module: Roles +list: + type: list + parameters: [] + sql: "SELECT + t0.* + FROM Roles t0 + + ;" +byId: + type: record + parameters: [ "id" ] + sql: "SELECT * FROM Roles WHERE role_id=:id;" +delete: + type: delete + parameters: [ "id" ] + sql: "DELETE * FROM Roles WHERE role_id=:id;" +update: + type: update + parameters: [":id",":name",":changedBy"] + sql: "UPDATE Roles SET + role_id=:id,role_name=:name,role_changedby=:changedBy,role_changed=NOW() + WHERE role_id=:id;" +insert: + type: insert + parameters: [":id",":name",":createdBy"] + sql: "INSERT INTO Roles(role_id,role_name,role_createdby,role_created) + VALUES(:id,:name,:createdBy,NOW());" diff --git a/metatool/rest_server/data/sql/users.sql.yaml b/metatool/rest_server/data/sql/users.sql.yaml new file mode 100644 index 0000000..85a06af --- /dev/null +++ b/metatool/rest_server/data/sql/users.sql.yaml @@ -0,0 +1,34 @@ +--- +# DO NOT CHANGE. This file is created by the meta_tool +# SQL statements of the module "Users": + +module: Users +list: + type: list + parameters: [] + sql: "SELECT + t0.*,t1.role_name AS role + FROM Users t0 + JOIN roles t1 ON t1.role_id=t0.user_role + ;" +byId: + type: record + parameters: [ "id" ] + sql: "SELECT * FROM Users WHERE user_id=:id;" +delete: + type: delete + parameters: [ "id" ] + sql: "DELETE * FROM Users WHERE user_id=:id;" +update: + type: update + parameters: [":id",":name",":displayName",":email",":role",":changedBy"] + sql: "UPDATE Users SET + user_id=:id,user_name=:name,user_displayname=:displayName,user_email=:email, + user_role=:role,user_changedby=:changedBy,user_changed=NOW() + WHERE user_id=:id;" +insert: + type: insert + parameters: [":id",":name",":displayName",":email",":role",":createdBy"] + sql: "INSERT INTO Users(user_id,user_name,user_displayname,user_email, + user_role,user_createdby,user_created) + VALUES(:id,:name,:displayName,:email,:role,:createdBy,NOW());" diff --git a/rest_client/lib/base b/rest_client/lib/base new file mode 120000 index 0000000..8396cec --- /dev/null +++ b/rest_client/lib/base @@ -0,0 +1 @@ +../../lib/base/ \ No newline at end of file diff --git a/rest_client/test/rest_client_test.dart b/rest_client/test/rest_client_test.dart index c6f196b..a048862 100644 --- a/rest_client/test/rest_client_test.dart +++ b/rest_client/test/rest_client_test.dart @@ -26,27 +26,31 @@ void main() async { group('query', () { Map data; test('list', () async { - data = {'module': 'Persons', 'sql': 'list'}; + data = { + 'module': 'Persons', + 'sql': 'list', + 'offset': '0', + 'size': '10', + }; var result = await client.query(what: 'query', data: data); - expect(result, isNotNull); - expect(result is Iterable, isTrue); - expect(result.length, greaterThan(2)); - var record = result[0]; + expect(result.recordList, isNotNull); + expect(result.recordList is Iterable, isTrue); + expect(result.recordList!.length, greaterThan(2)); + var record = result.recordList![0]; expect(record['person_id'], 11); expect(record['person_name'], 'Jones'); - record = result[1]; + record = result.recordList![1]; expect(record['person_id'], 12); expect(record['person_name'], 'Miller'); - record = result[2]; + record = result.recordList![2]; expect(record['person_id'], 13); expect(record['person_name'], 'Smith'); }); test('record', () async { data = {'module': 'Persons', 'sql': 'byId', ':id': '13'}; var result = await client.query(what: 'query', data: data); - expect(result, isNotNull); - expect(result, isNotEmpty); - expect(result['person_id'], 13); + expect(result.singleRecord, isNotNull); + expect(result.singleRecord!['person_id'], 13); }); }); group('store', () { @@ -73,7 +77,7 @@ void main() async { ':id': id.toString(), ':name': 'Bach', ':email': 'bach@wien.at', - ':changedby': 'unittest' + ':changedby': 'unittest', }; var result = await client.store(what: 'store', map: parameters); expect(result, isNotNull); @@ -82,11 +86,10 @@ void main() async { expect(match!.group(1)!, '1'); final data2 = {'module': 'Persons', 'sql': 'byId', ':id': id.toString()}; var result2 = await client.query(what: 'query', data: data2); - expect(result2, isNotNull); - expect(result2, isNotEmpty); - expect(result2['person_id'], id); - expect(result2['person_name'], 'Bach'); - expect(result2['person_email'], 'bach@wien.at'); + expect(result2.singleRecord, isNotNull); + expect(result2.singleRecord!['person_id'], id); + expect(result2.singleRecord!['person_name'], 'Bach'); + expect(result2.singleRecord!['person_email'], 'bach@wien.at'); }); test('delete', () async { parameters = { diff --git a/rest_server/data/sql/roles.sql.yaml b/rest_server/data/sql/roles.sql.yaml index e1ad906..3f68681 100644 --- a/rest_server/data/sql/roles.sql.yaml +++ b/rest_server/data/sql/roles.sql.yaml @@ -6,15 +6,19 @@ module: Roles list: type: list parameters: [] - sql: "select * from Roles;" + sql: "SELECT + t0.* + FROM Roles t0 + + ;" byId: type: record parameters: [ "id" ] - sql: "select * from Roles where role_id=:id;" + sql: "SELECT * FROM Roles WHERE role_id=:id;" delete: type: delete parameters: [ "id" ] - sql: "delete * from Roles where role_id=:id;" + sql: "DELETE * FROM Roles WHERE role_id=:id;" update: type: update parameters: [":id",":name",":changedBy"] diff --git a/rest_server/data/sql/users.sql.yaml b/rest_server/data/sql/users.sql.yaml index 72cfb38..85a06af 100644 --- a/rest_server/data/sql/users.sql.yaml +++ b/rest_server/data/sql/users.sql.yaml @@ -6,15 +6,19 @@ module: Users list: type: list parameters: [] - sql: "select * from Users;" + sql: "SELECT + t0.*,t1.role_name AS role + FROM Users t0 + JOIN roles t1 ON t1.role_id=t0.user_role + ;" byId: type: record parameters: [ "id" ] - sql: "select * from Users where user_id=:id;" + sql: "SELECT * FROM Users WHERE user_id=:id;" delete: type: delete parameters: [ "id" ] - sql: "delete * from Users where user_id=:id;" + sql: "DELETE * FROM Users WHERE user_id=:id;" update: type: update parameters: [":id",":name",":displayName",":email",":role",":changedBy"] diff --git a/rest_server/lib/rest_server.dart b/rest_server/lib/rest_server.dart index 1db9672..eb66cb2 100644 --- a/rest_server/lib/rest_server.dart +++ b/rest_server/lib/rest_server.dart @@ -11,6 +11,7 @@ import 'package:path/path.dart' as path; import 'package:mysql1/mysql1.dart'; import 'sql_storage.dart'; + const forbidden = 'forbidden'; const wrongData = 'wrong data'; const wrongParameters = 'wrong parameters'; @@ -253,8 +254,8 @@ clientSessionTimeout: 900 final configuration = Configuration.fromFile(filename, logger); MySqlDb db = MySqlDb.fromConfiguration(configuration, logger); await db.connect(); - if (db.hasConnection){ - for (var filename in args){ + if (db.hasConnection) { + for (var filename in args) { logger.log('=== script $filename:', LEVEL_DETAIL); rc.writeln(await db.executeScriptFile(filename)); } @@ -576,10 +577,30 @@ class ServiceWorker { ? 'NONE' : rowToJson(record, StringBuffer()).toString(); } else { - final records = await db!.readAll(sql, params: positionalParameters); + String sql2; + String prefix = ''; + if (!(parameters.containsKey('offset') && + parameters.containsKey('size'))) { + sql2 = sql; + } else { + final ix = sql.lastIndexOf(';'); + sql2 = sql.substring(0, ix) + + ' limit ${parameters["offset"]},${parameters["size"]};'; + final sqlCount = + 'SELECT count(*) ' + sql.substring(sql.indexOf(' FROM')); + final countParams = '?'.allMatches(sqlCount).length; + final positionalParameters2 = countParams == positionalParameters.length + ? positionalParameters + : positionalParameters + .skip(positionalParameters.length - countParams) + .toList(); + prefix = + '#${await db!.readOneInt(sqlCount, params: positionalParameters2)}'; + } + final records = await db!.readAll(sql2, params: positionalParameters); rc = records == null ? 'NONE' - : arrayToJson(records, StringBuffer()).toString(); + : arrayToJson(records, StringBuffer(prefix)).toString(); } return rc; } diff --git a/rest_server/test/sql_services_test.dart b/rest_server/test/sql_services_test.dart index 3f49d1f..d952b65 100644 --- a/rest_server/test/sql_services_test.dart +++ b/rest_server/test/sql_services_test.dart @@ -64,7 +64,8 @@ module: Persons list: type: list parameters: [] - sql: select * from persons; + sql: "select * + FROM persons t0; byId: type: record parameters: [ ":id" ]