2016-09-21 04:26:22 +00:00
|
|
|
part of angel_mongo.services;
|
2016-05-09 22:51:07 +00:00
|
|
|
|
2016-06-22 18:34:28 +00:00
|
|
|
/// Manipulates data from MongoDB as Maps.
|
2018-10-18 22:58:03 +00:00
|
|
|
class MongoService extends Service<String, Map<String, dynamic>> {
|
2016-05-09 22:51:07 +00:00
|
|
|
DbCollection collection;
|
2017-02-20 16:00:42 +00:00
|
|
|
|
|
|
|
/// If set to `true`, clients can remove all items by passing a `null` `id` to `remove`.
|
|
|
|
///
|
|
|
|
/// `false` by default.
|
|
|
|
final bool allowRemoveAll;
|
2017-02-23 01:03:30 +00:00
|
|
|
|
|
|
|
/// If set to `true`, parameters in `req.query` are applied to the database query.
|
|
|
|
final bool allowQuery;
|
|
|
|
|
2019-04-20 18:59:22 +00:00
|
|
|
/// No longer used. Will be removed by `2.1.0`.
|
|
|
|
@deprecated
|
2017-02-13 01:38:24 +00:00
|
|
|
final bool debug;
|
2016-05-09 22:51:07 +00:00
|
|
|
|
2017-02-20 16:00:42 +00:00
|
|
|
MongoService(DbCollection this.collection,
|
2019-04-20 18:59:22 +00:00
|
|
|
{this.allowRemoveAll = false, this.allowQuery = true, this.debug = true})
|
2017-02-20 16:00:42 +00:00
|
|
|
: super();
|
2016-06-22 18:34:28 +00:00
|
|
|
|
2018-10-18 22:58:03 +00:00
|
|
|
SelectorBuilder _makeQuery([Map<String, dynamic> params_]) {
|
2021-02-14 05:22:25 +00:00
|
|
|
Map params = Map.from(params_ ?? {});
|
2017-02-23 01:03:30 +00:00
|
|
|
params = params..remove('provider');
|
|
|
|
SelectorBuilder result = where.exists('_id');
|
|
|
|
|
|
|
|
// You can pass a SelectorBuilder as 'query';
|
|
|
|
if (params['query'] is SelectorBuilder) {
|
2018-10-18 22:58:03 +00:00
|
|
|
return params['query'] as SelectorBuilder;
|
2017-02-23 01:03:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2018-10-18 22:58:03 +00:00
|
|
|
result = result.sortBy(params[key] as String);
|
2017-02-23 01:03:30 +00:00
|
|
|
}
|
|
|
|
} else if (key == 'query' &&
|
|
|
|
(allowQuery == true || !params.containsKey('provider'))) {
|
2019-04-20 18:59:22 +00:00
|
|
|
var query = params[key] as Map;
|
2019-09-02 17:05:20 +00:00
|
|
|
query?.forEach((key, v) {
|
2018-10-18 22:58:03 +00:00
|
|
|
var value = v is Map<String, dynamic> ? _filterNoQuery(v) : v;
|
2017-02-23 01:03:30 +00:00
|
|
|
|
|
|
|
if (!_NO_QUERY.contains(key) &&
|
|
|
|
value is! RequestContext &&
|
|
|
|
value is! ResponseContext) {
|
2018-10-18 22:58:03 +00:00
|
|
|
result = result.and(where.eq(key as String, value));
|
2017-02-23 01:03:30 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-10-18 22:58:03 +00:00
|
|
|
Map<String, dynamic> _jsonify(Map<String, dynamic> doc,
|
|
|
|
[Map<String, dynamic> params]) {
|
|
|
|
var result = <String, dynamic>{};
|
2017-02-13 01:38:24 +00:00
|
|
|
|
2016-05-09 22:51:07 +00:00
|
|
|
for (var key in doc.keys) {
|
2017-02-13 01:38:24 +00:00
|
|
|
var value = doc[key];
|
|
|
|
if (value is ObjectId) {
|
|
|
|
result[key] = value.toHexString();
|
|
|
|
} else if (value is! RequestContext && value is! ResponseContext) {
|
|
|
|
result[key] = value;
|
|
|
|
}
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|
|
|
|
|
2016-06-22 18:34:28 +00:00
|
|
|
return _transformId(result);
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<List<Map<String, dynamic>>> index(
|
|
|
|
[Map<String, dynamic> params]) async {
|
2016-05-09 22:51:07 +00:00
|
|
|
return await (await collection.find(_makeQuery(params)))
|
2016-06-22 18:34:28 +00:00
|
|
|
.map((x) => _jsonify(x, params))
|
2016-05-09 22:51:07 +00:00
|
|
|
.toList();
|
|
|
|
}
|
|
|
|
|
2017-07-09 18:07:02 +00:00
|
|
|
static const String _NONCE_KEY = '__angel__mongo__nonce__key__';
|
|
|
|
|
2016-05-09 22:51:07 +00:00
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<Map<String, dynamic>> create(Map<String, dynamic> data,
|
|
|
|
[Map<String, dynamic> params]) async {
|
|
|
|
var item = _removeSensitive(data);
|
2016-05-09 22:51:07 +00:00
|
|
|
|
|
|
|
try {
|
2019-04-20 18:59:22 +00:00
|
|
|
var nonce = (await collection.db.getNonce())['nonce'] as String;
|
2017-07-09 18:07:02 +00:00
|
|
|
var result = await collection.findAndModify(
|
|
|
|
query: where.eq(_NONCE_KEY, nonce),
|
|
|
|
update: item,
|
|
|
|
returnNew: true,
|
|
|
|
upsert: true);
|
|
|
|
return _jsonify(result);
|
2016-06-22 18:34:28 +00:00
|
|
|
} catch (e, st) {
|
|
|
|
throw new AngelHttpException(e, stackTrace: st);
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|
2016-06-22 18:34:28 +00:00
|
|
|
}
|
2016-05-09 22:51:07 +00:00
|
|
|
|
2018-12-10 19:19:20 +00:00
|
|
|
@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);
|
|
|
|
}
|
|
|
|
|
2016-06-22 18:34:28 +00:00
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<Map<String, dynamic>> read(String id,
|
|
|
|
[Map<String, dynamic> params]) async {
|
2016-06-22 18:34:28 +00:00
|
|
|
ObjectId _id = _makeId(id);
|
2018-10-18 22:58:03 +00:00
|
|
|
var found = await collection.findOne(where.id(_id).and(_makeQuery(params)));
|
2016-05-09 22:51:07 +00:00
|
|
|
|
|
|
|
if (found == null) {
|
2017-02-13 01:38:24 +00:00
|
|
|
throw new AngelHttpException.notFound(
|
2016-06-22 18:34:28 +00:00
|
|
|
message: 'No record found for ID ${_id.toHexString()}');
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|
|
|
|
|
2016-06-22 18:34:28 +00:00
|
|
|
return _jsonify(found, params);
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|
2016-06-22 18:34:28 +00:00
|
|
|
|
2018-12-10 19:19:20 +00:00
|
|
|
@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();
|
|
|
|
}
|
|
|
|
|
2016-06-22 18:34:28 +00:00
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<Map<String, dynamic>> modify(String id, data,
|
|
|
|
[Map<String, dynamic> params]) async {
|
|
|
|
Map<String, dynamic> target;
|
2017-04-10 02:28:29 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
target = await read(id, params);
|
|
|
|
} on AngelHttpException catch (e) {
|
2018-10-18 22:58:03 +00:00
|
|
|
if (e.statusCode == 404)
|
2017-04-10 02:28:29 +00:00
|
|
|
return await create(data, params);
|
|
|
|
else
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
|
2018-10-18 22:58:03 +00:00
|
|
|
var result = mergeMap([target, _removeSensitive(data)]);
|
2017-07-09 18:07:02 +00:00
|
|
|
//result['updatedAt'] = new DateTime.now().toIso8601String();
|
2016-06-22 18:34:28 +00:00
|
|
|
|
|
|
|
try {
|
2017-07-09 18:07:02 +00:00
|
|
|
var modified = await collection.findAndModify(
|
|
|
|
query: where.id(_makeId(id)), update: result, returnNew: true);
|
|
|
|
result = _jsonify(modified, params);
|
|
|
|
result['id'] = _makeId(id).toHexString();
|
2016-06-22 18:34:28 +00:00
|
|
|
return result;
|
|
|
|
} catch (e, st) {
|
2017-07-09 18:07:02 +00:00
|
|
|
//printDebug(e, st, 'MODIFY');
|
2016-06-22 18:34:28 +00:00
|
|
|
throw new AngelHttpException(e, stackTrace: st);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<Map<String, dynamic>> update(String id, Map<String, dynamic> data,
|
|
|
|
[Map<String, dynamic> params]) async {
|
|
|
|
var result = _removeSensitive(data);
|
2016-06-22 18:34:28 +00:00
|
|
|
result['_id'] = _makeId(id);
|
2017-07-09 18:07:02 +00:00
|
|
|
/*result['createdAt'] =
|
2017-02-19 12:32:21 +00:00
|
|
|
target is Map ? target['createdAt'] : target.createdAt;
|
|
|
|
|
|
|
|
if (result['createdAt'] is DateTime)
|
|
|
|
result['createdAt'] = result['createdAt'].toIso8601String();
|
|
|
|
|
2017-07-09 18:07:02 +00:00
|
|
|
result['updatedAt'] = new DateTime.now().toIso8601String();*/
|
2016-06-22 18:34:28 +00:00
|
|
|
|
|
|
|
try {
|
2017-07-09 18:07:02 +00:00
|
|
|
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();
|
2016-06-22 18:34:28 +00:00
|
|
|
return result;
|
|
|
|
} catch (e, st) {
|
2017-07-09 18:07:02 +00:00
|
|
|
//printDebug(e, st, 'UPDATE');
|
2016-06-22 18:34:28 +00:00
|
|
|
throw new AngelHttpException(e, stackTrace: st);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-23 04:58:21 +00:00
|
|
|
@override
|
2018-10-18 22:58:03 +00:00
|
|
|
Future<Map<String, dynamic>> remove(String id,
|
|
|
|
[Map<String, dynamic> params]) async {
|
2019-04-20 18:59:22 +00:00
|
|
|
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 {};
|
|
|
|
}
|
2017-02-20 16:00:42 +00:00
|
|
|
}
|
|
|
|
|
2017-07-09 18:07:02 +00:00
|
|
|
// var result = await read(id, params);
|
2016-06-22 18:34:28 +00:00
|
|
|
|
2016-06-23 04:58:21 +00:00
|
|
|
try {
|
2017-07-09 18:07:02 +00:00
|
|
|
var result = await collection.findAndModify(
|
|
|
|
query: where.id(_makeId(id)), remove: true);
|
|
|
|
return _jsonify(result);
|
2016-06-23 04:58:21 +00:00
|
|
|
} catch (e, st) {
|
2017-07-09 18:07:02 +00:00
|
|
|
//printDebug(e, st, 'REMOVE');
|
2016-06-23 04:58:21 +00:00
|
|
|
throw new AngelHttpException(e, stackTrace: st);
|
|
|
|
}
|
|
|
|
}
|
2016-05-09 22:51:07 +00:00
|
|
|
}
|