diff --git a/.gitignore b/.gitignore index 0e47c8a..d88b0df 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,4 @@ fabric.properties logs/ *.pem .DS_Store +server_log.txt diff --git a/.idea/angel.iml b/.idea/angel.iml index d218f65..377564b 100644 --- a/.idea/angel.iml +++ b/.idea/angel.iml @@ -4,20 +4,12 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Multi_Threaded_Server__PRODUCTION_.xml b/.idea/runConfigurations/Multi_Threaded_Server__PRODUCTION_.xml deleted file mode 100644 index af4caad..0000000 --- a/.idea/runConfigurations/Multi_Threaded_Server__PRODUCTION_.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Start_Server.xml b/.idea/runConfigurations/Start_Server.xml deleted file mode 100644 index 10afb53..0000000 --- a/.idea/runConfigurations/Start_Server.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Start_Server__PRODUCTION_.xml b/.idea/runConfigurations/Start_Server__PRODUCTION_.xml deleted file mode 100644 index 688eed8..0000000 --- a/.idea/runConfigurations/Start_Server__PRODUCTION_.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/Users_Tests.xml b/.idea/runConfigurations/Users_Tests.xml deleted file mode 100644 index a5f788b..0000000 --- a/.idea/runConfigurations/Users_Tests.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/runConfigurations/dev_dart.xml b/.idea/runConfigurations/dev_dart.xml new file mode 100644 index 0000000..418187f --- /dev/null +++ b/.idea/runConfigurations/dev_dart.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Load_Balanced_Server__PRODUCTION_.xml b/.idea/runConfigurations/prod_dart.xml similarity index 51% rename from .idea/runConfigurations/Load_Balanced_Server__PRODUCTION_.xml rename to .idea/runConfigurations/prod_dart.xml index d6a3e0f..e93c56a 100644 --- a/.idea/runConfigurations/Load_Balanced_Server__PRODUCTION_.xml +++ b/.idea/runConfigurations/prod_dart.xml @@ -1,10 +1,10 @@ - + - diff --git a/README.md b/README.md index 370966c..9b7ddb7 100644 --- a/README.md +++ b/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/.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 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. \ No newline at end of file diff --git a/.analysis-options b/analysis_options.yaml similarity index 100% rename from .analysis-options rename to analysis_options.yaml diff --git a/bin/cluster.dart b/bin/cluster.dart deleted file mode 100644 index 14090b8..0000000 --- a/bin/cluster.dart +++ /dev/null @@ -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); -} diff --git a/bin/common.dart b/bin/common.dart deleted file mode 100644 index 87f1135..0000000 --- a/bin/common.dart +++ /dev/null @@ -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); - } -} diff --git a/bin/dev.dart b/bin/dev.dart new file mode 100644 index 0000000..fbf194a --- /dev/null +++ b/bin/dev.dart @@ -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}'); +} diff --git a/bin/multi_server.dart b/bin/multi_server.dart deleted file mode 100644 index a33a28e..0000000 --- a/bin/multi_server.dart +++ /dev/null @@ -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('', ''); - - // 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)'); -} diff --git a/bin/prod.dart b/bin/prod.dart new file mode 100644 index 0000000..6793255 --- /dev/null +++ b/bin/prod.dart @@ -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}'); + }); +} diff --git a/bin/scaled_server.dart b/bin/scaled_server.dart deleted file mode 100644 index 6e93f22..0000000 --- a/bin/scaled_server.dart +++ /dev/null @@ -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 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}'); - }); -} diff --git a/bin/server.dart b/bin/server.dart deleted file mode 100755 index 88a206c..0000000 --- a/bin/server.dart +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env dart -import 'dart:async'; -import 'common.dart'; - -main(args) async { - runZoned(startServer(args), onError: onError); -} diff --git a/lib/angel.dart b/lib/angel.dart index 28668ab..e7183f5 100644 --- a/lib/angel.dart +++ b/lib/angel.dart @@ -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 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())); } diff --git a/lib/src/config/config.dart b/lib/src/config/config.dart index 810f40d..ba224a7 100644 --- a/lib/src/config/config.dart +++ b/lib/src/config/config.dart @@ -1,29 +1,33 @@ /// 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 { - // Load configuration from the `config/` directory. - // - // See: https://github.com/angel-dart/configuration - await app.configure(loadConfigurationFile()); +AngelConfigurer configureServer(FileSystem fileSystem) { + return (Angel app) async { + // Load configuration from the `config/` directory. + // + // See: https://github.com/angel-dart/configuration + await app.configure(configuration(fileSystem)); - // Configure our application to render Mustache templates from the `views/` directory. - // - // See: https://github.com/angel-dart/mustache - await app.configure(mustache(new Directory('views'))); + // Configure our application to render Jael templates from the `views/` directory. + // + // 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. - // - // Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins - // that add functionality specific to your application. - // - // If you write a plug-in that you plan to use again, or are - // using one created by the community, include it in - // `lib/src/config/config.dart`. - await plugins.configureServer(app); + // Apply another plug-ins, i.e. ones that *you* have written. + // + // Typically, the plugins in `lib/src/config/plugins/plugins.dart` are plug-ins + // that add functionality specific to your application. + // + // If you write a plug-in that you plan to use again, or are + // using one created by the community, include it in + // `lib/src/config/config.dart`. + await plugins.configureServer(app); + }; } diff --git a/lib/src/config/plugins/plugins.dart b/lib/src/config/plugins/plugins.dart index 30bd9ca..5d9673d 100644 --- a/lib/src/config/plugins/plugins.dart +++ b/lib/src/config/plugins/plugins.dart @@ -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. diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart deleted file mode 100644 index 45dd7d2..0000000 --- a/lib/src/models/models.dart +++ /dev/null @@ -1,3 +0,0 @@ -library angel.models; - -export 'user.dart'; \ No newline at end of file diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart deleted file mode 100644 index 52f4e11..0000000 --- a/lib/src/models/user.dart +++ /dev/null @@ -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}); -} diff --git a/lib/src/routes/controllers/auth.dart b/lib/src/routes/controllers/auth.dart deleted file mode 100644 index 1ca5a97..0000000 --- a/lib/src/routes/controllers/auth.dart +++ /dev/null @@ -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 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'); -} diff --git a/lib/src/routes/controllers/controllers.dart b/lib/src/routes/controllers/controllers.dart index db68c44..e23b375 100644 --- a/lib/src/routes/controllers/controllers.dart +++ b/lib/src/routes/controllers/controllers.dart @@ -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()); } diff --git a/lib/src/routes/routes.dart b/lib/src/routes/routes.dart index 3a80b03..bbdd8ad 100644 --- a/lib/src/routes/routes.dart +++ b/lib/src/routes/routes.dart @@ -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. - // - // Read the following two sources for documentation: - // * https://medium.com/the-angel-framework/serving-static-files-with-the-angel-framework-2ddc7a2b84ae - // * https://github.com/angel-dart/static - await app.configure(new CachingVirtualDirectory()); - - // Set our application up to handle different errors. - // - // Read the following two sources for documentation: - // * https://github.com/angel-dart/angel/wiki/Error-Handling - // * https://github.com/angel-dart/errors - var errors = new ErrorHandler(handlers: { - // Display a 404 page if no resource is found. - 404: (req, res) async => - res.render('error', {'message': 'No file exists at ${req.path}.'}), - - // On generic errors, give information about why the application failed. + // Mount static server at web in development. + // This variant of `VirtualDirectory` also sends `Cache-Control` headers. // - // An `AngelHttpException` instance will be present in `req.properties` - // as `error`. - 500: (req, res) async => res.render('error', {'message': req.error.message}) - }); + // 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 + var vDir = new CachingVirtualDirectory( + app, + fileSystem, + source: fileSystem.directory('web'), + ); + app.use(vDir.handleRequest); - // Use a fatal error handler to attempt to resolve any issues that - // 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); + // Throw a 404 if no route matched the request. + app.use(() => throw new AngelHttpException.notFound()); - // *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); - } + // Set our application up to handle different errors. + // + // Read the following for documentation: + // * https://github.com/angel-dart/angel/wiki/Error-Handling + 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); } diff --git a/lib/src/services/services.dart b/lib/src/services/services.dart index 6230710..86c3eb9 100644 --- a/lib/src/services/services.dart +++ b/lib/src/services/services.dart @@ -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 {} diff --git a/lib/src/services/user.dart b/lib/src/services/user.dart deleted file mode 100644 index 45f1526..0000000 --- a/lib/src/services/user.dart +++ /dev/null @@ -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(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(); diff --git a/pubspec.yaml b/pubspec.yaml index 8b226af..aebc1f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,14 +4,21 @@ publish_to: none # Ensure we don't accidentally publish our private code! ;) 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. +dependencies: + 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. diff --git a/test/services/users_test.dart b/test/all_test.dart similarity index 75% rename from test/services/users_test.dart rename to test/all_test.dart index 0ffdcd2..c4cb235 100644 --- a/test/services/users_test.dart +++ b/test/all_test.dart @@ -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)); }); } diff --git a/views/error.jl b/views/error.jl new file mode 100644 index 0000000..a3850ae --- /dev/null +++ b/views/error.jl @@ -0,0 +1,5 @@ + + +
{{ message }}
+
+
\ No newline at end of file diff --git a/views/error.mustache b/views/error.mustache deleted file mode 100644 index 1a68904..0000000 --- a/views/error.mustache +++ /dev/null @@ -1,50 +0,0 @@ - - - - Error - - - - - - - -
-
-
{{message}}
-
-
- - \ No newline at end of file diff --git a/views/hello.jl b/views/hello.jl new file mode 100644 index 0000000..aeb5867 --- /dev/null +++ b/views/hello.jl @@ -0,0 +1,5 @@ + + +
Angel
+
+
\ No newline at end of file diff --git a/views/hello.mustache b/views/hello.mustache deleted file mode 100644 index 66b6687..0000000 --- a/views/hello.mustache +++ /dev/null @@ -1,46 +0,0 @@ - - - - Angel - - - - - - - -
-
-
Angel
-
-
- - \ No newline at end of file diff --git a/views/layout.jl b/views/layout.jl new file mode 100644 index 0000000..ea0714b --- /dev/null +++ b/views/layout.jl @@ -0,0 +1,17 @@ + + + + {{ title ?? 'Angel' }} + + + + + + +
+
+ +
+
+ + \ No newline at end of file diff --git a/web/css/site.css b/web/css/site.css new file mode 100644 index 0000000..9e40b8d --- /dev/null +++ b/web/css/site.css @@ -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; +} \ No newline at end of file