From ab89fc04de67b544c8f7171569fb954537dac999 Mon Sep 17 00:00:00 2001 From: Tobe Osakwe Date: Mon, 5 Nov 2018 23:55:54 -0500 Subject: [PATCH 1/9] Initial commit --- .gitignore | 13 +++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 36 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7bf00e82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f8e6088a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 The Angel Framework + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..b6a9bde6 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# sembast +package:sembast-powered CRUD services for the Angel framework. From d4a5b638d53f02390cbabfce6d429a0470eb8b04 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 5 Nov 2018 23:59:15 -0500 Subject: [PATCH 2/9] pubspec --- pubspec.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 pubspec.yaml diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..befba48f --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,12 @@ +name: angel_sembast +description: package:sembast-powered CRUD services for the Angel framework. +version: 1.0.0 +author: Tobe O +environment: + sdk: ">=2.0.0-dev <3.0.0" +dependencies: + angel_framework: ^2.0.0-alpha + sembast: ^1.9.5 +dev_dependencies: + test: ^1.0.0 + From e4969b0220de22819a7a7ef65f57e60cb518647a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 6 Nov 2018 01:04:37 -0500 Subject: [PATCH 3/9] All tests passed --- .gitignore | 1 + CHANGELOG.md | 2 + analysis_options.yaml | 3 + example/main.dart | 21 ++++++ lib/angel_sembast.dart | 168 +++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 1 + test/all_test.dart | 73 ++++++++++++++++++ 7 files changed, 269 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 analysis_options.yaml create mode 100644 example/main.dart create mode 100644 lib/angel_sembast.dart create mode 100644 test/all_test.dart diff --git a/.gitignore b/.gitignore index 7bf00e82..beaa4a61 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pubspec.lock # Directory created by dartdoc # If you don't generate documentation locally you can remove this line. doc/api/ +*.db \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..bdbca561 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# 1.0.0 +* First release. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..eae1e42a --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + strong-mode: + implicit-casts: false \ No newline at end of file diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 00000000..e89f1382 --- /dev/null +++ b/example/main.dart @@ -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'); +} diff --git a/lib/angel_sembast.dart b/lib/angel_sembast.dart new file mode 100644 index 00000000..72861546 --- /dev/null +++ b/lib/angel_sembast.dart @@ -0,0 +1,168 @@ +import 'dart:async'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:sembast/sembast.dart'; + +class SembastService extends Service> { + 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 params]) { + params = new Map.from(params ?? {}); + Filter out; + var sort = []; + + // 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 _jsonify(Record record) { + return new Map.from(record.value as Map) + ..['id'] = record.key.toString(); + } + + @override + Future> findOne( + [Map params, + String errorMessage = 'No record was found matching the given query.']) { + return store.findRecord(_makeQuery(params)).then(_jsonify); + } + + @override + Future>> index( + [Map params]) async { + var records = await store.findRecords(_makeQuery(params)); + return records.where((r) => r.value != null).map(_jsonify).toList(); + } + + @override + Future> read(String id, + [Map 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)..['id'] = id; + } + + @override + Future> create(Map data, + [Map 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.from(data)..['id'] = id; + return data; + }); + } + + @override + Future> modify(String id, Map data, + [Map params]) async { + data = new Map.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.from(existing as Map ?? {}) + ..addAll(data) + ..['id'] = id; + + await store.put(data, int.parse(id)); + return (await store.get(int.parse(id)) as Map) + ..['id'] = id; + }); + } + + @override + Future> update(String id, Map data, + [Map params]) async { + data = new Map.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) + ..['id'] = id; + }); + } + + @override + Future> remove(String id, + [Map params]) async { + return database.transaction((txn) async { + var store = txn.getStore(this.store.name); + var record = await store.get(int.parse(id)) as Map; + + if (record == null) { + throw new AngelHttpException.notFound( + message: 'No record found for ID $id'); + } else { + await store.delete(id); + } + + return record..['id'] = id; + }); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index befba48f..55fb5971 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,6 @@ name: angel_sembast description: package:sembast-powered CRUD services for the Angel framework. +homepage: https://github.com/angel-dart/sembast version: 1.0.0 author: Tobe O environment: diff --git a/test/all_test.dart b/test/all_test.dart new file mode 100644 index 00000000..365fc7b7 --- /dev/null +++ b/test/all_test.dart @@ -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], {'id': '1', 'name': 'Tobe'}); + expect(output[1], {'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())); + }); +} From 03789b20b004837941cee6237b6a7c776c297237 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 6 Nov 2018 01:06:45 -0500 Subject: [PATCH 4/9] Test remove all --- lib/angel_sembast.dart | 8 ++++++++ test/all_test.dart | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/angel_sembast.dart b/lib/angel_sembast.dart index 72861546..643cdd11 100644 --- a/lib/angel_sembast.dart +++ b/lib/angel_sembast.dart @@ -151,6 +151,14 @@ class SembastService extends Service> { @override Future> remove(String id, [Map params]) async { + if (id == null || + id == 'null' && + (allowRemoveAll == true || + params?.containsKey('provider') != true)) { + await store.deleteAll(await store.findKeys(new Finder())); + return {}; + } + return database.transaction((txn) async { var store = txn.getStore(this.store.name); var record = await store.get(int.parse(id)) as Map; diff --git a/test/all_test.dart b/test/all_test.dart index 365fc7b7..7b4df3df 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -66,6 +66,16 @@ main() async { expect(await database.get(id), isNull); }); + test('remove', () async { + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + + expect(await service.index(), isNotEmpty); + await service.remove(null); + expect(await service.index(), isEmpty); + }); + test('remove nonexistent', () async { expect(() => service.remove('440'), throwsA(const TypeMatcher())); From a637a394b5626fb8a77a789d5b9d087f28648c02 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 6 Nov 2018 01:06:56 -0500 Subject: [PATCH 5/9] Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..de2210c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: dart \ No newline at end of file From 9657ece53f330bba851b5aaf1d33d8ebfbc11e1c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 6 Nov 2018 01:08:57 -0500 Subject: [PATCH 6/9] README --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index b6a9bde6..e8d30cf3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ # sembast +[![Pub](https://img.shields.io/pub/v/angel_sembast.svg)](https://pub.dartlang.org/packages/angel_sembast) +[![build status](https://travis-ci.org/angel-dart/sembast.svg)](https://travis-ci.org/angel-dart/sembast) + package:sembast-powered CRUD services for the Angel framework. + +# Installation + +Add the following to your `pubspec.yaml`: + +```yaml +dependencies: + angel_sembast: ^1.0.0 +``` + +# Usage + +This library exposes one main class: `SembastService`. + +## SembastService + +This class interacts with a `Database` and `Store` (from `package:sembast`) and serializes data to and from Maps. + +## Querying + +You can query these services as follows: + + /path/to/service?foo=bar + +The above will query the database to find records where 'foo' equals 'bar'. + +The former will sort result in ascending order of creation, and so will the latter. + +```dart +List queried = await MyService.index({r"query": where.id(new Finder(filter: new Filter(...)))); +``` + +Of course, you can use `package:sembast` queries. Just pass it as `query` within `params`. + +See the tests for more usage examples. From ce8107afbbf774da22745ef31426cd8ce505592c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Tue, 6 Nov 2018 01:11:09 -0500 Subject: [PATCH 7/9] Update dev deps --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index 55fb5971..07ac297f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,5 +9,7 @@ dependencies: angel_framework: ^2.0.0-alpha sembast: ^1.9.5 dev_dependencies: + angel_http_exception: ^1.0.0 + logging: test: ^1.0.0 From d42172986a1a4b657805d15ea74d891d0cb250cf Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 20 Apr 2019 15:13:46 -0400 Subject: [PATCH 8/9] 1.0.1 --- CHANGELOG.md | 3 +++ analysis_options.yaml | 1 + lib/angel_sembast.dart | 18 +++++++++++------- pubspec.yaml | 3 ++- test/all_test.dart | 30 ++++++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdbca561..9195900b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,5 @@ +# 1.0.1 +* Fix flaw where clients could remove all records, even if `allowRemoveAll` were `false`. + # 1.0.0 * First release. \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index eae1e42a..c230cee7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,4 @@ +include: package:pedantic/analysis_options.yaml analyzer: strong-mode: implicit-casts: false \ No newline at end of file diff --git a/lib/angel_sembast.dart b/lib/angel_sembast.dart index 643cdd11..53814ed0 100644 --- a/lib/angel_sembast.dart +++ b/lib/angel_sembast.dart @@ -15,7 +15,7 @@ class SembastService extends Service> { final bool allowQuery; SembastService(this.database, - {String store, this.allowRemoveAll: false, this.allowQuery: true}) + {String store, this.allowRemoveAll = false, this.allowQuery = true}) : this.store = (store == null ? database.mainStore : database.getStore(store)), super(); @@ -151,12 +151,16 @@ class SembastService extends Service> { @override Future> remove(String id, [Map params]) async { - if (id == null || - id == 'null' && - (allowRemoveAll == true || - params?.containsKey('provider') != true)) { - await store.deleteAll(await store.findKeys(new Finder())); - return {}; + if (id == null || id == 'null') { + // Remove everything... + if (!(allowRemoveAll == true || + params?.containsKey('provider') != true)) { + throw AngelHttpException.forbidden( + message: 'Clients are not allowed to delete all items.'); + } else { + await store.deleteAll(await store.findKeys(new Finder())); + return {}; + } } return database.transaction((txn) async { diff --git a/pubspec.yaml b/pubspec.yaml index 07ac297f..b822c43f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: angel_sembast description: package:sembast-powered CRUD services for the Angel framework. homepage: https://github.com/angel-dart/sembast -version: 1.0.0 +version: 1.0.1 author: Tobe O environment: sdk: ">=2.0.0-dev <3.0.0" @@ -11,5 +11,6 @@ dependencies: dev_dependencies: angel_http_exception: ^1.0.0 logging: + pedantic: ^1.0.0 test: ^1.0.0 diff --git a/test/all_test.dart b/test/all_test.dart index 7b4df3df..c4862d58 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'package:angel_framework/angel_framework.dart'; import 'package:angel_http_exception/angel_http_exception.dart'; import 'package:angel_sembast/angel_sembast.dart'; import 'package:sembast/sembast.dart'; @@ -76,6 +77,35 @@ main() async { expect(await service.index(), isEmpty); }); + test('cannot remove all unless explicitly set', () async { + expect(() => service.remove(null, {'provider': Providers.rest}), + throwsA(const TypeMatcher())); + expect( + () => service.remove(null, {'provider': Providers.rest}), + throwsA(predicate((x) => x is AngelHttpException && x.statusCode == 403, + 'throws forbidden'))); + expect(() => service.remove('null', {'provider': Providers.rest}), + throwsA(const TypeMatcher())); + expect( + () => service.remove('null', {'provider': Providers.rest}), + throwsA(predicate((x) => x is AngelHttpException && x.statusCode == 403, + 'throws forbidden'))); + }); + + test('can remove all on server side', () async { + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + await service.remove(null); + expect(await service.index(), isEmpty); + + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + await service.create({'bar': 'baz'}); + await service.remove('null'); + expect(await service.index(), isEmpty); + }); + test('remove nonexistent', () async { expect(() => service.remove('440'), throwsA(const TypeMatcher())); From 611a785ffc40e832d4ade163093bd543d74e1014 Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 18 Jul 2019 13:08:46 +0200 Subject: [PATCH 9/9] feat: use the new sembast store API Also ran `dartfmt -w . --fix` to fix the lint warnings --- example/main.dart | 10 ++--- lib/angel_sembast.dart | 94 ++++++++++++++++++------------------------ pubspec.yaml | 4 +- test/all_test.dart | 10 ++--- 4 files changed, 52 insertions(+), 66 deletions(-) diff --git a/example/main.dart b/example/main.dart index e89f1382..8bb6357b 100644 --- a/example/main.dart +++ b/example/main.dart @@ -5,17 +5,17 @@ import 'package:logging/logging.dart'; import 'package:sembast/sembast_io.dart'; main() async { - var app = new Angel(); + var app = 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')) + ..logger = (Logger('angel_sembast_example')..onRecord.listen(print)) + ..use('/api/todos', SembastService(db, store: 'todos')) ..shutdownHooks.add((_) => db.close()); - var http = new AngelHttp(app); + var http = 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); + Uri(scheme: 'http', host: server.address.address, port: server.port); print('angel_sembast example listening at $uri'); } diff --git a/lib/angel_sembast.dart b/lib/angel_sembast.dart index 53814ed0..b0c00b9b 100644 --- a/lib/angel_sembast.dart +++ b/lib/angel_sembast.dart @@ -4,7 +4,7 @@ import 'package:sembast/sembast.dart'; class SembastService extends Service> { final Database database; - final Store store; + final StoreRef> store; /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`. /// @@ -16,12 +16,11 @@ class SembastService extends Service> { SembastService(this.database, {String store, this.allowRemoveAll = false, this.allowQuery = true}) - : this.store = - (store == null ? database.mainStore : database.getStore(store)), + : this.store = intMapStoreFactory.store(store), super(); Finder _makeQuery([Map params]) { - params = new Map.from(params ?? {}); + params = Map.from(params ?? {}); Filter out; var sort = []; @@ -36,16 +35,16 @@ class SembastService extends Service> { var v = params[key]; if (v is! Map) { - sort.add(new SortOrder(v.toString(), true)); + sort.add(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")); + sort.add(SortOrder(k.toString(), sorter == "-1")); } else if (sorter is num) { - sort.add(new SortOrder(k.toString(), sorter == -1)); + sort.add(SortOrder(k.toString(), sorter == -1)); } }); } @@ -57,94 +56,81 @@ class SembastService extends Service> { queryObj.forEach((k, v) { if (k != 'provider' && !const ['__requestctx', '__responsectx'].contains(k)) { - var filter = new Filter.equal(k.toString(), v); - if (out == null) + var filter = Filter.equals(k.toString(), v); + if (out == null) { out = filter; - else - out = new Filter.or([out, filter]); + } else { + out = Filter.or([out, filter]); + } } }); } } } - return new Finder(filter: out, sortOrders: sort); + return Finder(filter: out, sortOrders: sort); } - Map _jsonify(Record record) { - return new Map.from(record.value as Map) - ..['id'] = record.key.toString(); - } + Map _withId(Map data, String id) => + Map.from(data ?? {})..['id'] = id; @override Future> findOne( [Map params, - String errorMessage = 'No record was found matching the given query.']) { - return store.findRecord(_makeQuery(params)).then(_jsonify); + String errorMessage = + 'No record was found matching the given query.']) async { + return (await store.findFirst(database, finder: _makeQuery(params)))?.value; } @override Future>> index( [Map params]) async { - var records = await store.findRecords(_makeQuery(params)); - return records.where((r) => r.value != null).map(_jsonify).toList(); + var records = await store.find(database, finder: _makeQuery(params)); + return records + .where((r) => r.value != null) + .map((r) => _withId(r.value, r.key.toString())) + .toList(); } @override Future> read(String id, [Map params]) async { - var record = await store.get(int.parse(id)); + var record = await store.record(int.parse(id)).getSnapshot(database); if (record == null) { - throw new AngelHttpException.notFound( - message: 'No record found for ID $id'); + throw AngelHttpException.notFound(message: 'No record found for ID $id'); } - return (record as Map)..['id'] = id; + return _withId(record.value, id); } @override Future> create(Map data, [Map params]) async { return await database.transaction((txn) async { - var store = txn.getStore(this.store.name); - var key = await store.put(data) as int; + var key = await store.add(txn, data); var id = key.toString(); - data = new Map.from(data)..['id'] = id; - return data; + return _withId(data, id); }); } @override Future> modify(String id, Map data, [Map params]) async { - data = new Map.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.from(existing as Map ?? {}) - ..addAll(data) - ..['id'] = id; - - await store.put(data, int.parse(id)); - return (await store.get(int.parse(id)) as Map) - ..['id'] = id; + var record = store.record(int.parse(id)); + data = await record.put(txn, data, merge: true); + return _withId(data, id); }); } @override Future> update(String id, Map data, [Map params]) async { - data = new Map.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) - ..['id'] = id; + var record = store.record(int.parse(id)); + data = await record.put(txn, data); + return _withId(data, id); }); } @@ -158,23 +144,23 @@ class SembastService extends Service> { throw AngelHttpException.forbidden( message: 'Clients are not allowed to delete all items.'); } else { - await store.deleteAll(await store.findKeys(new Finder())); + await store.delete(database); return {}; } } return database.transaction((txn) async { - var store = txn.getStore(this.store.name); - var record = await store.get(int.parse(id)) as Map; + var record = store.record(int.parse(id)); + var snapshot = await record.getSnapshot(txn); - if (record == null) { - throw new AngelHttpException.notFound( + if (snapshot == null) { + throw AngelHttpException.notFound( message: 'No record found for ID $id'); } else { - await store.delete(id); + await record.delete(txn); } - return record..['id'] = id; + return _withId(snapshot.value, id); }); } } diff --git a/pubspec.yaml b/pubspec.yaml index b822c43f..a21ae48e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,10 +4,10 @@ homepage: https://github.com/angel-dart/sembast version: 1.0.1 author: Tobe O environment: - sdk: ">=2.0.0-dev <3.0.0" + sdk: ">=2.1.0-dev <3.0.0" dependencies: angel_framework: ^2.0.0-alpha - sembast: ^1.9.5 + sembast: ^1.19.0-dev.2 dev_dependencies: angel_http_exception: ^1.0.0 logging: diff --git a/test/all_test.dart b/test/all_test.dart index c4862d58..8398a1f9 100644 --- a/test/all_test.dart +++ b/test/all_test.dart @@ -12,7 +12,7 @@ main() async { setUp(() async { database = await memoryDatabaseFactory.openDatabase('test.db'); - service = new SembastService(database); + service = SembastService(database); }); tearDown(() => database.close()); @@ -37,7 +37,7 @@ main() async { }); test('read', () async { - var name = 'poobah${new DateTime.now().millisecondsSinceEpoch}'; + var name = 'poobah${DateTime.now().millisecondsSinceEpoch}'; var input = await service.create({'name': name, 'bar': 'baz'}); print(input); expect(await service.read(input['id'] as String), input); @@ -47,8 +47,8 @@ main() 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(SplayTreeMap.from(output), + SplayTreeMap.from({'id': id, 'bar': 'quux', 'yes': 'no'})); expect(await service.read(id), output); }); @@ -64,7 +64,7 @@ main() 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); + expect(await StoreRef.main().record(id).get(database), isNull); }); test('remove', () async {