All tests passed
This commit is contained in:
parent
d4a5b638d5
commit
e4969b0220
7 changed files with 269 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -11,3 +11,4 @@ pubspec.lock
|
||||||
# Directory created by dartdoc
|
# Directory created by dartdoc
|
||||||
# If you don't generate documentation locally you can remove this line.
|
# If you don't generate documentation locally you can remove this line.
|
||||||
doc/api/
|
doc/api/
|
||||||
|
*.db
|
2
CHANGELOG.md
Normal file
2
CHANGELOG.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# 1.0.0
|
||||||
|
* First release.
|
3
analysis_options.yaml
Normal file
3
analysis_options.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
analyzer:
|
||||||
|
strong-mode:
|
||||||
|
implicit-casts: false
|
21
example/main.dart
Normal file
21
example/main.dart
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:angel_framework/http.dart';
|
||||||
|
import 'package:angel_sembast/angel_sembast.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:sembast/sembast_io.dart';
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
var app = new Angel();
|
||||||
|
var db = await databaseFactoryIo.openDatabase('todos.db');
|
||||||
|
|
||||||
|
app
|
||||||
|
..logger = (new Logger('angel_sembast_example')..onRecord.listen(print))
|
||||||
|
..use('/api/todos', new SembastService(db, store: 'todos'))
|
||||||
|
..shutdownHooks.add((_) => db.close());
|
||||||
|
|
||||||
|
var http = new AngelHttp(app);
|
||||||
|
var server = await http.startServer('127.0.0.1', 3000);
|
||||||
|
var uri =
|
||||||
|
new Uri(scheme: 'http', host: server.address.address, port: server.port);
|
||||||
|
print('angel_sembast example listening at $uri');
|
||||||
|
}
|
168
lib/angel_sembast.dart
Normal file
168
lib/angel_sembast.dart
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
|
import 'package:sembast/sembast.dart';
|
||||||
|
|
||||||
|
class SembastService extends Service<String, Map<String, dynamic>> {
|
||||||
|
final Database database;
|
||||||
|
final Store store;
|
||||||
|
|
||||||
|
/// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
|
||||||
|
///
|
||||||
|
/// `false` by default.
|
||||||
|
final bool allowRemoveAll;
|
||||||
|
|
||||||
|
/// If set to `true`, parameters in `req.query` are applied to the database query.
|
||||||
|
final bool allowQuery;
|
||||||
|
|
||||||
|
SembastService(this.database,
|
||||||
|
{String store, this.allowRemoveAll: false, this.allowQuery: true})
|
||||||
|
: this.store =
|
||||||
|
(store == null ? database.mainStore : database.getStore(store)),
|
||||||
|
super();
|
||||||
|
|
||||||
|
Finder _makeQuery([Map<String, dynamic> params]) {
|
||||||
|
params = new Map<String, dynamic>.from(params ?? {});
|
||||||
|
Filter out;
|
||||||
|
var sort = <SortOrder>[];
|
||||||
|
|
||||||
|
// You can pass a Finder as 'query':
|
||||||
|
if (params['query'] is Finder) {
|
||||||
|
return params['query'] as Finder;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key in params.keys) {
|
||||||
|
if (key == r'$sort' &&
|
||||||
|
(allowQuery == true || !params.containsKey('provider'))) {
|
||||||
|
var v = params[key];
|
||||||
|
|
||||||
|
if (v is! Map) {
|
||||||
|
sort.add(new SortOrder(v.toString(), true));
|
||||||
|
} else {
|
||||||
|
var m = v as Map;
|
||||||
|
m.forEach((k, sorter) {
|
||||||
|
if (sorter is SortOrder) {
|
||||||
|
sort.add(sorter);
|
||||||
|
} else if (sorter is String) {
|
||||||
|
sort.add(new SortOrder(k.toString(), sorter == "-1"));
|
||||||
|
} else if (sorter is num) {
|
||||||
|
sort.add(new SortOrder(k.toString(), sorter == -1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (key == 'query' &&
|
||||||
|
(allowQuery == true || !params.containsKey('provider'))) {
|
||||||
|
var queryObj = params[key];
|
||||||
|
|
||||||
|
if (queryObj is Map) {
|
||||||
|
queryObj.forEach((k, v) {
|
||||||
|
if (k != 'provider' &&
|
||||||
|
!const ['__requestctx', '__responsectx'].contains(k)) {
|
||||||
|
var filter = new Filter.equal(k.toString(), v);
|
||||||
|
if (out == null)
|
||||||
|
out = filter;
|
||||||
|
else
|
||||||
|
out = new Filter.or([out, filter]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Finder(filter: out, sortOrders: sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _jsonify(Record record) {
|
||||||
|
return new Map<String, dynamic>.from(record.value as Map)
|
||||||
|
..['id'] = record.key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> findOne(
|
||||||
|
[Map<String, dynamic> params,
|
||||||
|
String errorMessage = 'No record was found matching the given query.']) {
|
||||||
|
return store.findRecord(_makeQuery(params)).then(_jsonify);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Map<String, dynamic>>> index(
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
var records = await store.findRecords(_makeQuery(params));
|
||||||
|
return records.where((r) => r.value != null).map(_jsonify).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> read(String id,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
var record = await store.get(int.parse(id));
|
||||||
|
|
||||||
|
if (record == null) {
|
||||||
|
throw new AngelHttpException.notFound(
|
||||||
|
message: 'No record found for ID $id');
|
||||||
|
}
|
||||||
|
|
||||||
|
return (record as Map<String, dynamic>)..['id'] = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> create(Map<String, dynamic> data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
return await database.transaction((txn) async {
|
||||||
|
var store = txn.getStore(this.store.name);
|
||||||
|
var key = await store.put(data) as int;
|
||||||
|
var id = key.toString();
|
||||||
|
data = new Map<String, dynamic>.from(data)..['id'] = id;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> modify(String id, Map<String, dynamic> data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
data = new Map<String, dynamic>.from(data)..['id'] = id;
|
||||||
|
|
||||||
|
return await database.transaction((txn) async {
|
||||||
|
var store = txn.getStore(this.store.name);
|
||||||
|
var existing = await store.get(int.parse(id));
|
||||||
|
|
||||||
|
data =
|
||||||
|
new Map<String, dynamic>.from(existing as Map<String, dynamic> ?? {})
|
||||||
|
..addAll(data)
|
||||||
|
..['id'] = id;
|
||||||
|
|
||||||
|
await store.put(data, int.parse(id));
|
||||||
|
return (await store.get(int.parse(id)) as Map<String, dynamic>)
|
||||||
|
..['id'] = id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> update(String id, Map<String, dynamic> data,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
data = new Map<String, dynamic>.from(data)..['id'] = id;
|
||||||
|
|
||||||
|
return await database.transaction((txn) async {
|
||||||
|
var store = txn.getStore(this.store.name);
|
||||||
|
await store.put(data, int.parse(id));
|
||||||
|
return (await store.get(int.parse(id)) as Map<String, dynamic>)
|
||||||
|
..['id'] = id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, dynamic>> remove(String id,
|
||||||
|
[Map<String, dynamic> params]) async {
|
||||||
|
return database.transaction((txn) async {
|
||||||
|
var store = txn.getStore(this.store.name);
|
||||||
|
var record = await store.get(int.parse(id)) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (record == null) {
|
||||||
|
throw new AngelHttpException.notFound(
|
||||||
|
message: 'No record found for ID $id');
|
||||||
|
} else {
|
||||||
|
await store.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return record..['id'] = id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
name: angel_sembast
|
name: angel_sembast
|
||||||
description: package:sembast-powered CRUD services for the Angel framework.
|
description: package:sembast-powered CRUD services for the Angel framework.
|
||||||
|
homepage: https://github.com/angel-dart/sembast
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
author: Tobe O <thosakwe@gmail.com>
|
author: Tobe O <thosakwe@gmail.com>
|
||||||
environment:
|
environment:
|
||||||
|
|
73
test/all_test.dart
Normal file
73
test/all_test.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'package:angel_http_exception/angel_http_exception.dart';
|
||||||
|
import 'package:angel_sembast/angel_sembast.dart';
|
||||||
|
import 'package:sembast/sembast.dart';
|
||||||
|
import 'package:sembast/sembast_memory.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
main() async {
|
||||||
|
Database database;
|
||||||
|
SembastService service;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
database = await memoryDatabaseFactory.openDatabase('test.db');
|
||||||
|
service = new SembastService(database);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() => database.close());
|
||||||
|
|
||||||
|
test('index', () async {
|
||||||
|
await service.create({'id': '0', 'name': 'Tobe'});
|
||||||
|
await service.create({'id': '1', 'name': 'Osakwe'});
|
||||||
|
|
||||||
|
var output = await service.index();
|
||||||
|
print(output);
|
||||||
|
expect(output, hasLength(2));
|
||||||
|
expect(output[0], <String, dynamic>{'id': '1', 'name': 'Tobe'});
|
||||||
|
expect(output[1], <String, dynamic>{'id': '2', 'name': 'Osakwe'});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create', () async {
|
||||||
|
var input = {'bar': 'baz'};
|
||||||
|
var output = await service.create(input);
|
||||||
|
print(output);
|
||||||
|
expect(output.keys, contains('id'));
|
||||||
|
expect(output, containsPair('bar', 'baz'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('read', () async {
|
||||||
|
var name = 'poobah${new DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
var input = await service.create({'name': name, 'bar': 'baz'});
|
||||||
|
print(input);
|
||||||
|
expect(await service.read(input['id'] as String), input);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('modify', () async {
|
||||||
|
var input = await service.create({'bar': 'baz', 'yes': 'no'});
|
||||||
|
var id = input['id'] as String;
|
||||||
|
var output = await service.modify(id, {'bar': 'quux'});
|
||||||
|
expect(new SplayTreeMap.from(output),
|
||||||
|
new SplayTreeMap.from({'id': id, 'bar': 'quux', 'yes': 'no'}));
|
||||||
|
expect(await service.read(id), output);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('update', () async {
|
||||||
|
var input = await service.create({'bar': 'baz'});
|
||||||
|
var id = input['id'] as String;
|
||||||
|
var output = await service.update(id, {'yes': 'no'});
|
||||||
|
expect(output, {'id': id, 'yes': 'no'});
|
||||||
|
expect(await service.read(id), output);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('remove', () async {
|
||||||
|
var input = await service.create({'bar': 'baz'});
|
||||||
|
var id = input['id'] as String;
|
||||||
|
expect(await service.remove(id), input);
|
||||||
|
expect(await database.get(id), isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('remove nonexistent', () async {
|
||||||
|
expect(() => service.remove('440'),
|
||||||
|
throwsA(const TypeMatcher<AngelHttpException>()));
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue