1.1.0-alpha

This commit is contained in:
Tobe O 2017-10-19 17:53:33 -04:00
parent d7076144bf
commit ae2e2db6d7
35 changed files with 244 additions and 733 deletions

1
.gitignore vendored
View file

@ -87,3 +87,4 @@ fabric.properties
logs/ logs/
*.pem *.pem
.DS_Store .DS_Store
server_log.txt

View file

@ -4,20 +4,12 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" /> <excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" /> <excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/bin/packages" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/temp" /> <excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/test/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/services/packages" />
<excludeFolder url="file://$MODULE_DIR$/tmp" /> <excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/tool/packages" />
<excludeFolder url="file://$MODULE_DIR$/web/images/packages" />
<excludeFolder url="file://$MODULE_DIR$/web/packages" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" /> <orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component> </component>
</module> </module>

View file

@ -1,11 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Multi-Threaded Server (PRODUCTION)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="envs">
<entry key="ANGEL_ENV" value="production" />
</option>
<option name="filePath" value="$PROJECT_DIR$/bin/scaled_server.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start Server" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="VMOptions" value="--enable-vm-service" />
<option name="filePath" value="$PROJECT_DIR$/bin/server.dart" />
<method />
</configuration>
</component>

View file

@ -1,10 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start Server (PRODUCTION)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true">
<option name="checkedMode" value="false" />
<option name="envs">
<entry key="ANGEL_ENV" value="production" />
</option>
<option name="filePath" value="$PROJECT_DIR$/bin/server.dart" />
<method />
</configuration>
</component>

View file

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Users Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/services/users_test.dart" />
<method />
</configuration>
</component>

View file

@ -0,0 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="dev.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="VMOptions" value="--observe" />
<option name="filePath" value="$PROJECT_DIR$/bin/dev.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -1,10 +1,10 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Load-Balanced Server (PRODUCTION)" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true"> <configuration default="false" name="prod.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="checkedMode" value="false" /> <option name="checkedMode" value="false" />
<option name="envs"> <option name="envs">
<entry key="ANGEL_ENV" value="production" /> <entry key="ANGEL_ENV" value="production" />
</option> </option>
<option name="filePath" value="$PROJECT_DIR$/bin/multi_server.dart" /> <option name="filePath" value="$PROJECT_DIR$/bin/prod.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" /> <option name="workingDirectory" value="$PROJECT_DIR$" />
<method /> <method />
</configuration> </configuration>

View file

