Add 'packages/mongo/' from commit '6acfbc02d95d94bf1020ade556e372e8a60794af'
git-subtree-dir: packages/mongo git-subtree-mainline:4ab233c5b6
git-subtree-split:6acfbc02d9
This commit is contained in:
commit
567ddd3897
20 changed files with 789 additions and 0 deletions
88
packages/mongo/.gitignore
vendored
Normal file
88
packages/mongo/.gitignore
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
# See https://www.dartlang.org/tools/private-files.html
|
||||
|
||||
# Files and directories created by pub
|
||||
.buildlog
|
||||
.packages
|
||||
.project
|
||||
.pub/
|
||||
build/
|
||||
**/packages/
|
||||
.dart_tool
|
||||
|
||||
# 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
|
||||
### 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
|
18
packages/mongo/.idea/angel_mongo.iml
Normal file
18
packages/mongo/.idea/angel_mongo.iml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</module>
|
8
packages/mongo/.idea/modules.xml
Normal file
8
packages/mongo/.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/angel_mongo.iml" filepath="$PROJECT_DIR$/.idea/angel_mongo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
7
packages/mongo/.idea/runConfigurations/All_Tests.xml
Normal file
7
packages/mongo/.idea/runConfigurations/All_Tests.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test" />
|
||||
<option name="scope" value="FOLDER" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
packages/mongo/.idea/runConfigurations/Generic_Tests.xml
Normal file
6
packages/mongo/.idea/runConfigurations/Generic_Tests.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Generic Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/generic_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
packages/mongo/.idea/runConfigurations/Typed_Tests.xml
Normal file
6
packages/mongo/.idea/runConfigurations/Typed_Tests.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Typed Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
|
||||
<option name="filePath" value="$PROJECT_DIR$/test/typed_test.dart" />
|
||||
<method />
|
||||
</configuration>
|
||||
</component>
|
6
packages/mongo/.idea/vcs.xml
Normal file
6
packages/mongo/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
3
packages/mongo/.travis.yml
Normal file
3
packages/mongo/.travis.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
language: dart
|
||||
services:
|
||||
- mongodb
|
10
packages/mongo/.vscode/tasks.json
vendored
Normal file
10
packages/mongo/.vscode/tasks.json
vendored
Normal file
|
@ -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
|
||||
}
|
12
packages/mongo/CHANGELOG.md
Normal file
12
packages/mongo/CHANGELOG.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# 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`.
|
||||
|
||||
# 2.0.1
|
||||
* Override `readMany` and `findOne`.
|
||||
|
||||
# 2.0.0-
|
||||
* Delete `mongo_service_typed`.
|
||||
* Update for Angel 2.
|
21
packages/mongo/LICENSE
Normal file
21
packages/mongo/LICENSE
Normal file
|
@ -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.
|
57
packages/mongo/README.md
Normal file
57
packages/mongo/README.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
# angel_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.
|
||||
|
||||
# Installation
|
||||
Add the following to your `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
angel_mongo: ^2.0.0
|
||||
```
|
||||
|
||||
# Usage
|
||||
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.
|
||||
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 {
|
||||
String username;
|
||||
String password;
|
||||
}
|
||||
|
||||
main() async {
|
||||
var 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}");
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## MongoService
|
||||
This class interacts with a `DbCollection` (from mongo_dart) and serializing data to and from Maps.
|
||||
|
||||
## Querying
|
||||
You can query these services as follows:
|
||||
|
||||
/path/to/service?foo=bar
|
||||
|
||||
The above will query the database to find records where 'foo' equals 'bar'.
|
||||
|
||||
The former will sort result in ascending order of creation, and so will the latter.
|
||||
|
||||
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.
|
4
packages/mongo/analysis_options.yaml
Normal file
4
packages/mongo/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:pedantic/analysis_options.yaml
|
||||
analyzer:
|
||||
strong-mode:
|
||||
implicit-casts: false
|
15
packages/mongo/example/example.dart
Normal file
15
packages/mongo/example/example.dart
Normal file
|
@ -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}");
|
||||
});
|
||||
}
|
3
packages/mongo/lib/angel_mongo.dart
Normal file
3
packages/mongo/lib/angel_mongo.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
library angel_mongo;
|
||||
|
||||
export 'services.dart';
|
14
packages/mongo/lib/model.dart
Normal file
14
packages/mongo/lib/model.dart
Normal file
|
@ -0,0 +1,14 @@
|
|||
library angel_mongo.model;
|
||||
|
||||
/// Use the `Model` class defined in `package:angel_framework/common.dart` instead.
|
||||
@deprecated
|
||||
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;
|
||||
}
|
237
packages/mongo/lib/mongo_service.dart
Normal file
237
packages/mongo/lib/mongo_service.dart
Normal file
|
@ -0,0 +1,237 @@
|
|||
part of angel_mongo.services;
|
||||
|
||||
/// Manipulates data from MongoDB as Maps.
|
||||
class MongoService extends Service<String, Map<String, dynamic>> {
|
||||
DbCollection collection;
|
||||
|
||||
/// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
|
||||
///
|
||||
/// `false` by default.
|
||||
final bool allowRemoveAll;
|
||||
|
||||
/// If set to `true`, parameters in `req.query` are applied to the database query.
|
||||
final bool allowQuery;
|
||||
|
||||
/// 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})
|
||||
: super();
|
||||
|
||||
SelectorBuilder _makeQuery([Map<String, dynamic> 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'] as SelectorBuilder;
|
||||
}
|
||||
|
||||
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] as String);
|
||||
}
|
||||
} else if (key == 'query' &&
|
||||
(allowQuery == true || !params.containsKey('provider'))) {
|
||||
var query = params[key] as Map;
|
||||
query?.forEach((key, v) {
|
||||
var value = v is Map<String, dynamic> ? _filterNoQuery(v) : v;
|
||||
|
||||
if (!_NO_QUERY.contains(key) &&
|
||||
value is! RequestContext &&
|
||||
value is! ResponseContext) {
|
||||
result = result.and(where.eq(key as String, value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _jsonify(Map<String, dynamic> doc,
|
||||
[Map<String, dynamic> params]) {
|
||||
var result = <String, dynamic>{};
|
||||
|
||||
for (var key in doc.keys) {
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Map<String, dynamic>>> index(
|
||||
[Map<String, dynamic> params]) async {
|
||||
return await (await collection.find(_makeQuery(params)))
|
||||
.map((x) => _jsonify(x, params))
|
||||
.toList();
|
||||
}
|
||||
|
||||
static const String _NONCE_KEY = '__angel__mongo__nonce__key__';
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> create(Map<String, dynamic> data,
|
||||
[Map<String, dynamic> params]) async {
|
||||
var item = _removeSensitive(data);
|
||||
|
||||
try {
|
||||
var nonce = (await collection.db.getNonce())['nonce'] as String;
|
||||
var result = await collection.findAndModify(
|
||||
query: where.eq(_NONCE_KEY, nonce),
|
||||
update: item,
|
||||
returnNew: true,
|
||||
upsert: true);
|
||||
return _jsonify(result);
|
||||
} catch (e, st) {
|
||||
throw new AngelHttpException(e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> findOne(
|
||||
[Map<String, dynamic> 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<Map<String, dynamic>> read(String id,
|
||||
[Map<String, dynamic> params]) async {
|
||||
ObjectId _id = _makeId(id);
|
||||
var 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<List<Map<String, dynamic>>> readMany(List<String> ids,
|
||||
[Map<String, dynamic> 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<Map<String, dynamic>> modify(String id, data,
|
||||
[Map<String, dynamic> params]) async {
|
||||
Map<String, dynamic> target;
|
||||
|
||||
try {
|
||||
target = await read(id, params);
|
||||
} on AngelHttpException catch (e) {
|
||||
if (e.statusCode == 404)
|
||||
return await create(data, params);
|
||||
else
|
||||
rethrow;
|
||||
}
|
||||
|
||||
var result = mergeMap([target, _removeSensitive(data)]);
|
||||
//result['updatedAt'] = new DateTime.now().toIso8601String();
|
||||
|
||||
try {
|
||||
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');
|
||||
throw new AngelHttpException(e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> update(String id, Map<String, dynamic> data,
|
||||
[Map<String, dynamic> params]) async {
|
||||
var result = _removeSensitive(data);
|
||||
result['_id'] = _makeId(id);
|
||||
/*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 {
|
||||
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');
|
||||
throw new AngelHttpException(e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> remove(String id,
|
||||
[Map<String, dynamic> params]) async {
|
||||
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);
|
||||
|
||||
try {
|
||||
var result = await collection.findAndModify(
|
||||
query: where.id(_makeId(id)), remove: true);
|
||||
return _jsonify(result);
|
||||
} catch (e, st) {
|
||||
//printDebug(e, st, 'REMOVE');
|
||||
throw new AngelHttpException(e, stackTrace: st);
|
||||
}
|
||||
}
|
||||
}
|
52
packages/mongo/lib/services.dart
Normal file
52
packages/mongo/lib/services.dart
Normal file
|
@ -0,0 +1,52 @@
|
|||
library angel_mongo.services;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:merge_map/merge_map.dart';
|
||||
import 'package:mongo_dart/mongo_dart.dart';
|
||||
|
||||
part 'mongo_service.dart';
|
||||
|
||||
Map<String, dynamic> _transformId(Map<String, dynamic> doc) {
|
||||
var result = new Map<String, dynamic>.from(doc);
|
||||
result
|
||||
..['id'] = doc['_id']
|
||||
..remove('_id');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ObjectId _makeId(id) {
|
||||
try {
|
||||
return (id is ObjectId) ? id : new ObjectId.fromHexString(id.toString());
|
||||
} catch (e) {
|
||||
throw new AngelHttpException.badRequest();
|
||||
}
|
||||
}
|
||||
|
||||
const List<String> _sensitiveFieldNames = const [
|
||||
'id',
|
||||
'_id',
|
||||
'createdAt',
|
||||
'updatedAt'
|
||||
];
|
||||
|
||||
Map<String, dynamic> _removeSensitive(Map<String, dynamic> data) {
|
||||
return data.keys
|
||||
.where((k) => !_sensitiveFieldNames.contains(k))
|
||||
.fold({}, (map, key) => map..[key] = data[key]);
|
||||
}
|
||||
|
||||
const List<String> _NO_QUERY = const ['__requestctx', '__responsectx'];
|
||||
|
||||
Map<String, dynamic> _filterNoQuery(Map<String, dynamic> 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 as Map<String, dynamic>);
|
||||
});
|
||||
}
|
16
packages/mongo/pubspec.yaml
Normal file
16
packages/mongo/pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: angel_mongo
|
||||
version: 2.0.3
|
||||
description: MongoDB-enabled services for the Angel framework. Well-tested.
|
||||
author: Tobe O <thosakwe@gmail.com>
|
||||
homepage: https://github.com/angel-dart/angel_mongo
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev <3.0.0"
|
||||
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"
|
||||
pedantic: ^1.0.0
|
||||
test: ^1.0.0
|
206
packages/mongo/test/generic_test.dart
Normal file
206
packages/mongo/test/generic_test.dart
Normal file
|
@ -0,0 +1,206 @@
|
|||
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;
|
||||
import 'package:mongo_dart/mongo_dart.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
final headers = {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json'
|
||||
};
|
||||
|
||||
final Map testGreeting = {'to': 'world'};
|
||||
|
||||
wireHooked(HookedService hooked) {
|
||||
hooked.afterAll((HookedServiceEvent event) {
|
||||
print("Just ${event.eventName}: ${event.result}");
|
||||
print('Params: ${event.params}');
|
||||
});
|
||||
}
|
||||
|
||||
main() {
|
||||
group('Generic Tests', () {
|
||||
Angel app;
|
||||
AngelHttp transport;
|
||||
http.Client client;
|
||||
Db db = new Db('mongodb://localhost:27017/angel_mongo');
|
||||
DbCollection testData;
|
||||
String url;
|
||||
HookedService<String, Map<String, dynamic>, 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(<String, dynamic>{});
|
||||
|
||||
var service = new MongoService(testData, debug: true);
|
||||
greetingService = new HookedService(service);
|
||||
wireHooked(greetingService);
|
||||
|
||||
app.use('/api', greetingService);
|
||||
|
||||
await transport.startServer('127.0.0.1', 0);
|
||||
url = transport.uri.toString();
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
// Delete anything left over
|
||||
await testData.remove(<String, dynamic>{});
|
||||
await db.close();
|
||||
await transport.close();
|
||||
client = null;
|
||||
url = null;
|
||||
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);
|
||||
expect(response.statusCode, isIn([200, 201]));
|
||||
|
||||
response = await client.get("$url/api");
|
||||
expect(response.statusCode, isIn([200, 201]));
|
||||
var users = god.deserialize(response.body,
|
||||
outputType: <Map>[].runtimeType) as List<Map>;
|
||||
expect(users.length, equals(1));
|
||||
});
|
||||
|
||||
test('read item', () async {
|
||||
var response = await client.post("$url/api",
|
||||
body: god.serialize(testGreeting), headers: headers);
|
||||
expect(response.statusCode, isIn([200, 201]));
|
||||
var created = god.deserialize(response.body) as Map;
|
||||
|
||||
response = await client.get("$url/api/${created['id']}");
|
||||
expect(response.statusCode, isIn([200, 201]));
|
||||
var read = god.deserialize(response.body) as Map;
|
||||
expect(read['id'], equals(created['id']));
|
||||
expect(read['to'], equals('world'));
|
||||
//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]));
|
||||
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)});
|
||||
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]));
|
||||
var created = god.deserialize(response.body) as Map;
|
||||
|
||||
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);
|
||||
expect(response.statusCode, isIn([200, 201]));
|
||||
var created = god.deserialize(response.body) as Map;
|
||||
|
||||
response = await client.patch("$url/api/${created['id']}",
|
||||
body: god.serialize({"to": "Mom"}), headers: headers);
|
||||
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'));
|
||||
//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, isIn([200, 201]));
|
||||
var created = god.deserialize(response.body) as Map;
|
||||
|
||||
response = await client.post("$url/api/${created['id']}",
|
||||
body: god.serialize({"to": "Updated"}), headers: headers);
|
||||
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'));
|
||||
//expect(modified['updatedAt'], isNot(null));
|
||||
});
|
||||
|
||||
test('remove item', () async {
|
||||
var response = await client.post("$url/api",
|
||||
body: god.serialize(testGreeting), headers: headers);
|
||||
var created = god.deserialize(response.body) as Map;
|
||||
|
||||
int lastCount = (await greetingService.index()).length;
|
||||
|
||||
await client.delete("$url/api/${created['id']}");
|
||||
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"});
|
||||
await greetingService.create({"to": "Mom"});
|
||||
await greetingService.create({"to": "Updated"});
|
||||
|
||||
var response = await client.get("$url/api?to=world");
|
||||
print(response.body);
|
||||
var queried = god.deserialize(response.body,
|
||||
outputType: <Map>[].runtimeType) as List<Map>;
|
||||
expect(queried.length, equals(1));
|
||||
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"]));
|
||||
|
||||
/*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"] as String))
|
||||
}
|
||||
});
|
||||
print(queried);
|
||||
expect(queried.length, equals(1));
|
||||
expect(queried[0], equals(world));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue