1.1.0-alpha
This commit is contained in:
parent
d7076144bf
commit
ae2e2db6d7
35 changed files with 244 additions and 733 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -87,3 +87,4 @@ fabric.properties
|
||||||
logs/
|
logs/
|
||||||
*.pem
|
*.pem
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
server_log.txt
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
8
.idea/runConfigurations/dev_dart.xml
Normal file
8
.idea/runConfigurations/dev_dart.xml
Normal 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>
|
|
@ -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>
|
59
README.md
59
README.md
|
@ -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
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
27
bin/dev.dart
Normal 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}');
|
||||||
|
}
|
|
@ -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
45
bin/prod.dart
Normal 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}');
|
||||||
|
});
|
||||||
|
}
|
|
@ -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}');
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env dart
|
|
||||||
import 'dart:async';
|
|
||||||
import 'common.dart';
|
|
||||||
|
|
||||||
main(args) async {
|
|
||||||
runZoned(startServer(args), onError: onError);
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
/// 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) {
|
||||||
|
return (Angel app) async {
|
||||||
// Load configuration from the `config/` directory.
|
// Load configuration from the `config/` directory.
|
||||||
//
|
//
|
||||||
// See: https://github.com/angel-dart/configuration
|
// See: https://github.com/angel-dart/configuration
|
||||||
await app.configure(loadConfigurationFile());
|
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.
|
||||||
//
|
//
|
||||||
|
@ -26,4 +29,5 @@ configureServer(Angel app) async {
|
||||||
// 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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
library angel.models;
|
|
||||||
|
|
||||||
export 'user.dart';
|
|
|
@ -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});
|
|
||||||
}
|
|
|
@ -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');
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
//
|
||||||
|
// In production, however, prefer serving static files through NGINX or a
|
||||||
|
// similar reverse proxy.
|
||||||
//
|
//
|
||||||
// Read the following two sources for documentation:
|
// Read the following two sources for documentation:
|
||||||
// * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae
|
// * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae
|
||||||
// * https://github.com/angel-dart/static
|
// * https://github.com/angel-dart/static
|
||||||
await app.configure(new CachingVirtualDirectory());
|
var vDir = new CachingVirtualDirectory(
|
||||||
|
app,
|
||||||
|
fileSystem,
|
||||||
|
source: fileSystem.directory('web'),
|
||||||
|
);
|
||||||
|
app.use(vDir.handleRequest);
|
||||||
|
|
||||||
|
// Throw a 404 if no route matched the request.
|
||||||
|
app.use(() => throw new AngelHttpException.notFound());
|
||||||
|
|
||||||
// Set our application up to handle different errors.
|
// Set our application up to handle different errors.
|
||||||
//
|
//
|
||||||
// Read the following two sources for documentation:
|
// Read the following for documentation:
|
||||||
// * https://github.com/angel-dart/angel/wiki/Error-Handling
|
// * https://github.com/angel-dart/angel/wiki/Error-Handling
|
||||||
// * https://github.com/angel-dart/errors
|
app.errorHandler = (e, req, res) async {
|
||||||
var errors = new ErrorHandler(handlers: {
|
if (e.statusCode == 404) {
|
||||||
// Display a 404 page if no resource is found.
|
return await res
|
||||||
404: (req, res) async =>
|
.render('error', {'message': 'No file exists at ${req.path}.'});
|
||||||
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`
|
|
||||||
// as `error`.
|
|
||||||
500: (req, res) async => res.render('error', {'message': req.error.message})
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use a fatal error handler to attempt to resolve any issues that
|
|
||||||
// result in Angel not being able to send the user a response.
|
|
||||||
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.
|
|
||||||
res.render('error', {'message': 'Internal Server Error: ${e.error}'});
|
|
||||||
await app.sendResponse(e.request, req, res);
|
|
||||||
} catch (_) {
|
|
||||||
// If execution fails here, there is nothing we can do.
|
|
||||||
stderr..writeln('Fatal error: ${e.error}')..writeln(e.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
|
@ -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();
|
|
17
pubspec.yaml
17
pubspec.yaml
|
@ -5,13 +5,20 @@ 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.
|
||||||
|
|
|
@ -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
5
views/error.jl
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<extend src="layout.jl">
|
||||||
|
<block name="content">
|
||||||
|
<div class="title">{{ message }}</div>
|
||||||
|
</block>
|
||||||
|
</extend>
|
|
@ -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
5
views/hello.jl
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<extend src="layout.jl">
|
||||||
|
<block name="content">
|
||||||
|
<div class="title">Angel</div>
|
||||||
|
</block>
|
||||||
|
</extend>
|
|
@ -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
17
views/layout.jl
Normal 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
27
web/css/site.css
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue