2016-09-15 19:53:01 +00:00
|
|
|
library angel_framework.http.service;
|
|
|
|
|
|
|
|
import 'dart:async';
|
2017-03-28 23:29:22 +00:00
|
|
|
import 'package:angel_framework/src/http/response_context.dart';
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'package:merge_map/merge_map.dart';
|
|
|
|
import '../util.dart';
|
|
|
|
import 'angel_base.dart';
|
|
|
|
import 'angel_http_exception.dart';
|
2017-02-23 00:37:15 +00:00
|
|
|
import 'hooked_service.dart' show HookedService;
|
2016-09-15 19:53:01 +00:00
|
|
|
import 'metadata.dart';
|
|
|
|
import 'routable.dart';
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2016-06-21 22:56:04 +00:00
|
|
|
/// Indicates how the service was accessed.
|
|
|
|
///
|
|
|
|
/// This will be passed to the `params` object in a service method.
|
|
|
|
/// When requested on the server side, this will be null.
|
2016-06-19 05:02:41 +00:00
|
|
|
class Providers {
|
2016-06-21 22:56:04 +00:00
|
|
|
/// The transport through which the client is accessing this service.
|
2016-06-19 05:02:41 +00:00
|
|
|
final String via;
|
|
|
|
|
2016-06-21 22:56:04 +00:00
|
|
|
const Providers(String this.via);
|
2016-06-19 05:02:41 +00:00
|
|
|
|
2016-06-21 22:56:04 +00:00
|
|
|
static const String VIA_REST = "rest";
|
|
|
|
static const String VIA_WEBSOCKET = "websocket";
|
|
|
|
|
|
|
|
/// Represents a request via REST.
|
|
|
|
static final Providers REST = const Providers(VIA_REST);
|
|
|
|
|
|
|
|
/// Represents a request over WebSockets.
|
|
|
|
static final Providers WEBSOCKET = const Providers(VIA_WEBSOCKET);
|
2017-01-28 03:47:00 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
bool operator ==(other) => other is Providers && other.via == via;
|
2016-06-19 05:02:41 +00:00
|
|
|
}
|
|
|
|
|
2016-06-21 22:56:04 +00:00
|
|
|
/// A front-facing interface that can present data to and operate on data on behalf of the user.
|
|
|
|
///
|
|
|
|
/// Heavily inspired by FeathersJS. <3
|
2016-04-18 03:27:23 +00:00
|
|
|
class Service extends Routable {
|
2017-06-06 12:42:33 +00:00
|
|
|
/// A [List] of keys that services should ignore, should they see them in the query.
|
|
|
|
static const List<String> SPECIAL_QUERY_KEYS = const [
|
|
|
|
r'$limit',
|
|
|
|
r'$sort',
|
2017-07-09 16:50:46 +00:00
|
|
|
'page',
|
|
|
|
'token'
|
2017-06-06 12:42:33 +00:00
|
|
|
];
|
|
|
|
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
/// The [Angel] app powering this service.
|
2016-09-15 19:53:01 +00:00
|
|
|
AngelBase app;
|
2016-04-18 03:27:23 +00:00
|
|
|
|
2017-04-25 02:44:22 +00:00
|
|
|
/// Closes this service, including any database connections or stream controllers.
|
|
|
|
Future close() async {}
|
|
|
|
|
2016-04-18 03:27:23 +00:00
|
|
|
/// Retrieves all resources.
|
2017-02-13 00:38:33 +00:00
|
|
|
Future index([Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Retrieves the desired resource.
|
2016-05-02 22:28:14 +00:00
|
|
|
Future read(id, [Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a resource.
|
2016-06-19 05:02:41 +00:00
|
|
|
Future create(data, [Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Modifies a resource.
|
2016-06-19 05:02:41 +00:00
|
|
|
Future modify(id, data, [Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Overwrites a resource.
|
2016-06-19 05:02:41 +00:00
|
|
|
Future update(id, data, [Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes the given resource.
|
2016-05-02 22:28:14 +00:00
|
|
|
Future remove(id, [Map params]) {
|
2017-01-28 21:08:07 +00:00
|
|
|
throw new AngelHttpException.methodNotAllowed();
|
2016-04-18 03:27:23 +00:00
|
|
|
}
|
|
|
|
|
2017-02-01 21:43:18 +00:00
|
|
|
/// Transforms an [id] string into one acceptable by a service.
|
|
|
|
toId(String id) {
|
|
|
|
if (id == 'null' || id == null)
|
|
|
|
return null;
|
|
|
|
else
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generates RESTful routes pointing to this class's methods.
|
2016-11-23 09:10:47 +00:00
|
|
|
void addRoutes() {
|
2016-06-19 05:02:41 +00:00
|
|
|
Map restProvider = {'provider': Providers.REST};
|
|
|
|
|
2016-06-23 19:09:49 +00:00
|
|
|
// Add global middleware if declared on the instance itself
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware before = getAnnotation(this, Middleware);
|
2016-10-22 20:41:36 +00:00
|
|
|
final handlers = [];
|
|
|
|
|
2016-11-23 09:10:47 +00:00
|
|
|
if (before != null) handlers.addAll(before.handlers);
|
2016-06-23 19:09:49 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware indexMiddleware = getAnnotation(this.index, Middleware);
|
2016-06-21 04:19:43 +00:00
|
|
|
get('/', (req, res) async {
|
2017-02-13 00:38:33 +00:00
|
|
|
return await this.index(mergeMap([
|
|
|
|
{'query': req.query},
|
2017-02-23 00:37:15 +00:00
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
2017-02-13 00:38:33 +00:00
|
|
|
]));
|
2016-10-22 20:41:36 +00:00
|
|
|
},
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(indexMiddleware == null) ? [] : indexMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware createMiddleware = getAnnotation(this.create, Middleware);
|
2017-03-28 23:29:22 +00:00
|
|
|
post('/', (req, ResponseContext res) async {
|
|
|
|
var r = await this.create(
|
|
|
|
await req.lazyBody(),
|
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
|
|
|
]));
|
|
|
|
res.statusCode = 201;
|
|
|
|
return r;
|
|
|
|
},
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(createMiddleware == null) ? [] : createMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware readMiddleware = getAnnotation(this.read, Middleware);
|
2016-06-23 19:05:55 +00:00
|
|
|
|
|
|
|
get(
|
|
|
|
'/:id',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.read(
|
2017-02-13 00:38:33 +00:00
|
|
|
toId(req.params['id']),
|
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
2017-02-23 00:37:15 +00:00
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
2017-02-13 00:38:33 +00:00
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(readMiddleware == null) ? [] : readMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware modifyMiddleware = getAnnotation(this.modify, Middleware);
|
2016-06-23 19:05:55 +00:00
|
|
|
patch(
|
|
|
|
'/:id',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.modify(
|
2017-02-13 00:38:33 +00:00
|
|
|
toId(req.params['id']),
|
2017-03-28 23:29:22 +00:00
|
|
|
await req.lazyBody(),
|
2017-02-13 00:38:33 +00:00
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
2017-02-23 00:37:15 +00:00
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
2017-02-13 00:38:33 +00:00
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(modifyMiddleware == null) ? [] : modifyMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware updateMiddleware = getAnnotation(this.update, Middleware);
|
2016-06-23 19:05:55 +00:00
|
|
|
post(
|
|
|
|
'/:id',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.update(
|
2017-02-13 00:38:33 +00:00
|
|
|
toId(req.params['id']),
|
2017-03-28 23:29:22 +00:00
|
|
|
await req.lazyBody(),
|
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
2017-03-28 23:29:22 +00:00
|
|
|
put(
|
|
|
|
'/:id',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.update(
|
2017-03-28 23:29:22 +00:00
|
|
|
toId(req.params['id']),
|
|
|
|
await req.lazyBody(),
|
2017-02-13 00:38:33 +00:00
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
2017-02-23 00:37:15 +00:00
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
2017-02-13 00:38:33 +00:00
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(updateMiddleware == null) ? [] : updateMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
|
2016-09-15 19:53:01 +00:00
|
|
|
Middleware removeMiddleware = getAnnotation(this.remove, Middleware);
|
2017-03-28 23:29:22 +00:00
|
|
|
delete(
|
|
|
|
'/',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.remove(
|
2017-03-28 23:29:22 +00:00
|
|
|
null,
|
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
2016-06-23 19:05:55 +00:00
|
|
|
delete(
|
|
|
|
'/:id',
|
2017-06-06 12:42:33 +00:00
|
|
|
(req, res) async =>
|
|
|
|
await this.remove(
|
2017-02-13 00:38:33 +00:00
|
|
|
toId(req.params['id']),
|
|
|
|
mergeMap([
|
|
|
|
{'query': req.query},
|
2017-02-23 00:37:15 +00:00
|
|
|
restProvider,
|
|
|
|
req.serviceParams
|
2017-02-13 00:38:33 +00:00
|
|
|
])),
|
2017-06-06 12:42:33 +00:00
|
|
|
middleware: []..addAll(handlers)..addAll(
|
|
|
|
(removeMiddleware == null) ? [] : removeMiddleware.handlers));
|
2017-03-28 23:29:22 +00:00
|
|
|
|
|
|
|
// REST compliance
|
|
|
|
put('/', () => throw new AngelHttpException.notFound());
|
|
|
|
patch('/', () => throw new AngelHttpException.notFound());
|
Angel.secure, fallback routes, 404, app.addRoute, app.all, services are a go (just missing params, i.e. $sort?), now have service.app, app.before, app.after, angel.configure now uses futures, errors are implemented
2016-04-29 00:01:58 +00:00
|
|
|
}
|
2017-02-23 00:37:15 +00:00
|
|
|
|
|
|
|
/// Invoked when this service is wrapped within a [HookedService].
|
|
|
|
void onHooked(HookedService hookedService) {}
|
2016-06-23 19:05:55 +00:00
|
|
|
}
|