]> gitweb.hamatoma.de Git - exhibition.git/commitdiff
daily work
authorHamatoma <author.hamatoma.de>
Mon, 2 Aug 2021 08:38:33 +0000 (10:38 +0200)
committerHamatoma <author.hamatoma.de>
Mon, 2 Aug 2021 08:38:33 +0000 (10:38 +0200)
* adaptions to fulfill the wiki descriptions

17 files changed:
ReCreateMetaTool [new file with mode: 0755]
analysis_options.yaml
bin/meta_tool.dart
lib/base/defines.dart [new file with mode: 0644]
lib/base/helper.dart [new file with mode: 0644]
lib/meta/module_meta_data.dart
lib/meta/users_meta.dart
lib/page/user/user_data.dart [deleted file]
lib/page/user/user_list.dart [deleted file]
lib/page/users/user_data.dart [new file with mode: 0644]
lib/page/users/user_list.dart [new file with mode: 0644]
lib/page/users/user_state.dart [new file with mode: 0644]
lib/persistence/rest_persistence.dart
lib/setting/global_data.dart
pubspec.yaml
rest_client/test/rest_client_test.dart
rest_server/lib/rest_server.dart

diff --git a/ReCreateMetaTool b/ReCreateMetaTool
new file mode 100755 (executable)
index 0000000..bbfbb6b
--- /dev/null
@@ -0,0 +1,3 @@
+#! /bin/bash
+APP=meta_tool
+dart compile exe bin/$APP.dart -o tools/$APP
index 61b6c4de17c96863d24279f06b85e01b6ebbdb34..1383f10d05128dd832895c21c0b51e32cf5959d3 100644 (file)
@@ -7,7 +7,7 @@
 
 # The following line activates a set of recommended lints for Flutter apps,
 # packages, and plugins designed to encourage good coding practices.
-include: package:flutter_lints/flutter.yaml
+#include: package:flutter_lints/flutter.yaml
 
 linter:
   # The lint rules applied to this project can be customized in the
index 7939f7efe026ade97e16b2641b3f4c972faf40ee..5f0f97c0ab4227de83bea29e672d8f6f6ee905b6 100644 (file)
@@ -156,6 +156,7 @@ void updateModules(){
   for (var name in modules){
     ModuleMetaData module = moduleByName(name);
     String filename = classToFilename(module.moduleNameSingular) + '_data.dart';
-    writeFile('lib/page/$filename', module.createModuleData());
+    final directory = name.toLowerCase();
+    writeFile('lib/page/$directory/$filename', module.createModuleData());
   }
 }
\ No newline at end of file
diff --git a/lib/base/defines.dart b/lib/base/defines.dart
new file mode 100644 (file)
index 0000000..0b8ecc6
--- /dev/null
@@ -0,0 +1,19 @@
+typedef YamlMap = Map<String, dynamic>;
+typedef YamlList = List<YamlMap>;
+
+///JsonMap or JsonList
+typedef YamlData = Object;
+
+enum EventSource { undef, localData, remoteData }
+enum EventCardinality { undef, record, list }
+enum DataType {
+  bool,
+  currency,
+  date,
+  datetime,
+  float,
+  int,
+  nat,
+  reference,
+  string
+}
diff --git a/lib/base/helper.dart b/lib/base/helper.dart
new file mode 100644 (file)
index 0000000..1ee83b3
--- /dev/null
@@ -0,0 +1,28 @@
+import 'defines.dart';
+
+/// Returns the [dataType] specific value of [data].
+dynamic valueOf(DataType dataType, String data) {
+  dynamic rc;
+  switch (dataType) {
+    case DataType.bool:
+      rc = data == 'true';
+      break;
+    case DataType.currency:
+    case DataType.float:
+      rc = double.tryParse(data);
+      break;
+    case DataType.date:
+    case DataType.datetime:
+      rc = DateTime.parse(data);
+      break;
+    case DataType.int:
+    case DataType.nat:
+    case DataType.reference:
+      rc = int.tryParse(data);
+      break;
+    case DataType.string:
+      rc = data;
+      break;
+  }
+  return rc;
+}
index 712fdb82f386aa4abfdb35fdce4098e9c0024aab..46b9f63c85ca9e9abdcdd91caedd59f669b67abd 100644 (file)
@@ -1,18 +1,8 @@
 import 'package:path/path.dart';
