final client = RestPersistence.fromConfig(configuration, logger);
group('query', () {
Map result;
- Map<String, String> parameters;
- test('record', () async {
- parameters = {'module': 'Persons', 'sql': 'byId', ':id': '13'};
- var result = await client.query(what: 'query', parameters: parameters);
- expect(result, isNotNull);
- expect(result, isNotEmpty);
- expect(result['person_id'], 13);
- });
+ Map<String, String> data;
test('list', () async {
- parameters = {'module': 'Persons', 'sql': 'list'};
- var result = await client.query(what: 'query', parameters: parameters);
+ data = {'module': 'Persons', 'sql': 'list'};
+ var result = await client.query(what: 'query', data: data);
expect(result, isNotNull);
expect(result is Iterable, isTrue);
expect(result.length, greaterThan(2));
- final record = result[0];
+ var record = result[0];
+ expect(record['person_id'], 11);
+ expect(record['person_name'], 'Jones');
+ record = result[1];
+ expect(record['person_id'], 12);
+ expect(record['person_name'], 'Miller');
+ record = result[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);
});
});
group('store', () {
Map<String, dynamic> parameters;
- parameters = { };
- //client.store(what: 'store', data: parameters);
+ int id = 0;
+ test('insert', () async {
+ parameters = {
+ 'module': 'Persons',
+ 'sql': 'insert',
+ ':name': 'Mozart',
+ ':email': 'mozart@salzburg.au',
+ ':createdby': 'unittest'
+ };
+ var result = await client.store(what: 'store', data: parameters);
+ expect(result, isNotNull);
+ final match = RegExp(r'^id:(\d+)$').firstMatch(result);
+ expect(match, isNotNull);
+ id = int.parse(match!.group(1)!);
+ });
+ test('update', () async {
+ parameters = {
+ 'module': 'Persons',
+ 'sql': 'update',
+ ':id': id.toString(),
+ ':name': 'Bach',
+ ':email': 'bach@wien.at',
+ ':changedby': 'unittest'
+ };
+ var result = await client.store(what: 'store', data: parameters);
+ expect(result, isNotNull);
+ final match = RegExp(r'^rows:(\d+)$').firstMatch(result);
+ expect(match, 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');
+ });
+ test('delete', () async {
+ parameters = {
+ 'module': 'Persons',
+ 'sql': 'delete',
+ ':id': id.toString(),
+ };
+ var result = await client.store(what: 'store', data: parameters);
+ expect(result, isNotNull);
+ final match = RegExp(r'^rows:(\d+)$').firstMatch(result);
+ expect(match, isNotNull);
+ expect(match!.group(1), '1');
+ });
});
}
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
import 'package:rest_server/sql_storage.dart';
+import 'package:mysql1/mysql1.dart';
const forbidden = 'forbidden';
const wrongData = 'wrong data';
String dbUser = 'exhibition',
String dbCode = 'TopSecret',
String dbHost = 'localhost',
+ String dbPrimaryTable = 'users',
int dbPort = 3306,
int watchDogPause = 60,
int traceDataLength = 80,
'code': dbCode,
'host': dbHost,
'port': dbPort,
+ 'primaryTable': dbPrimaryTable,
'traceDataLength': traceDataLength,
},
'clientSessionTimeout': clientSessionTimeout,
}, logger);
}
- /// Konstruktor, der die Parameter aus der YAML-Konfigurationsdatei
- /// [filename] bezieht.
- ///
+ /// Constructor using parameters defined in the YAML configuration named
+ /// [filename].
RestServer.fromConfig(String filename, {this.serviceName = 'exhibition'}) {
final logger2 = unittestLogger ?? MemoryLogger();
configuration = Configuration.fromFile(filename, logger2);
String restVersion = '';
FileSync? _fileSync = FileSync();
SqlStorage sqlStorage = SqlStorage(globalLogger);
+ String sqlTestPrimaryTable = '';
+
ServiceWorker(this.threadId, this.configuration, this.serviceName) {
final fnLog = '/var/log/local/$serviceName.$threadId.log';
logger = RestServer.unittestLogger ??
Logger(fnLog,
configuration.asInt('logLevel', section: 'service') ?? LEVEL_FINE);
logger.log('db: ${configuration.asString('db', section: 'db')}');
+ final table = configuration.asString('primaryTable', section: 'db');
+ sqlTestPrimaryTable = 'select count(*) from $table;';
db = MySqlDb.fromConfiguration(configuration, logger);
clientSessionTimeout = configuration.asInt('clientSessionTimeout') ?? 30;
_fileSync = FileSync(logger);
/// We cannot use jsonDecode(): data types like DateTime are not supported.
StringBuffer arrayToJson(Iterable list, StringBuffer buffer) {
buffer.write('[');
+ String? separator;
for (var item in list) {
- if (item is Iterable) {
- arrayToJson(item, buffer);
+ if (separator == null){
+ separator = ',';
+ } else {
+ buffer.write(separator);
+ }
+ if (item is ResultRow) {
+ rowToJson(item.fields, buffer);
} else if (item is Map) {
rowToJson(item, buffer);
+ } else if (item is Iterable) {
+ arrayToJson(item, buffer);
}
}
buffer.write(']');
return buffer;
}
- /// Checks whether a valid connection is available. If not a reconnection is
- /// done.
+ /// Checks whether a valid connection is available.
+ /// If not a reconnection is done.
Future checkConnection() async {
var ready = false;
- final sql = 'select count(*) from loginusers;';
int? count;
try {
- count = await db!.readOneInt(sql);
+ count = await db!.readOneInt(sqlTestPrimaryTable);
} catch (exc) {
logger.error(exc.toString());
ready = true;
}
}
+ /// Tests wheter all [names] are keys in [parameters].
+ /// @throws FormatException on error.
void checkParameters(List<String> names, Map<String, dynamic> parameters) {
for (var name in names) {
if (!parameters.containsKey(name)) {
}
}
- /// Prüft die Session-ID.
- /// Liefert true, wenn OK, false sonst.
+ /// Checks the validity of the session id.
+ /// Not implemented yet.
Future<bool> checkSession(Map<String, dynamic> parameters) async {
- var rc = false;
- if (parameters.containsKey('sessionid')) {
- final id = parameters['sessionid'];
- final sql = '''SELECT
- connection_id, connection_apiaristid, connection_start
-FROM connections
-WHERE
- connection_name=?
- AND connection_start >= NOW() - INTERVAL $clientSessionTimeout SECOND
-;
-''';
- final record = await db!.readOneAsMap(sql, params: [id]);
- if (record == null) {
- logger.log('ungültige Session: $id', LEVEL_DETAIL);
- } else {
- logger.log(
- 'Session: $id apiarist: ${record['connection_apiaristid']} start: ${record['connection_start']}',
- LEVEL_DETAIL);
- rc = true;
- final sql2 = '''UPDATE connections SET
- connection_requests=connection_requests+1,
- connection_end=NOW()
-WHERE
- connection_name=?
-;
-''';
- await db!.updateOne(sql2, params: [id]);
- }
- }
+ var rc = true;
return rc;
}
return rc;
}
- /// Bearbeitet die Anforderung 'sessionid':
- /// Prüfen der Lizenz ("Token"). Wenn erfolgreich, wird die Session
- /// in die DB eingetragen.
- /// Liefert eine JSon-Map (als String) oder null, wenn Fehler.
+ /// Handles the request "sessionId".
+ /// Not implemented yet.
Future<String?> getSessionId(Map<String, dynamic> parameters) async {
String? rc;
- var sql = '''SELECT apiarist_id
-FROM apiarists
-WHERE apiarist_token=?;
-''';
- final token = parameters['token'];
- final params = [token];
- final apiaristId = await db!.readOneInt(sql, params: params);
- if (apiaristId == null) {
- logger.log('unknown token: $token', LEVEL_DETAIL);
- } else {
- sql = '''INSERT
- INTO connections
- (connection_name, connection_apiaristid, connection_start, connection_end, connection_requests)
- VALUES (?, ?, NOW(), NOW(), 0);
- ''';
-
- /// Use only 31 bit (non negativ numbers on 32 bit clients):
- final sessionId = int.parse(
- buildMd5Hash(
- token + DateTime.now().microsecondsSinceEpoch.toString())
- .substring(0, 8),
- radix: 16) &
- 0x7fffffff;
- final params2 = [sessionId, apiaristId];
- final id = await db!.insertOne(sql, params: params2);
- if (id <= 0) {
- logger.error('insert into connection failed: $token');
- } else {
- logger.log('sessionid: $sessionId', LEVEL_DETAIL);
- rc = convert.jsonEncode({'sessionid': sessionId});
- }
- }
return rc;
}
- /// Liefert zu einem [name] das SQL-Statement.
- /// [paramCount] ist die Anzahl der Parameter. Dient zur Konsistenzprüfung.
- /// Liefert null oder das SQL-Statement
- String? getSql(String name, int paramCount) {
- String? sql;
- var expectedCount = 0;
- switch (name) {
- case 'hives':
- sql = '''SELECT
- *
-FROM hives
-WHERE hive_apiaryid=:id
-''';
- expectedCount = 1;
- break;
- case 'apiaristByToken':
- sql = '''SELECT
- *
-FROM apiary
-WHERE
- apiarist_token=:token
-''';
- expectedCount = 1;
- break;
- default:
- logger.error('unknown SQL name: $name');
- break;
- }
- if (expectedCount != paramCount) {
- logger.error(
- 'unexpected parameter count in $name: $paramCount instead of $expectedCount');
- }
- return sql;
- }
-
- /// Bearbeitet die Anforderung 'register':
- /// Test, ob der Name in den Parametern bei den Imkern existiert.
- /// Wenn ja, wird der Token geliefert, sonst null.
+ /// Handles the request 'register':
+ /// not implemented yet.
Future<String> getToken(Map<String, dynamic> parameters) async {
- String rc;
- final sql = '''SELECT apiarist_token
-FROM apiarists
-WHERE apiarist_registername=?;
-''';
- final name = parameters['name'];
- final params = [name];
- final token = await db!.readOneString(sql, params: params);
- if (token == null) {
- logger.log('unknown name: $name', LEVEL_DETAIL);
- rc = wrongData;
- } else {
- rc = convert.jsonEncode({'token': token});
- }
- return rc;
+ return '<not implemented yet>';
}
/// Handles a POST request.
final sql = sqlStatement.sqlStatement(parameters, positionalParameters);
if (sqlStatement.type == SqlStatementType.record) {
final record = await db!.readOneAsMap(sql, params: positionalParameters);
- rc = record == null ? 'NONE' : rowToJson(record, StringBuffer()).toString();
+ rc = record == null
+ ? 'NONE'
+ : rowToJson(record, StringBuffer()).toString();
} else {
final records = await db!.readAll(sql, params: positionalParameters);
- rc = records == null ? 'NONE' : arrayToJson(records, StringBuffer()).toString();
+ rc = records == null
+ ? 'NONE'
+ : arrayToJson(records, StringBuffer()).toString();
}
return rc;
}
final sql = sqlStatement.sqlStatement(parameters, positionalParameters);
switch (sqlStatement.type) {
case SqlStatementType.execute:
+ final count = await db!.execute(sql, params: positionalParameters);
+ rc = count < 0 ? 'ERROR' : 'rows:$count';
+ break;
case SqlStatementType.delete:
- await db!.execute(sql, params: positionalParameters);
+ final count = await db!.deleteRaw(sql, params: positionalParameters);
+ rc = count < 0 ? 'ERROR' : 'rows:$count';
break;
case SqlStatementType.insert:
final id = await db!.insertOne(sql, params: positionalParameters);
}
break;
case SqlStatementType.update:
- final success =
- await db!.updateOne(sql, params: positionalParameters);
- rc = success ? 'OK' : 'FAILED';
+ final count =
+ await db!.updateRaw(sql, params: positionalParameters);
+ rc = 'rows:$count';
break;
default:
logger.error('unexpected type ${sqlStatement.type} in storeBySql');
const baseService = 2;
const baseTrace = baseService + 5;
const baseDb = baseTrace + 1;
- const baseRest = baseDb + 7;
+ const baseRest = baseDb + 8;
final configuration = BaseConfiguration({
'service': {
'address': parts[baseService],
'port': int.parse(parts[baseDb + 4]),
'timeout': int.parse(parts[baseDb + 5]),
'traceDataLength': int.parse(parts[baseDb + 6]),
+ 'primaryTable': parts[baseDb + 7],
},
'clientSessionTimeout': int.parse(parts[baseRest]),
}, MemoryLogger());
configuration
.asInt('traceDataLength', section: section3, defaultValue: 3306)
.toString(),
+ configuration.asString('primaryTable', section: section3) ?? 'users',
configuration.asInt('clientSessionTimeout', defaultValue: 900).toString(),
serviceName,
].join('\t');