@ -1,7 +1,7 @@
[![The Angel Framework](https://angel-dart.github.io/images/logo.png)](https://angel-dart.github.io) [![The Angel Framework](https://angel-dart.github.io/images/logo.png)](https://angel-dart.github.io)
[![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion) [![Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg)](https://gitter.im/angel_dart/discussion)
[![version: v1.0.0](https://img.shields.io/badge/pub-v1.0.0-brightgreen.svg)](https://pub.dartlang.org/packages/angel_common) [![Pub](https://img.shields.io/pub/v/angel_framework.svg)](https://pub.dartlang.org/packages/angel_framework)
**Fill out the [v1.0.0 survey](https://docs.google.com/forms/d/e/1FAIpQLSfEgBNsOoi_nYZMmg2IAGyMv1nNaa6B3kUk3QdNJU5987ucVA/viewform?usp=sf_link) now!!!** **Fill out the [v1.0.0 survey](https://docs.google.com/forms/d/e/1FAIpQLSfEgBNsOoi_nYZMmg2IAGyMv1nNaa6B3kUk3QdNJU5987ucVA/viewform?usp=sf_link) now!!!**
@ -46,7 +46,7 @@ angel init hello
You can even have your server run and be *live-reloaded* on file changes: You can even have your server run and be *live-reloaded* on file changes:
```bash ```bash
dart bin/server.dart dart bin/dev.dart
``` ```
Next, check out the [detailed documentation](https://github.com/angel-dart/angel/wiki) to learn to flesh out your project. Next, check out the [detailed documentation](https://github.com/angel-dart/angel/wiki) to learn to flesh out your project.
@ -60,57 +60,4 @@ With features like the following, Angel is the all-in-one framework you should c
* And [much more](https://github.com/angel-dart)... * And [much more](https://github.com/angel-dart)...
## Basic Example ## Basic Example
More examples and complete projects can be found in the [angel-example](https://github.com/angel-example) organization. Examples and complete projects can be found in the [angel-example](https://github.com/angel-example) organization.
The following is an [explosive application](https://github.com/angel-example/explode) complete with a REST API and
WebSocket support. It interacts with a MongoDB database, and reads configuration automatically from a `config/<ANGEL-ENV-NAME>.yaml` file. Templates are rendered with Mustache, and all responses are compressed via GZIP.
**All in just about 20 lines of actual code.**
```dart
import 'dart:async';
import 'package:angel_common/angel_common.dart';
import 'package:angel_websocket/server.dart';
import 'package:mongo_dart/mongo_dart.dart';
main() async {
var app = await createServer();
var server = await app.startServer(InternetAddress.LOOPBACK_IP_V4, 8080);
print('Angel listening at http://${server.address.address}:${server.port}');
}
Future<Angel> createServer() async {
// New instance...
var app = new Angel();
// Configuration
await app.configure(loadConfigurationFile());
await app.configure(mustache());
Db db = new Db();
await db.open(app.mongodb_url);
app.container.singleton(db); // Add to DI
// Routes
app.get('/foo', (req, res) => res.render('hello'));
app.post('/query', (Db db) async {
// Db is an injected dependency here :)
return await db.collection('foo').find().toList();
});
// Services (which build REST API's and are broadcasted over WS)
app.use('/bombs', new MongoService(db.collection('bombs')));
app.use('/users', new MongoService(db.collection('users')));
app.use('/explosions', new AnonymousService(create: (data, [params]) => data));
// Setup WebSockets, add GZIP, etc.
await app.configure(new AngelWebSocket());
app.responseFinalizers.add(gzip());
return app;
}
```
## Get Social
Join an activate chat about the Angel Framework, or seek assistance: https://gitter.im/angel_dart/discussion

View file

@ -1,11 +0,0 @@
/// This should be used with `multiserver` as an entry
/// point for `spawnIsolates`.
library angel.cluster;
import 'dart:async';
import 'common.dart';
import 'dart:isolate';
main(args, SendPort sendPort) async {
runZoned(startServer(args, sendPort: sendPort), onError: onError);
}

View file

@ -1,65 +0,0 @@
import 'dart:io';
import 'dart:isolate';
import 'package:angel/angel.dart';
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_hot/angel_hot.dart';
import 'package:intl/intl.dart';
/// Start a single instance of this application.
///
/// If a [sendPort] is provided, then the URL of the mounted server will be sent through the port.
/// Use this if you are starting multiple instances of your server.
startServer(args, {SendPort sendPort}) {
return () async {
var app = await createServer();
var dateFormat = new DateFormat("y-MM-dd");
var logFile = new File("logs/${dateFormat.format(new DateTime.now())}.txt");
InternetAddress host;
int port;
// Load the right host and port from application config.
host = new InternetAddress(app.properties['host']);
// Listen on port 0 if we are using the load balancer.
port = sendPort != null ? 0 : app.properties['port'];
// Log requests and errors to a log file.
await app.configure(logRequests(logFile));
HttpServer server;
// Use `package:angel_hot` in any case, EXCEPT if starting in production mode.
//
// With hot-reloading, our server will automatically reload in-place on file changes,
// for a faster development cycle. :)
if (Platform.environment['ANGEL_ENV'] == 'production')
server = await app.startServer(host, port);
else {
var hot = new HotReloader(() async {
// If we are hot-reloading, we need to provide a callback
// to use to start a fresh instance on-the-fly.
var app = await createServer();
await app.configure(logRequests(logFile));
return app;
},
// Paths we might want to listen for changes on...
[
new Directory('config'),
new Directory('lib'),
new Directory('views')
]);
server = await hot.startServer(host, port);
}
if (sendPort == null) {
print('Listening at http://${server.address.address}:${server.port}');
} else
sendPort?.send([server.address.address, server.port]);
};
}
onError(error, [StackTrace stackTrace]) {
stderr.writeln("Unhandled error occurred: $error");
if (stackTrace != null) {
stderr.writeln(stackTrace);
}
}

27
bin/dev.dart Normal file
View file

@ -0,0 +1,27 @@
import 'dart:io';
import 'package:angel/angel.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_hot/angel_hot.dart';
import 'package:logging/logging.dart';
main() async {
// Watch the config/ and web/ directories for changes, and hot-reload the server.
var hot = new HotReloader(() async {
var app = new Angel()..lazyParseBodies = true;
await app.configure(configureServer);
app.logger = new Logger('angel')..onRecord.listen((rec) {
print(rec);
if (rec.error != null) {
print(rec.error);
print(rec.stackTrace);
}
});
return app;
}, [
new Directory('config'),
new Directory('lib'),
]);
var server = await hot.startServer('127.0.0.1', 3000);
print('Listening at http://${server.address.address}:${server.port}');
}

View file

@ -1,43 +0,0 @@
#!/usr/bin/env dart
/// This is intended to replace Nginx in your web stack.
/// Either use this or another reverse proxy, but there is no real
/// reason to use them together.
///
/// In most cases, you should run `scaled_server.dart` instead of this file.
library angel.multi_server;
import 'dart:io';
import 'package:angel_compress/angel_compress.dart';
import 'package:angel_multiserver/angel_multiserver.dart';
final Uri cluster = Platform.script.resolve('cluster.dart');
/// The number of isolates to spawn. You might consider starting one instance
/// per processor core on your machine.
final int nInstances = Platform.numberOfProcessors;
main() async {
var app = new LoadBalancer();
// Or, for SSL:
// var app = new LoadBalancer.secure('<server-chain>', '<server-key>');
// Response compression!
app.responseFinalizers.add(gzip());
// Cache static assets - just to lower response time
await app.configure(cacheResponses(filters: [new RegExp(r'images/.*')]));
// Start up multiple instances of our main application.
await app.spawnIsolates(cluster, count: nInstances);
app.onCrash.listen((_) async {
// Boot up a new instance on crash
await app.spawnIsolates(cluster);
});
var host = InternetAddress.ANY_IP_V4;
var port = 3000;
var server = await app.startServer(host, port);
print('Listening at http://${server.address.address}:${server.port}');
print('Load-balancing $nInstances instance(s)');
}

45
bin/prod.dart Normal file
View file

@ -0,0 +1,45 @@
import 'dart:io';
import 'dart:isolate';
import 'package:angel/angel.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:logging/logging.dart';
const String hostname = '127.0.0.1';
const int port = 3000;
void main() {
// Start a server instance in multiple isolates.
for (int id = 0; id < Platform.numberOfProcessors; id++)
Isolate.spawn(isolateMain, id);
isolateMain(Platform.numberOfProcessors);
}
void isolateMain(int id) {
// Passing `startShared` to the constructor allows us to start multiple
// instances of our application concurrently, listening on a single port.
//
// This effectively lets us multi-thread the application.
var app = new Angel.custom(startShared);
app.configure(configureServer).then((_) async {
// In production, we'll want to log errors to a file.
// Alternatives include sending logs to a service like Sentry.
app.logger = new Logger('angel')
..onRecord.listen((rec) {
if (rec.error != null) {
var sink =
new File('server_log.txt').openWrite(mode: FileMode.APPEND);
sink
..writeln(rec.error)
..writeln(rec.stackTrace)
..close();
}
});
var server = await app.startServer(hostname, port);
print(
'Instance #$id listening at http://${server.address.address}:${server.port}');
});
}

View file

@ -1,71 +0,0 @@
#!/usr/bin/env dart
/// Most Angel applications will not need to use the load balancer.
/// Instead, you can start up a multi-threaded cluster.
library angel.scaled_server;
import 'dart:io';
import 'dart:isolate';
import 'package:angel_compress/angel_compress.dart';
import 'package:angel_diagnostics/angel_diagnostics.dart';
import 'package:angel_multiserver/angel_multiserver.dart';
import 'package:angel/angel.dart';
/// The number of isolates to spawn. You might consider starting one instance
/// per processor core on your machine.
final int nInstances = Platform.numberOfProcessors;
main() {
var startupPort = new ReceivePort();
List<String> startupMessages = [];
// Start up multiple instances of our application.
for (int i = 0; i < nInstances; i++) {
Isolate.spawn(isolateMain, [i, startupPort.sendPort]);
}
int nStarted = 0;
// Listen for notifications of application startup...
startupPort.listen((String startupMessage) {
startupMessages.add(startupMessage);
if (++nStarted == nInstances) {
// Keep track of how many instances successfully started up,
// and print a success message when they all boot.
startupMessages.forEach(print);
print('Spawned $nInstances instance(s) of Angel.');
}
});
}
void isolateMain(List args) {
int instanceId = args[0];
SendPort startupPort = args[1];
createServer().then((app) async {
// Response compression via GZIP.
//
// See the documentation here:
// https://github.com/angel-dart/compress
app.responseFinalizers.add(gzip());
// Cache static assets - just to lower response time.
//
// See the documentation here:
// https://github.com/angel-dart/multiserver
//
// Here is an example of response caching:
// https://github.com/angel-dart/multiserver/blob/master/example/cache.dart
await app.configure(cacheResponses(filters: [new RegExp(r'images/.*')]));
var server = await app.startServer(
InternetAddress.ANY_IP_V4, app.properties['port'] ?? 3000);
// Print request and error information to the console.
await app.configure(logRequests());
// Send a notification back to the main isolate
startupPort.send('Instance #$instanceId listening at http://${server.address.address}:${server.port}');
});
}

View file

@ -1,7 +0,0 @@
#!/usr/bin/env dart
import 'dart:async';
import 'common.dart';
main(args) async {
runZoned(startServer(args), onError: onError);
}

View file

@ -2,23 +2,20 @@
library angel; library angel;
import 'dart:async'; import 'dart:async';
import 'package:angel_common/angel_common.dart'; import 'package:angel_cors/angel_cors.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:file/local.dart';
import 'src/config/config.dart' as configuration; import 'src/config/config.dart' as configuration;
import 'src/routes/routes.dart' as routes; import 'src/routes/routes.dart' as routes;
import 'src/services/services.dart' as services; import 'src/services/services.dart' as services;
/// Creates and configures the server instance. /// Configures the server instance.
Future<Angel> createServer() async { Future configureServer(Angel app) async {
/// Passing `startShared` to the constructor allows us to start multiple // Enable CORS
/// instances of our application concurrently, listening on a single port. app.use(cors());
///
/// This effectively lets us multi-thread the application.
var app = new Angel.custom(startShared);
/// Set up our application, using three plug-ins defined with this project. // Set up our application, using the plug-ins defined with this project.
await app.configure(configuration.configureServer); await app.configure(configuration.configureServer(const LocalFileSystem()));
await app.configure(services.configureServer); await app.configure(services.configureServer);
await app.configure(routes.configureServer); await app.configure(routes.configureServer(const LocalFileSystem()));
return app;
} }

View file

@ -1,29 +1,33 @@
/// Configuration for this Angel instance. /// Configuration for this Angel instance.
library angel.config; library angel.src.config;
import 'dart:io'; import 'package:angel_configuration/angel_configuration.dart';
import 'package:angel_common/angel_common.dart'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_jael/angel_jael.dart';
import 'package:file/file.dart';
import 'plugins/plugins.dart' as plugins; import 'plugins/plugins.dart' as plugins;
/// This is a perfect place to include configuration and load plug-ins. /// This is a perfect place to include configuration and load plug-ins.
configureServer(Angel app) async { AngelConfigurer configureServer(FileSystem fileSystem) {
// Load configuration from the `config/` directory. return (Angel app) async {
// // Load configuration from the `config/` directory.
// See: https://github.com/angel-dart/configuration //
await app.configure(loadConfigurationFile()); // See: https://github.com/angel-dart/configuration
await app.configure(configuration(fileSystem));
// Configure our application to render Mustache templates from the `views/` directory. // Configure our application to render Jael templates from the `views/` directory.
// //
// See: https://github.com/angel-dart/mustache // See: https://github.com/angel-dart/jael
await app.configure(mustache(new Directory('views'))); await app.configure(jael(fileSystem.directory('views')));
// Apply another plug-ins, i.e. ones that *you* have written. // Apply another plug-ins, i.e. ones that *you* have written.
// //
// Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins // Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins
// that add functionality specific to your application. // that add functionality specific to your application.
// //
// If you write a plug-in that you plan to use again, or are // If you write a plug-in that you plan to use again, or are
// using one created by the community, include it in // using one created by the community, include it in
// `lib/src/config/config.dart`. // `lib/src/config/config.dart`.
await plugins.configureServer(app); await plugins.configureServer(app);
};
} }

View file

@ -1,8 +1,8 @@
/// Custom plugins go here. /// Custom plugins go here.
library angel.config.plugins; library angel.src.config.plugins;
import 'dart:async'; import 'dart:async';
import 'package:angel_common/angel_common.dart'; import 'package:angel_framework/angel_framework.dart';
Future configureServer(Angel app) async { Future configureServer(Angel app) async {
// Include any plugins you have made here. // Include any plugins you have made here.

View file

@ -1,3 +0,0 @@
library angel.models;
export 'user.dart';

View file

@ -1,40 +0,0 @@
library angel.models.user;
import 'package:angel_framework/common.dart';
// Model classes in Angel, as a convention, should extend the `Model`
// class.
//
// Angel doesn't box you into using a specific ORM. In fact, you might not
// need one at all.
//
// The out-of-the-box configuration for Angel is to assume
// all data you handle is a Map. You might consider leaving it this way, and just
// (de)serializing data when you need typing support.
//
// If you use a `TypedService`, then Angel will perform (de)serialization for you automatically:
// https://github.com/angel-dart/angel/wiki/TypedService
//
// You also have the option of using a source-generated serialization library.
// Consider `package:owl` (not affiliated with Angel):
// https://github.com/agilord/owl
//
// The `Model` class has no server-side dependency, and thus you can use it as-is, cross-platform.
// This is good for full-stack applications, as you do not have to maintain duplicate class files. ;)
class User extends Model {
@override
String id;
String email, username, password, salt;
@override
DateTime createdAt, updatedAt;
User(
{this.id,
this.email,
this.username,
this.password,
this.salt,
this.createdAt,
this.updatedAt});
}

View file

@ -1,58 +0,0 @@
library angel.routes.controllers.auth;
import 'package:angel_common/angel_common.dart';
import '../../services/user.dart';
/// Configures the application to authenticate users securely.
/// See the documentation for controllers:
///
/// https://github.com/angel-dart/angel/wiki/Controllers
@Expose('/auth')
class AuthController extends Controller {
/// Controls application authentication.
///
/// See the documentation:
/// * https://medium.com/the-angel-framework/logging-users-in-to-angel-applications-ccf32aba0dac
/// * https://github.com/angel-dart/auth
AngelAuth auth;
/// Clients will see the result of `deserializer`, so let's pretend to be a client.
///
/// Our User service is already wired to remove sensitive data from serialized JSON.
deserializer(String id) async =>
app.service('api/users').read(id, {'provider': Providers.REST});
serializer(User user) async => user.id;
/// Attempts to log a user in.
LocalAuthVerifier localVerifier(Service userService) {
return (String username, String password) async {
Iterable<User> users = await userService.index({
'query': {'username': username}
});
if (users.isNotEmpty) {
return users.firstWhere((user) {
var hash = hashPassword(password, user.salt, app.jwt_secret);
return user.username == username && user.password == hash;
}, orElse: () => null);
}
};
}
@override
call(Angel app) async {
// Wire up local authentication, connected to our User service
auth = new AngelAuth(jwtKey: app.jwt_secret)
..serializer = serializer
..deserializer = deserializer
..strategies
.add(new LocalAuthStrategy(localVerifier(app.service('api/users'))));
await super.call(app);
await app.configure(auth);
}
@Expose('/local', method: 'POST')
login() => auth.authenticate('local');
}

View file

@ -1,9 +1,8 @@
library angel.routes.controllers; library angel.src.routes.controllers;
import 'package:angel_common/angel_common.dart'; import 'dart:async';
import 'auth.dart'; import 'package:angel_framework/angel_framework.dart';
configureServer(Angel app) async { Future configureServer(Angel app) async {
/// Controllers will not function unless wired to the application! /// Controllers will not function unless wired to the application!
await app.configure(new AuthController());
} }

View file

@ -1,138 +1,55 @@
/// This app's route configuration. /// This app's route configuration.
library angel.routes; library angel.src.routes;
import 'dart:io'; import 'package:angel_framework/angel_framework.dart';
import 'package:angel_common/angel_common.dart'; import 'package:angel_static/angel_static.dart';
import 'package:file/file.dart';
import 'controllers/controllers.dart' as controllers; import 'controllers/controllers.dart' as controllers;
/// Adds global middleware to the application.
///
/// Use these to apply functionality to requests before business logic is run.
///
/// More on the request lifecycle:
/// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureBefore(Angel app) async {
app.before.add(cors());
}
/// Put your app routes here! /// Put your app routes here!
/// ///
/// See the wiki for information about routing, requests, and responses: /// See the wiki for information about routing, requests, and responses:
/// * https://github.com/angel-dart/angel/wiki/Basic-Routing /// * https://github.com/angel-dart/angel/wiki/Basic-Routing
/// * https://github.com/angel-dart/angel/wiki/Requests-&-Responses /// * https://github.com/angel-dart/angel/wiki/Requests-&-Responses
configureRoutes(Angel app) async { AngelConfigurer configureServer(FileSystem fileSystem) {
// Render `views/hello.mustache` when a user visits the application root. return (Angel app) async {
app.get('/', (req, ResponseContext res) => res.render('hello')); // Typically, you want to mount controllers first, after any global middleware.
} await app.configure(controllers.configureServer);
/// Configures fallback middleware. // Render `views/hello.jl` when a user visits the application root.
/// app.get(
/// Use these to run generic actions on requests that were not terminated by '/', (RequestContext req, ResponseContext res) => res.render('hello'));
/// earlier request handlers.
///
/// Note that these middleware might not always run.
///
/// More on the request lifecycle: https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureAfter(Angel app) async {
// Uncomment this to proxy over `pub serve` while in development.
// This is a useful feature for full-stack applications, especially if you
// are using Angular2.
//
// For documentation, see `package:angel_proxy`:
// https://github.com/angel-dart/proxy
//
// await app.configure(new PubServeLayer());
// Mount static server at /web or /build/web, depending on if // Mount static server at web in development.
// you are running in production mode. `Cache-Control` headers will also be enabled. // This variant of `VirtualDirectory` also sends `Cache-Control` headers.
//
// Read the following two sources for documentation:
// * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae
// * https://github.com/angel-dart/static
await app.configure(new CachingVirtualDirectory());
// Set our application up to handle different errors.
//
// Read the following two sources for documentation:
// * https://github.com/angel-dart/angel/wiki/Error-Handling
// * https://github.com/angel-dart/errors
var errors = new ErrorHandler(handlers: {
// Display a 404 page if no resource is found.
404: (req, res) async =>
res.render('error', {'message': 'No file exists at ${req.path}.'}),
// On generic errors, give information about why the application failed.
// //
// An `AngelHttpException` instance will be present in `req.properties` // In production, however, prefer serving static files through NGINX or a
// as `error`. // similar reverse proxy.
500: (req, res) async => res.render('error', {'message': req.error.message}) //
}); // Read the following two sources for documentation:
// * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae
// * https://github.com/angel-dart/static
var vDir = new CachingVirtualDirectory(
app,
fileSystem,
source: fileSystem.directory('web'),
);
app.use(vDir.handleRequest);
// Use a fatal error handler to attempt to resolve any issues that // Throw a 404 if no route matched the request.
// result in Angel not being able to send the user a response. app.use(() => throw new AngelHttpException.notFound());
errors.fatalErrorHandler = (AngelFatalError e) async {
try {
// Manually create a request and response context.
var req = await RequestContext.from(e.request, app);
var res = new ResponseContext(e.request.response, app);
// *Attempt* to render an error page. // Set our application up to handle different errors.
res.render('error', {'message': 'Internal Server Error: ${e.error}'}); //
await app.sendResponse(e.request, req, res); // Read the following for documentation:
} catch (_) { // * https://github.com/angel-dart/angel/wiki/Error-Handling
// If execution fails here, there is nothing we can do. app.errorHandler = (e, req, res) async {
stderr..writeln('Fatal error: ${e.error}')..writeln(e.stack); if (e.statusCode == 404) {
} return await res
.render('error', {'message': 'No file exists at ${req.path}.'});
}
return await res.render('error', {'message': e.message});
};
}; };
// Throw a 404 if no route matched the request.
app.after.add(() => throw new AngelHttpException.notFound());
// Handle errors when they occur, based on outgoing status code.
// By default, requests will go through the 500 handler, unless
// they have an outgoing 200, or their status code has a handler
// registered.
app.after.add(errors.middleware());
// Pass AngelHttpExceptions through handler as well.
//
// Again, here is the error handling documentation:
// * https://github.com/angel-dart/angel/wiki/Error-Handling
// * https://github.com/angel-dart/errors
await app.configure(errors);
}
/// Adds response finalizers to the application.
///
/// These run after every request.
///
/// See more on the request lifecycle here:
/// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
configureFinalizers(Angel app) async {}
/// Adds routes to our application.
///
/// See the wiki for information about routing, requests, and responses:
/// * https://github.com/angel-dart/angel/wiki/Basic-Routing
/// * https://github.com/angel-dart/angel/wiki/Requests-&-Responses
configureServer(Angel app) async {
// The order in which we run these plug-ins is relatively significant.
// Try not to change it.
// Add global middleware.
await configureBefore(app);
// Typically, you want to mount controllers first, after any global middleware.
await app.configure(controllers.configureServer);
// Next, you can add any supplemental routes.
await configureRoutes(app);
// Add handlers to run after requests are handled.
//
// See the request lifecycle docs to find out why these two
// are separate:
// https://github.com/angel-dart/angel/wiki/Request-Lifecycle
await configureAfter(app);
await configureFinalizers(app);
} }

View file

@ -1,8 +1,8 @@
/// Declare services here! /// Declare services here!
library angel.services; library angel.services;
import 'package:angel_common/angel_common.dart'; import 'dart:async';
import 'user.dart' as user; import 'package:angel_framework/angel_framework.dart';
/// Configure our application to use *services*. /// Configure our application to use *services*.
/// Services must be wired to the app via `app.use`. /// Services must be wired to the app via `app.use`.
@ -12,6 +12,4 @@ import 'user.dart' as user;
/// ///
/// Read more here: /// Read more here:
/// https://github.com/angel-dart/angel/wiki/Service-Basics /// https://github.com/angel-dart/angel/wiki/Service-Basics
configureServer(Angel app) async { Future configureServer(Angel app) async {}
await app.configure(user.configureServer());
}

View file

@ -1,58 +0,0 @@
import 'package:angel_common/angel_common.dart';
import 'package:angel_framework/hooks.dart' as hooks;
import 'package:crypto/crypto.dart' show sha256;
import 'package:random_string/random_string.dart' as rs;
import '../models/user.dart';
import '../validators/user.dart';
export '../models/user.dart';
/// Sets up a service mounted at `api/users`.
///
/// In the real world, you will want to hook this up to a database.
/// However, for the sake of the boilerplate, an in-memory service is used,
/// so that users are not tied into using just one database. :)
configureServer() {
return (Angel app) async {
// A TypedService can be used to serialize and deserialize data to a class, somewhat like an ORM.
//
// See here: https://github.com/angel-dart/angel/wiki/TypedService
app.use('/api/users', new TypedService<User>(new MapService()));
// Configure hooks for the user service.
// Hooks can be used to add additional functionality, or change the behavior
// of services, and run on any service, regardless of which database you are using.
//
// If you have not already, *definitely* read the service hook documentation:
// https://github.com/angel-dart/angel/wiki/Hooks
var service = app.service('api/users') as HookedService;
// Prevent clients from doing anything to the `users` service,
// apart from reading a single user's data.
service.before([
HookedServiceEvent.INDEXED,
HookedServiceEvent.CREATED,
HookedServiceEvent.MODIFIED,
HookedServiceEvent.UPDATED,
HookedServiceEvent.REMOVED
], hooks.disable());
// Validate new users, and also hash their passwords.
service.beforeCreated
// ..listen(validateEvent(CREATE_USER))
..listen((e) {
var salt = rs.randomAlphaNumeric(12);
e.data
..['password'] =
hashPassword(e.data['password'], salt, app.jwt_secret)
..['salt'] = salt;
});
// Remove sensitive data from serialized JSON.
service.afterAll(hooks.remove(['password', 'salt']));
};
}
/// SHA-256 hash any string, particularly a password.
String hashPassword(String password, String salt, String pepper) =>
sha256.convert(('$salt:$password:$pepper').codeUnits).toString();

View file

@ -4,14 +4,21 @@ publish_to: none # Ensure we don't accidentally publish our private code! ;)
environment: environment:
sdk: ">=1.19.0" sdk: ">=1.19.0"
homepage: https://github.com/angel-dart/angel homepage: https://github.com/angel-dart/angel
dependencies: dependencies:
angel_common: ^1.0.0 # Bundles the most commonly-used Angel packages. angel_framework: ^1.1.0-alpha # The core server library.
angel_configuration: ^1.0.0 # Included in `angel_common`, but also exposes a transformer angel_serialize: ^1.0.0-alpha # Model definition metadata.
angel_hot: ^1.0.0-rc.1 # Hot-reloading support. :)
angel_multiserver: ^1.0.0 # Helpful for applications running a scale. angel_auth: ^1.1.0-alpha # Supports stateless authentication via JWT
angel_configuration: ^1.1.0-alpha # Loads application configuration, along with support for .env files.
angel_cors: ^1.0.0 # CORS support
angel_hot: ^1.1.0-alpha # Hot-reloading support. :)
angel_jael: ^1.0.0-alpha # Server-side templating engine
angel_static: ^1.3.0-alpha # Static file server
dev_dependencies: dev_dependencies:
angel_serialize_generator: ^1.0.0-alpha # Generates serialization code for models.
angel_test: ^1.1.0-alpha # Utilities for testing Angel servers.
build_runner: ^0.5.0
grinder: ^0.8.0 grinder: ^0.8.0
http: ^0.11.3
test: ^0.12.13 test: ^0.12.13
transformers: transformers:
# Injects data from application configuration into Dart code. # Injects data from application configuration into Dart code.

View file

@ -1,5 +1,5 @@
import 'dart:io';
import 'package:angel/angel.dart'; import 'package:angel/angel.dart';
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_test/angel_test.dart'; import 'package:angel_test/angel_test.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
@ -23,7 +23,9 @@ main() async {
TestClient client; TestClient client;
setUp(() async { setUp(() async {
var app = await createServer(); var app = new Angel();
await app.configure(configureServer);
client = await connectTo(app); client = await connectTo(app);
}); });
@ -31,12 +33,11 @@ main() async {
await client.close(); await client.close();
}); });
test('index users', () async { test('index returns 200', () async {
// Request a resource at the given path. // Request a resource at the given path.
var response = await client.get('/api/users'); var response = await client.get('/');
// By default, we locked this away from the Internet... // Expect a 200 response.
// Expect a 403 response. expect(response, hasStatus(200));
expect(response, hasStatus(HttpStatus.FORBIDDEN));
}); });
} }

5
views/error.jl Normal file
View file

@ -0,0 +1,5 @@
<extend src="layout.jl">
<block name="content">
<div class="title">{{ message }}</div>
</block>
</extend>

View file

@ -1,50 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
<link rel="icon" href="/images/favicon.png">
<style>
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
display: table;
font-weight: 100;
font-family: 'Lato', sans-serif;
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 96px;
}
.subtitle {
font-size: 32px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">{{message}}</div>
</div>
</div>
</body>
</html>

5
views/hello.jl Normal file
View file

@ -0,0 +1,5 @@
<extend src="layout.jl">
<block name="content">
<div class="title">Angel</div>
</block>
</extend>

View file

@ -1,46 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Angel</title>
<link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
<link rel="icon" href="/images/favicon.png">
<style>
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
display: table;
font-weight: 100;
font-family: 'Lato', sans-serif;
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 96px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">Angel</div>
</div>
</div>
</body>
</html>

17
views/layout.jl Normal file
View file

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title ?? 'Angel' }}</title>
<link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="/css/site.css">
<link rel="icon" href="/images/favicon.png">
</head>
<body>
<div class="container">
<div class="content">
<block name="content"></block>
</div>
</div>
</body>
</html>

27
web/css/site.css Normal file
View file

@ -0,0 +1,27 @@
html, body {
height: 100%;
}
body {
margin: 0;
padding: 0;
width: 100%;
display: table;
font-weight: 100;
font-family: 'Lato', sans-serif;
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle;
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 96px;
}