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/
|
||||
*.pem
|
||||
.DS_Store
|
||||
server_log.txt
|
||||
|
|
|
@ -4,20 +4,12 @@
|
|||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/bin/packages" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/packages" />
|
||||
<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$/tool/packages" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/images/packages" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/web/packages" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||
</component>
|
||||
</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">
|
||||
<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="envs">
|
||||
<entry key="ANGEL_ENV" value="production" />
|
||||
</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$" />
|
||||
<method />
|
||||
</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)
|
||||
|
||||
[![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!!!**
|
||||
|
||||
|
@ -46,7 +46,7 @@ angel init hello
|
|||
You can even have your server run and be *live-reloaded* on file changes:
|
||||
|
||||
```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.
|
||||
|
@ -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)...
|
||||
|
||||
## Basic Example
|
||||
More 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
|
||||
Examples and complete projects can be found in the [angel-example](https://github.com/angel-example) organization.
|
|
@ -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;
|
||||
|
||||
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/routes/routes.dart' as routes;
|
||||
import 'src/services/services.dart' as services;
|
||||
|
||||
/// Creates and configures the server instance.
|
||||
Future<Angel> createServer() async {
|
||||
/// 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);
|
||||
/// Configures the server instance.
|
||||
Future configureServer(Angel app) async {
|
||||
// Enable CORS
|
||||
app.use(cors());
|
||||
|
||||
/// Set up our application, using three plug-ins defined with this project.
|
||||
await app.configure(configuration.configureServer);
|
||||
// Set up our application, using the plug-ins defined with this project.
|
||||
await app.configure(configuration.configureServer(const LocalFileSystem()));
|
||||
await app.configure(services.configureServer);
|
||||
await app.configure(routes.configureServer);
|
||||
|
||||
return app;
|
||||
await app.configure(routes.configureServer(const LocalFileSystem()));
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
/// Configuration for this Angel instance.
|
||||
library angel.config;
|
||||
library angel.src.config;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:angel_common/angel_common.dart';
|
||||
import 'package:angel_configuration/angel_configuration.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;
|
||||
|
||||
/// 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.
|
||||
//
|
||||
// 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
|
||||
await app.configure(mustache(new Directory('views')));
|
||||
// See: https://github.com/angel-dart/jael
|
||||
await app.configure(jael(fileSystem.directory('views')));
|
||||
|
||||
// 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
|
||||
// `lib/src/config/config.dart`.
|
||||
await plugins.configureServer(app);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/// Custom plugins go here.
|
||||
library angel.config.plugins;
|
||||
library angel.src.config.plugins;
|
||||
|
||||
import 'dart:async';
|
||||
import 'package:angel_common/angel_common.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
Future configureServer(Angel app) async {
|
||||
// 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 'auth.dart';
|
||||
import 'dart:async';
|
||||
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!
|
||||
await app.configure(new AuthController());
|
||||
}
|
||||
|
|
|
@ -1,138 +1,55 @@
|
|||
/// This app's route configuration.
|
||||
library angel.routes;
|
||||
library angel.src.routes;
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:angel_common/angel_common.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_static/angel_static.dart';
|
||||
import 'package:file/file.dart';
|
||||
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!
|
||||
///
|
||||
/// 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
|
||||
configureRoutes(Angel app) async {
|
||||
// Render `views/hello.mustache` when a user visits the application root.
|
||||
app.get('/', (req, ResponseContext res) => res.render('hello'));
|
||||
}
|
||||
AngelConfigurer configureServer(FileSystem fileSystem) {
|
||||
return (Angel app) async {
|
||||
// Typically, you want to mount controllers first, after any global middleware.
|
||||
await app.configure(controllers.configureServer);
|
||||
|
||||
/// Configures fallback middleware.
|
||||
///
|
||||
/// Use these to run generic actions on requests that were not terminated by
|
||||
/// 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());
|
||||
// Render `views/hello.jl` when a user visits the application root.
|
||||
app.get(
|
||||
'/', (RequestContext req, ResponseContext res) => res.render('hello'));
|
||||
|
||||
// Mount static server at /web or /build/web, depending on if
|
||||
// you are running in production mode. `Cache-Control` headers will also be enabled.
|
||||
// Mount static server at web in development.
|
||||
// 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:
|
||||
// * 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());
|
||||
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.
|
||||
//
|
||||
// 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/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`
|
||||
// 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);
|
||||
app.errorHandler = (e, req, res) async {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/// Declare services here!
|
||||
library angel.services;
|
||||
|
||||
import 'package:angel_common/angel_common.dart';
|
||||
import 'user.dart' as user;
|
||||
import 'dart:async';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
|
||||
/// Configure our application to use *services*.
|
||||
/// Services must be wired to the app via `app.use`.
|
||||
|
@ -12,6 +12,4 @@ import 'user.dart' as user;
|
|||
///
|
||||
/// Read more here:
|
||||
/// https://github.com/angel-dart/angel/wiki/Service-Basics
|
||||
configureServer(Angel app) async {
|
||||
await app.configure(user.configureServer());
|
||||
}
|
||||
Future configureServer(Angel app) async {}
|
||||
|
|
|
@ -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"
|
||||
homepage: https://github.com/angel-dart/angel
|
||||
dependencies:
|
||||
angel_common: ^1.0.0 # Bundles the most commonly-used Angel packages.
|
||||
angel_configuration: ^1.0.0 # Included in `angel_common`, but also exposes a transformer
|
||||
angel_hot: ^1.0.0-rc.1 # Hot-reloading support. :)
|
||||
angel_multiserver: ^1.0.0 # Helpful for applications running a scale.
|
||||
angel_framework: ^1.1.0-alpha # The core server library.
|
||||
angel_serialize: ^1.0.0-alpha # Model definition metadata.
|
||||
|
||||
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:
|
||||
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
|
||||
http: ^0.11.3
|
||||
test: ^0.12.13
|
||||
transformers:
|
||||
# Injects data from application configuration into Dart code.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'package:angel/angel.dart';
|
||||
import 'package:angel_framework/angel_framework.dart';
|
||||
import 'package:angel_test/angel_test.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
|
@ -23,7 +23,9 @@ main() async {
|
|||
TestClient client;
|
||||
|
||||
setUp(() async {
|
||||
var app = await createServer();
|
||||
var app = new Angel();
|
||||
await app.configure(configureServer);
|
||||
|
||||
client = await connectTo(app);
|
||||
});
|
||||
|
||||
|
@ -31,12 +33,11 @@ main() async {
|
|||
await client.close();
|
||||
});
|
||||
|
||||
test('index users', () async {
|
||||
test('index returns 200', () async {
|
||||
// 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 403 response.
|
||||
expect(response, hasStatus(HttpStatus.FORBIDDEN));
|
||||
// Expect a 200 response.
|
||||
expect(response, hasStatus(200));
|
||||
});
|
||||
}
|
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