-enum DataType {
-  bool,
-  currency,
-  date,
-  datetime,
-  float,
-  int,
-  nat,
-  reference,
-  string,
-}
+import '../base/defines.dart';
+
+class MetaException extends FormatException {}
 
-class MetaException extends FormatException{
-}
 /// Stores the meta data of a module.
 class ModuleMetaData {
   /// The module name, e.g. users
@@ -23,26 +13,36 @@ class ModuleMetaData {
 
   /// The related database table.
   String tableName;
+  final bool shortModifiedLabel;
   List<PropertyMetaData> list;
   final Map<String, PropertyMetaData> properties = {};
   String columnPrefix;
   ModuleMetaData(this.moduleName, this.list,
       {this.tableName = '',
       this.moduleNameSingular = '',
-      this.columnPrefix = ''}) {
+      this.columnPrefix = '',
+      this.shortModifiedLabel = false}) {
     tableName = tableName.isEmpty ? moduleName : tableName;
     moduleNameSingular = moduleNameSingular.isEmpty
         ? (moduleName.endsWith('s')
             ? moduleName.substring(0, moduleName.length - 1)
             : moduleName)
         : moduleNameSingular;
-    for (var item in list) {
-      properties[item.name] = item;
-      item.columnName ??= columnPrefix + '_' + item.name.toLowerCase();
-    }
     columnPrefix = columnPrefix.isNotEmpty
         ? columnPrefix
         : moduleNameSingular.toLowerCase();
+    for (var item in list) {
+      item.module = this;
+      properties[item.name] = item;
+      if (item.columnName.isEmpty) {
+        final prefix = shortModifiedLabel &&
+                ['created', 'createdBy', 'changed', 'changedBy']
+                    .contains(item.name)
+            ? ''
+            : columnPrefix + '_';
+        item.columnName = prefix + item.name.toLowerCase();
+      }
+    }
   }
 
   /// Returns a DDL statement creating the database table.
@@ -68,17 +68,43 @@ class ModuleMetaData {
   String createModuleData() {
     final buffer = StringBuffer();
     buffer.write('// DO NOT CHANGE. This file is created by the meta_tool\n');
-    buffer.write('class $moduleNameSingular{\n');
+    buffer.write("import '../../base/defines.dart';\n");
+    buffer.write("import '../../base/helper.dart';\n");
+    buffer.write('class ${moduleNameSingular}Data{\n');
     for (var item in list) {
       buffer.write('  ' + dartType(item.dataType) + '? ${item.name};\n');
     }
-    buffer.write('  $moduleNameSingular({');
+    buffer.write('  ${moduleNameSingular}Data({');
     String separator = '';
     for (var item in list) {
       buffer.write('$separator this.${item.name}');
       separator = ',';
     }
-    buffer.write('});\n}\n');
+    buffer.write('});\n');
+    buffer.write('  ${moduleNameSingular}Data.fromYaml(YamlMap data) {\n');
+    for (var item in list) {
+      final name = item.columnName;
+      final type = item.dataType.toString();
+      //id = data.containsKey('id') ? int.tryParse(data['id']) : null;
+      buffer.write(
+          "    ${item.name} = data.containsKey('$name') ? valueOf($type, data['$name']) : null;\n");
+    }
+    buffer.write('  }\n');
+    buffer.write('  static DataType? dataTypeOf(String name) {\n');
+    buffer.write('    DataType? rc;\n');
+    buffer.write('    switch(name){\n');
+    for (var item in list) {
+      buffer.write("    case '${item.name}':\n");
+      buffer.write('      rc = ${item.dataType};\n');
+      buffer.write('      break;\n');
+    }
+    buffer.write('''    default:
+      break;
+    }
+    return rc;
+  }
+''');
+    buffer.write('}\n');
 
     return buffer.toString();
   }
@@ -180,6 +206,7 @@ class ModuleMetaData {
 class PropertyMetaData {
   /// Name of the property (Dart name).
   final String name;
+  ModuleMetaData module = ModuleMetaData('', []);
 
   /// A colon delimited list of options, e.g. ':notnull:unique:'.
   final String options;
@@ -190,15 +217,15 @@ class PropertyMetaData {
 
   /// The size if dataType is DataType.string.
   final int size;
-  String? columnName;
+  String columnName;
 
   /// The foreign key if dataType is DataType.reference, e.g. 'users.user_id'
   String? reference;
   PropertyMetaData(this.name, this.dataType, this.options, this.widgetType,
-      {this.columnName, this.size = 0, this.reference});
+      {this.columnName = '', this.size = 0, this.reference});
 }
 
-String filenameToClass(String filename){
+String filenameToClass(String filename) {
   final parts = basenameWithoutExtension(filename).split('_');
   String rc = '';
   for (var part in parts) {
@@ -208,14 +235,15 @@ String filenameToClass(String filename){
   }
   return rc;
 }
-String classToFilename(String className){
+
+String classToFilename(String className) {
   String upperCase = className.toUpperCase();
   String rc = '';
-  for (var ix = 0; ix < className.length; ix++){
-    if (className[ix] == upperCase[ix] && ix > 0){
+  for (var ix = 0; ix < className.length; ix++) {
+    if (className[ix] == upperCase[ix] && ix > 0) {
       rc += '_';
     }
     rc = className[ix];
   }
   return rc.toLowerCase();
-}
\ No newline at end of file
+}
index 092f07a529f9a26820cbc7bcea5f31f540494b06..8a6ec101ec0414bbd00bd2a84aac0eceb0c4fcbb 100644 (file)
@@ -1,22 +1,30 @@
+import '../base/defines.dart';
 import 'module_meta_data.dart';
 
-class UserMeta extends ModuleMetaData  {
+class UserMeta extends ModuleMetaData {
   static UserMeta instance = UserMeta.internal();
-  UserMeta.internal() : super('Users',
-    [
-      PropertyMetaData('id', DataType.reference, ':primary:', 'combo'),
-      PropertyMetaData('name', DataType.string, ':notnull:', '', size: 64),
-      PropertyMetaData('displayName', DataType.string, ':unique:notnull:', '', size: 32),
-      PropertyMetaData('email', DataType.string, ':unique:notnull:', '', size: 255),
-      PropertyMetaData('role', DataType.reference, ':notnull:', ''),
-      PropertyMetaData('created', DataType.datetime, '', ''),
-      PropertyMetaData('createdBy', DataType.string, '', '', size: 32),
-      PropertyMetaData('changed', DataType.datetime, '', ''),
-      PropertyMetaData('changedBy', DataType.string, '', '', size: 32),
-    ],
-    tableName: 'loginusers',
-  );
-  factory UserMeta(){
+  UserMeta.internal()
+      : super(
+          'Users',
+          [
+            PropertyMetaData('id', DataType.reference, ':primary:', 'combo'),
+            PropertyMetaData('name', DataType.string, ':notnull:', '',
+                size: 64),
+            PropertyMetaData(
+                'displayName', DataType.string, ':unique:notnull:', '',
+                size: 32),
+            PropertyMetaData('email', DataType.string, ':unique:notnull:', '',
+                size: 255),
+            PropertyMetaData('role', DataType.reference, ':notnull:', ''),
+            PropertyMetaData('created', DataType.datetime, '', ''),
+            PropertyMetaData('createdBy', DataType.string, '', '', size: 32),
+            PropertyMetaData('changed', DataType.datetime, '', ''),
+            PropertyMetaData('changedBy', DataType.string, '', '', size: 32),
+          ],
+          tableName: 'loginusers',
+          shortModifiedLabel: true,
+        );
+  factory UserMeta() {
     return instance;
   }
-}
\ No newline at end of file
+}
diff --git a/lib/page/user/user_data.dart b/lib/page/user/user_data.dart
deleted file mode 100644 (file)
index 03244c5..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-class User {
-  int? id;
-  String? name;
-  String? displayName;
-  String? email;
-  int? role;
-  DateTime? created;
-  String? createdBy;
-  DateTime? changed;
-  String? changedBy;
-  User(
-      {this.id,
-      this.name,
-      this.displayName,
-      this.email,
-      this.role,
-      this.created,
-      this.createdBy,
-      this.changed,
-      this.changedBy});
-}
diff --git a/lib/page/user/user_list.dart b/lib/page/user/user_list.dart
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/lib/page/users/user_data.dart b/lib/page/users/user_data.dart
new file mode 100644 (file)
index 0000000..8ff59ad
--- /dev/null
@@ -0,0 +1,62 @@
+// DO NOT CHANGE. This file is created by the meta_tool
+import '../../base/defines.dart';
+import '../../base/helper.dart';
+class UserData{
+  int? id;
+  String? name;
+  String? displayName;
+  String? email;
+  int? role;
+  DateTime? created;
+  String? createdBy;
+  DateTime? changed;
+  String? changedBy;
+  UserData({ this.id, this.name, this.displayName, this.email, this.role, this.created, this.createdBy, this.changed, this.changedBy});
+  UserData.fromYaml(YamlMap data) {
+    id = data.containsKey('user_id') ? valueOf(DataType.reference, data['user_id']) : null;
+    name = data.containsKey('user_name') ? valueOf(DataType.string, data['user_name']) : null;
+    displayName = data.containsKey('user_displayname') ? valueOf(DataType.string, data['user_displayname']) : null;
+    email = data.containsKey('user_email') ? valueOf(DataType.string, data['user_email']) : null;
+    role = data.containsKey('user_role') ? valueOf(DataType.reference, data['user_role']) : null;
+    created = data.containsKey('created') ? valueOf(DataType.datetime, data['created']) : null;
+    createdBy = data.containsKey('createdby') ? valueOf(DataType.string, data['createdby']) : null;
+    changed = data.containsKey('changed') ? valueOf(DataType.datetime, data['changed']) : null;
+    changedBy = data.containsKey('changedby') ? valueOf(DataType.string, data['changedby']) : null;
+  }
+  static DataType? dataTypeOf(String name) {
+    DataType? rc;
+    switch(name){
+    case 'id':
+      rc = DataType.reference;
+      break;
+    case 'name':
+      rc = DataType.string;
+      break;
+    case 'displayName':
+      rc = DataType.string;
+      break;
+    case 'email':
+      rc = DataType.string;
+      break;
+    case 'role':
+      rc = DataType.reference;
+      break;
+    case 'created':
+      rc = DataType.datetime;
+      break;
+    case 'createdBy':
+      rc = DataType.string;
+      break;
+    case 'changed':
+      rc = DataType.datetime;
+      break;
+    case 'changedBy':
+      rc = DataType.string;
+      break;
+    default:
+      break;
+    }
+    return rc;
+  }
+}
+
diff --git a/lib/page/users/user_list.dart b/lib/page/users/user_list.dart
new file mode 100644 (file)
index 0000000..316e529
--- /dev/null
@@ -0,0 +1,147 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import '../../base/defines.dart';
+import 'user_state.dart';
+import 'user_data.dart';
+import 'package:equatable/equatable.dart';
+
+class User extends StatefulWidget {
+  @override
+  _UserState createState() => _UserState();
+}
+
+class _UserState extends State<User> {
+  double? width;
+
+  @override
+  void initState() {
+    context.read<UserBloc>().add(UserEvent(EventSource.undef, EventCardinality.undef));
+    super.initState();
+  }
+
+  @override
+  void didChangeDependencies() {
+    width = MediaQuery.of(context).size.width;
+    super.didChangeDependencies();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<UserBloc, UserState>(
+      builder: (context, UserState state) {
+        return Column(
+          children: [
+            ResultDisplay(
+              text: _getDisplayText(state.calculationModel),
+            ),
+            Row(
+              children: [
+                _getButton(text: '7', onTap: () => numberPressed(7)),
+                _getButton(text: '8', onTap: () => numberPressed(8)),
+                _getButton(text: '9', onTap: () => numberPressed(9)),
+                _getButton(
+                    text: 'x',
+                    onTap: () => operatorPressed('*'),
+                    backgroundColor: Color.fromRGBO(220, 220, 220, 1)),
+              ],
+            ),
+            Row(
+              children: [
+                _getButton(text: '4', onTap: () => numberPressed(4)),
+                _getButton(text: '5', onTap: () => numberPressed(5)),
+                _getButton(text: '6', onTap: () => numberPressed(6)),
+                _getButton(
+                    text: '/',
+                    onTap: () => operatorPressed('/'),
+                    backgroundColor: Color.fromRGBO(220, 220, 220, 1)),
+              ],
+            ),
+            Row(
+              children: [
+                _getButton(text: '1', onTap: () => numberPressed(1)),
+                _getButton(text: '2', onTap: () => numberPressed(2)),
+                _getButton(text: '3', onTap: () => numberPressed(3)),
+                _getButton(
+                    text: '+',
+                    onTap: () => operatorPressed('+'),
+                    backgroundColor: Color.fromRGBO(220, 220, 220, 1))
+              ],
+            ),
+            Row(
+              children: [
+                _getButton(
+                    text: '=',
+                    onTap: calculateResult,
+                    backgroundColor: Colors.orange,
+                    textColor: Colors.white),
+                _getButton(text: '0', onTap: () => numberPressed(0)),
+                _getButton(
+                    text: 'C',
+                    onTap: clear,
+                    backgroundColor: Color.fromRGBO(220, 220, 220, 1)),
+                _getButton(
+                    text: '-',
+                    onTap: () => operatorPressed('-'),
+                    backgroundColor: Color.fromRGBO(220, 220, 220, 1)),
+              ],
+            ),
+            Spacer(),
+            UserHistoryContainer(
+                calculations: state.history.reversed.toList())
+          ],
+        );
+      },
+    );
+  }
+
+  Widget _getButton(
+      {required String text,
+        required void Function() onTap,
+        Color backgroundColor = Colors.white,
+        Color textColor = Colors.black}) {
+    return CalculatorButton(
+      label: text,
+      onTap: onTap,
+      // @ToDo: width always != null?
+      size: width! / 4 - 12,
+      backgroundColor: backgroundColor,
+      labelColor: textColor,
+    );
+  }
+
+  numberPressed(int number) {
+    context.read<UserBloc>().add(NumberPressed(number: number));
+  }
+
+  operatorPressed(String operator) {
+    context.read<UserBloc>().add(OperatorPressed(operator: operator));
+  }
+
+  calculateResult() {
+    context.read<UserBloc>().add(CalculateResult());
+  }
+
+  clear() {
+    context.read<UserBloc>().add(ClearUser());
+  }
+
+  String _getDisplayText(UserModel model) {
+    if (model.result != null) {
+      return '${model.result}';
+    }
+
+    if (model.secondOperand != null) {
+      return '${model.firstOperand}${model.operator}${model.secondOperand}';
+    }
+
+    if (model.operator != null) {
+      return '${model.firstOperand}${model.operator}';
+    }
+
+    if (model.firstOperand != null) {
+      return '${model.firstOperand}';
+    }
+
+    return "${model.result ?? 0}";
+  }
+}
diff --git a/lib/page/users/user_state.dart b/lib/page/users/user_state.dart
new file mode 100644 (file)
index 0000000..e23b4a5
--- /dev/null
@@ -0,0 +1,55 @@
+import 'package:equatable/equatable.dart';
+import 'package:exhibition/setting/global_data.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import '../../base/defines.dart';
+import '../../persistence/rest_persistence.dart';
+import 'user_data.dart';
+
+class RemoteListUserEvent extends UserEvent {
+  final Map<String, dynamic> filters;
+  RemoteListUserEvent(this.filters)
+      : super(EventSource.remoteData, EventCardinality.list);
+  @override
+  List<Object> get props => [filters];
+}
+
+class UserBloc extends Bloc<UserEvent, UserState> {
+  final YamlList? recordList;
+
+  UserBloc({this.recordList}) : super(UserStateInitial());
+
+  @override
+  Stream<UserState> mapEventToState(
+    UserEvent event,
+  ) async* {
+    if (event is RemoteListUserEvent) {
+      final list = await GlobalData()
+          .restPersistence
+          ?.query(what: 'query', data: event.filters);
+      yield UserListRemote(list ?? []);
+    } else {
+      yield UserStateInitial();
+    }
+  }
+}
+
+abstract class UserEvent extends Equatable {
+  final EventSource eventSource;
+  final EventCardinality eventCardinality;
+  UserEvent(this.eventSource, this.eventCardinality);
+}
+
+class UserListRemote extends UserState {
+  final YamlList records;
+  UserListRemote(this.records);
+  @override
+  List<Object> get props => [records];
+}
+
+abstract class UserState extends Equatable {}
+
+class UserStateInitial extends UserState {
+  @override
+  List<Object> get props => [];
+}
index d94597cb5a2067dd37825f4dd59ed074e211e7ec..54206b78d830811a567819e9fa83da37e13c3a8e 100644 (file)
@@ -89,7 +89,7 @@ class RestPersistence extends Persistence {
   @override
   Future<dynamic> query(
       {required String what, Map<String, dynamic>? data}) async {
-    var rc;
+    String rc;
     final params2 = data == null ? '{}' : convert.jsonEncode(data);
     final answer = await runRequest(what, body: params2, headers: jsonHeader);
     if (answer.isNotEmpty && (answer.startsWith('{') || answer.startsWith('['))) {
index abe004c4d701a4480119bdbd4ab29f724ee66d15..8ec169443f6f1388c73e1a04e542390f676b505b 100644 (file)
@@ -1,4 +1,5 @@
 import 'package:dart_bones/dart_bones.dart';
+import 'package:exhibition/persistence/rest_persistence.dart';
 import 'package:flutter/material.dart';
 
 import '../page/page_controller_exhibition.dart';
@@ -32,17 +33,19 @@ class GlobalData {
   final DrawerBuilder drawerBuilder;
   final FooterBuilder footerBuilder;
   final BaseConfiguration configuration;
+  final RestPersistence? restPersistence;
 
   factory GlobalData() => _instance ?? GlobalData();
   GlobalData.dummy()
       : this.internal(BaseConfiguration({}, globalLogger), (input) => '',
-            (input) => '', DummyFooter.builder, globalLogger);
+            (input) => '', DummyFooter.builder, null, globalLogger);
 
   /// [configuration]: general settings.
   /// [appBarBuilder]: a factory to create the Hamburger menu.
   /// [footerBuilder]: a factory to create a footer area.
   GlobalData.internal(this.configuration, this.appBarBuilder,
-      this.drawerBuilder, this.footerBuilder, this.logger) {
+      this.drawerBuilder, this.footerBuilder,
+      this.restPersistence, this.logger) {
     logger.log('Start');
   }
 }
index 078722e89d315e6388e6da4952078eafa52d5dcd..a4d9bf19f8309c02c61b8de091dc3aa62d4a1ee3 100644 (file)
@@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 version: 1.0.0+1
 
 environment:
-  sdk: ">=2.12.0 <3.0.0"
+  sdk: ">=2.14.0-321.0.dev <3.0.0"
 
 # Dependencies specify other packages that your package needs in order to work.
 # To automatically upgrade your package dependencies to the latest versions
@@ -33,6 +33,8 @@ dependencies:
   dart_bones: "^1.1.1"
   url_launcher: ^6.0.3
   flutter_markdown: ^0.6.1
+  flutter_bloc: ^7.1.0
+  equatable: ^2.0.3
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.2
index 6442bbe862f17d4f4d940bd7b12a1623a45ad292..f50395f351afc95477827461818af2fa47598d5b 100644 (file)
@@ -24,7 +24,6 @@ void main() async {
   final configuration = Configuration.fromFile(config, logger);
   final client = RestPersistence.fromConfig(configuration, logger);
   group('query', () {
-    Map result;
     Map<String, String> data;
     test('list', () async {
       data = {'module': 'Persons', 'sql': 'list'};
index f59e3230c375cc8bb41253660478effa95b05ada..44b95ad05324bfab080edc01062e074e6e631ed6 100644 (file)
@@ -8,7 +8,7 @@ import 'package:crypto/crypto.dart';
 import 'package:dart_bones/dart_bones.dart';
 import 'package:http/http.dart' as http;
 import 'package:path/path.dart' as path;
-import 'package:rest_server/sql_storage.dart';
+import 'sql_storage.dart';
 import 'package:mysql1/mysql1.dart';
 
 const forbidden = 'forbidden';