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