From 9008e8cbe2ac2b5fd6c1ffedbb4a034fd5f9f166 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 9 May 2016 17:16:44 -0400 Subject: [PATCH 01/37] Initial commit --- .gitignore | 27 +++++++++++++++++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 3 files changed, 50 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..7c280441 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.buildlog +.packages +.project +.pub/ +build/ +**/packages/ + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc +doc/api/ + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +pubspec.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..eb4ce33e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 angel-dart + +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..d4df454c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# angel_mongo +MongoDB-enabled services for the Angel framework. From a3f9d6ce28eccd780a10d5f70c07ba052f8fa5b8 Mon Sep 17 00:00:00 2001 From: regiostech Date: Mon, 9 May 2016 18:51:07 -0400 Subject: [PATCH 02/37] Will be done soon --- .gitignore | 60 +++++++++++++++ .idea/runConfigurations/All_Tests.xml | 6 ++ .idea/vcs.xml | 6 ++ lib/angel_mongo.dart | 10 +++ lib/mongo_service.dart | 92 ++++++++++++++++++++++ pubspec.yaml | 12 +++ test/all_tests.dart | 106 ++++++++++++++++++++++++++ 7 files changed, 292 insertions(+) create mode 100644 .idea/runConfigurations/All_Tests.xml create mode 100644 .idea/vcs.xml create mode 100644 lib/angel_mongo.dart create mode 100644 lib/mongo_service.dart create mode 100644 pubspec.yaml create mode 100644 test/all_tests.dart diff --git a/.gitignore b/.gitignore index 7c280441..c9d518bb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,63 @@ doc/api/ # Don't commit pubspec lock file # (Library packages only! Remove pattern if developing an application package) pubspec.lock +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) + +# Directory created by dartdoc + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/workspace.xml +.idea/tasks.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml + +# Sensitive or high-churn files: +.idea/dataSources.ids +.idea/dataSources.xml +.idea/dataSources.local.xml +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml + +# Gradle: +.idea/gradle.xml +.idea/libraries + +# Mongo Explorer plugin: +.idea/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml new file mode 100644 index 00000000..a824b209 --- /dev/null +++ b/.idea/runConfigurations/All_Tests.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart new file mode 100644 index 00000000..314a2e16 --- /dev/null +++ b/lib/angel_mongo.dart @@ -0,0 +1,10 @@ +library angel_mongo; +import 'dart:async'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:json_god/json_god.dart'; +import 'package:merge_map/merge_map.dart'; +import 'package:mongo_dart/mongo_dart.dart'; + +part 'mongo_service.dart'; + +final _god = new God(); diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart new file mode 100644 index 00000000..c0a3bb7b --- /dev/null +++ b/lib/mongo_service.dart @@ -0,0 +1,92 @@ +part of angel_mongo; + +class MongoService extends Service { + DbCollection collection; + + MongoService(DbCollection this.collection); + + Map _jsonify(Map doc) { + Map result = {}; + for (var key in doc.keys) { + if (doc[key] is ObjectId) { + result[key] = doc[key].toHexString(); + } else result[key] = doc[key]; + } + return result; + } + + _lastItem() async { + return (await (await collection.find( + where.sortBy('\$natural', descending: true))).toList()) + .map(_jsonify) + .first; + } + + SelectorBuilder _makeQuery([Map params_]) { + Map params = params_ ?? {}; + SelectorBuilder result = where.exists('_id'); + + for (var key in params.keys) { + if (key == r'$sort') { + if (params[key] is Map) { + // If they send a map, then we'll sort by every key in the map + for (String fieldName in params[key].keys.where((x) => x is String)) { + var sorter = params[key][fieldName]; + if (sorter is num) { + result = result.sortBy(fieldName, descending: sorter == -1); + } else if (sorter is String) { + result = result.sortBy(fieldName, descending: sorter == "-1"); + } + } + } else if (params[key] is String) { + // If they send just a string, then we'll sort + // by that, ascending + result = result.sortBy(params[key]); + } + } + + else if (key is String) { + result = result.and(where.eq(key, params[key])); + } + } + + return result; + } + + @override + Future index([Map params]) async { + return await (await collection.find(_makeQuery(params))) + .map(_jsonify) + .toList(); + } + + @override + Future create(data, [Map params]) async { + Map item = (data is Map) ? data : _god.serializeToMap(data); + item = mergeMap([item, params]); + item['createdAt'] = new DateTime.now(); + await collection.insert(item); + return await _lastItem(); + } + + @override + Future read(id, [Map params]) async { + ObjectId id_; + try { + id_ = (id is ObjectId) ? id : new ObjectId.fromHexString( + id.toString()); + } catch (e) { + throw new AngelHttpException.BadRequest(); + } + + Map found = await collection.findOne( + where.id(id_).and(_makeQuery(params))); + + if (found == null) { + throw new AngelHttpException.NotFound( + message: 'No record found for ID ${id_.toHexString()}'); + } + + return _jsonify(found); + } +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..44a65084 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,12 @@ +name: angel_mongo +version: 1.0.0-dev +description: Core libraries for the Angel framework. +author: Tobe O +homepage: https://github.com/angel-dart/angel_framework +dependencies: + angel_framework: ">=0.0.0-dev.17 < 0.1.0" + json_god: ">=1.0.0 <2.0.0" + mongo_dart: ">= 0.2.5+1 < 1.0.0" +dev_dependencies: + http: ">= 0.11.3 < 0.12.0" + test: ">= 0.12.13 < 0.13.0" \ No newline at end of file diff --git a/test/all_tests.dart b/test/all_tests.dart new file mode 100644 index 00000000..832c17c8 --- /dev/null +++ b/test/all_tests.dart @@ -0,0 +1,106 @@ +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mongo/angel_mongo.dart'; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart'; +import 'package:mongo_dart/mongo_dart.dart'; +import 'package:test/test.dart'; + +final headers = { + HttpHeaders.ACCEPT: ContentType.JSON.mimeType, + HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType +}; + +wireHooked(HookedService hooked) { + hooked.onCreated.listen((item) { + print("Just created: $item"); + }); +} + +main() { + group('angel_mongo', () { + Angel app = new Angel(); + http.Client client; + God god = new God(); + Db db = new Db('mongodb://localhost:27017/angel_mongo'); + DbCollection testData; + String url; + + setUp(() async { + client = new http.Client(); + await db.open(); + testData = db.collection('test_data'); + // Delete anything before we start + await testData.remove(); + var service = new MongoService(testData); + var hooked = new HookedService(service); + wireHooked(hooked); + + app.use('/api', hooked); + HttpServer server = await app.startServer( + InternetAddress.LOOPBACK_IP_V4, 0); + url = "http://${server.address.host}:${server.port}"; + }); + + tearDown(() async { + // Delete anything left over + await testData.remove(); + await db.close(); + await app.httpServer.close(force: true); + client = null; + url = null; + }); + + test('insert items', () async { + Map testUser = {'hello': 'world'}; + + var response = await client.post( + "$url/api", body: god.serialize(testUser), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + + response = await client.get("$url/api"); + expect(response.statusCode, 200); + List users = god.deserialize(response.body); + expect(users.length, equals(1)); + }); + + test('read item', () async { + Map testUser = {'hello': 'world'}; + var response = await client.post( + "$url/api", body: god.serialize(testUser), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + + response = await client.get("$url/api/${created['_id']}"); + expect(response.statusCode, equals(HttpStatus.OK)); + Map read = god.deserialize(response.body); + expect(read['_id'], equals(created['_id'])); + expect(read['hello'], equals('world')); + expect(read['createdAt'], isNot(null)); + }); + + test('modify item', () async { + + }); + + test('update item', () async { + + }); + + test('remove item', () async { + + }); + + test('sort by string', () async { + + }); + + test('sort by map', () async { + + }); + + test('query parameters', () async { + + }); + }); +} \ No newline at end of file From ddd8fea18b650eea70be2e781aba3e256ab7fa43 Mon Sep 17 00:00:00 2001 From: regiostech Date: Tue, 21 Jun 2016 16:34:52 -0400 Subject: [PATCH 03/37] Need to eventually upgrade all plugins to JSON God v2 --- .idea/runConfigurations/All_Tests.xml | 6 ------ .idea/runConfigurations/Generic_Tests.xml | 6 ++++++ .idea/runConfigurations/Typed_Tests.xml | 6 ++++++ test/{all_tests.dart => generic_tests.dart} | 20 ++++++++++++++++++++ test/typed_tests.dart | 0 5 files changed, 32 insertions(+), 6 deletions(-) delete mode 100644 .idea/runConfigurations/All_Tests.xml create mode 100644 .idea/runConfigurations/Generic_Tests.xml create mode 100644 .idea/runConfigurations/Typed_Tests.xml rename test/{all_tests.dart => generic_tests.dart} (84%) create mode 100644 test/typed_tests.dart diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml deleted file mode 100644 index a824b209..00000000 --- a/.idea/runConfigurations/All_Tests.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Generic_Tests.xml b/.idea/runConfigurations/Generic_Tests.xml new file mode 100644 index 00000000..afb55d6f --- /dev/null +++ b/.idea/runConfigurations/Generic_Tests.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Typed_Tests.xml b/.idea/runConfigurations/Typed_Tests.xml new file mode 100644 index 00000000..ea002d9b --- /dev/null +++ b/.idea/runConfigurations/Typed_Tests.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/test/all_tests.dart b/test/generic_tests.dart similarity index 84% rename from test/all_tests.dart rename to test/generic_tests.dart index 832c17c8..7e72b76f 100644 --- a/test/all_tests.dart +++ b/test/generic_tests.dart @@ -6,6 +6,11 @@ import 'package:json_god/json_god.dart'; import 'package:mongo_dart/mongo_dart.dart'; import 'package:test/test.dart'; +class Greeting { + final String to; + const Greeting(String this.to); +} + final headers = { HttpHeaders.ACCEPT: ContentType.JSON.mimeType, HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType @@ -17,6 +22,12 @@ wireHooked(HookedService hooked) { }); } +wireGreeting(HookedService hooked) { + hooked.onCreated.listen((item) { + print("Greeting: $item"); + }); +} + main() { group('angel_mongo', () { Angel app = new Angel(); @@ -24,19 +35,28 @@ main() { God god = new God(); Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; + DbCollection testGreetings; String url; setUp(() async { client = new http.Client(); await db.open(); testData = db.collection('test_data'); + testGreetings = db.collection('test_greetings'); // Delete anything before we start await testData.remove(); + await testGreetings.remove(); + var service = new MongoService(testData); var hooked = new HookedService(service); wireHooked(hooked); + var greetings = new MongoService(testGreetings); + var hookedGreetings = new HookedService(greetings); + wireGreeting(hookedGreetings); + app.use('/api', hooked); + app.use('/greetings', hookedGreetings); HttpServer server = await app.startServer( InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; diff --git a/test/typed_tests.dart b/test/typed_tests.dart new file mode 100644 index 00000000..e69de29b From 7d5493662323b223c47687bd39f89ad868eb5077 Mon Sep 17 00:00:00 2001 From: regiostech Date: Wed, 22 Jun 2016 14:34:28 -0400 Subject: [PATCH 04/37] Need remove, sort, docs --- lib/angel_mongo.dart | 81 ++++++++++++++++++- lib/mongo_service.dart | 99 +++++++++++++++-------- lib/mongo_service_typed.dart | 131 +++++++++++++++++++++++++++++++ pubspec.yaml | 6 +- test/generic_tests.dart | 107 +++++++++++++------------ test/typed_tests.dart | 148 +++++++++++++++++++++++++++++++++++ 6 files changed, 483 insertions(+), 89 deletions(-) create mode 100644 lib/mongo_service_typed.dart diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart index 314a2e16..95808b28 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -1,10 +1,87 @@ library angel_mongo; + import 'dart:async'; +import 'dart:mirrors'; import 'package:angel_framework/angel_framework.dart'; -import 'package:json_god/json_god.dart'; +import 'package:json_god/json_god.dart' as god; import 'package:merge_map/merge_map.dart'; import 'package:mongo_dart/mongo_dart.dart'; part 'mongo_service.dart'; -final _god = new God(); +part 'mongo_service_typed.dart'; + +/// A data type that can be serialized to MongoDB. +class Model { + /// This instance's ID. + String id; + + /// The time at which this instance was created. + DateTime createdAt; + + /// The time at which this instance was last updated. + DateTime updatedAt; +} + + +Map _transformId(Map doc) { + Map result = mergeMap([doc]); + result['id'] = doc['_id']; + + return result..remove('_id'); +} + +_lastItem(DbCollection collection, Function _jsonify, [Map params]) async { + return (await (await collection + .find(where.sortBy('\$natural', descending: true))) + .toList()) + .map((x) => _jsonify(x, params)) + .first; +} + +ObjectId _makeId(id) { + try { + return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString()); + } catch (e) { + throw new AngelHttpException.BadRequest(); + } +} + +Map _removeSensitive(Map data) { + return data..remove('id')..remove('_id')..remove('createdAt')..remove( + 'updatedAt'); +} + +SelectorBuilder _makeQuery([Map params_]) { + Map params = params_ ?? {}; + params = params..remove('provider'); + SelectorBuilder result = where.exists('_id'); + + // You can pass a SelectorBuilder as 'query'; + if (params['query'] != null && params['query'] is SelectorBuilder) + return params['query']; + + for (var key in params.keys) { + if (key == r'$sort') { + if (params[key] is Map) { + // If they send a map, then we'll sort by every key in the map + for (String fieldName in params[key].keys.where((x) => x is String)) { + var sorter = params[key][fieldName]; + if (sorter is num) { + result = result.sortBy(fieldName, descending: sorter == -1); + } else if (sorter is String) { + result = result.sortBy(fieldName, descending: sorter == "-1"); + } + } + } else if (params[key] is String) { + // If they send just a string, then we'll sort + // by that, ascending + result = result.sortBy(params[key]); + } + } else if (key is String) { + result = result.and(where.eq(key, params[key])); + } + } + + return result; +} diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index c0a3bb7b..cf0f071e 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -1,29 +1,33 @@ part of angel_mongo; +/// Manipulates data from MongoDB as Maps. class MongoService extends Service { DbCollection collection; - MongoService(DbCollection this.collection); + MongoService(DbCollection this.collection):super(); - Map _jsonify(Map doc) { + Map _transformId(Map doc) { + Map result = mergeMap([doc]); + result['id'] = doc['_id']; + + return result..remove('_id'); + } + + _jsonify(Map doc, [Map params]) { Map result = {}; for (var key in doc.keys) { if (doc[key] is ObjectId) { result[key] = doc[key].toHexString(); - } else result[key] = doc[key]; + } else + result[key] = doc[key]; } - return result; - } - _lastItem() async { - return (await (await collection.find( - where.sortBy('\$natural', descending: true))).toList()) - .map(_jsonify) - .first; + return _transformId(result); } SelectorBuilder _makeQuery([Map params_]) { Map params = params_ ?? {}; + params = params..remove('provider'); SelectorBuilder result = where.exists('_id'); for (var key in params.keys) { @@ -43,9 +47,7 @@ class MongoService extends Service { // by that, ascending result = result.sortBy(params[key]); } - } - - else if (key is String) { + } else if (key is String) { result = result.and(where.eq(key, params[key])); } } @@ -56,37 +58,70 @@ class MongoService extends Service { @override Future index([Map params]) async { return await (await collection.find(_makeQuery(params))) - .map(_jsonify) + .map((x) => _jsonify(x, params)) .toList(); } @override - Future create(data, [Map params]) async { - Map item = (data is Map) ? data : _god.serializeToMap(data); - item = mergeMap([item, params]); - item['createdAt'] = new DateTime.now(); - await collection.insert(item); - return await _lastItem(); + Future create(Map data, [Map params]) async { + Map item = (data is Map) ? data : god.serializeObject(data); + item = _removeSensitive(item); + + try { + item['createdAt'] = new DateTime.now(); + await collection.insert(item); + return await _lastItem(collection, _jsonify, params); + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } } @override Future read(id, [Map params]) async { - ObjectId id_; - try { - id_ = (id is ObjectId) ? id : new ObjectId.fromHexString( - id.toString()); - } catch (e) { - throw new AngelHttpException.BadRequest(); - } - - Map found = await collection.findOne( - where.id(id_).and(_makeQuery(params))); + ObjectId _id = _makeId(id); + Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); if (found == null) { throw new AngelHttpException.NotFound( - message: 'No record found for ID ${id_.toHexString()}'); + message: 'No record found for ID ${_id.toHexString()}'); } - return _jsonify(found); + return _jsonify(found, params); } + + @override + Future modify(id, Map data, [Map params]) async { + Map target = await read(id, params); + Map result = mergeMap([target, _removeSensitive(data)]); + result['updatedAt'] = new DateTime.now(); + + try { + await collection.update(where.id(_makeId(id)), result); + result = _jsonify(result, params); + result['id'] = id; + return result; + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } + + @override + Future update(id, data, [Map params]) async { + Map target = await read(id, params); + Map result = _removeSensitive(data); + result['_id'] = _makeId(id); + result['createdAt'] = target['createdAt']; + result['updatedAt'] = new DateTime.now(); + + try { + await collection.update(where.id(_makeId(id)), result); + result = _jsonify(result, params); + result['id'] = id; + return result; + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } + + } diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart new file mode 100644 index 00000000..c4a11ec9 --- /dev/null +++ b/lib/mongo_service_typed.dart @@ -0,0 +1,131 @@ +part of angel_mongo; + +/// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. +class MongoTypedService extends Service { + DbCollection collection; + + MongoTypedService(DbCollection this.collection):super() { + if (!reflectType(T).isAssignableTo(reflectType(Model))) + throw new Exception( + "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); + } + + Map _transformId(Map doc) { + Map result = mergeMap([doc]); + result['id'] = doc['_id']; + + return result..remove('_id'); + } + + _jsonify(Map doc, [Map params]) { + Map result = {}; + for (var key in doc.keys) { + if (doc[key] is ObjectId) { + result[key] = doc[key].toHexString(); + } else + result[key] = doc[key]; + } + + result = _transformId(result); + + // Clients will always receive JSON. + if ((params != null && params['provider'] != null)) { + return result; + } + else { + // However, when we run server-side, we should return a T, not a Map. + Model typedResult = god.deserializeDatum(result, outputType: T); + typedResult.createdAt = result['createdAt']; + typedResult.updatedAt = result['updatedAt']; + return typedResult; + } + } + + @override + Future index([Map params]) async { + return await (await collection.find(_makeQuery(params))) + .map((x) => _jsonify(x, params)) + .toList(); + } + + @override + Future create(data, [Map params]) async { + Map item; + + try { + Model target = + (data is T) ? data : god.deserializeDatum(data, outputType: T); + item = god.serializeObject(target); + item = _removeSensitive(item); + + item['createdAt'] = new DateTime.now(); + await collection.insert(item); + return await _lastItem(collection, _jsonify, params); + } catch (e, st) { + print(e); + print(st); + throw new AngelHttpException.BadRequest(); + } + } + + @override + Future read(id, [Map params]) async { + ObjectId _id = _makeId(id); + + Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); + + if (found == null) { + throw new AngelHttpException.NotFound( + message: 'No record found for ID ${_id.toHexString()}'); + } + + return _jsonify(found, params); + } + + @override + Future modify(id, Map data, [Map params]) async { + ObjectId _id = _makeId(id); + try { + Map result = await collection.findOne( + where.id(_id).and(_makeQuery(params))); + + if (result == null) { + throw new AngelHttpException.NotFound( + message: 'No record found for ID ${_id.toHexString()}'); + } + + result = mergeMap([result, _removeSensitive(data)]); + result['_id'] = _id; + result['updatedAt'] = new DateTime.now(); + + await collection.update(where.id(_id), result); + return await read(_id, params); + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } + + @override + Future update(id, _data, [Map params]) async { + try { + Model data = (_data is T) ? _data : god.deserializeDatum( + _data, outputType: T); + ObjectId _id = _makeId(id); + Map rawData = _removeSensitive(god.serializeObject(data)); + rawData['_id'] = _id; + rawData['createdAt'] = data.createdAt; + rawData['updatedAt'] = new DateTime.now(); + + await collection.update(where.id(_id).and(_makeQuery(params)), rawData); + var result = _jsonify(rawData, params); + + if (result is T) { + result.createdAt = data.createdAt; + result.updatedAt = rawData['updatedAt']; + } + return result; + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 44a65084..3c50d85f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,9 +4,9 @@ description: Core libraries for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_framework dependencies: - angel_framework: ">=0.0.0-dev.17 < 0.1.0" - json_god: ">=1.0.0 <2.0.0" - mongo_dart: ">= 0.2.5+1 < 1.0.0" + angel_framework: ">=1.0.0-dev < 2.0.0" + json_god: ">=2.0.0-beta <3.0.0" + mongo_dart: ">= 0.2.7 < 1.0.0" dev_dependencies: http: ">= 0.11.3 < 0.12.0" test: ">= 0.12.13 < 0.13.0" \ No newline at end of file diff --git a/test/generic_tests.dart b/test/generic_tests.dart index 7e72b76f..4e346135 100644 --- a/test/generic_tests.dart +++ b/test/generic_tests.dart @@ -2,63 +2,53 @@ import 'dart:io'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_mongo/angel_mongo.dart'; import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart'; +import 'package:json_god/json_god.dart' as god; import 'package:mongo_dart/mongo_dart.dart'; import 'package:test/test.dart'; -class Greeting { - final String to; - const Greeting(String this.to); -} - final headers = { HttpHeaders.ACCEPT: ContentType.JSON.mimeType, HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType }; -wireHooked(HookedService hooked) { - hooked.onCreated.listen((item) { - print("Just created: $item"); - }); -} +final Map testGreeting = {'to': 'world'}; -wireGreeting(HookedService hooked) { - hooked.onCreated.listen((item) { - print("Greeting: $item"); - }); +wireHooked(HookedService hooked) { + hooked + ..afterCreated.listen((HookedServiceEvent event) { + print("Just created: ${event.result}"); + }) + ..afterModified.listen((HookedServiceEvent event) { + print("Just modified: ${event.result}"); + }) + ..afterUpdated.listen((HookedServiceEvent event) { + print("Just updated: ${event.result}"); + }); } main() { group('angel_mongo', () { Angel app = new Angel(); http.Client client; - God god = new God(); Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; - DbCollection testGreetings; String url; + HookedService Greetings; setUp(() async { client = new http.Client(); await db.open(); testData = db.collection('test_data'); - testGreetings = db.collection('test_greetings'); // Delete anything before we start await testData.remove(); - await testGreetings.remove(); var service = new MongoService(testData); - var hooked = new HookedService(service); - wireHooked(hooked); + Greetings = new HookedService(service); + wireHooked(Greetings); - var greetings = new MongoService(testGreetings); - var hookedGreetings = new HookedService(greetings); - wireGreeting(hookedGreetings); - - app.use('/api', hooked); - app.use('/greetings', hookedGreetings); - HttpServer server = await app.startServer( - InternetAddress.LOOPBACK_IP_V4, 0); + app.use('/api', Greetings); + HttpServer server = + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; }); @@ -69,13 +59,12 @@ main() { await app.httpServer.close(force: true); client = null; url = null; + Greetings = null; }); test('insert items', () async { - Map testUser = {'hello': 'world'}; - - var response = await client.post( - "$url/api", body: god.serialize(testUser), headers: headers); + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, equals(HttpStatus.OK)); response = await client.get("$url/api"); @@ -85,42 +74,56 @@ main() { }); test('read item', () async { - Map testUser = {'hello': 'world'}; - var response = await client.post( - "$url/api", body: god.serialize(testUser), headers: headers); + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, equals(HttpStatus.OK)); Map created = god.deserialize(response.body); - response = await client.get("$url/api/${created['_id']}"); + response = await client.get("$url/api/${created['id']}"); expect(response.statusCode, equals(HttpStatus.OK)); Map read = god.deserialize(response.body); - expect(read['_id'], equals(created['_id'])); - expect(read['hello'], equals('world')); + expect(read['id'], equals(created['id'])); + expect(read['to'], equals('world')); expect(read['createdAt'], isNot(null)); }); test('modify item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + response = await client.patch( + "$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), + headers: headers); + Map modified = god.deserialize(response.body); + expect(response.statusCode, equals(HttpStatus.OK)); + expect(modified['id'], equals(created['id'])); + expect(modified['to'], equals('Mom')); + expect(modified['updatedAt'], isNot(null)); }); test('update item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + response = await client.post( + "$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), + headers: headers); + Map modified = god.deserialize(response.body); + expect(response.statusCode, equals(HttpStatus.OK)); + expect(modified['id'], equals(created['id'])); + expect(modified['to'], equals('Updated')); + expect(modified['updatedAt'], isNot(null)); }); - test('remove item', () async { + test('remove item', () async {}); + test(r'$sort', () async { }); - test('sort by string', () async { - - }); - - test('sort by map', () async { - - }); - - test('query parameters', () async { - - }); + test('query parameters', () async {}); }); -} \ No newline at end of file +} diff --git a/test/typed_tests.dart b/test/typed_tests.dart index e69de29b..d29eaeae 100644 --- a/test/typed_tests.dart +++ b/test/typed_tests.dart @@ -0,0 +1,148 @@ +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mongo/angel_mongo.dart'; +import 'package:http/http.dart' as http; +import 'package:json_god/json_god.dart' as god; +import 'package:mongo_dart/mongo_dart.dart'; +import 'package:test/test.dart'; + +class Greeting extends Model { + String to; + + Greeting({String this.to}); +} + +final headers = { + HttpHeaders.ACCEPT: ContentType.JSON.mimeType, + HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType +}; + +final Map testGreeting = {'to': 'world'}; + +wireHooked(HookedService hooked) { + hooked + ..afterCreated.listen((HookedServiceEvent event) { + print("Just created: ${god.serialize(event.result)}"); + }) + ..afterModified.listen((HookedServiceEvent event) { + print("Just modified: ${god.serialize(event.result)}"); + }) + ..afterUpdated.listen((HookedServiceEvent event) { + print("Just updated: ${event.result}"); + }); +} + +main() { + group('angel_mongo', () { + Angel app = new Angel(); + http.Client client; + Db db = new Db('mongodb://localhost:27017/angel_mongo'); + DbCollection testData; + String url; + Service Greetings; + + setUp(() async { + client = new http.Client(); + await db.open(); + testData = db.collection('test_greetings'); + // Delete anything before we start + await testData.remove(); + + var service = new MongoTypedService(testData); + Greetings = new HookedService(service); + wireHooked(Greetings); + + app.use('/api', Greetings); + HttpServer server = + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + url = "http://${server.address.host}:${server.port}"; + }); + + tearDown(() async { + // Delete anything left over + await testData.remove(); + await db.close(); + await app.httpServer.close(force: true); + client = null; + url = null; + Greetings = null; + }); + + test('insert items', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + + response = await client.get("$url/api"); + expect(response.statusCode, 200); + List greetings = god.deserialize(response.body); + expect(greetings.length, equals(1)); + + Greeting greeting = new Greeting(to: "Mom"); + await Greetings.create(greeting); + greetings = await (await testData.find()).toList(); + expect(greetings.length, equals(2)); + }); + + test('read item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + + response = await client.get("$url/api/${created['id']}"); + expect(response.statusCode, equals(HttpStatus.OK)); + Map read = god.deserialize(response.body); + expect(read['id'], equals(created['id'])); + expect(read['to'], equals('world')); + expect(read['createdAt'], isNot(null)); + + Greeting greeting = await Greetings.read(created['id']); + expect(greeting.id, equals(created['id'])); + expect(greeting.to, equals('world')); + expect(greeting.createdAt, isNot(null)); + }); + + test('modify item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + + response = await client.patch( + "$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), + headers: headers); + Map modified = god.deserialize(response.body); + expect(response.statusCode, equals(HttpStatus.OK)); + expect(modified['id'], equals(created['id'])); + expect(modified['to'], equals('Mom')); + expect(modified['updatedAt'], isNot(null)); + + await Greetings.modify(created['id'], {"to": "Batman"}); + Greeting greeting = await Greetings.read(created['id']); + expect(greeting.to, equals("Batman")); + }); + + test('update item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, equals(HttpStatus.OK)); + Map created = god.deserialize(response.body); + + response = await client.post( + "$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), + headers: headers); + Map modified = god.deserialize(response.body); + expect(response.statusCode, equals(HttpStatus.OK)); + expect(modified['id'], equals(created['id'])); + expect(modified['to'], equals('Updated')); + expect(modified['updatedAt'], isNot(null)); + }); + + test('remove item', () async {}); + + test(r'$sort', () async {}); + + test('query parameters', () async {}); + }); +} From 71fe379db94d06def761c349fe982435b96d0d60 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 23 Jun 2016 00:58:21 -0400 Subject: [PATCH 05/37] Functionality done --- README.md | 33 +++++++++++++++++++++++++++++++++ lib/angel_mongo.dart | 12 +++++++----- lib/mongo_service.dart | 19 +++++++++++-------- lib/mongo_service_typed.dart | 34 +++++++++++++++++++--------------- test/generic_tests.dart | 27 ++++++++++++++++++--------- test/packages | 1 + test/typed_tests.dart | 30 ++++++++++++++++++++++-------- 7 files changed, 111 insertions(+), 45 deletions(-) create mode 120000 test/packages diff --git a/README.md b/README.md index d4df454c..a29d9a53 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ # angel_mongo + +![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) + MongoDB-enabled services for the Angel framework. + +# Installation +Add the following to your `pubspec.yaml`: + +```yaml +dependencies: + angel_mongo: ^1.0.0-dev +``` + +# Usage +This library exposes three main classes: `Model`, `MongoService` and `MongoTypedService`. + +## Model +`Model` is class with no real functionality; however, it represents a basic MongoDB document, and your services should host inherited classes. + +```dart +@Hooked() +class User extends Model { + String username; + String password; +} +``` + +## MongoService +This class interacts with a `DbCollection` (from mongo_dart) and serializing data to and from Maps. + +## MongoTypedService +Does the same as above, but serializes to and from a target class using json_god and its support for reflection. + +See the tests for more usage examples. diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart index 95808b28..2e6f1b88 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -23,7 +23,6 @@ class Model { DateTime updatedAt; } - Map _transformId(Map doc) { Map result = mergeMap([doc]); result['id'] = doc['_id']; @@ -33,8 +32,8 @@ Map _transformId(Map doc) { _lastItem(DbCollection collection, Function _jsonify, [Map params]) async { return (await (await collection - .find(where.sortBy('\$natural', descending: true))) - .toList()) + .find(where.sortBy('\$natural', descending: true))) + .toList()) .map((x) => _jsonify(x, params)) .first; } @@ -48,8 +47,11 @@ ObjectId _makeId(id) { } Map _removeSensitive(Map data) { - return data..remove('id')..remove('_id')..remove('createdAt')..remove( - 'updatedAt'); + return data + ..remove('id') + ..remove('_id') + ..remove('createdAt') + ..remove('updatedAt'); } SelectorBuilder _makeQuery([Map params_]) { diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index cf0f071e..4d0fd33e 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -4,14 +4,7 @@ part of angel_mongo; class MongoService extends Service { DbCollection collection; - MongoService(DbCollection this.collection):super(); - - Map _transformId(Map doc) { - Map result = mergeMap([doc]); - result['id'] = doc['_id']; - - return result..remove('_id'); - } + MongoService(DbCollection this.collection) : super(); _jsonify(Map doc, [Map params]) { Map result = {}; @@ -123,5 +116,15 @@ class MongoService extends Service { } } + @override + Future remove(id, [Map params]) async { + Map result = await read(id, params); + try { + await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); + return result; + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } } diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index c4a11ec9..4a995ad1 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -4,19 +4,12 @@ part of angel_mongo; class MongoTypedService extends Service { DbCollection collection; - MongoTypedService(DbCollection this.collection):super() { + MongoTypedService(DbCollection this.collection) : super() { if (!reflectType(T).isAssignableTo(reflectType(Model))) throw new Exception( "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); } - Map _transformId(Map doc) { - Map result = mergeMap([doc]); - result['id'] = doc['_id']; - - return result..remove('_id'); - } - _jsonify(Map doc, [Map params]) { Map result = {}; for (var key in doc.keys) { @@ -31,8 +24,7 @@ class MongoTypedService extends Service { // Clients will always receive JSON. if ((params != null && params['provider'] != null)) { return result; - } - else { + } else { // However, when we run server-side, we should return a T, not a Map. Model typedResult = god.deserializeDatum(result, outputType: T); typedResult.createdAt = result['createdAt']; @@ -54,7 +46,7 @@ class MongoTypedService extends Service { try { Model target = - (data is T) ? data : god.deserializeDatum(data, outputType: T); + (data is T) ? data : god.deserializeDatum(data, outputType: T); item = god.serializeObject(target); item = _removeSensitive(item); @@ -86,8 +78,8 @@ class MongoTypedService extends Service { Future modify(id, Map data, [Map params]) async { ObjectId _id = _makeId(id); try { - Map result = await collection.findOne( - where.id(_id).and(_makeQuery(params))); + Map result = + await collection.findOne(where.id(_id).and(_makeQuery(params))); if (result == null) { throw new AngelHttpException.NotFound( @@ -108,8 +100,8 @@ class MongoTypedService extends Service { @override Future update(id, _data, [Map params]) async { try { - Model data = (_data is T) ? _data : god.deserializeDatum( - _data, outputType: T); + Model data = + (_data is T) ? _data : god.deserializeDatum(_data, outputType: T); ObjectId _id = _makeId(id); Map rawData = _removeSensitive(god.serializeObject(data)); rawData['_id'] = _id; @@ -128,4 +120,16 @@ class MongoTypedService extends Service { throw new AngelHttpException(e, stackTrace: st); } } + + @override + Future remove(id, [Map params]) async { + Map result = await read(id, params); + + try { + await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); + return result; + } catch (e, st) { + throw new AngelHttpException(e, stackTrace: st); + } + } } diff --git a/test/generic_tests.dart b/test/generic_tests.dart index 4e346135..c9e4c97e 100644 --- a/test/generic_tests.dart +++ b/test/generic_tests.dart @@ -23,6 +23,9 @@ wireHooked(HookedService hooked) { }) ..afterUpdated.listen((HookedServiceEvent event) { print("Just updated: ${event.result}"); + }) + ..afterRemoved.listen((HookedServiceEvent event) { + print("Just removed: ${event.result}"); }); } @@ -48,7 +51,7 @@ main() { app.use('/api', Greetings); HttpServer server = - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; }); @@ -93,9 +96,8 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); Map created = god.deserialize(response.body); - response = await client.patch( - "$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), - headers: headers); + response = await client.patch("$url/api/${created['id']}", + body: god.serialize({"to": "Mom"}), headers: headers); Map modified = god.deserialize(response.body); expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); @@ -109,9 +111,8 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); Map created = god.deserialize(response.body); - response = await client.post( - "$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), - headers: headers); + response = await client.post("$url/api/${created['id']}", + body: god.serialize({"to": "Updated"}), headers: headers); Map modified = god.deserialize(response.body); expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); @@ -119,11 +120,19 @@ main() { expect(modified['updatedAt'], isNot(null)); }); - test('remove item', () async {}); + test('remove item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + Map created = god.deserialize(response.body); - test(r'$sort', () async { + int lastCount = (await Greetings.index()).length; + + await client.delete("$url/api/${created['id']}"); + expect((await Greetings.index()).length, equals(lastCount - 1)); }); + test(r'$sort', () async {}); + test('query parameters', () async {}); }); } diff --git a/test/packages b/test/packages new file mode 120000 index 00000000..a16c4050 --- /dev/null +++ b/test/packages @@ -0,0 +1 @@ +../packages \ No newline at end of file diff --git a/test/typed_tests.dart b/test/typed_tests.dart index d29eaeae..0f06f653 100644 --- a/test/typed_tests.dart +++ b/test/typed_tests.dart @@ -54,7 +54,7 @@ main() { app.use('/api', Greetings); HttpServer server = - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; }); @@ -109,9 +109,8 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); Map created = god.deserialize(response.body); - response = await client.patch( - "$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), - headers: headers); + response = await client.patch("$url/api/${created['id']}", + body: god.serialize({"to": "Mom"}), headers: headers); Map modified = god.deserialize(response.body); expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); @@ -129,9 +128,8 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); Map created = god.deserialize(response.body); - response = await client.post( - "$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), - headers: headers); + response = await client.post("$url/api/${created['id']}", + body: god.serialize({"to": "Updated"}), headers: headers); Map modified = god.deserialize(response.body); expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); @@ -139,7 +137,23 @@ main() { expect(modified['updatedAt'], isNot(null)); }); - test('remove item', () async {}); + test('remove item', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + Map created = god.deserialize(response.body); + + int lastCount = (await Greetings.index()).length; + + await client.delete("$url/api/${created['id']}"); + expect((await Greetings.index()).length, equals(lastCount - 1)); + + Greeting bernie = + await Greetings.create(new Greeting(to: "Bernie Sanders")); + lastCount = (await Greetings.index()).length; + + await Greetings.remove(bernie.id); + expect((await Greetings.index()).length, equals(lastCount - 1)); + }); test(r'$sort', () async {}); From 3b34578cb6aa224c7b991d2d0576f3aac877c30d Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 23 Jun 2016 15:59:10 -0400 Subject: [PATCH 06/37] Seems done for now --- README.md | 26 ++++++++++++++++++++++++++ lib/angel_mongo.dart | 9 ++++++--- lib/mongo_service.dart | 30 ------------------------------ test/generic_tests.dart | 33 ++++++++++++++++++++++++++++++--- test/typed_tests.dart | 35 ++++++++++++++++++++++++++++++++--- 5 files changed, 94 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index a29d9a53..54b80ed1 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,15 @@ class User extends Model { String username; String password; } + +Db db = new Db('mongodb://localhost:27017/local'); +await db.open(); + +app.use('/api/users', new MongoTypedService()); + +app.service('api/users').afterCreated.listen((HookedServiceEvent event) { + print("New user: ${event.result}"); +}); ``` ## MongoService @@ -32,4 +41,21 @@ This class interacts with a `DbCollection` (from mongo_dart) and serializing dat ## MongoTypedService Does the same as above, but serializes to and from a target class using json_god and its support for reflection. +## 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'. Thanks to body_parser, this +also works with numbers, and even Maps. + + /path/to/service?$sort=createdAt + /path/to/service?$sort.createdAt=1 + +The former will sort result in ascending order of creation, and so will the latter. + + List queried = await MyService.index({r"$query": where.id(new ObjectId.fromHexString("some hex string"}))); + +And, of course, you can use mongo_dart queries. Just pass it as `query` within `params`. + See the tests for more usage examples. diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart index 2e6f1b88..3924e2f6 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -60,11 +60,12 @@ SelectorBuilder _makeQuery([Map params_]) { SelectorBuilder result = where.exists('_id'); // You can pass a SelectorBuilder as 'query'; - if (params['query'] != null && params['query'] is SelectorBuilder) + if (params['query'] != null && params['query'] is SelectorBuilder) { return params['query']; + } for (var key in params.keys) { - if (key == r'$sort') { + if (key == r'$sort' || key == r'$query') { if (params[key] is Map) { // If they send a map, then we'll sort by every key in the map for (String fieldName in params[key].keys.where((x) => x is String)) { @@ -73,9 +74,11 @@ SelectorBuilder _makeQuery([Map params_]) { result = result.sortBy(fieldName, descending: sorter == -1); } else if (sorter is String) { result = result.sortBy(fieldName, descending: sorter == "-1"); + } else if (sorter is SelectorBuilder) { + result = result.and(sorter); } } - } else if (params[key] is String) { + } else if (params[key] is String && key == r'$sort') { // If they send just a string, then we'll sort // by that, ascending result = result.sortBy(params[key]); diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 4d0fd33e..b38a1515 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -18,36 +18,6 @@ class MongoService extends Service { return _transformId(result); } - SelectorBuilder _makeQuery([Map params_]) { - Map params = params_ ?? {}; - params = params..remove('provider'); - SelectorBuilder result = where.exists('_id'); - - for (var key in params.keys) { - if (key == r'$sort') { - if (params[key] is Map) { - // If they send a map, then we'll sort by every key in the map - for (String fieldName in params[key].keys.where((x) => x is String)) { - var sorter = params[key][fieldName]; - if (sorter is num) { - result = result.sortBy(fieldName, descending: sorter == -1); - } else if (sorter is String) { - result = result.sortBy(fieldName, descending: sorter == "-1"); - } - } - } else if (params[key] is String) { - // If they send just a string, then we'll sort - // by that, ascending - result = result.sortBy(params[key]); - } - } else if (key is String) { - result = result.and(where.eq(key, params[key])); - } - } - - return result; - } - @override Future index([Map params]) async { return await (await collection.find(_makeQuery(params))) diff --git a/test/generic_tests.dart b/test/generic_tests.dart index c9e4c97e..539926e3 100644 --- a/test/generic_tests.dart +++ b/test/generic_tests.dart @@ -30,7 +30,7 @@ wireHooked(HookedService hooked) { } main() { - group('angel_mongo', () { + group('Generic Tests', () { Angel app = new Angel(); http.Client client; Db db = new Db('mongodb://localhost:27017/angel_mongo'); @@ -131,8 +131,35 @@ main() { expect((await Greetings.index()).length, equals(lastCount - 1)); }); - test(r'$sort', () async {}); + test('\$sort and query parameters', () async { + // Search by where.eq + Map world = await Greetings.create({"to": "world"}); + Map Mom = await Greetings.create({"to": "Mom"}); + Map Updated = await Greetings.create({"to": "Updated"}); - test('query parameters', () async {}); + var response = await client.get("$url/api?to=world"); + print(response.body); + List queried = god.deserialize(response.body); + expect(queried.length, equals(1)); + expect(queried[0].keys.length, equals(3)); + expect(queried[0]["id"], equals(world["id"])); + expect(queried[0]["to"], equals(world["to"])); + expect(queried[0]["createdAt"], + equals(world["createdAt"].toIso8601String())); + + response = await client.get("$url/api?\$sort.createdAt=-1"); + print(response.body); + queried = god.deserialize(response.body); + expect(queried[0]["id"], equals(Updated["id"])); + expect(queried[1]["id"], equals(Mom["id"])); + expect(queried[2]["id"], equals(world["id"])); + + queried = await Greetings.index({ + "\$query": {"_id": where.id(new ObjectId.fromHexString(world["id"]))} + }); + print(queried); + expect(queried.length, equals(1)); + expect(queried[0], equals(world)); + }); }); } diff --git a/test/typed_tests.dart b/test/typed_tests.dart index 0f06f653..db9a2614 100644 --- a/test/typed_tests.dart +++ b/test/typed_tests.dart @@ -33,7 +33,7 @@ wireHooked(HookedService hooked) { } main() { - group('angel_mongo', () { + group('Typed Tests', () { Angel app = new Angel(); http.Client client; Db db = new Db('mongodb://localhost:27017/angel_mongo'); @@ -155,8 +155,37 @@ main() { expect((await Greetings.index()).length, equals(lastCount - 1)); }); - test(r'$sort', () async {}); + test('\$sort and query parameters', () async { + // Search by where.eq + Greeting world = await Greetings.create(new Greeting(to: "world")); + Greeting Mom = await Greetings.create(new Greeting(to: "Mom")); + Greeting Updated = await Greetings.create(new Greeting(to: "Updated")); - test('query parameters', () async {}); + var response = await client.get("$url/api?to=world"); + print(response.body); + List queried = god.deserialize(response.body); + expect(queried.length, equals(1)); + expect(queried[0].keys.length, equals(4)); + expect(queried[0]["id"], equals(world.id)); + expect(queried[0]["to"], equals(world.to)); + expect( + queried[0]["createdAt"], equals(world.createdAt.toIso8601String())); + + response = await client.get("$url/api?\$sort.createdAt=-1"); + print(response.body); + queried = god.deserialize(response.body); + expect(queried[0]["id"], equals(Updated.id)); + expect(queried[1]["id"], equals(Mom.id)); + expect(queried[2]["id"], equals(world.id)); + + queried = await Greetings.index({ + "\$query": {"_id": where.id(new ObjectId.fromHexString(world.id))} + }); + print(queried.map(god.serialize).toList()); + expect(queried.length, equals(1)); + expect(queried[0].id, equals(world.id)); + expect(queried[0].to, equals(world.to)); + expect(queried[0].createdAt, equals(world.createdAt)); + }); }); } From 91b0b9df0e32462b6d2536c55eedd505d38984f9 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 23 Jun 2016 16:00:45 -0400 Subject: [PATCH 07/37] Fixed example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54b80ed1..e361f9cd 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ class User extends Model { Db db = new Db('mongodb://localhost:27017/local'); await db.open(); -app.use('/api/users', new MongoTypedService()); +app.use('/api/users', new MongoTypedService(db.collection("users"))); app.service('api/users').afterCreated.listen((HookedServiceEvent event) { print("New user: ${event.result}"); From 43ef011025d6a47010ecb4ffb1a247a1840a9954 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Thu, 23 Jun 2016 16:04:10 -0400 Subject: [PATCH 08/37] . --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3c50d85f..6225bea7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: angel_mongo -version: 1.0.0-dev +version: 1.0.0-dev+1 description: Core libraries for the Angel framework. author: Tobe O -homepage: https://github.com/angel-dart/angel_framework +homepage: https://github.com/angel-dart/angel_mongo dependencies: angel_framework: ">=1.0.0-dev < 2.0.0" json_god: ">=2.0.0-beta <3.0.0" From 7fb59ee694e31bc50613bc90875e182304b0e560 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 21 Sep 2016 00:26:22 -0400 Subject: [PATCH 09/37] Isomorphic --- README.md | 19 ++++---- lib/angel_mongo.dart | 92 +----------------------------------- lib/model.dart | 13 +++++ lib/mongo_service.dart | 2 +- lib/mongo_service_typed.dart | 2 +- lib/services.dart | 81 +++++++++++++++++++++++++++++++ pubspec.yaml | 2 +- 7 files changed, 109 insertions(+), 102 deletions(-) create mode 100644 lib/model.dart create mode 100644 lib/services.dart diff --git a/README.md b/README.md index e361f9cd..eb3b6faa 100644 --- a/README.md +++ b/README.md @@ -19,20 +19,21 @@ This library exposes three main classes: `Model`, `MongoService` and `MongoTyped `Model` is class with no real functionality; however, it represents a basic MongoDB document, and your services should host inherited classes. ```dart -@Hooked() class User extends Model { String username; String password; } -Db db = new Db('mongodb://localhost:27017/local'); -await db.open(); - -app.use('/api/users', new MongoTypedService(db.collection("users"))); - -app.service('api/users').afterCreated.listen((HookedServiceEvent event) { - print("New user: ${event.result}"); -}); +main() async { + Db db = new Db('mongodb://localhost:27017/local'); + await db.open(); + + app.use('/api/users', new MongoTypedService(db.collection("users"))); + + app.service('api/users').afterCreated.listen((event) { + print("New user: ${event.result}"); + }); +} ``` ## MongoService diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart index 3924e2f6..654e4b6d 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -1,92 +1,4 @@ library angel_mongo; -import 'dart:async'; -import 'dart:mirrors'; -import 'package:angel_framework/angel_framework.dart'; -import 'package:json_god/json_god.dart' as god; -import 'package:merge_map/merge_map.dart'; -import 'package:mongo_dart/mongo_dart.dart'; - -part 'mongo_service.dart'; - -part 'mongo_service_typed.dart'; - -/// A data type that can be serialized to MongoDB. -class Model { - /// This instance's ID. - String id; - - /// The time at which this instance was created. - DateTime createdAt; - - /// The time at which this instance was last updated. - DateTime updatedAt; -} - -Map _transformId(Map doc) { - Map result = mergeMap([doc]); - result['id'] = doc['_id']; - - return result..remove('_id'); -} - -_lastItem(DbCollection collection, Function _jsonify, [Map params]) async { - return (await (await collection - .find(where.sortBy('\$natural', descending: true))) - .toList()) - .map((x) => _jsonify(x, params)) - .first; -} - -ObjectId _makeId(id) { - try { - return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString()); - } catch (e) { - throw new AngelHttpException.BadRequest(); - } -} - -Map _removeSensitive(Map data) { - return data - ..remove('id') - ..remove('_id') - ..remove('createdAt') - ..remove('updatedAt'); -} - -SelectorBuilder _makeQuery([Map params_]) { - Map params = params_ ?? {}; - params = params..remove('provider'); - SelectorBuilder result = where.exists('_id'); - - // You can pass a SelectorBuilder as 'query'; - if (params['query'] != null && params['query'] is SelectorBuilder) { - return params['query']; - } - - for (var key in params.keys) { - if (key == r'$sort' || key == r'$query') { - if (params[key] is Map) { - // If they send a map, then we'll sort by every key in the map - for (String fieldName in params[key].keys.where((x) => x is String)) { - var sorter = params[key][fieldName]; - if (sorter is num) { - result = result.sortBy(fieldName, descending: sorter == -1); - } else if (sorter is String) { - result = result.sortBy(fieldName, descending: sorter == "-1"); - } else if (sorter is SelectorBuilder) { - result = result.and(sorter); - } - } - } else if (params[key] is String && key == r'$sort') { - // If they send just a string, then we'll sort - // by that, ascending - result = result.sortBy(params[key]); - } - } else if (key is String) { - result = result.and(where.eq(key, params[key])); - } - } - - return result; -} +export 'model.dart'; +export 'services.dart'; \ No newline at end of file diff --git a/lib/model.dart b/lib/model.dart new file mode 100644 index 00000000..cf2d5152 --- /dev/null +++ b/lib/model.dart @@ -0,0 +1,13 @@ +library angel_mongo.model; + +/// A data type that can be serialized to MongoDB. +class Model { + /// This instance's ID. + String id; + + /// The time at which this instance was created. + DateTime createdAt; + + /// The time at which this instance was last updated. + DateTime updatedAt; +} \ No newline at end of file diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index b38a1515..b2d35308 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -1,4 +1,4 @@ -part of angel_mongo; +part of angel_mongo.services; /// Manipulates data from MongoDB as Maps. class MongoService extends Service { diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index 4a995ad1..736d3f17 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -1,4 +1,4 @@ -part of angel_mongo; +part of angel_mongo.services; /// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. class MongoTypedService extends Service { diff --git a/lib/services.dart b/lib/services.dart new file mode 100644 index 00000000..0b2f3cc5 --- /dev/null +++ b/lib/services.dart @@ -0,0 +1,81 @@ +library angel_mongo.services; + +import 'dart:async'; +import 'dart:mirrors'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:json_god/json_god.dart' as god; +import 'package:merge_map/merge_map.dart'; +import 'package:mongo_dart/mongo_dart.dart'; +import 'model.dart'; + +part 'mongo_service.dart'; + +part 'mongo_service_typed.dart'; + +Map _transformId(Map doc) { + Map result = mergeMap([doc]); + result['id'] = doc['_id']; + + return result..remove('_id'); +} + +_lastItem(DbCollection collection, Function _jsonify, [Map params]) async { + return (await (await collection + .find(where.sortBy('\$natural', descending: true))) + .toList()) + .map((x) => _jsonify(x, params)) + .first; +} + +ObjectId _makeId(id) { + try { + return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString()); + } catch (e) { + throw new AngelHttpException.BadRequest(); + } +} + +Map _removeSensitive(Map data) { + return data + ..remove('id') + ..remove('_id') + ..remove('createdAt') + ..remove('updatedAt'); +} + +SelectorBuilder _makeQuery([Map params_]) { + Map params = params_ ?? {}; + params = params..remove('provider'); + SelectorBuilder result = where.exists('_id'); + + // You can pass a SelectorBuilder as 'query'; + if (params['query'] != null && params['query'] is SelectorBuilder) { + return params['query']; + } + + for (var key in params.keys) { + if (key == r'$sort' || key == r'$query') { + if (params[key] is Map) { + // If they send a map, then we'll sort by every key in the map + for (String fieldName in params[key].keys.where((x) => x is String)) { + var sorter = params[key][fieldName]; + if (sorter is num) { + result = result.sortBy(fieldName, descending: sorter == -1); + } else if (sorter is String) { + result = result.sortBy(fieldName, descending: sorter == "-1"); + } else if (sorter is SelectorBuilder) { + result = result.and(sorter); + } + } + } else if (params[key] is String && key == r'$sort') { + // If they send just a string, then we'll sort + // by that, ascending + result = result.sortBy(params[key]); + } + } else if (key is String) { + result = result.and(where.eq(key, params[key])); + } + } + + return result; +} diff --git a/pubspec.yaml b/pubspec.yaml index 6225bea7..6d453fab 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0-dev+1 +version: 1.0.0-dev+2 description: Core libraries for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From fa7cc701bf8d056168b1e8a9295c864f0bda2f13 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 21 Sep 2016 00:28:35 -0400 Subject: [PATCH 10/37] desc --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 6d453fab..a7d63b20 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_mongo -version: 1.0.0-dev+2 -description: Core libraries for the Angel framework. +version: 1.0.0-dev+3 +description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo dependencies: From 84e63e8dc658aba4ad38ad8849e40d230a7eaef2 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Dec 2016 12:39:48 -0500 Subject: [PATCH 11/37] +4 --- .idea/runConfigurations/Generic_Tests.xml | 2 +- .idea/runConfigurations/Typed_Tests.xml | 2 +- .travis.yml | 1 + README.md | 3 ++- lib/mongo_service.dart | 14 +++++++++++++- lib/mongo_service_typed.dart | 16 +++++++++++++--- lib/services.dart | 1 + pubspec.yaml | 2 +- test/{generic_tests.dart => generic_test.dart} | 0 test/{typed_tests.dart => typed_test.dart} | 0 10 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 .travis.yml rename test/{generic_tests.dart => generic_test.dart} (100%) rename test/{typed_tests.dart => typed_test.dart} (100%) diff --git a/.idea/runConfigurations/Generic_Tests.xml b/.idea/runConfigurations/Generic_Tests.xml index afb55d6f..034d37e1 100644 --- a/.idea/runConfigurations/Generic_Tests.xml +++ b/.idea/runConfigurations/Generic_Tests.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/runConfigurations/Typed_Tests.xml b/.idea/runConfigurations/Typed_Tests.xml index ea002d9b..0f5d6a48 100644 --- a/.idea/runConfigurations/Typed_Tests.xml +++ b/.idea/runConfigurations/Typed_Tests.xml @@ -1,6 +1,6 @@ - \ No newline at end of file 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 diff --git a/README.md b/README.md index eb3b6faa..57ab7239 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # angel_mongo -![version 1.0.0-dev](https://img.shields.io/badge/version-1.0.0--dev-red.svg) +![version 1.0.0-dev+4](https://img.shields.io/badge/version-1.0.0--dev+4-red.svg) +![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index b2d35308..bfc7ffaa 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -3,8 +3,9 @@ part of angel_mongo.services; /// Manipulates data from MongoDB as Maps. class MongoService extends Service { DbCollection collection; + bool debug; - MongoService(DbCollection this.collection) : super(); + MongoService(DbCollection this.collection, {this.debug: true}) : super(); _jsonify(Map doc, [Map params]) { Map result = {}; @@ -18,6 +19,13 @@ class MongoService extends Service { return _transformId(result); } + void log(e, st, msg) { + if (debug) { + stderr.writeln('$msg ERROR: $e'); + stderr.writeln(st); + } + } + @override Future index([Map params]) async { return await (await collection.find(_makeQuery(params))) @@ -35,6 +43,7 @@ class MongoService extends Service { await collection.insert(item); return await _lastItem(collection, _jsonify, params); } catch (e, st) { + log(e, st, 'CREATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -64,6 +73,7 @@ class MongoService extends Service { result['id'] = id; return result; } catch (e, st) { + log(e, st, 'MODIFY'); throw new AngelHttpException(e, stackTrace: st); } } @@ -82,6 +92,7 @@ class MongoService extends Service { result['id'] = id; return result; } catch (e, st) { + log(e, st); throw new AngelHttpException(e, stackTrace: st); } } @@ -94,6 +105,7 @@ class MongoService extends Service { await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); return result; } catch (e, st) { + log(e, st, 'REMOVE'); throw new AngelHttpException(e, stackTrace: st); } } diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index 736d3f17..ecb02229 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -3,8 +3,9 @@ part of angel_mongo.services; /// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. class MongoTypedService extends Service { DbCollection collection; + bool debug; - MongoTypedService(DbCollection this.collection) : super() { + MongoTypedService(DbCollection this.collection, {this.debug: true}) : super() { if (!reflectType(T).isAssignableTo(reflectType(Model))) throw new Exception( "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); @@ -33,6 +34,13 @@ class MongoTypedService extends Service { } } + void log(e, st, msg) { + if (debug) { + stderr.writeln('$msg ERROR: $e'); + stderr.writeln(st); + } + } + @override Future index([Map params]) async { return await (await collection.find(_makeQuery(params))) @@ -54,8 +62,7 @@ class MongoTypedService extends Service { await collection.insert(item); return await _lastItem(collection, _jsonify, params); } catch (e, st) { - print(e); - print(st); + log(e, st, 'CREATE'); throw new AngelHttpException.BadRequest(); } } @@ -93,6 +100,7 @@ class MongoTypedService extends Service { await collection.update(where.id(_id), result); return await read(_id, params); } catch (e, st) { + log(e, st, 'MODIFY'); throw new AngelHttpException(e, stackTrace: st); } } @@ -117,6 +125,7 @@ class MongoTypedService extends Service { } return result; } catch (e, st) { + log(e, st, 'UPDATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -129,6 +138,7 @@ class MongoTypedService extends Service { await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); return result; } catch (e, st) { + log(e, st, 'REMOVE'); throw new AngelHttpException(e, stackTrace: st); } } diff --git a/lib/services.dart b/lib/services.dart index 0b2f3cc5..fb70688f 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -1,6 +1,7 @@ library angel_mongo.services; import 'dart:async'; +import 'dart:io'; import 'dart:mirrors'; import 'package:angel_framework/angel_framework.dart'; import 'package:json_god/json_god.dart' as god; diff --git a/pubspec.yaml b/pubspec.yaml index a7d63b20..9ef7f0b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0-dev+3 +version: 1.0.0-dev+4 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_tests.dart b/test/generic_test.dart similarity index 100% rename from test/generic_tests.dart rename to test/generic_test.dart diff --git a/test/typed_tests.dart b/test/typed_test.dart similarity index 100% rename from test/typed_tests.dart rename to test/typed_test.dart From c6e17a79692371b33460e8b6fbdb565b708c4354 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Dec 2016 12:45:40 -0500 Subject: [PATCH 12/37] +5 --- .idea/runConfigurations/All_Tests.xml | 7 +++++++ README.md | 2 +- lib/mongo_service_typed.dart | 2 +- pubspec.yaml | 2 +- test/packages | 1 - test/typed_test.dart | 2 ++ 6 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .idea/runConfigurations/All_Tests.xml delete mode 120000 test/packages diff --git a/.idea/runConfigurations/All_Tests.xml b/.idea/runConfigurations/All_Tests.xml new file mode 100644 index 00000000..ac11209e --- /dev/null +++ b/.idea/runConfigurations/All_Tests.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index 57ab7239..58c53166 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -![version 1.0.0-dev+4](https://img.shields.io/badge/version-1.0.0--dev+4-red.svg) +![version 1.0.0-dev+5](https://img.shields.io/badge/version-1.0.0--dev+5-red.svg) ![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index ecb02229..bb1e7812 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -132,7 +132,7 @@ class MongoTypedService extends Service { @override Future remove(id, [Map params]) async { - Map result = await read(id, params); + var result = await read(id, params); try { await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); diff --git a/pubspec.yaml b/pubspec.yaml index 9ef7f0b7..077dc845 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0-dev+4 +version: 1.0.0-dev+5 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/packages b/test/packages deleted file mode 120000 index a16c4050..00000000 --- a/test/packages +++ /dev/null @@ -1 +0,0 @@ -../packages \ No newline at end of file diff --git a/test/typed_test.dart b/test/typed_test.dart index db9a2614..f9948e66 100644 --- a/test/typed_test.dart +++ b/test/typed_test.dart @@ -151,8 +151,10 @@ main() { await Greetings.create(new Greeting(to: "Bernie Sanders")); lastCount = (await Greetings.index()).length; + print('b'); await Greetings.remove(bernie.id); expect((await Greetings.index()).length, equals(lastCount - 1)); + print('c'); }); test('\$sort and query parameters', () async { From 48dab69db70673e7879769122eecb2e794ccdd68 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Dec 2016 12:48:40 -0500 Subject: [PATCH 13/37] Updated .travis.yml --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index de2210c9..cf32cda7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,3 @@ -language: dart \ No newline at end of file +language: dart +services: + - mongodb \ No newline at end of file From c892ccbd09ec29427f827bff92181ee6cbbb15f1 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sat, 3 Dec 2016 13:20:36 -0500 Subject: [PATCH 14/37] +6 --- README.md | 2 +- lib/mongo_service.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 58c53166..d771798a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -![version 1.0.0-dev+5](https://img.shields.io/badge/version-1.0.0--dev+5-red.svg) +![version 1.0.0-dev+6](https://img.shields.io/badge/version-1.0.0--dev+6-red.svg) ![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index bfc7ffaa..fb9f4520 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -92,7 +92,7 @@ class MongoService extends Service { result['id'] = id; return result; } catch (e, st) { - log(e, st); + log(e, st, 'UPDATE'); throw new AngelHttpException(e, stackTrace: st); } } diff --git a/pubspec.yaml b/pubspec.yaml index 077dc845..cb2b19a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0-dev+5 +version: 1.0.0-dev+6 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From 5f5c00352ceb4d5f60f2e864997853fd38c8fef1 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 12 Feb 2017 20:38:24 -0500 Subject: [PATCH 15/37] Broken --- .idea/angel_mongo.iml | 18 ++++ .idea/modules.xml | 8 ++ README.md | 6 +- lib/angel_mongo.dart | 1 - lib/model.dart | 3 +- lib/mongo_service.dart | 40 ++++---- lib/mongo_service_typed.dart | 154 +++++++------------------------ lib/mongo_service_typed.old.dart | 145 +++++++++++++++++++++++++++++ lib/services.dart | 53 ++++++++--- pubspec.yaml | 2 +- test/generic_test.dart | 39 ++++---- test/typed_test.dart | 65 +++++++------ 12 files changed, 331 insertions(+), 203 deletions(-) create mode 100644 .idea/angel_mongo.iml create mode 100644 .idea/modules.xml create mode 100644 lib/mongo_service_typed.old.dart diff --git a/.idea/angel_mongo.iml b/.idea/angel_mongo.iml new file mode 100644 index 00000000..a928d34a --- /dev/null +++ b/.idea/angel_mongo.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..571c4566 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index d771798a..3fc5f217 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,7 @@ 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'. Thanks to body_parser, this -also works with numbers, and even Maps. - - /path/to/service?$sort=createdAt - /path/to/service?$sort.createdAt=1 +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. diff --git a/lib/angel_mongo.dart b/lib/angel_mongo.dart index 654e4b6d..91f85241 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -1,4 +1,3 @@ library angel_mongo; -export 'model.dart'; export 'services.dart'; \ No newline at end of file diff --git a/lib/model.dart b/lib/model.dart index cf2d5152..84c81e6a 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -1,6 +1,7 @@ library angel_mongo.model; -/// A data type that can be serialized to MongoDB. +/// Use the `Model` class defined in `package:angel_framework/common.dart` instead. +@deprecated class Model { /// This instance's ID. String id; diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index fb9f4520..c836cf40 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -3,23 +3,26 @@ part of angel_mongo.services; /// Manipulates data from MongoDB as Maps. class MongoService extends Service { DbCollection collection; - bool debug; + final bool debug; MongoService(DbCollection this.collection, {this.debug: true}) : super(); _jsonify(Map doc, [Map params]) { Map result = {}; + for (var key in doc.keys) { - if (doc[key] is ObjectId) { - result[key] = doc[key].toHexString(); - } else - result[key] = doc[key]; + var value = doc[key]; + if (value is ObjectId) { + result[key] = value.toHexString(); + } else if (value is! RequestContext && value is! ResponseContext) { + result[key] = value; + } } return _transformId(result); } - void log(e, st, msg) { + void printDebug(e, st, msg) { if (debug) { stderr.writeln('$msg ERROR: $e'); stderr.writeln(st); @@ -34,7 +37,7 @@ class MongoService extends Service { } @override - Future create(Map data, [Map params]) async { + Future create(data, [Map params]) async { Map item = (data is Map) ? data : god.serializeObject(data); item = _removeSensitive(item); @@ -43,7 +46,7 @@ class MongoService extends Service { await collection.insert(item); return await _lastItem(collection, _jsonify, params); } catch (e, st) { - log(e, st, 'CREATE'); + printDebug(e, st, 'CREATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -54,7 +57,7 @@ class MongoService extends Service { Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); if (found == null) { - throw new AngelHttpException.NotFound( + throw new AngelHttpException.notFound( message: 'No record found for ID ${_id.toHexString()}'); } @@ -62,9 +65,12 @@ class MongoService extends Service { } @override - Future modify(id, Map data, [Map params]) async { - Map target = await read(id, params); - Map result = mergeMap([target, _removeSensitive(data)]); + Future modify(id, data, [Map params]) async { + var target = await read(id, params); + Map result = mergeMap([ + target is Map ? target : god.serializeObject(target), + _removeSensitive(data) + ]); result['updatedAt'] = new DateTime.now(); try { @@ -73,17 +79,17 @@ class MongoService extends Service { result['id'] = id; return result; } catch (e, st) { - log(e, st, 'MODIFY'); + printDebug(e, st, 'MODIFY'); throw new AngelHttpException(e, stackTrace: st); } } @override Future update(id, data, [Map params]) async { - Map target = await read(id, params); + var target = await read(id, params); Map result = _removeSensitive(data); result['_id'] = _makeId(id); - result['createdAt'] = target['createdAt']; + result['createdAt'] = target is Map ? ['createdAt'] : target.createdAt; result['updatedAt'] = new DateTime.now(); try { @@ -92,7 +98,7 @@ class MongoService extends Service { result['id'] = id; return result; } catch (e, st) { - log(e, st, 'UPDATE'); + printDebug(e, st, 'UPDATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -105,7 +111,7 @@ class MongoService extends Service { await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); return result; } catch (e, st) { - log(e, st, 'REMOVE'); + printDebug(e, st, 'REMOVE'); throw new AngelHttpException(e, stackTrace: st); } } diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index bb1e7812..09b232fe 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -1,145 +1,61 @@ part of angel_mongo.services; -/// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. -class MongoTypedService extends Service { - DbCollection collection; - bool debug; - - MongoTypedService(DbCollection this.collection, {this.debug: true}) : super() { +class MongoTypedService extends MongoService { + MongoTypedService(DbCollection collection, {bool debug}) + : super(collection, debug: debug == true) { if (!reflectType(T).isAssignableTo(reflectType(Model))) throw new Exception( - "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); + "If you specify a type for MongoService, it must extend Model."); } - _jsonify(Map doc, [Map params]) { - Map result = {}; - for (var key in doc.keys) { - if (doc[key] is ObjectId) { - result[key] = doc[key].toHexString(); - } else - result[key] = doc[key]; - } + _deserialize(x) { + if (x == dynamic || x == Object || x is T) + return x; + else if (x is Map) { + Map data = x.keys.fold({}, (map, key) { + var value = x[key]; - result = _transformId(result); + if ((key == 'createdAt' || key == 'updatedAt') && value is String) { + return map..[key] = '44'; // DateTime.parse(value).toIso8601String(); + } else + return map..[key] = value; + }); - // Clients will always receive JSON. - if ((params != null && params['provider'] != null)) { - return result; - } else { - // However, when we run server-side, we should return a T, not a Map. - Model typedResult = god.deserializeDatum(result, outputType: T); - typedResult.createdAt = result['createdAt']; - typedResult.updatedAt = result['updatedAt']; - return typedResult; - } + print('x: $x\ndata: $data'); + return god.deserializeDatum(data, outputType: T); + } else + return x; } - void log(e, st, msg) { - if (debug) { - stderr.writeln('$msg ERROR: $e'); - stderr.writeln(st); - } + _serialize(x) { + if (x is Model) + return god.serializeObject(x); + else + return x; } @override Future index([Map params]) async { - return await (await collection.find(_makeQuery(params))) - .map((x) => _jsonify(x, params)) - .toList(); + var result = await super.index(params); + return result.map(_deserialize).toList(); } @override - Future create(data, [Map params]) async { - Map item; - - try { - Model target = - (data is T) ? data : god.deserializeDatum(data, outputType: T); - item = god.serializeObject(target); - item = _removeSensitive(item); - - item['createdAt'] = new DateTime.now(); - await collection.insert(item); - return await _lastItem(collection, _jsonify, params); - } catch (e, st) { - log(e, st, 'CREATE'); - throw new AngelHttpException.BadRequest(); - } - } + Future create(data, [Map params]) => + super.create(_serialize(data), params).then(_deserialize); @override - Future read(id, [Map params]) async { - ObjectId _id = _makeId(id); - - Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); - - if (found == null) { - throw new AngelHttpException.NotFound( - message: 'No record found for ID ${_id.toHexString()}'); - } - - return _jsonify(found, params); - } + Future read(id, [Map params]) => super.read(id, params).then(_deserialize); @override - Future modify(id, Map data, [Map params]) async { - ObjectId _id = _makeId(id); - try { - Map result = - await collection.findOne(where.id(_id).and(_makeQuery(params))); - - if (result == null) { - throw new AngelHttpException.NotFound( - message: 'No record found for ID ${_id.toHexString()}'); - } - - result = mergeMap([result, _removeSensitive(data)]); - result['_id'] = _id; - result['updatedAt'] = new DateTime.now(); - - await collection.update(where.id(_id), result); - return await read(_id, params); - } catch (e, st) { - log(e, st, 'MODIFY'); - throw new AngelHttpException(e, stackTrace: st); - } - } + Future modify(id, data, [Map params]) => + super.modify(id, _serialize(data), params).then(_deserialize); @override - Future update(id, _data, [Map params]) async { - try { - Model data = - (_data is T) ? _data : god.deserializeDatum(_data, outputType: T); - ObjectId _id = _makeId(id); - Map rawData = _removeSensitive(god.serializeObject(data)); - rawData['_id'] = _id; - rawData['createdAt'] = data.createdAt; - rawData['updatedAt'] = new DateTime.now(); - - await collection.update(where.id(_id).and(_makeQuery(params)), rawData); - var result = _jsonify(rawData, params); - - if (result is T) { - result.createdAt = data.createdAt; - result.updatedAt = rawData['updatedAt']; - } - return result; - } catch (e, st) { - log(e, st, 'UPDATE'); - throw new AngelHttpException(e, stackTrace: st); - } - } + Future update(id, data, [Map params]) => + super.update(id, _serialize(data), params).then(_deserialize); @override - Future remove(id, [Map params]) async { - var result = await read(id, params); - - try { - await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); - return result; - } catch (e, st) { - log(e, st, 'REMOVE'); - throw new AngelHttpException(e, stackTrace: st); - } - } + Future remove(id, [Map params]) => + super.remove(id, params).then(_deserialize); } diff --git a/lib/mongo_service_typed.old.dart b/lib/mongo_service_typed.old.dart new file mode 100644 index 00000000..45cb155d --- /dev/null +++ b/lib/mongo_service_typed.old.dart @@ -0,0 +1,145 @@ +part of angel_mongo.services; + +/// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. +class MongoTypedService extends Service { + DbCollection collection; + bool debug; + + MongoTypedService(DbCollection this.collection, {this.debug: true}) : super() { + if (!reflectType(T).isAssignableTo(reflectType(Model))) + throw new Exception( + "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); + } + + _jsonify(Map doc, [Map params]) { + Map result = {}; + for (var key in doc.keys) { + if (doc[key] is ObjectId) { + result[key] = doc[key].toHexString(); + } else + result[key] = doc[key]; + } + + result = _transformId(result); + + // Clients will always receive JSON. + if ((params != null && params['provider'] != null)) { + return result; + } else { + // However, when we run server-side, we should return a T, not a Map. + Model typedResult = god.deserializeDatum(result, outputType: T); + typedResult.createdAt = result['createdAt']; + typedResult.updatedAt = result['updatedAt']; + return typedResult; + } + } + + void log(e, st, msg) { + if (debug) { + stderr.writeln('$msg ERROR: $e'); + stderr.writeln(st); + } + } + + @override + Future index([Map params]) async { + return await (await collection.find(_makeQuery(params))) + .map((x) => _jsonify(x, params)) + .toList(); + } + + @override + Future create(data, [Map params]) async { + Map item; + + try { + Model target = + (data is T) ? data : god.deserializeDatum(data, outputType: T); + item = god.serializeObject(target); + item = _removeSensitive(item); + + item['createdAt'] = new DateTime.now(); + await collection.insert(item); + return await _lastItem(collection, _jsonify, params); + } catch (e, st) { + log(e, st, 'CREATE'); + throw new AngelHttpException.badRequest(); + } + } + + @override + Future read(id, [Map params]) async { + ObjectId _id = _makeId(id); + + Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); + + if (found == null) { + throw new AngelHttpException.notFound( + message: 'No record found for ID ${_id.toHexString()}'); + } + + return _jsonify(found, params); + } + + @override + Future modify(id, Map data, [Map params]) async { + ObjectId _id = _makeId(id); + try { + Map result = + await collection.findOne(where.id(_id).and(_makeQuery(params))); + + if (result == null) { + throw new AngelHttpException.notFound( + message: 'No record found for ID ${_id.toHexString()}'); + } + + result = mergeMap([result, _removeSensitive(data)]); + result['_id'] = _id; + result['updatedAt'] = new DateTime.now(); + + await collection.update(where.id(_id), result); + return await read(_id, params); + } catch (e, st) { + log(e, st, 'MODIFY'); + throw new AngelHttpException(e, stackTrace: st); + } + } + + @override + Future update(id, _data, [Map params]) async { + try { + Model data = + (_data is T) ? _data : god.deserializeDatum(_data, outputType: T); + ObjectId _id = _makeId(id); + Map rawData = _removeSensitive(god.serializeObject(data)); + rawData['_id'] = _id; + rawData['createdAt'] = data.createdAt; + rawData['updatedAt'] = new DateTime.now(); + + await collection.update(where.id(_id).and(_makeQuery(params)), rawData); + var result = _jsonify(rawData, params); + + if (result is T) { + result.createdAt = data.createdAt; + result.updatedAt = rawData['updatedAt']; + } + return result; + } catch (e, st) { + log(e, st, 'UPDATE'); + throw new AngelHttpException(e, stackTrace: st); + } + } + + @override + Future remove(id, [Map params]) async { + var result = await read(id, params); + + try { + await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); + return result; + } catch (e, st) { + log(e, st, 'REMOVE'); + throw new AngelHttpException(e, stackTrace: st); + } + } +} diff --git a/lib/services.dart b/lib/services.dart index fb70688f..9c163224 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -4,10 +4,10 @@ import 'dart:async'; import 'dart:io'; import 'dart:mirrors'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/common.dart' show Model; import 'package:json_god/json_god.dart' as god; import 'package:merge_map/merge_map.dart'; import 'package:mongo_dart/mongo_dart.dart'; -import 'model.dart'; part 'mongo_service.dart'; @@ -15,15 +15,17 @@ part 'mongo_service_typed.dart'; Map _transformId(Map doc) { Map result = mergeMap([doc]); - result['id'] = doc['_id']; + result + ..['id'] = doc['_id'] + ..remove('_id'); - return result..remove('_id'); + return result; } _lastItem(DbCollection collection, Function _jsonify, [Map params]) async { return (await (await collection - .find(where.sortBy('\$natural', descending: true))) - .toList()) + .find(where.sortBy('\$natural', descending: true))) + .toList()) .map((x) => _jsonify(x, params)) .first; } @@ -32,25 +34,27 @@ ObjectId _makeId(id) { try { return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString()); } catch (e) { - throw new AngelHttpException.BadRequest(); + throw new AngelHttpException.badRequest(); } } +const List _SENSITIVE = const ['id', '_id', 'createdAt', 'updatedAt']; + Map _removeSensitive(Map data) { - return data - ..remove('id') - ..remove('_id') - ..remove('createdAt') - ..remove('updatedAt'); + return data.keys + .where((k) => !_SENSITIVE.contains(k)) + .fold({}, (map, key) => map..[key] = data[key]); } +const List _NO_QUERY = const ['__requestctx', '__responsectx']; + SelectorBuilder _makeQuery([Map params_]) { Map params = params_ ?? {}; params = params..remove('provider'); SelectorBuilder result = where.exists('_id'); // You can pass a SelectorBuilder as 'query'; - if (params['query'] != null && params['query'] is SelectorBuilder) { + if (params['query'] is SelectorBuilder) { return params['query']; } @@ -73,10 +77,31 @@ SelectorBuilder _makeQuery([Map params_]) { // by that, ascending result = result.sortBy(params[key]); } - } else if (key is String) { - result = result.and(where.eq(key, params[key])); + } else if (key == 'query') { + Map query = params[key]; + query.forEach((key, v) { + var value = v is Map ? _filterNoQuery(v) : v; + + if (!_NO_QUERY.contains(key) && + value is! RequestContext && + value is! ResponseContext) { + result = result.and(where.eq(key, value)); + } + }); } } return result; } + +Map _filterNoQuery(Map data) { + return data.keys.fold({}, (map, key) { + var value = data[key]; + + if (_NO_QUERY.contains(key) || + value is RequestContext || + value is ResponseContext) return map; + if (key is! Map) return map..[key] = value; + return map..[key] = _filterNoQuery(value); + }); +} diff --git a/pubspec.yaml b/pubspec.yaml index cb2b19a2..aa15a840 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0-dev+6 +version: 1.0.0 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_test.dart b/test/generic_test.dart index 539926e3..56e99df6 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -36,7 +36,7 @@ main() { Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; String url; - HookedService Greetings; + HookedService greetingService; setUp(() async { client = new http.Client(); @@ -45,13 +45,18 @@ main() { // Delete anything before we start await testData.remove(); - var service = new MongoService(testData); - Greetings = new HookedService(service); - wireHooked(Greetings); + var service = new MongoService(testData, debug: true); + greetingService = new HookedService(service); + wireHooked(greetingService); - app.use('/api', Greetings); - HttpServer server = - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + app.use('/api', greetingService); + + app.fatalErrorStream.listen((AngelFatalError e) { + print('Fatal error: ${e.error}'); + print(e.stack); + }); + + var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; }); @@ -62,7 +67,7 @@ main() { await app.httpServer.close(force: true); client = null; url = null; - Greetings = null; + greetingService = null; }); test('insert items', () async { @@ -125,17 +130,17 @@ main() { body: god.serialize(testGreeting), headers: headers); Map created = god.deserialize(response.body); - int lastCount = (await Greetings.index()).length; + int lastCount = (await greetingService.index()).length; await client.delete("$url/api/${created['id']}"); - expect((await Greetings.index()).length, equals(lastCount - 1)); + expect((await greetingService.index()).length, equals(lastCount - 1)); }); test('\$sort and query parameters', () async { // Search by where.eq - Map world = await Greetings.create({"to": "world"}); - Map Mom = await Greetings.create({"to": "Mom"}); - Map Updated = await Greetings.create({"to": "Updated"}); + Map world = await greetingService.create({"to": "world"}); + await greetingService.create({"to": "Mom"}); + await greetingService.create({"to": "Updated"}); var response = await client.get("$url/api?to=world"); print(response.body); @@ -147,14 +152,14 @@ main() { expect(queried[0]["createdAt"], equals(world["createdAt"].toIso8601String())); - response = await client.get("$url/api?\$sort.createdAt=-1"); + /*response = await client.get("$url/api?\$sort.createdAt=-1"); print(response.body); queried = god.deserialize(response.body); expect(queried[0]["id"], equals(Updated["id"])); expect(queried[1]["id"], equals(Mom["id"])); - expect(queried[2]["id"], equals(world["id"])); - - queried = await Greetings.index({ + expect(queried[2]["id"], equals(world["id"]));*/ + + queried = await greetingService.index({ "\$query": {"_id": where.id(new ObjectId.fromHexString(world["id"]))} }); print(queried); diff --git a/test/typed_test.dart b/test/typed_test.dart index f9948e66..656c7e3b 100644 --- a/test/typed_test.dart +++ b/test/typed_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/common.dart' show Model; import 'package:angel_mongo/angel_mongo.dart'; import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; @@ -9,7 +10,8 @@ import 'package:test/test.dart'; class Greeting extends Model { String to; - Greeting({String this.to}); + Greeting({String id, this.to, DateTime createdAt, DateTime updatedAt}) + : super(id: id, createdAt: createdAt, updatedAt: updatedAt); } final headers = { @@ -39,7 +41,7 @@ main() { Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; String url; - Service Greetings; + Service greetingService; setUp(() async { client = new http.Client(); @@ -49,10 +51,16 @@ main() { await testData.remove(); var service = new MongoTypedService(testData); - Greetings = new HookedService(service); - wireHooked(Greetings); + greetingService = new HookedService(service); + wireHooked(greetingService); + + app.use('/api', greetingService); + + app.fatalErrorStream.listen((AngelFatalError e) { + print('Fatal error: ${e.error}'); + print(e.stack); + }); - app.use('/api', Greetings); HttpServer server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); url = "http://${server.address.host}:${server.port}"; @@ -65,7 +73,7 @@ main() { await app.httpServer.close(force: true); client = null; url = null; - Greetings = null; + greetingService = null; }); test('insert items', () async { @@ -79,7 +87,7 @@ main() { expect(greetings.length, equals(1)); Greeting greeting = new Greeting(to: "Mom"); - await Greetings.create(greeting); + await greetingService.create(greeting); greetings = await (await testData.find()).toList(); expect(greetings.length, equals(2)); }); @@ -93,14 +101,15 @@ main() { response = await client.get("$url/api/${created['id']}"); expect(response.statusCode, equals(HttpStatus.OK)); Map read = god.deserialize(response.body); + print('Read: $read'); expect(read['id'], equals(created['id'])); expect(read['to'], equals('world')); - expect(read['createdAt'], isNot(null)); + expect(read['createdAt'], isNotNull); - Greeting greeting = await Greetings.read(created['id']); + Greeting greeting = await greetingService.read(created['id']); expect(greeting.id, equals(created['id'])); expect(greeting.to, equals('world')); - expect(greeting.createdAt, isNot(null)); + expect(greeting.createdAt, isNotNull); }); test('modify item', () async { @@ -115,10 +124,10 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Mom')); - expect(modified['updatedAt'], isNot(null)); + expect(modified['updatedAt'], isNotNull); - await Greetings.modify(created['id'], {"to": "Batman"}); - Greeting greeting = await Greetings.read(created['id']); + await greetingService.modify(created['id'], {"to": "Batman"}); + Greeting greeting = await greetingService.read(created['id']); expect(greeting.to, equals("Batman")); }); @@ -134,7 +143,7 @@ main() { expect(response.statusCode, equals(HttpStatus.OK)); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Updated')); - expect(modified['updatedAt'], isNot(null)); + expect(modified['updatedAt'], isNotNull); }); test('remove item', () async { @@ -142,26 +151,26 @@ main() { body: god.serialize(testGreeting), headers: headers); Map created = god.deserialize(response.body); - int lastCount = (await Greetings.index()).length; + int lastCount = (await greetingService.index()).length; await client.delete("$url/api/${created['id']}"); - expect((await Greetings.index()).length, equals(lastCount - 1)); + expect((await greetingService.index()).length, equals(lastCount - 1)); Greeting bernie = - await Greetings.create(new Greeting(to: "Bernie Sanders")); - lastCount = (await Greetings.index()).length; + await greetingService.create(new Greeting(to: "Bernie Sanders")); + lastCount = (await greetingService.index()).length; print('b'); - await Greetings.remove(bernie.id); - expect((await Greetings.index()).length, equals(lastCount - 1)); + await greetingService.remove(bernie.id); + expect((await greetingService.index()).length, equals(lastCount - 1)); print('c'); }); - test('\$sort and query parameters', () async { + test('query parameters', () async { // Search by where.eq - Greeting world = await Greetings.create(new Greeting(to: "world")); - Greeting Mom = await Greetings.create(new Greeting(to: "Mom")); - Greeting Updated = await Greetings.create(new Greeting(to: "Updated")); + Greeting world = await greetingService.create(new Greeting(to: "world")); + await greetingService.create(new Greeting(to: "Mom")); + await greetingService.create(new Greeting(to: "Updated")); var response = await client.get("$url/api?to=world"); print(response.body); @@ -171,16 +180,16 @@ main() { expect(queried[0]["id"], equals(world.id)); expect(queried[0]["to"], equals(world.to)); expect( - queried[0]["createdAt"], equals(world.createdAt.toIso8601String())); + queried[0]["createdAt"], equals(world.createdAt?.toIso8601String())); - response = await client.get("$url/api?\$sort.createdAt=-1"); + /*response = await client.get("$url/api?\$sort.createdAt=-1"); print(response.body); queried = god.deserialize(response.body); expect(queried[0]["id"], equals(Updated.id)); expect(queried[1]["id"], equals(Mom.id)); - expect(queried[2]["id"], equals(world.id)); + expect(queried[2]["id"], equals(world.id));*/ - queried = await Greetings.index({ + queried = await greetingService.index({ "\$query": {"_id": where.id(new ObjectId.fromHexString(world.id))} }); print(queried.map(god.serialize).toList()); From 026d75cf7ab9619373165bbf7b940c640e8bca36 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 19 Feb 2017 07:32:21 -0500 Subject: [PATCH 16/37] 1.1.0 --- .vscode/tasks.json | 10 ++++++++++ README.md | 4 ++-- lib/mongo_service.dart | 15 ++++++++++----- lib/mongo_service_typed.dart | 30 +++++++++++++++++++++++++----- pubspec.yaml | 2 +- test/generic_test.dart | 3 +-- 6 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 .vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..6969f9b5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,10 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "0.1.0", + "command": "test", + "isShellCommand": true, + "args": ["pub", "run", "test"], + "showOutput": "always", + "suppressTaskName": true +} \ No newline at end of file diff --git a/README.md b/README.md index 3fc5f217..0a73a211 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # angel_mongo -![version 1.0.0-dev+6](https://img.shields.io/badge/version-1.0.0--dev+6-red.svg) -![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master) +[![version 1.1.0](https://img.shields.io/badge/pub-1.1.0-red.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index c836cf40..b8a8219c 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -42,7 +42,7 @@ class MongoService extends Service { item = _removeSensitive(item); try { - item['createdAt'] = new DateTime.now(); + item['createdAt'] = new DateTime.now().toIso8601String(); await collection.insert(item); return await _lastItem(collection, _jsonify, params); } catch (e, st) { @@ -71,7 +71,7 @@ class MongoService extends Service { target is Map ? target : god.serializeObject(target), _removeSensitive(data) ]); - result['updatedAt'] = new DateTime.now(); + result['updatedAt'] = new DateTime.now().toIso8601String(); try { await collection.update(where.id(_makeId(id)), result); @@ -89,8 +89,13 @@ class MongoService extends Service { var target = await read(id, params); Map result = _removeSensitive(data); result['_id'] = _makeId(id); - result['createdAt'] = target is Map ? ['createdAt'] : target.createdAt; - result['updatedAt'] = new DateTime.now(); + result['createdAt'] = + target is Map ? target['createdAt'] : target.createdAt; + + if (result['createdAt'] is DateTime) + result['createdAt'] = result['createdAt'].toIso8601String(); + + result['updatedAt'] = new DateTime.now().toIso8601String(); try { await collection.update(where.id(_makeId(id)), result); @@ -105,7 +110,7 @@ class MongoService extends Service { @override Future remove(id, [Map params]) async { - Map result = await read(id, params); + var result = await read(id, params); try { await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index 09b232fe..d1c2d00c 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -9,6 +9,7 @@ class MongoTypedService extends MongoService { } _deserialize(x) { + // print('DESERIALIZE: $x (${x.runtimeType})'); if (x == dynamic || x == Object || x is T) return x; else if (x is Map) { @@ -16,13 +17,30 @@ class MongoTypedService extends MongoService { var value = x[key]; if ((key == 'createdAt' || key == 'updatedAt') && value is String) { - return map..[key] = '44'; // DateTime.parse(value).toIso8601String(); - } else + return map..[key] = DateTime.parse(value).toIso8601String(); + } else if (value is DateTime) { + return map..[key] = value.toIso8601String(); + } else { return map..[key] = value; + } }); - print('x: $x\ndata: $data'); - return god.deserializeDatum(data, outputType: T); + Model result = god.deserializeDatum(data, outputType: T); + + if (x['createdAt'] is String) { + result.createdAt = DateTime.parse(x['createdAt']); + } else if (x['createdAt'] is DateTime) { + result.createdAt = x['createdAt']; + } + + if (x['updatedAt'] is String) { + result.updatedAt = DateTime.parse(x['updatedAt']); + } else if (x['updatedAt'] is DateTime) { + result.updatedAt = x['updatedAt']; + } + + // print('x: $x\nresult: $result'); + return result; } else return x; } @@ -30,8 +48,10 @@ class MongoTypedService extends MongoService { _serialize(x) { if (x is Model) return god.serializeObject(x); - else + else if (x is Map) return x; + else + throw new ArgumentError('Cannot serialize ${x.runtimeType}'); } @override diff --git a/pubspec.yaml b/pubspec.yaml index aa15a840..ba351e7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.0.0 +version: 1.1.0 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_test.dart b/test/generic_test.dart index 56e99df6..3adc3eaf 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -149,8 +149,7 @@ main() { expect(queried[0].keys.length, equals(3)); expect(queried[0]["id"], equals(world["id"])); expect(queried[0]["to"], equals(world["to"])); - expect(queried[0]["createdAt"], - equals(world["createdAt"].toIso8601String())); + expect(queried[0]["createdAt"], equals(world["createdAt"])); /*response = await client.get("$url/api?\$sort.createdAt=-1"); print(response.body); From d36312487f64e6927763f43f7a12b483a0641781 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 19 Feb 2017 07:32:43 -0500 Subject: [PATCH 17/37] README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a73a211..a3cb4fe3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.0](https://img.shields.io/badge/pub-1.1.0-red.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.0](https://img.shields.io/badge/pub-1.1.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. From 627e9545260e04cff0c3a98c957bf77eca814cd5 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 19 Feb 2017 07:33:33 -0500 Subject: [PATCH 18/37] Delete old --- lib/mongo_service_typed.old.dart | 145 ------------------------------- 1 file changed, 145 deletions(-) delete mode 100644 lib/mongo_service_typed.old.dart diff --git a/lib/mongo_service_typed.old.dart b/lib/mongo_service_typed.old.dart deleted file mode 100644 index 45cb155d..00000000 --- a/lib/mongo_service_typed.old.dart +++ /dev/null @@ -1,145 +0,0 @@ -part of angel_mongo.services; - -/// Manipulates data from MongoDB by serializing BSON from and deserializing BSON to a target class. -class MongoTypedService extends Service { - DbCollection collection; - bool debug; - - MongoTypedService(DbCollection this.collection, {this.debug: true}) : super() { - if (!reflectType(T).isAssignableTo(reflectType(Model))) - throw new Exception( - "If you specify a type for MongoService, it must be dynamic, Map, or extend from Model."); - } - - _jsonify(Map doc, [Map params]) { - Map result = {}; - for (var key in doc.keys) { - if (doc[key] is ObjectId) { - result[key] = doc[key].toHexString(); - } else - result[key] = doc[key]; - } - - result = _transformId(result); - - // Clients will always receive JSON. - if ((params != null && params['provider'] != null)) { - return result; - } else { - // However, when we run server-side, we should return a T, not a Map. - Model typedResult = god.deserializeDatum(result, outputType: T); - typedResult.createdAt = result['createdAt']; - typedResult.updatedAt = result['updatedAt']; - return typedResult; - } - } - - void log(e, st, msg) { - if (debug) { - stderr.writeln('$msg ERROR: $e'); - stderr.writeln(st); - } - } - - @override - Future index([Map params]) async { - return await (await collection.find(_makeQuery(params))) - .map((x) => _jsonify(x, params)) - .toList(); - } - - @override - Future create(data, [Map params]) async { - Map item; - - try { - Model target = - (data is T) ? data : god.deserializeDatum(data, outputType: T); - item = god.serializeObject(target); - item = _removeSensitive(item); - - item['createdAt'] = new DateTime.now(); - await collection.insert(item); - return await _lastItem(collection, _jsonify, params); - } catch (e, st) { - log(e, st, 'CREATE'); - throw new AngelHttpException.badRequest(); - } - } - - @override - Future read(id, [Map params]) async { - ObjectId _id = _makeId(id); - - Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); - - if (found == null) { - throw new AngelHttpException.notFound( - message: 'No record found for ID ${_id.toHexString()}'); - } - - return _jsonify(found, params); - } - - @override - Future modify(id, Map data, [Map params]) async { - ObjectId _id = _makeId(id); - try { - Map result = - await collection.findOne(where.id(_id).and(_makeQuery(params))); - - if (result == null) { - throw new AngelHttpException.notFound( - message: 'No record found for ID ${_id.toHexString()}'); - } - - result = mergeMap([result, _removeSensitive(data)]); - result['_id'] = _id; - result['updatedAt'] = new DateTime.now(); - - await collection.update(where.id(_id), result); - return await read(_id, params); - } catch (e, st) { - log(e, st, 'MODIFY'); - throw new AngelHttpException(e, stackTrace: st); - } - } - - @override - Future update(id, _data, [Map params]) async { - try { - Model data = - (_data is T) ? _data : god.deserializeDatum(_data, outputType: T); - ObjectId _id = _makeId(id); - Map rawData = _removeSensitive(god.serializeObject(data)); - rawData['_id'] = _id; - rawData['createdAt'] = data.createdAt; - rawData['updatedAt'] = new DateTime.now(); - - await collection.update(where.id(_id).and(_makeQuery(params)), rawData); - var result = _jsonify(rawData, params); - - if (result is T) { - result.createdAt = data.createdAt; - result.updatedAt = rawData['updatedAt']; - } - return result; - } catch (e, st) { - log(e, st, 'UPDATE'); - throw new AngelHttpException(e, stackTrace: st); - } - } - - @override - Future remove(id, [Map params]) async { - var result = await read(id, params); - - try { - await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); - return result; - } catch (e, st) { - log(e, st, 'REMOVE'); - throw new AngelHttpException(e, stackTrace: st); - } - } -} From c82febdf327ebe71017b4736732f2994d5e7f928 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 20 Feb 2017 11:00:42 -0500 Subject: [PATCH 19/37] 1.1.1 --- README.md | 9 +++++---- lib/mongo_service.dart | 17 ++++++++++++++++- pubspec.yaml | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a3cb4fe3..a15029d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.0](https://img.shields.io/badge/pub-1.1.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.1](https://img.shields.io/badge/pub-1.1.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. @@ -10,14 +10,15 @@ Add the following to your `pubspec.yaml`: ```yaml dependencies: - angel_mongo: ^1.0.0-dev + angel_mongo: ^1.0.0 ``` # Usage -This library exposes three main classes: `Model`, `MongoService` and `MongoTypedService`. +This library exposes two main classes: `MongoService` and `MongoTypedService`. ## Model -`Model` is class with no real functionality; however, it represents a basic MongoDB document, and your services should host inherited classes. +`Model` is class with no real functionality; however, it represents a basic document, and your services should host inherited classes. +Other Angel service providers host `Model` as well, so you will easily be able to modify your application if you ever switch databases. ```dart class User extends Model { diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index b8a8219c..6874c3d1 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -3,9 +3,16 @@ part of angel_mongo.services; /// Manipulates data from MongoDB as Maps. class MongoService extends Service { DbCollection collection; + + /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`. + /// + /// `false` by default. + final bool allowRemoveAll; final bool debug; - MongoService(DbCollection this.collection, {this.debug: true}) : super(); + MongoService(DbCollection this.collection, + {this.allowRemoveAll: false, this.debug: true}) + : super(); _jsonify(Map doc, [Map params]) { Map result = {}; @@ -110,6 +117,14 @@ class MongoService extends Service { @override Future remove(id, [Map params]) async { + if (id == null || + id == 'null' && + (allowRemoveAll == true || + params?.containsKey('provider') != true)) { + await collection.remove(null); + return {}; + } + var result = await read(id, params); try { diff --git a/pubspec.yaml b/pubspec.yaml index ba351e7c..e1b8d031 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.0 +version: 1.1.1 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From de9d9d39d20ad259cdc12902b5b126ccf6a8d4cc Mon Sep 17 00:00:00 2001 From: thosakwe Date: Mon, 20 Feb 2017 11:03:56 -0500 Subject: [PATCH 20/37] 1.1.2 --- README.md | 2 +- lib/mongo_service_typed.dart | 5 +++-- pubspec.yaml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a15029d8..24488af0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.1](https://img.shields.io/badge/pub-1.1.1-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.2](https://img.shields.io/badge/pub-1.1.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index d1c2d00c..bed64368 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -1,8 +1,9 @@ part of angel_mongo.services; class MongoTypedService extends MongoService { - MongoTypedService(DbCollection collection, {bool debug}) - : super(collection, debug: debug == true) { + MongoTypedService(DbCollection collection, {bool allowRemoveAll, bool debug}) + : super(collection, + allowRemoveAll: allowRemoveAll == true, debug: debug == true) { if (!reflectType(T).isAssignableTo(reflectType(Model))) throw new Exception( "If you specify a type for MongoService, it must extend Model."); diff --git a/pubspec.yaml b/pubspec.yaml index e1b8d031..13d0215c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.1 +version: 1.1.2 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From e4a697e9c7a593c228608124e26ac250f66af553 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 22 Feb 2017 20:00:25 -0500 Subject: [PATCH 21/37] 1.1.3 --- README.md | 9 +- lib/mongo_service_typed.dart | 2 + pubspec.yaml | 2 +- test/typed_test.dart | 202 ----------------------------------- 4 files changed, 6 insertions(+), 209 deletions(-) delete mode 100644 test/typed_test.dart diff --git a/README.md b/README.md index 24488af0..3f6697fa 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.2](https://img.shields.io/badge/pub-1.1.2-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.3](https://img.shields.io/badge/pub-1.1.3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. @@ -14,7 +14,7 @@ dependencies: ``` # Usage -This library exposes two main classes: `MongoService` and `MongoTypedService`. +This library exposes one main class: `MongoService`. ## Model `Model` is class with no real functionality; however, it represents a basic document, and your services should host inherited classes. @@ -30,7 +30,7 @@ main() async { Db db = new Db('mongodb://localhost:27017/local'); await db.open(); - app.use('/api/users', new MongoTypedService(db.collection("users"))); + app.use('/api/users', new TypedService(new MongoService(db.collection("users")))); app.service('api/users').afterCreated.listen((event) { print("New user: ${event.result}"); @@ -41,9 +41,6 @@ main() async { ## MongoService This class interacts with a `DbCollection` (from mongo_dart) and serializing data to and from Maps. -## MongoTypedService -Does the same as above, but serializes to and from a target class using json_god and its support for reflection. - ## Querying You can query these services as follows: diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart index bed64368..1948296d 100644 --- a/lib/mongo_service_typed.dart +++ b/lib/mongo_service_typed.dart @@ -1,5 +1,7 @@ part of angel_mongo.services; +/// Use a normal TypedService instead. +@deprecated class MongoTypedService extends MongoService { MongoTypedService(DbCollection collection, {bool allowRemoveAll, bool debug}) : super(collection, diff --git a/pubspec.yaml b/pubspec.yaml index 13d0215c..a74e091d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.2 +version: 1.1.3 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/typed_test.dart b/test/typed_test.dart deleted file mode 100644 index 656c7e3b..00000000 --- a/test/typed_test.dart +++ /dev/null @@ -1,202 +0,0 @@ -import 'dart:io'; -import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/common.dart' show Model; -import 'package:angel_mongo/angel_mongo.dart'; -import 'package:http/http.dart' as http; -import 'package:json_god/json_god.dart' as god; -import 'package:mongo_dart/mongo_dart.dart'; -import 'package:test/test.dart'; - -class Greeting extends Model { - String to; - - Greeting({String id, this.to, DateTime createdAt, DateTime updatedAt}) - : super(id: id, createdAt: createdAt, updatedAt: updatedAt); -} - -final headers = { - HttpHeaders.ACCEPT: ContentType.JSON.mimeType, - HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType -}; - -final Map testGreeting = {'to': 'world'}; - -wireHooked(HookedService hooked) { - hooked - ..afterCreated.listen((HookedServiceEvent event) { - print("Just created: ${god.serialize(event.result)}"); - }) - ..afterModified.listen((HookedServiceEvent event) { - print("Just modified: ${god.serialize(event.result)}"); - }) - ..afterUpdated.listen((HookedServiceEvent event) { - print("Just updated: ${event.result}"); - }); -} - -main() { - group('Typed Tests', () { - Angel app = new Angel(); - http.Client client; - Db db = new Db('mongodb://localhost:27017/angel_mongo'); - DbCollection testData; - String url; - Service greetingService; - - setUp(() async { - client = new http.Client(); - await db.open(); - testData = db.collection('test_greetings'); - // Delete anything before we start - await testData.remove(); - - var service = new MongoTypedService(testData); - greetingService = new HookedService(service); - wireHooked(greetingService); - - app.use('/api', greetingService); - - app.fatalErrorStream.listen((AngelFatalError e) { - print('Fatal error: ${e.error}'); - print(e.stack); - }); - - HttpServer server = - await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); - url = "http://${server.address.host}:${server.port}"; - }); - - tearDown(() async { - // Delete anything left over - await testData.remove(); - await db.close(); - await app.httpServer.close(force: true); - client = null; - url = null; - greetingService = null; - }); - - test('insert items', () async { - var response = await client.post("$url/api", - body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); - - response = await client.get("$url/api"); - expect(response.statusCode, 200); - List greetings = god.deserialize(response.body); - expect(greetings.length, equals(1)); - - Greeting greeting = new Greeting(to: "Mom"); - await greetingService.create(greeting); - greetings = await (await testData.find()).toList(); - expect(greetings.length, equals(2)); - }); - - test('read item', () async { - var response = await client.post("$url/api", - body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); - Map created = god.deserialize(response.body); - - response = await client.get("$url/api/${created['id']}"); - expect(response.statusCode, equals(HttpStatus.OK)); - Map read = god.deserialize(response.body); - print('Read: $read'); - expect(read['id'], equals(created['id'])); - expect(read['to'], equals('world')); - expect(read['createdAt'], isNotNull); - - Greeting greeting = await greetingService.read(created['id']); - expect(greeting.id, equals(created['id'])); - expect(greeting.to, equals('world')); - expect(greeting.createdAt, isNotNull); - }); - - test('modify item', () async { - var response = await client.post("$url/api", - body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); - Map created = god.deserialize(response.body); - - response = await client.patch("$url/api/${created['id']}", - body: god.serialize({"to": "Mom"}), headers: headers); - Map modified = god.deserialize(response.body); - expect(response.statusCode, equals(HttpStatus.OK)); - expect(modified['id'], equals(created['id'])); - expect(modified['to'], equals('Mom')); - expect(modified['updatedAt'], isNotNull); - - await greetingService.modify(created['id'], {"to": "Batman"}); - Greeting greeting = await greetingService.read(created['id']); - expect(greeting.to, equals("Batman")); - }); - - test('update item', () async { - var response = await client.post("$url/api", - body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); - Map created = god.deserialize(response.body); - - response = await client.post("$url/api/${created['id']}", - body: god.serialize({"to": "Updated"}), headers: headers); - Map modified = god.deserialize(response.body); - expect(response.statusCode, equals(HttpStatus.OK)); - expect(modified['id'], equals(created['id'])); - expect(modified['to'], equals('Updated')); - expect(modified['updatedAt'], isNotNull); - }); - - test('remove item', () async { - var response = await client.post("$url/api", - body: god.serialize(testGreeting), headers: headers); - Map created = god.deserialize(response.body); - - int lastCount = (await greetingService.index()).length; - - await client.delete("$url/api/${created['id']}"); - expect((await greetingService.index()).length, equals(lastCount - 1)); - - Greeting bernie = - await greetingService.create(new Greeting(to: "Bernie Sanders")); - lastCount = (await greetingService.index()).length; - - print('b'); - await greetingService.remove(bernie.id); - expect((await greetingService.index()).length, equals(lastCount - 1)); - print('c'); - }); - - test('query parameters', () async { - // Search by where.eq - Greeting world = await greetingService.create(new Greeting(to: "world")); - await greetingService.create(new Greeting(to: "Mom")); - await greetingService.create(new Greeting(to: "Updated")); - - var response = await client.get("$url/api?to=world"); - print(response.body); - List queried = god.deserialize(response.body); - expect(queried.length, equals(1)); - expect(queried[0].keys.length, equals(4)); - expect(queried[0]["id"], equals(world.id)); - expect(queried[0]["to"], equals(world.to)); - expect( - queried[0]["createdAt"], equals(world.createdAt?.toIso8601String())); - - /*response = await client.get("$url/api?\$sort.createdAt=-1"); - print(response.body); - queried = god.deserialize(response.body); - expect(queried[0]["id"], equals(Updated.id)); - expect(queried[1]["id"], equals(Mom.id)); - expect(queried[2]["id"], equals(world.id));*/ - - queried = await greetingService.index({ - "\$query": {"_id": where.id(new ObjectId.fromHexString(world.id))} - }); - print(queried.map(god.serialize).toList()); - expect(queried.length, equals(1)); - expect(queried[0].id, equals(world.id)); - expect(queried[0].to, equals(world.to)); - expect(queried[0].createdAt, equals(world.createdAt)); - }); - }); -} From fbad320e831a37796d8a00d15c166c8e3c485a97 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Wed, 22 Feb 2017 20:03:30 -0500 Subject: [PATCH 22/37] 1.1.4 --- README.md | 2 +- lib/mongo_service.dart | 55 +++++++++++++++++++++++++++++++++++++++++- lib/services.dart | 46 ----------------------------------- pubspec.yaml | 2 +- 4 files changed, 56 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 3f6697fa..af5838d0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.3](https://img.shields.io/badge/pub-1.1.3-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.4](https://img.shields.io/badge/pub-1.1.4-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 6874c3d1..17dc115f 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -8,12 +8,65 @@ class MongoService extends Service { /// /// `false` by default. final bool allowRemoveAll; + + /// If set to `true`, parameters in `req.query` are applied to the database query. + final bool allowQuery; + final bool debug; MongoService(DbCollection this.collection, - {this.allowRemoveAll: false, this.debug: true}) + {this.allowRemoveAll: false, this.allowQuery: true, this.debug: true}) : super(); + SelectorBuilder _makeQuery([Map params_]) { + Map params = params_ ?? {}; + params = params..remove('provider'); + SelectorBuilder result = where.exists('_id'); + + // You can pass a SelectorBuilder as 'query'; + if (params['query'] is SelectorBuilder) { + return params['query']; + } + + for (var key in params.keys) { + if (key == r'$sort' || + key == r'$query' && + (allowQuery == true || !params.containsKey('provider'))) { + if (params[key] is Map) { + // If they send a map, then we'll sort by every key in the map + for (String fieldName in params[key].keys.where((x) => x is String)) { + var sorter = params[key][fieldName]; + if (sorter is num) { + result = result.sortBy(fieldName, descending: sorter == -1); + } else if (sorter is String) { + result = result.sortBy(fieldName, descending: sorter == "-1"); + } else if (sorter is SelectorBuilder) { + result = result.and(sorter); + } + } + } else if (params[key] is String && key == r'$sort') { + // If they send just a string, then we'll sort + // by that, ascending + result = result.sortBy(params[key]); + } + } else if (key == 'query' && + (allowQuery == true || !params.containsKey('provider'))) { + Map query = params[key]; + query.forEach((key, v) { + var value = v is Map ? _filterNoQuery(v) : v; + + if (!_NO_QUERY.contains(key) && + value is! RequestContext && + value is! ResponseContext) { + result = result.and(where.eq(key, value)); + } + }); + } + } + + return result; + } + _jsonify(Map doc, [Map params]) { Map result = {}; diff --git a/lib/services.dart b/lib/services.dart index 9c163224..d01a7976 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -48,52 +48,6 @@ Map _removeSensitive(Map data) { const List _NO_QUERY = const ['__requestctx', '__responsectx']; -SelectorBuilder _makeQuery([Map params_]) { - Map params = params_ ?? {}; - params = params..remove('provider'); - SelectorBuilder result = where.exists('_id'); - - // You can pass a SelectorBuilder as 'query'; - if (params['query'] is SelectorBuilder) { - return params['query']; - } - - for (var key in params.keys) { - if (key == r'$sort' || key == r'$query') { - if (params[key] is Map) { - // If they send a map, then we'll sort by every key in the map - for (String fieldName in params[key].keys.where((x) => x is String)) { - var sorter = params[key][fieldName]; - if (sorter is num) { - result = result.sortBy(fieldName, descending: sorter == -1); - } else if (sorter is String) { - result = result.sortBy(fieldName, descending: sorter == "-1"); - } else if (sorter is SelectorBuilder) { - result = result.and(sorter); - } - } - } else if (params[key] is String && key == r'$sort') { - // If they send just a string, then we'll sort - // by that, ascending - result = result.sortBy(params[key]); - } - } else if (key == 'query') { - Map query = params[key]; - query.forEach((key, v) { - var value = v is Map ? _filterNoQuery(v) : v; - - if (!_NO_QUERY.contains(key) && - value is! RequestContext && - value is! ResponseContext) { - result = result.and(where.eq(key, value)); - } - }); - } - } - - return result; -} - Map _filterNoQuery(Map data) { return data.keys.fold({}, (map, key) { var value = data[key]; diff --git a/pubspec.yaml b/pubspec.yaml index a74e091d..08e1c9f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.3 +version: 1.1.4 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From dff4f228c093541169281305ece5ca255bd73d39 Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 9 Apr 2017 22:28:29 -0400 Subject: [PATCH 23/37] 1.1.5 --- README.md | 2 +- lib/mongo_service.dart | 24 ++++++++++++++++++++++-- pubspec.yaml | 2 +- test/generic_test.dart | 16 ++++++++-------- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index af5838d0..4f3cab7f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # angel_mongo -[![version 1.1.4](https://img.shields.io/badge/pub-1.1.4-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![version 1.1.5](https://img.shields.io/badge/pub-1.1.5-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) [![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 17dc115f..4659a0eb 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -126,7 +126,17 @@ class MongoService extends Service { @override Future modify(id, data, [Map params]) async { - var target = await read(id, params); + var target; + + try { + target = await read(id, params); + } on AngelHttpException catch (e) { + if (e.statusCode == HttpStatus.NOT_FOUND) + return await create(data, params); + else + rethrow; + } + Map result = mergeMap([ target is Map ? target : god.serializeObject(target), _removeSensitive(data) @@ -146,7 +156,17 @@ class MongoService extends Service { @override Future update(id, data, [Map params]) async { - var target = await read(id, params); + var target; + + try { + target = await read(id, params); + } on AngelHttpException catch (e) { + if (e.statusCode == HttpStatus.NOT_FOUND) + return await create(data, params); + else + rethrow; + } + Map result = _removeSensitive(data); result['_id'] = _makeId(id); result['createdAt'] = diff --git a/pubspec.yaml b/pubspec.yaml index 08e1c9f3..3798e5db 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.4 +version: 1.1.5 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_test.dart b/test/generic_test.dart index 3adc3eaf..aecc6cbd 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -73,10 +73,10 @@ main() { test('insert items', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); response = await client.get("$url/api"); - expect(response.statusCode, 200); + expect(response.statusCode, isIn([200, 201])); List users = god.deserialize(response.body); expect(users.length, equals(1)); }); @@ -84,11 +84,11 @@ main() { test('read item', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); Map created = god.deserialize(response.body); response = await client.get("$url/api/${created['id']}"); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); Map read = god.deserialize(response.body); expect(read['id'], equals(created['id'])); expect(read['to'], equals('world')); @@ -98,13 +98,13 @@ main() { test('modify item', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); Map created = god.deserialize(response.body); response = await client.patch("$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), headers: headers); Map modified = god.deserialize(response.body); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Mom')); expect(modified['updatedAt'], isNot(null)); @@ -113,13 +113,13 @@ main() { test('update item', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); Map created = god.deserialize(response.body); response = await client.post("$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), headers: headers); Map modified = god.deserialize(response.body); - expect(response.statusCode, equals(HttpStatus.OK)); + expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Updated')); expect(modified['updatedAt'], isNot(null)); From d52fd36e45b788b28dbaa4cd4216a15405040bba Mon Sep 17 00:00:00 2001 From: thosakwe Date: Sun, 9 Jul 2017 14:07:02 -0400 Subject: [PATCH 24/37] Friendlier to concurrency --- .idea/angel_mongo.iml | 2 +- README.md | 4 +-- lib/mongo_service.dart | 69 +++++++++++++++++++----------------------- lib/services.dart | 2 +- pubspec.yaml | 2 +- test/generic_test.dart | 27 ++++++----------- 6 files changed, 45 insertions(+), 61 deletions(-) diff --git a/.idea/angel_mongo.iml b/.idea/angel_mongo.iml index a928d34a..02bd9dfb 100644 --- a/.idea/angel_mongo.iml +++ b/.idea/angel_mongo.iml @@ -12,7 +12,7 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 4f3cab7f..45e8fe19 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # angel_mongo -[![version 1.1.5](https://img.shields.io/badge/pub-1.1.5-brightgreen.svg)](https://pub.dartlang.org/packages/angel_mongo) -[![build status](https://travis-ci.org/angel-dart/mongo.svg?branch=master)](https://travis-ci.org/angel-dart/mongo) +[![Pub](https://img.shields.io/pub/v/angel_mongo.svg)](https://pub.dartlang.org/packages/angel_mongo) +[![build status](https://travis-ci.org/angel-dart/mongo.svg)](https://travis-ci.org/angel-dart/mongo) MongoDB-enabled services for the Angel framework. diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 4659a0eb..3e9b4d8f 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -19,7 +19,7 @@ class MongoService extends Service { : super(); SelectorBuilder _makeQuery([Map params_]) { - Map params = params_ ?? {}; + Map params = new Map.from(params_ ?? {}); params = params..remove('provider'); SelectorBuilder result = where.exists('_id'); @@ -82,13 +82,6 @@ class MongoService extends Service { return _transformId(result); } - void printDebug(e, st, msg) { - if (debug) { - stderr.writeln('$msg ERROR: $e'); - stderr.writeln(st); - } - } - @override Future index([Map params]) async { return await (await collection.find(_makeQuery(params))) @@ -96,17 +89,22 @@ class MongoService extends Service { .toList(); } + static const String _NONCE_KEY = '__angel__mongo__nonce__key__'; + @override Future create(data, [Map params]) async { Map item = (data is Map) ? data : god.serializeObject(data); item = _removeSensitive(item); try { - item['createdAt'] = new DateTime.now().toIso8601String(); - await collection.insert(item); - return await _lastItem(collection, _jsonify, params); + String nonce = (await collection.db.getNonce())['nonce']; + var result = await collection.findAndModify( + query: where.eq(_NONCE_KEY, nonce), + update: item, + returnNew: true, + upsert: true); + return _jsonify(result); } catch (e, st) { - printDebug(e, st, 'CREATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -141,49 +139,43 @@ class MongoService extends Service { target is Map ? target : god.serializeObject(target), _removeSensitive(data) ]); - result['updatedAt'] = new DateTime.now().toIso8601String(); + //result['updatedAt'] = new DateTime.now().toIso8601String(); try { - await collection.update(where.id(_makeId(id)), result); - result = _jsonify(result, params); - result['id'] = id; + var modified = await collection.findAndModify( + query: where.id(_makeId(id)), update: result, returnNew: true); + result = _jsonify(modified, params); + result['id'] = _makeId(id).toHexString(); return result; } catch (e, st) { - printDebug(e, st, 'MODIFY'); + //printDebug(e, st, 'MODIFY'); throw new AngelHttpException(e, stackTrace: st); } } @override Future update(id, data, [Map params]) async { - var target; - - try { - target = await read(id, params); - } on AngelHttpException catch (e) { - if (e.statusCode == HttpStatus.NOT_FOUND) - return await create(data, params); - else - rethrow; - } - Map result = _removeSensitive(data); result['_id'] = _makeId(id); - result['createdAt'] = + /*result['createdAt'] = target is Map ? target['createdAt'] : target.createdAt; if (result['createdAt'] is DateTime) result['createdAt'] = result['createdAt'].toIso8601String(); - result['updatedAt'] = new DateTime.now().toIso8601String(); + result['updatedAt'] = new DateTime.now().toIso8601String();*/ try { - await collection.update(where.id(_makeId(id)), result); - result = _jsonify(result, params); - result['id'] = id; + var updated = await collection.findAndModify( + query: where.id(_makeId(id)), + update: result, + returnNew: true, + upsert: true); + result = _jsonify(updated, params); + result['id'] = _makeId(id).toHexString(); return result; } catch (e, st) { - printDebug(e, st, 'UPDATE'); + //printDebug(e, st, 'UPDATE'); throw new AngelHttpException(e, stackTrace: st); } } @@ -198,13 +190,14 @@ class MongoService extends Service { return {}; } - var result = await read(id, params); + // var result = await read(id, params); try { - await collection.remove(where.id(_makeId(id)).and(_makeQuery(params))); - return result; + var result = await collection.findAndModify( + query: where.id(_makeId(id)), remove: true); + return _jsonify(result); } catch (e, st) { - printDebug(e, st, 'REMOVE'); + //printDebug(e, st, 'REMOVE'); throw new AngelHttpException(e, stackTrace: st); } } diff --git a/lib/services.dart b/lib/services.dart index d01a7976..e78c5c6b 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -14,7 +14,7 @@ part 'mongo_service.dart'; part 'mongo_service_typed.dart'; Map _transformId(Map doc) { - Map result = mergeMap([doc]); + Map result = new Map.from(doc); result ..['id'] = doc['_id'] ..remove('_id'); diff --git a/pubspec.yaml b/pubspec.yaml index 3798e5db..2f6057ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 1.1.5 +version: 1.1.6 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_test.dart b/test/generic_test.dart index aecc6cbd..21fcac95 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -14,19 +14,10 @@ final headers = { final Map testGreeting = {'to': 'world'}; wireHooked(HookedService hooked) { - hooked - ..afterCreated.listen((HookedServiceEvent event) { - print("Just created: ${event.result}"); - }) - ..afterModified.listen((HookedServiceEvent event) { - print("Just modified: ${event.result}"); - }) - ..afterUpdated.listen((HookedServiceEvent event) { - print("Just updated: ${event.result}"); - }) - ..afterRemoved.listen((HookedServiceEvent event) { - print("Just removed: ${event.result}"); - }); + hooked.afterAll((HookedServiceEvent event) { + print("Just ${event.eventName}: ${event.result}"); + print('Params: ${event.params}'); + }); } main() { @@ -92,7 +83,7 @@ main() { Map read = god.deserialize(response.body); expect(read['id'], equals(created['id'])); expect(read['to'], equals('world')); - expect(read['createdAt'], isNot(null)); + //expect(read['createdAt'], isNot(null)); }); test('modify item', () async { @@ -107,7 +98,7 @@ main() { expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Mom')); - expect(modified['updatedAt'], isNot(null)); + //expect(modified['updatedAt'], isNot(null)); }); test('update item', () async { @@ -122,7 +113,7 @@ main() { expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Updated')); - expect(modified['updatedAt'], isNot(null)); + //expect(modified['updatedAt'], isNot(null)); }); test('remove item', () async { @@ -146,10 +137,10 @@ main() { print(response.body); List queried = god.deserialize(response.body); expect(queried.length, equals(1)); - expect(queried[0].keys.length, equals(3)); + expect(queried[0].keys.length, equals(2)); expect(queried[0]["id"], equals(world["id"])); expect(queried[0]["to"], equals(world["to"])); - expect(queried[0]["createdAt"], equals(world["createdAt"])); + //expect(queried[0]["createdAt"], equals(world["createdAt"])); /*response = await client.get("$url/api?\$sort.createdAt=-1"); print(response.body); From 31ff91378447460472530c62cbcb796b54509784 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 18:58:03 -0400 Subject: [PATCH 25/37] Broken --- .gitignore | 1 + CHANGELOG.md | 3 ++ analysis_options.yaml | 3 ++ lib/angel_mongo.dart | 2 +- lib/model.dart | 2 +- lib/mongo_service.dart | 51 +++++++++++----------- lib/mongo_service_typed.dart | 84 ------------------------------------ lib/services.dart | 24 +++-------- pubspec.yaml | 8 ++-- test/generic_test.dart | 27 ++++++------ 10 files changed, 58 insertions(+), 147 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 analysis_options.yaml delete mode 100644 lib/mongo_service_typed.dart diff --git a/.gitignore b/.gitignore index c9d518bb..d95a2f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ .pub/ build/ **/packages/ +.dart_tool # Files created by dart2js # (Most Dart developers will use pub build to compile Dart, use/modify these diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..1c587589 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 2.0.0-rc.0 +* Delete `mongo_service_typed`. +* Update for Angel 2. \ 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/lib/angel_mongo.dart b/lib/angel_mongo.dart index 91f85241..56747813 100644 --- a/lib/angel_mongo.dart +++ b/lib/angel_mongo.dart @@ -1,3 +1,3 @@ library angel_mongo; -export 'services.dart'; \ No newline at end of file +export 'services.dart'; diff --git a/lib/model.dart b/lib/model.dart index 84c81e6a..1c8c8ed4 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -11,4 +11,4 @@ class Model { /// The time at which this instance was last updated. DateTime updatedAt; -} \ No newline at end of file +} diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 3e9b4d8f..98b3449f 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -1,7 +1,7 @@ part of angel_mongo.services; /// Manipulates data from MongoDB as Maps. -class MongoService extends Service { +class MongoService extends Service> { DbCollection collection; /// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`. @@ -18,14 +18,14 @@ class MongoService extends Service { {this.allowRemoveAll: false, this.allowQuery: true, this.debug: true}) : super(); - SelectorBuilder _makeQuery([Map params_]) { + SelectorBuilder _makeQuery([Map params_]) { Map params = new Map.from(params_ ?? {}); params = params..remove('provider'); SelectorBuilder result = where.exists('_id'); // You can pass a SelectorBuilder as 'query'; if (params['query'] is SelectorBuilder) { - return params['query']; + return params['query'] as SelectorBuilder; } for (var key in params.keys) { @@ -47,18 +47,18 @@ class MongoService extends Service { } else if (params[key] is String && key == r'$sort') { // If they send just a string, then we'll sort // by that, ascending - result = result.sortBy(params[key]); + result = result.sortBy(params[key] as String); } } else if (key == 'query' && (allowQuery == true || !params.containsKey('provider'))) { Map query = params[key]; query.forEach((key, v) { - var value = v is Map ? _filterNoQuery(v) : v; + var value = v is Map ? _filterNoQuery(v) : v; if (!_NO_QUERY.contains(key) && value is! RequestContext && value is! ResponseContext) { - result = result.and(where.eq(key, value)); + result = result.and(where.eq(key as String, value)); } }); } @@ -67,8 +67,9 @@ class MongoService extends Service { return result; } - _jsonify(Map doc, [Map params]) { - Map result = {}; + Map _jsonify(Map doc, + [Map params]) { + var result = {}; for (var key in doc.keys) { var value = doc[key]; @@ -83,7 +84,8 @@ class MongoService extends Service { } @override - Future index([Map params]) async { + Future>> index( + [Map params]) async { return await (await collection.find(_makeQuery(params))) .map((x) => _jsonify(x, params)) .toList(); @@ -92,9 +94,9 @@ class MongoService extends Service { static const String _NONCE_KEY = '__angel__mongo__nonce__key__'; @override - Future create(data, [Map params]) async { - Map item = (data is Map) ? data : god.serializeObject(data); - item = _removeSensitive(item); + Future> create(Map data, + [Map params]) async { + var item = _removeSensitive(data); try { String nonce = (await collection.db.getNonce())['nonce']; @@ -110,9 +112,10 @@ class MongoService extends Service { } @override - Future read(id, [Map params]) async { + Future> read(String id, + [Map params]) async { ObjectId _id = _makeId(id); - Map found = await collection.findOne(where.id(_id).and(_makeQuery(params))); + var found = await collection.findOne(where.id(_id).and(_makeQuery(params))); if (found == null) { throw new AngelHttpException.notFound( @@ -123,22 +126,20 @@ class MongoService extends Service { } @override - Future modify(id, data, [Map params]) async { - var target; + Future> modify(String id, data, + [Map params]) async { + Map target; try { target = await read(id, params); } on AngelHttpException catch (e) { - if (e.statusCode == HttpStatus.NOT_FOUND) + if (e.statusCode == 404) return await create(data, params); else rethrow; } - Map result = mergeMap([ - target is Map ? target : god.serializeObject(target), - _removeSensitive(data) - ]); + var result = mergeMap([target, _removeSensitive(data)]); //result['updatedAt'] = new DateTime.now().toIso8601String(); try { @@ -154,8 +155,9 @@ class MongoService extends Service { } @override - Future update(id, data, [Map params]) async { - Map result = _removeSensitive(data); + Future> update(String id, Map data, + [Map params]) async { + var result = _removeSensitive(data); result['_id'] = _makeId(id); /*result['createdAt'] = target is Map ? target['createdAt'] : target.createdAt; @@ -181,7 +183,8 @@ class MongoService extends Service { } @override - Future remove(id, [Map params]) async { + Future> remove(String id, + [Map params]) async { if (id == null || id == 'null' && (allowRemoveAll == true || diff --git a/lib/mongo_service_typed.dart b/lib/mongo_service_typed.dart deleted file mode 100644 index 1948296d..00000000 --- a/lib/mongo_service_typed.dart +++ /dev/null @@ -1,84 +0,0 @@ -part of angel_mongo.services; - -/// Use a normal TypedService instead. -@deprecated -class MongoTypedService extends MongoService { - MongoTypedService(DbCollection collection, {bool allowRemoveAll, bool debug}) - : super(collection, - allowRemoveAll: allowRemoveAll == true, debug: debug == true) { - if (!reflectType(T).isAssignableTo(reflectType(Model))) - throw new Exception( - "If you specify a type for MongoService, it must extend Model."); - } - - _deserialize(x) { - // print('DESERIALIZE: $x (${x.runtimeType})'); - if (x == dynamic || x == Object || x is T) - return x; - else if (x is Map) { - Map data = x.keys.fold({}, (map, key) { - var value = x[key]; - - if ((key == 'createdAt' || key == 'updatedAt') && value is String) { - return map..[key] = DateTime.parse(value).toIso8601String(); - } else if (value is DateTime) { - return map..[key] = value.toIso8601String(); - } else { - return map..[key] = value; - } - }); - - Model result = god.deserializeDatum(data, outputType: T); - - if (x['createdAt'] is String) { - result.createdAt = DateTime.parse(x['createdAt']); - } else if (x['createdAt'] is DateTime) { - result.createdAt = x['createdAt']; - } - - if (x['updatedAt'] is String) { - result.updatedAt = DateTime.parse(x['updatedAt']); - } else if (x['updatedAt'] is DateTime) { - result.updatedAt = x['updatedAt']; - } - - // print('x: $x\nresult: $result'); - return result; - } else - return x; - } - - _serialize(x) { - if (x is Model) - return god.serializeObject(x); - else if (x is Map) - return x; - else - throw new ArgumentError('Cannot serialize ${x.runtimeType}'); - } - - @override - Future index([Map params]) async { - var result = await super.index(params); - return result.map(_deserialize).toList(); - } - - @override - Future create(data, [Map params]) => - super.create(_serialize(data), params).then(_deserialize); - - @override - Future read(id, [Map params]) => super.read(id, params).then(_deserialize); - - @override - Future modify(id, data, [Map params]) => - super.modify(id, _serialize(data), params).then(_deserialize); - - @override - Future update(id, data, [Map params]) => - super.update(id, _serialize(data), params).then(_deserialize); - - @override - Future remove(id, [Map params]) => - super.remove(id, params).then(_deserialize); -} diff --git a/lib/services.dart b/lib/services.dart index e78c5c6b..d322a289 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -1,20 +1,14 @@ library angel_mongo.services; import 'dart:async'; -import 'dart:io'; -import 'dart:mirrors'; import 'package:angel_framework/angel_framework.dart'; -import 'package:angel_framework/common.dart' show Model; -import 'package:json_god/json_god.dart' as god; import 'package:merge_map/merge_map.dart'; import 'package:mongo_dart/mongo_dart.dart'; part 'mongo_service.dart'; -part 'mongo_service_typed.dart'; - -Map _transformId(Map doc) { - Map result = new Map.from(doc); +Map _transformId(Map doc) { + var result = new Map.from(doc); result ..['id'] = doc['_id'] ..remove('_id'); @@ -22,14 +16,6 @@ Map _transformId(Map doc) { return result; } -_lastItem(DbCollection collection, Function _jsonify, [Map params]) async { - return (await (await collection - .find(where.sortBy('\$natural', descending: true))) - .toList()) - .map((x) => _jsonify(x, params)) - .first; -} - ObjectId _makeId(id) { try { return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString()); @@ -40,7 +26,7 @@ ObjectId _makeId(id) { const List _SENSITIVE = const ['id', '_id', 'createdAt', 'updatedAt']; -Map _removeSensitive(Map data) { +Map _removeSensitive(Map data) { return data.keys .where((k) => !_SENSITIVE.contains(k)) .fold({}, (map, key) => map..[key] = data[key]); @@ -48,7 +34,7 @@ Map _removeSensitive(Map data) { const List _NO_QUERY = const ['__requestctx', '__responsectx']; -Map _filterNoQuery(Map data) { +Map _filterNoQuery(Map data) { return data.keys.fold({}, (map, key) { var value = data[key]; @@ -56,6 +42,6 @@ Map _filterNoQuery(Map data) { value is RequestContext || value is ResponseContext) return map; if (key is! Map) return map..[key] = value; - return map..[key] = _filterNoQuery(value); + return map..[key] = _filterNoQuery(value as Map); }); } diff --git a/pubspec.yaml b/pubspec.yaml index 2f6057ba..e5002301 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,14 @@ name: angel_mongo -version: 1.1.6 +version: 2.0.0-rc.0 description: MongoDB-enabled services for the Angel framework. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo +environment: + sdk: ">=2.0.0-dev <3.0.0" dependencies: - angel_framework: ">=1.0.0-dev < 2.0.0" + angel_framework: ^2.0.0-alpha json_god: ">=2.0.0-beta <3.0.0" mongo_dart: ">= 0.2.7 < 1.0.0" dev_dependencies: http: ">= 0.11.3 < 0.12.0" - test: ">= 0.12.13 < 0.13.0" \ No newline at end of file + test: ^1.0.0 \ No newline at end of file diff --git a/test/generic_test.dart b/test/generic_test.dart index 21fcac95..1fc3e9e0 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:angel_framework/angel_framework.dart'; import 'package:angel_mongo/angel_mongo.dart'; import 'package:http/http.dart' as http; @@ -7,8 +6,8 @@ import 'package:mongo_dart/mongo_dart.dart'; import 'package:test/test.dart'; final headers = { - HttpHeaders.ACCEPT: ContentType.JSON.mimeType, - HttpHeaders.CONTENT_TYPE: ContentType.JSON.mimeType + 'accept': 'application/json', + 'content-type': 'application/json' }; final Map testGreeting = {'to': 'world'}; @@ -23,18 +22,19 @@ wireHooked(HookedService hooked) { main() { group('Generic Tests', () { Angel app = new Angel(); + AngelHttp transport = new AngelHttp(app); http.Client client; Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; String url; - HookedService greetingService; + HookedService, MongoService> greetingService; setUp(() async { client = new http.Client(); await db.open(); testData = db.collection('test_data'); // Delete anything before we start - await testData.remove(); + await testData.remove({}); var service = new MongoService(testData, debug: true); greetingService = new HookedService(service); @@ -42,20 +42,15 @@ main() { app.use('/api', greetingService); - app.fatalErrorStream.listen((AngelFatalError e) { - print('Fatal error: ${e.error}'); - print(e.stack); - }); - - var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 0); + var server = await transport.startServer('127.0.0.1', 0); url = "http://${server.address.host}:${server.port}"; }); tearDown(() async { // Delete anything left over - await testData.remove(); + await testData.remove({}); await db.close(); - await app.httpServer.close(force: true); + await transport.close(); client = null; url = null; greetingService = null; @@ -150,8 +145,10 @@ main() { expect(queried[2]["id"], equals(world["id"]));*/ queried = await greetingService.index({ - "\$query": {"_id": where.id(new ObjectId.fromHexString(world["id"]))} - }); + "\$query": { + "_id": where.id(new ObjectId.fromHexString(world["id"] as String)) + } + }) as List>; print(queried); expect(queried.length, equals(1)); expect(queried[0], equals(world)); From e8a46d580434e083a1f4a99241c5159042d382c5 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:00:55 -0400 Subject: [PATCH 26/37] Fixed --- test/generic_test.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/generic_test.dart b/test/generic_test.dart index 1fc3e9e0..a060ac56 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -21,8 +21,8 @@ wireHooked(HookedService hooked) { main() { group('Generic Tests', () { - Angel app = new Angel(); - AngelHttp transport = new AngelHttp(app); + Angel app; + AngelHttp transport; http.Client client; Db db = new Db('mongodb://localhost:27017/angel_mongo'); DbCollection testData; @@ -30,11 +30,13 @@ main() { HookedService, MongoService> greetingService; setUp(() async { + app = new Angel(); + transport = new AngelHttp(app); client = new http.Client(); await db.open(); testData = db.collection('test_data'); // Delete anything before we start - await testData.remove({}); + await testData.remove({}); var service = new MongoService(testData, debug: true); greetingService = new HookedService(service); @@ -48,7 +50,7 @@ main() { tearDown(() async { // Delete anything left over - await testData.remove({}); + await testData.remove({}); await db.close(); await transport.close(); client = null; @@ -63,7 +65,8 @@ main() { response = await client.get("$url/api"); expect(response.statusCode, isIn([200, 201])); - List users = god.deserialize(response.body); + List users = + god.deserialize(response.body, outputType: [].runtimeType); expect(users.length, equals(1)); }); @@ -130,7 +133,7 @@ main() { var response = await client.get("$url/api?to=world"); print(response.body); - List queried = god.deserialize(response.body); + List queried = god.deserialize(response.body, outputType: [].runtimeType); expect(queried.length, equals(1)); expect(queried[0].keys.length, equals(2)); expect(queried[0]["id"], equals(world["id"])); From 4b2aefd807db077bd138a3edbb3ab9743050cc69 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:01:58 -0400 Subject: [PATCH 27/37] Add example --- example/example.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 example/example.dart diff --git a/example/example.dart b/example/example.dart new file mode 100644 index 00000000..161c9173 --- /dev/null +++ b/example/example.dart @@ -0,0 +1,15 @@ +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_mongo/angel_mongo.dart'; +import 'package:mongo_dart/mongo_dart.dart'; + +main() async { + var app = new Angel(); + Db db = new Db('mongodb://localhost:27017/local'); + await db.open(); + + var service = app.use('/api/users', new MongoService(db.collection("users"))); + + service.afterCreated.listen((event) { + print("New user: ${event.result}"); + }); +} From a3b2e59a1f41939cfe3a74c4b2b671c54676c8c7 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:03:08 -0400 Subject: [PATCH 28/37] desc --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e5002301..54c5d8cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: angel_mongo version: 2.0.0-rc.0 -description: MongoDB-enabled services for the Angel framework. +description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo environment: From c1ba9c5b3831a0a5af7e21b3db36d1ac0573864c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:03:43 -0400 Subject: [PATCH 29/37] merge_map dep --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 54c5d8cf..20629df2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,6 +8,7 @@ environment: dependencies: angel_framework: ^2.0.0-alpha json_god: ">=2.0.0-beta <3.0.0" + merge_map: ^1.0.0 mongo_dart: ">= 0.2.7 < 1.0.0" dev_dependencies: http: ">= 0.11.3 < 0.12.0" From de45585ac6d9bdfad6fbe179f072bbaecf37d856 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:06:17 -0400 Subject: [PATCH 30/37] Update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 45e8fe19..de3dd1c5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Add the following to your `pubspec.yaml`: ```yaml dependencies: - angel_mongo: ^1.0.0 + angel_mongo: ^2.0.0 ``` # Usage @@ -27,12 +27,12 @@ class User extends Model { } main() async { - Db db = new Db('mongodb://localhost:27017/local'); + var db = new Db('mongodb://localhost:27017/local'); await db.open(); - app.use('/api/users', new TypedService(new MongoService(db.collection("users")))); + var service = app.use('/api/users', new MongoService(db.collection("users"))); - app.service('api/users').afterCreated.listen((event) { + service.afterCreated.listen((event) { print("New user: ${event.result}"); }); } From cece31e2ddd9d04cd4ef89916ba0f98cb23f7b16 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Thu, 18 Oct 2018 19:49:48 -0400 Subject: [PATCH 31/37] bump to v2 --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 20629df2..4f22cb85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 2.0.0-rc.0 +version: 2.0.0 description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo @@ -12,4 +12,4 @@ dependencies: mongo_dart: ">= 0.2.7 < 1.0.0" dev_dependencies: http: ">= 0.11.3 < 0.12.0" - test: ^1.0.0 \ No newline at end of file + test: ^1.0.0 From 0a3fc8d6ceea692041e61695b77f8f8c8d220593 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 10 Dec 2018 10:33:55 -0500 Subject: [PATCH 32/37] Fixed tests --- test/generic_test.dart | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/generic_test.dart b/test/generic_test.dart index a060ac56..cd9592a6 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -1,4 +1,5 @@ import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_framework/http.dart'; import 'package:angel_mongo/angel_mongo.dart'; import 'package:http/http.dart' as http; import 'package:json_god/json_god.dart' as god; @@ -44,8 +45,8 @@ main() { app.use('/api', greetingService); - var server = await transport.startServer('127.0.0.1', 0); - url = "http://${server.address.host}:${server.port}"; + await transport.startServer('127.0.0.1', 0); + url = transport.uri.toString(); }); tearDown(() async { @@ -58,6 +59,20 @@ main() { greetingService = null; }); + test('query fields mapped to filters', () async { + await greetingService.create({'foo': 'bar'}); + expect( + await greetingService.index({ + 'query': {'foo': 'not bar'} + }), + isEmpty, + ); + expect( + await greetingService.index(), + isNotEmpty, + ); + }); + test('insert items', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); @@ -133,7 +148,8 @@ main() { var response = await client.get("$url/api?to=world"); print(response.body); - List queried = god.deserialize(response.body, outputType: [].runtimeType); + List queried = + god.deserialize(response.body, outputType: [].runtimeType); expect(queried.length, equals(1)); expect(queried[0].keys.length, equals(2)); expect(queried[0]["id"], equals(world["id"])); @@ -151,7 +167,7 @@ main() { "\$query": { "_id": where.id(new ObjectId.fromHexString(world["id"] as String)) } - }) as List>; + }); print(queried); expect(queried.length, equals(1)); expect(queried[0], equals(world)); From e1b85092d55098ab0cc6a639ea3c6de64e5a4b37 Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 10 Dec 2018 14:19:20 -0500 Subject: [PATCH 33/37] 2.0.1 --- CHANGELOG.md | 5 ++++- lib/mongo_service.dart | 24 ++++++++++++++++++++++++ lib/services.dart | 9 +++++++-- pubspec.yaml | 2 +- test/generic_test.dart | 25 +++++++++++++++++++++++++ 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c587589..93ad432d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ -# 2.0.0-rc.0 +# 2.0.1 +* Override `readMany` and `findOne`. + +# 2.0.0- * Delete `mongo_service_typed`. * Update for Angel 2. \ No newline at end of file diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 98b3449f..38e25951 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -111,6 +111,20 @@ class MongoService extends Service> { } } + @override + Future> findOne( + [Map params, + String errorMessage = + 'No record was found matching the given query.']) async { + var found = await collection.findOne(_makeQuery(params)); + + if (found == null) { + throw new AngelHttpException.notFound(message: errorMessage); + } + + return _jsonify(found, params); + } + @override Future> read(String id, [Map params]) async { @@ -125,6 +139,16 @@ class MongoService extends Service> { return _jsonify(found, params); } + @override + Future>> readMany(List ids, + [Map params]) async { + var q = _makeQuery(params); + q = ids.fold(q, (q, id) => q.or(where.id(_makeId(id)))); + return await (await collection.find(q)) + .map((x) => _jsonify(x, params)) + .toList(); + } + @override Future> modify(String id, data, [Map params]) async { diff --git a/lib/services.dart b/lib/services.dart index d322a289..a44dedd4 100644 --- a/lib/services.dart +++ b/lib/services.dart @@ -24,11 +24,16 @@ ObjectId _makeId(id) { } } -const List _SENSITIVE = const ['id', '_id', 'createdAt', 'updatedAt']; +const List _sensitiveFieldNames = const [ + 'id', + '_id', + 'createdAt', + 'updatedAt' +]; Map _removeSensitive(Map data) { return data.keys - .where((k) => !_SENSITIVE.contains(k)) + .where((k) => !_sensitiveFieldNames.contains(k)) .fold({}, (map, key) => map..[key] = data[key]); } diff --git a/pubspec.yaml b/pubspec.yaml index 4f22cb85..d34ceacd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 2.0.0 +version: 2.0.1 description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo diff --git a/test/generic_test.dart b/test/generic_test.dart index cd9592a6..e47fdf2c 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -99,6 +99,31 @@ main() { //expect(read['createdAt'], isNot(null)); }); + test('findOne', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, isIn([200, 201])); + Map created = god.deserialize(response.body); + + var id = new ObjectId.fromHexString(created['id'] as String); + var read = await greetingService.findOne({'query': where.id(id)}); + expect(read['id'], equals(created['id'])); + expect(read['to'], equals('world')); + //expect(read['createdAt'], isNot(null)); + }); + + test('readMany', () async { + var response = await client.post("$url/api", + body: god.serialize(testGreeting), headers: headers); + expect(response.statusCode, isIn([200, 201])); + Map created = god.deserialize(response.body); + + var id = new ObjectId.fromHexString(created['id'] as String); + var read = await greetingService.readMany([id.toHexString()]); + expect(read, [created]); + //expect(read['createdAt'], isNot(null)); + }); + test('modify item', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); From 2c15ae94646fb4a2a76df866321aeaa5b3b18f0a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sat, 20 Apr 2019 14:59:22 -0400 Subject: [PATCH 34/37] Patch allowRemoveAll flaw --- CHANGELOG.md | 3 +++ analysis_options.yaml | 1 + lib/mongo_service.dart | 24 +++++++++++++++--------- pubspec.yaml | 3 ++- test/generic_test.dart | 31 ++++++++++++++++++------------- 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93ad432d..3d65fa20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.2 +* Fix flaw where clients could remove all records, even if `allowRemoveAll` were `false`. + # 2.0.1 * Override `readMany` and `findOne`. 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/mongo_service.dart b/lib/mongo_service.dart index 38e25951..9a3e5deb 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -12,10 +12,12 @@ class MongoService extends Service> { /// If set to `true`, parameters in `req.query` are applied to the database query. final bool allowQuery; + /// No longer used. Will be removed by `2.1.0`. + @deprecated final bool debug; MongoService(DbCollection this.collection, - {this.allowRemoveAll: false, this.allowQuery: true, this.debug: true}) + {this.allowRemoveAll = false, this.allowQuery = true, this.debug = true}) : super(); SelectorBuilder _makeQuery([Map params_]) { @@ -51,7 +53,7 @@ class MongoService extends Service> { } } else if (key == 'query' && (allowQuery == true || !params.containsKey('provider'))) { - Map query = params[key]; + var query = params[key] as Map; query.forEach((key, v) { var value = v is Map ? _filterNoQuery(v) : v; @@ -99,7 +101,7 @@ class MongoService extends Service> { var item = _removeSensitive(data); try { - String nonce = (await collection.db.getNonce())['nonce']; + var nonce = (await collection.db.getNonce())['nonce'] as String; var result = await collection.findAndModify( query: where.eq(_NONCE_KEY, nonce), update: item, @@ -209,12 +211,16 @@ class MongoService extends Service> { @override Future> remove(String id, [Map params]) async { - if (id == null || - id == 'null' && - (allowRemoveAll == true || - params?.containsKey('provider') != true)) { - await collection.remove(null); - 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 collection.remove(null); + return {}; + } } // var result = await read(id, params); diff --git a/pubspec.yaml b/pubspec.yaml index d34ceacd..512ab659 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 2.0.1 +version: 2.0.2 description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo @@ -12,4 +12,5 @@ dependencies: mongo_dart: ">= 0.2.7 < 1.0.0" dev_dependencies: http: ">= 0.11.3 < 0.12.0" + pedantic: ^1.0.0 test: ^1.0.0 diff --git a/test/generic_test.dart b/test/generic_test.dart index e47fdf2c..74d3adba 100644 --- a/test/generic_test.dart +++ b/test/generic_test.dart @@ -80,8 +80,8 @@ main() { response = await client.get("$url/api"); expect(response.statusCode, isIn([200, 201])); - List users = - god.deserialize(response.body, outputType: [].runtimeType); + var users = god.deserialize(response.body, + outputType: [].runtimeType) as List; expect(users.length, equals(1)); }); @@ -89,11 +89,11 @@ main() { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, isIn([200, 201])); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; response = await client.get("$url/api/${created['id']}"); expect(response.statusCode, isIn([200, 201])); - Map read = god.deserialize(response.body); + var read = god.deserialize(response.body) as Map; expect(read['id'], equals(created['id'])); expect(read['to'], equals('world')); //expect(read['createdAt'], isNot(null)); @@ -103,7 +103,7 @@ main() { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, isIn([200, 201])); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; var id = new ObjectId.fromHexString(created['id'] as String); var read = await greetingService.findOne({'query': where.id(id)}); @@ -116,7 +116,7 @@ main() { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, isIn([200, 201])); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; var id = new ObjectId.fromHexString(created['id'] as String); var read = await greetingService.readMany([id.toHexString()]); @@ -128,11 +128,11 @@ main() { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, isIn([200, 201])); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; response = await client.patch("$url/api/${created['id']}", body: god.serialize({"to": "Mom"}), headers: headers); - Map modified = god.deserialize(response.body); + var modified = god.deserialize(response.body) as Map; expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Mom')); @@ -143,11 +143,11 @@ main() { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); expect(response.statusCode, isIn([200, 201])); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; response = await client.post("$url/api/${created['id']}", body: god.serialize({"to": "Updated"}), headers: headers); - Map modified = god.deserialize(response.body); + var modified = god.deserialize(response.body) as Map; expect(response.statusCode, isIn([200, 201])); expect(modified['id'], equals(created['id'])); expect(modified['to'], equals('Updated')); @@ -157,7 +157,7 @@ main() { test('remove item', () async { var response = await client.post("$url/api", body: god.serialize(testGreeting), headers: headers); - Map created = god.deserialize(response.body); + var created = god.deserialize(response.body) as Map; int lastCount = (await greetingService.index()).length; @@ -165,6 +165,11 @@ main() { expect((await greetingService.index()).length, equals(lastCount - 1)); }); + test('cannot remove all unless explicitly set', () async { + var response = await client.delete('$url/api/null'); + expect(response.statusCode, 403); + }); + test('\$sort and query parameters', () async { // Search by where.eq Map world = await greetingService.create({"to": "world"}); @@ -173,8 +178,8 @@ main() { var response = await client.get("$url/api?to=world"); print(response.body); - List queried = - god.deserialize(response.body, outputType: [].runtimeType); + var queried = god.deserialize(response.body, + outputType: [].runtimeType) as List; expect(queried.length, equals(1)); expect(queried[0].keys.length, equals(2)); expect(queried[0]["id"], equals(world["id"])); From 7767be1f1a513e93390a1460cbcf75d638842a51 Mon Sep 17 00:00:00 2001 From: Pierre Plagnes Date: Mon, 2 Sep 2019 19:05:20 +0200 Subject: [PATCH 35/37] Check if query is null --- lib/mongo_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mongo_service.dart b/lib/mongo_service.dart index 9a3e5deb..5a2dfee6 100644 --- a/lib/mongo_service.dart +++ b/lib/mongo_service.dart @@ -54,7 +54,7 @@ class MongoService extends Service> { } else if (key == 'query' && (allowQuery == true || !params.containsKey('provider'))) { var query = params[key] as Map; - query.forEach((key, v) { + query?.forEach((key, v) { var value = v is Map ? _filterNoQuery(v) : v; if (!_NO_QUERY.contains(key) && From 925188078d1a0b7cd2ddbafa3bb9a3eb3d9fd56a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 2 Sep 2019 13:16:06 -0400 Subject: [PATCH 36/37] 2.0.4 hotfix --- CHANGELOG.md | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d65fa20..2a97dbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.3 +* Add null-coalescing check when processing queries: https://github.com/angel-dart/mongo/pull/12 + # 2.0.2 * Fix flaw where clients could remove all records, even if `allowRemoveAll` were `false`. @@ -6,4 +9,4 @@ # 2.0.0- * Delete `mongo_service_typed`. -* Update for Angel 2. \ No newline at end of file +* Update for Angel 2. diff --git a/pubspec.yaml b/pubspec.yaml index 512ab659..4b9a9e42 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 2.0.2 +version: 2.0.3 description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo From b2753bcdfe76507eae30ebd87691ac0a07d0d96d Mon Sep 17 00:00:00 2001 From: Tobe O Date: Mon, 2 Sep 2019 13:16:06 -0400 Subject: [PATCH 37/37] 2.0.3 hotfix --- CHANGELOG.md | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d65fa20..2a97dbec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.3 +* Add null-coalescing check when processing queries: https://github.com/angel-dart/mongo/pull/12 + # 2.0.2 * Fix flaw where clients could remove all records, even if `allowRemoveAll` were `false`. @@ -6,4 +9,4 @@ # 2.0.0- * Delete `mongo_service_typed`. -* Update for Angel 2. \ No newline at end of file +* Update for Angel 2. diff --git a/pubspec.yaml b/pubspec.yaml index 512ab659..4b9a9e42 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_mongo -version: 2.0.2 +version: 2.0.3 description: MongoDB-enabled services for the Angel framework. Well-tested. author: Tobe O homepage: https://github.com/angel-dart/angel_mongo