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 ?? '');
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;
}
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) {
}
return rc;
}
-
/// Returns the module names as string list.
-List<String> moduleNames() {
+List<String> moduleNames(){
return [
'Roles',
'Users',
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(
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<InfoPage> {
- final GlobalData globalData = GlobalData();
-
+class _InfoPageState extends State<InfoPage> {
+ final GlobalData globalData;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
final markdownData = '''# Pollector
''';
int clickCounter = 0;
- InfoPageState();
+ _InfoPageState(this.globalData);
@override
Widget build(BuildContext context) {
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<LogPage> {
- final GlobalData globalData = GlobalData();
+ final GlobalData globalData;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(debugLabel: 'Log');
String logName = '';
- LogPageState();
+ LogPageState(this.globalData);
@override
Widget build(BuildContext context) {
// 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';
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;
/// 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) {
// 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;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ CreateRoleCustom();
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
final i18n = I18N();
class EditRoleCustom extends State<EditRolePage> {
+ final int primaryKey;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ EditRoleCustom(this.primaryKey);
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ ListRoleCustom();
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ CreateUserCustom();
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
final i18n = I18N();
class DeleteUserCustom extends State<DeleteUserPage> {
+ final int primaryKey;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ DeleteUserCustom(this.primaryKey);
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
final i18n = I18N();
class EditUserCustom extends State<EditUserPage> {
+ final int primaryKey;
final globalData = GlobalData();
AttendedPage? attendedPage;
+ EditUserCustom(this.primaryKey);
@override
Widget build(BuildContext context) {
final padding = 16.0;
padding: padding)));
return rc;
}
-
@override
void initState() {
super.initState();
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;
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>[
+ 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;
}
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;
import 'persistence.dart';
import 'data_record.dart';
+import '../base/defines.dart';
/// Implements a persistence layer storing data in Json files.
class FilePersistence extends Persistence {
logger: logger);
@override
- Future<dynamic> query({required String what, DataMap? data}) async {
+ Future<DbData> query({required String what, DataMap? data}) async {
var rc;
final fn = path.join(dataDirectory, '$what.json');
final file = File(fn);
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;
}
+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<Map> instance).
- Future<dynamic> query({required String what, DataMap? data});
+ Future<DbData> query({required String what, DataMap? data});
/// Stores a map or a data container in the persistence layer.
///
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;
}
@override
- Future<dynamic> query(
+ Future<DbData> query(
{required String what, Map<String, dynamic>? 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<int>(answer.length, 8))}');
+ }
} else {
- rc = answer;
+ rc = DbData.message(answer.substring(0, max<int>(answer.length, 120)));
}
if (logger.logLevel >= LEVEL_FINE) {
logger.log('answer: ${limitString(answer, sqlTraceLimit)}');
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 {
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;
}
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 {
static List<MenuItem> menuItems(MenuConverter converter) {
final globalData = GlobalData();
final logger = globalData.logger;
+ final collection = PageCollection();
return <MenuItem>[
- 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)),
];
}
+import 'package:exhibition/persistence/persistence.dart';
import 'package:exhibition/widget/attended_widget.dart';
import 'package:flutter/material.dart';
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<DataRow> getRows(String columnList, String what,
+ Map<String, dynamic> parameters, Function() onDone, String routeEdit, BuildContext context) {
+ List<DataRow>? 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 = <DataRow>[];
+ pageStates.dbData?.recordList!.map((record) {
+ final cells = <DataCell>[];
+ 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 ?? <DataRow>[];
+ }
+
/// Validates the text [value] of the [textAttended].
///
/// Returns null on success or the error message on error.
double screenWidth = 0;
double screenHeight = 0;
AttendedPage? attendedPage;
+ DbData? dbData;
+ bool openQuery = false;
}
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].
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;
final i18n = I18N();
class EditUserCustom extends State<EditUserPage> {
- final globalData = GlobalData();
+DEF_PRIMARY final globalData = GlobalData();
AttendedPage? attendedPage;
-
+ EditUserCustom(THIS_PRIMARY);
@override
Widget build(BuildContext context) {
final padding = 16.0;
''';
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#
/// 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) {
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;
}
''';
static final templateCase = ''' case '/users/edit':
- rc = EditUserPage(globalData);
+ rc = EditUserPage(ARG1);
break;
''';
PageGenerator(BaseLogger logger) : super(logger);
/// 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;
}
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())
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);
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;
+ }
}
--- /dev/null
+../../lib/base
\ No newline at end of file
--- /dev/null
+../../lib/meta/
\ No newline at end of file
--- /dev/null
+---
+# 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());"
--- /dev/null
+---
+# 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());"
--- /dev/null
+../../lib/base/
\ No newline at end of file
group('query', () {
Map<String, String> 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', () {
':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);
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 = {
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"]
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"]
import 'package:mysql1/mysql1.dart';
import 'sql_storage.dart';
+
const forbidden = 'forbidden';
const wrongData = 'wrong data';
const wrongParameters = 'wrong parameters';
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));
}
? '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;
}
list:
type: list
parameters: []
- sql: select * from persons;
+ sql: "select *
+ FROM persons t0;
byId:
type: record
parameters: [ ":id" ]