├── config.yaml ├── heroku.yaml ├── config.src.yaml ├── .travis.yml ├── Procfile ├── lib ├── src │ ├── utils │ │ ├── constants.dart │ │ ├── authorization.dart │ │ ├── passwod.dart │ │ ├── token.dart │ │ └── multipart.dart │ ├── services │ │ └── mongo.dart │ └── controllers │ │ ├── login.dart │ │ ├── register.dart │ │ ├── categories.dart │ │ ├── users.dart │ │ ├── favorites.dart │ │ └── articles.dart ├── hello_aqueduct.dart └── channel.dart ├── .gitignore ├── bin └── main.dart ├── test ├── example_test.dart └── harness │ └── app.dart ├── pubspec.yaml ├── README.md ├── LICENSE ├── client.html ├── analysis_options.yaml └── pubspec.lock /config.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /heroku.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config.src.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: dart 2 | script: pub run test -r expanded 3 | branches: 4 | only: 5 | - master -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: /app/dart-sdk/bin/pub global run aqueduct:aqueduct serve --port $PORT --config-path heroku.yaml -------------------------------------------------------------------------------- /lib/src/utils/constants.dart: -------------------------------------------------------------------------------- 1 | abstract class Constants { 2 | static const String jwtKey = '404hellowordl404'; 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/host.dart 2 | public/ 3 | .dart_tool/ 4 | .buildlog 5 | .pub/ 6 | build/ 7 | packages 8 | .packages 9 | *.dart.js 10 | *.js_ 11 | *.js.deps 12 | *.js.map 13 | workspace.xml 14 | Dart_Packages.xml 15 | .DS_Store 16 | .idea/ 17 | *.aqueduct.pid -------------------------------------------------------------------------------- /bin/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:hello_aqueduct/hello_aqueduct.dart'; 2 | 3 | Future main() async { 4 | final app = Application() 5 | ..options.configurationFilePath = "config.yaml" 6 | ..options.port = 8888; 7 | 8 | await app.start(); 9 | } 10 | -------------------------------------------------------------------------------- /test/example_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | 3 | import 'harness/app.dart'; 4 | 5 | Future main() async { 6 | final harness = Harness()..install(); 7 | 8 | test("GET /example returns 200 {'key': 'value'}", () async { 9 | expectResponse(await harness.agent.get("/example"), 200, 10 | body: {"key": "value"}); 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hello_aqueduct 2 | description: A blog using Aqueduct with MongoDB. 3 | version: 0.0.1 4 | author: Abdelouahed Medjoudja 5 | 6 | environment: 7 | sdk: ">=2.0.0 <3.0.0" 8 | 9 | dependencies: 10 | aqueduct: 3.3.0+1 11 | mongo_dart: ^0.4.4 12 | crypto: ^2.1.5 13 | jaguar_jwt: ^2.1.6 14 | mime: any 15 | http_server: ^0.9.8+3 16 | 17 | dev_dependencies: 18 | test: ^1.0.0 19 | aqueduct_test: ^1.0.0 -------------------------------------------------------------------------------- /lib/src/utils/authorization.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | abstract class AuthorizationUtils { 4 | static Future verifyAuthorization(Request request) async { 5 | final token = request.raw.headers.value('Authorization'); 6 | 7 | final isTokenValid = TokenUtils.isTokenValid(token); 8 | 9 | if (isTokenValid) 10 | return request; 11 | else 12 | return Response.unauthorized(body: { 13 | 'status': false, 14 | 'message': 'Unauthorized', 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/services/mongo.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class MongoDBService { 4 | Db _db; 5 | 6 | Db get db => _db; 7 | 8 | Future open() async { 9 | _db = await Db.create(host); 10 | await _db.open(); 11 | return Future.value(); 12 | } 13 | 14 | Future initIndex() => Future.any( 15 | [ 16 | _db.collection('users').createIndex( 17 | keys: {'email': 1}, 18 | unique: true, 19 | ), 20 | _db.collection('categories').createIndex( 21 | keys: {'name': 1}, 22 | unique: true, 23 | ) 24 | ], 25 | ); 26 | 27 | Future close() => _db.close(); 28 | } 29 | -------------------------------------------------------------------------------- /lib/hello_aqueduct.dart: -------------------------------------------------------------------------------- 1 | library hello_aqueduct; 2 | 3 | export 'dart:async'; 4 | export 'dart:io'; 5 | 6 | export 'package:aqueduct/aqueduct.dart'; 7 | export 'package:mime/mime.dart'; 8 | export 'package:mongo_dart/mongo_dart.dart'; 9 | 10 | export 'channel.dart'; 11 | 12 | export 'host.dart'; 13 | 14 | export 'src/controllers/articles.dart'; 15 | export 'src/controllers/categories.dart'; 16 | export 'src/controllers/favorites.dart'; 17 | export 'src/controllers/login.dart'; 18 | export 'src/controllers/register.dart'; 19 | export 'src/controllers/users.dart'; 20 | export 'src/services/mongo.dart'; 21 | 22 | export 'src/utils/authorization.dart'; 23 | export 'src/utils/constants.dart'; 24 | export 'src/utils/multipart.dart'; 25 | export 'src/utils/passwod.dart'; 26 | export 'src/utils/token.dart'; 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Blog API built by Dart with Aqueduct framework + MongoDB 2 | 3 | ### [Frontend code](https://github.com/GeekAbdelouahed/Flutter-Blog) 4 | 5 | ## Running the Application Locally 6 | 7 | Run `aqueduct serve` from this directory to run the application. For running within an IDE, run `bin/main.dart`. By default, a configuration file named `config.yaml` will be used. 8 | 9 | To generate a SwaggerUI client, run `aqueduct document client`. 10 | 11 | ## Running Application Tests 12 | 13 | To run all tests for this application, run the following in this directory: 14 | 15 | ``` 16 | pub run test 17 | ``` 18 | 19 | The default configuration file used when testing is `config.src.yaml`. This file should be checked into version control. It also the template for configuration files used in deployment. 20 | 21 | ## Deploying an Application 22 | 23 | See the documentation for [Deployment](https://aqueduct.io/docs/deploy/). 24 | -------------------------------------------------------------------------------- /test/harness/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:hello_aqueduct/hello_aqueduct.dart'; 2 | import 'package:aqueduct_test/aqueduct_test.dart'; 3 | 4 | export 'package:hello_aqueduct/hello_aqueduct.dart'; 5 | export 'package:aqueduct_test/aqueduct_test.dart'; 6 | 7 | export 'package:aqueduct/aqueduct.dart'; 8 | 9 | /// A testing harness for hello_aqueduct. 10 | /// 11 | /// A harness for testing an aqueduct application. Example test file: 12 | /// 13 | /// void main() { 14 | /// Harness harness = Harness()..install(); 15 | /// 16 | /// test("GET /path returns 200", () async { 17 | /// final response = await harness.agent.get("/path"); 18 | /// expectResponse(response, 200); 19 | /// }); 20 | /// } 21 | /// 22 | class Harness extends TestHarness { 23 | @override 24 | Future onSetUp() async {} 25 | 26 | @override 27 | Future onTearDown() async {} 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/utils/passwod.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:math'; 3 | 4 | import 'package:crypto/crypto.dart'; 5 | 6 | abstract class PasswordUtils { 7 | static bool isPasswordValid( 8 | String givenPassword, String salt, String hashedPassword) { 9 | final newHashedPassword = hashPassword(givenPassword, salt); 10 | return newHashedPassword == hashedPassword; 11 | } 12 | 13 | static String hashPassword(String password, String salt) { 14 | const codec = Utf8Codec(); 15 | final key = codec.encode(password); 16 | final saltBytes = codec.encode(salt); 17 | final hmacSha256 = Hmac(sha256, key); 18 | final digest = hmacSha256.convert(saltBytes); 19 | return digest.toString(); 20 | } 21 | 22 | static String getRandomSlat() { 23 | final rand = Random.secure(); 24 | final saltBytes = List.generate(32, (_) => rand.nextInt(256)); 25 | final salt = base64.encode(saltBytes); 26 | return salt; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/utils/token.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:jaguar_jwt/jaguar_jwt.dart'; 4 | 5 | import '../../hello_aqueduct.dart'; 6 | 7 | abstract class TokenUtils { 8 | static bool isTokenValid(String token) { 9 | try { 10 | final jwtClaim = verifyJwtHS256Signature(token, Constants.jwtKey); 11 | 12 | final isExpired = jwtClaim?.expiry?.isAfter(DateTime.now()) ?? false; 13 | 14 | return isExpired; 15 | } catch (e) { 16 | return false; 17 | } 18 | } 19 | 20 | static String generatToken(List audience) { 21 | audience.add(_randomString(32)); 22 | final claimSet = JwtClaim( 23 | audience: audience, 24 | jwtId: _randomString(32), 25 | maxAge: const Duration(days: 360), 26 | ); 27 | return issueJwtHS256(claimSet, Constants.jwtKey); 28 | } 29 | 30 | static String _randomString(int length) { 31 | const chars = 32 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 33 | final rnd = Random(DateTime.now().millisecondsSinceEpoch); 34 | final buf = StringBuffer(); 35 | for (var x = 0; x < length; x++) { 36 | buf.write(chars[rnd.nextInt(chars.length)]); 37 | } 38 | return buf.toString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Abdelouahed Medjoudja 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Aqueduct OpenAPI Client 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /lib/src/controllers/login.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class LoginController extends ResourceController { 4 | LoginController(this._db); 5 | 6 | final Db _db; 7 | 8 | final String _collection = 'users'; 9 | 10 | @Operation.post() 11 | Future login(@Bind.body() Map user) async { 12 | if (!user.containsKey('email')) 13 | return Response.badRequest(body: { 14 | 'status': false, 15 | 'message': 'Email is Required', 16 | }); 17 | if (!user.containsKey('password')) 18 | return Response.badRequest(body: { 19 | 'status': false, 20 | 'message': 'Password is Required', 21 | }); 22 | 23 | try { 24 | final savedUser = await _db.collection(_collection).findOne({ 25 | 'email': user['email'], 26 | }); 27 | 28 | if (savedUser == null) 29 | return Response.notFound(body: { 30 | 'status': false, 31 | 'message': 'User not found!', 32 | }); 33 | 34 | final isPasswordValid = PasswordUtils.isPasswordValid( 35 | user['password'] as String, 36 | savedUser['salt'] as String, 37 | savedUser['password'] as String, 38 | ); 39 | 40 | if (isPasswordValid) { 41 | final token = TokenUtils.generatToken( 42 | [user['email'] as String], 43 | ); 44 | 45 | return Response.ok({ 46 | 'status': true, 47 | 'message': 'login successfully', 48 | 'data': { 49 | '_id': savedUser['_id'], 50 | 'token': token, 51 | } 52 | }); 53 | } else 54 | return Response.unauthorized(body: { 55 | 'status': false, 56 | 'message': 'Email or password wrong!', 57 | }); 58 | } catch (e) { 59 | return Response.serverError(body: { 60 | 'status': false, 61 | 'message': 'Login failed!', 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/controllers/register.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class RegisterController extends ResourceController { 4 | RegisterController(this._db); 5 | 6 | final Db _db; 7 | 8 | final String _collection = 'users'; 9 | 10 | @Operation.post() 11 | Future register(@Bind.body() Map user) async { 12 | if (!user.containsKey('first_name')) 13 | return Response.badRequest(body: { 14 | 'status': false, 15 | 'message': 'FirstName is Required', 16 | }); 17 | if (!user.containsKey('last_name')) 18 | return Response.badRequest(body: { 19 | 'status': false, 20 | 'message': 'LastName is Required', 21 | }); 22 | if (!user.containsKey('email')) 23 | return Response.badRequest(body: { 24 | 'status': false, 25 | 'message': 'Email is Required', 26 | }); 27 | if (!user.containsKey('password')) 28 | return Response.badRequest(body: { 29 | 'status': false, 30 | 'message': 'Password is Required', 31 | }); 32 | 33 | final salt = PasswordUtils.getRandomSlat(); 34 | final hashedPassword = 35 | PasswordUtils.hashPassword('${user['password']}', salt); 36 | final token = TokenUtils.generatToken([ 37 | user['email'] as String, 38 | ]); 39 | 40 | try { 41 | final createdUser = await _db.collection(_collection).insert({ 42 | 'first_name': user['first_name'], 43 | 'last_name': user['last_name'], 44 | 'email': user['email'], 45 | 'password': hashedPassword, 46 | 'salt': salt, 47 | 'created_at': DateTime.now().toString(), 48 | }); 49 | if (createdUser != null) { 50 | final lastUser = await _db.collection(_collection).find({ 51 | 'email': user['email'], 52 | }).last; 53 | return Response.created('', body: { 54 | 'status': true, 55 | 'message': 'user created successfully', 56 | 'data': { 57 | '_id': lastUser['_id'], 58 | 'token': token, 59 | } 60 | }); 61 | } else 62 | return Response.serverError(body: { 63 | 'status': false, 64 | 'message': 'user created failed!', 65 | }); 66 | } catch (e) { 67 | return Response.serverError(body: { 68 | 'status': false, 69 | 'message': 'Email already exist!', 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/utils/multipart.dart: -------------------------------------------------------------------------------- 1 | import 'package:http_server/http_server.dart'; 2 | 3 | import '../../hello_aqueduct.dart'; 4 | 5 | class MultipartsUtils { 6 | MultipartsUtils({this.request}); 7 | 8 | final Request request; 9 | List _mulitparts; 10 | 11 | Future parse() async { 12 | final transformer = MimeMultipartTransformer( 13 | request.raw.headers.contentType.parameters['boundary'], 14 | ); 15 | final bodyStream = Stream.fromIterable( 16 | [await request.body.decode>()], 17 | ); 18 | _mulitparts = await transformer 19 | .bind(bodyStream) 20 | .map(HttpMultipartFormData.parse) 21 | .toList(); 22 | 23 | return Future.value(); 24 | } 25 | 26 | bool containsKey(String key) { 27 | return _mulitparts.firstWhere( 28 | (multipart) => multipart.contentDisposition.parameters['name'] == key, 29 | orElse: () => null, 30 | ) != 31 | null; 32 | } 33 | 34 | bool containsFiles() { 35 | return _mulitparts.firstWhere( 36 | (multipart) => multipart.isBinary, 37 | orElse: () => null, 38 | ) != 39 | null; 40 | } 41 | 42 | Future getValue(String key) async { 43 | try { 44 | final multipart = _mulitparts.firstWhere( 45 | (multipart) => 46 | multipart.isText && 47 | multipart.contentDisposition.parameters['name'] == key, 48 | ); 49 | return await multipart.join(); 50 | } catch (e) { 51 | return null; 52 | } 53 | } 54 | 55 | Future> saveFiles(String filesFolderName) async { 56 | final imagesPath = []; 57 | await Future.any( 58 | _mulitparts.where((multipart) => multipart.isBinary).map( 59 | (multipart) async { 60 | final content = multipart.cast>(); 61 | 62 | final fileName = 'image${imagesPath.length}.jpg'; 63 | 64 | imagesPath.add(fileName); 65 | 66 | final file = await File( 67 | 'public/images/articles/$filesFolderName/$fileName', 68 | ).create(recursive: true); 69 | 70 | final sink = file.openWrite(); 71 | 72 | await content.forEach(sink.add); 73 | 74 | await sink.flush(); 75 | await sink.close(); 76 | 77 | return imagesPath; 78 | }, 79 | ).toList(), 80 | ); 81 | 82 | return imagesPath; 83 | } 84 | 85 | static Future deleteFiles(String filesFolderName) => File( 86 | 'public/images/articles/$filesFolderName', 87 | ).delete(recursive: true); 88 | } 89 | -------------------------------------------------------------------------------- /lib/channel.dart: -------------------------------------------------------------------------------- 1 | import 'hello_aqueduct.dart'; 2 | 3 | class HelloAqueductChannel extends ApplicationChannel { 4 | final _mongoDBController = MongoDBService(); 5 | 6 | @override 7 | Future prepare() async { 8 | logger.onRecord.listen( 9 | (rec) => print('$rec ${rec.error ?? ''} ${rec.stackTrace ?? ''}'), 10 | ); 11 | 12 | await _mongoDBController.open(); 13 | await _mongoDBController.initIndex(); 14 | 15 | return Future.value(); 16 | } 17 | 18 | @override 19 | Controller get entryPoint { 20 | final router = Router(); 21 | 22 | router 23 | .route('/files/articles/*') 24 | .link(() => FileController('public/images/articles')); 25 | 26 | router 27 | .route('/auth/login') 28 | .link(() => LoginController(_mongoDBController.db)); 29 | 30 | router 31 | .route('/auth/register') 32 | .link(() => RegisterController(_mongoDBController.db)); 33 | 34 | router 35 | .route('/users/[:id]') 36 | .linkFunction(AuthorizationUtils.verifyAuthorization) 37 | .link(() => UsersController(_mongoDBController.db)); 38 | 39 | router.route('/categories/[:id]').linkFunction((request) { 40 | switch (request.method) { 41 | case 'GET': 42 | return request; 43 | default: 44 | return AuthorizationUtils.verifyAuthorization(request); 45 | } 46 | }).link(() => CategoriesController(_mongoDBController.db)); 47 | 48 | router.route('/articles/[:id]').linkFunction((request) { 49 | switch (request.method) { 50 | case 'GET': 51 | return request; 52 | default: 53 | return AuthorizationUtils.verifyAuthorization(request); 54 | } 55 | }).link(() => ArticlesController(_mongoDBController.db)); 56 | 57 | router.route('/articles/byCategory/[:categoryId]').linkFunction((request) { 58 | switch (request.method) { 59 | case 'GET': 60 | return request; 61 | default: 62 | return AuthorizationUtils.verifyAuthorization(request); 63 | } 64 | }).link(() => ArticlesController(_mongoDBController.db)); 65 | 66 | router 67 | .route('/articles/byUser/[:userId]') 68 | .linkFunction(AuthorizationUtils.verifyAuthorization) 69 | .link(() => ArticlesController(_mongoDBController.db)); 70 | 71 | router 72 | .route('/favorites/[:userId]') 73 | .linkFunction(AuthorizationUtils.verifyAuthorization) 74 | .link(() => FavoritesController(_mongoDBController.db)); 75 | 76 | return router; 77 | } 78 | 79 | @override 80 | Future close() async { 81 | await _mongoDBController.close(); 82 | return super.close(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/controllers/categories.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class CategoriesController extends ResourceController { 4 | CategoriesController(this._db); 5 | final Db _db; 6 | 7 | final String _collection = 'categories'; 8 | 9 | @Operation.post() 10 | Future createCategory( 11 | @Bind.body() Map category) async { 12 | if (!category.containsKey('name')) 13 | return Response.badRequest(body: { 14 | 'status': false, 15 | 'message': 'Name is Required', 16 | }); 17 | 18 | try { 19 | final createdCategory = await _db.collection(_collection).insert({ 20 | 'name': category['name'], 21 | 'created_at': DateTime.now().toString(), 22 | }); 23 | if (createdCategory != null) 24 | return Response.created('', body: { 25 | 'status': true, 26 | 'message': 'Category created successfully', 27 | }); 28 | else 29 | return Response.serverError(body: { 30 | 'status': false, 31 | 'message': 'Category created failed!', 32 | }); 33 | } catch (e) { 34 | print(e); 35 | return Response.serverError(body: { 36 | 'status': false, 37 | 'message': 'Category already exist!', 38 | }); 39 | } 40 | } 41 | 42 | @Operation.get() 43 | Future getCategories() async { 44 | try { 45 | final categories = await _db.collection(_collection).find().toList(); 46 | 47 | if (categories != null) { 48 | return Response.ok({ 49 | 'status': true, 50 | 'message': 'Categories found successfully', 51 | 'data': categories, 52 | }); 53 | } else 54 | return Response.notFound(body: { 55 | 'status': false, 56 | 'message': 'Categories not found!', 57 | }); 58 | } catch (e) { 59 | print(e); 60 | return Response.serverError(body: { 61 | 'status': false, 62 | 'message': 'Categories not found!', 63 | }); 64 | } 65 | } 66 | 67 | @Operation.delete() 68 | Future deleteCategory( 69 | @Bind.body() Map category) async { 70 | try { 71 | if (!category.containsKey('category_id')) 72 | return Response.badRequest(body: { 73 | 'status': false, 74 | 'message': 'Category id is required!', 75 | }); 76 | 77 | await _db.collection(_collection).remove( 78 | { 79 | '_id': ObjectId.parse(category['category_id'] as String), 80 | }, 81 | ); 82 | 83 | return Response.ok({ 84 | 'status': true, 85 | 'message': 'Category deleted successfully', 86 | }); 87 | } catch (e) { 88 | print(e); 89 | return Response.serverError(body: { 90 | 'status': false, 91 | 'message': 'Category not found!', 92 | }); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | strong-mode: 3 | implicit-casts: false 4 | 5 | linter: 6 | rules: 7 | - always_declare_return_types 8 | - always_put_control_body_on_new_line 9 | - always_put_required_named_parameters_first 10 | - always_require_non_null_named_parameters 11 | - annotate_overrides 12 | - avoid_bool_literals_in_conditional_expressions 13 | - avoid_double_and_int_checks 14 | - avoid_empty_else 15 | - avoid_field_initializers_in_const_classes 16 | - avoid_init_to_null 17 | - avoid_null_checks_in_equality_operators 18 | - avoid_positional_boolean_parameters 19 | - avoid_relative_lib_imports 20 | - avoid_renaming_method_parameters 21 | - avoid_return_types_on_setters 22 | - avoid_returning_null 23 | - avoid_single_cascade_in_expression_statements 24 | - avoid_slow_async_io 25 | - avoid_types_as_parameter_names 26 | - avoid_unused_constructor_parameters 27 | - await_only_futures 28 | - camel_case_types 29 | - cancel_subscriptions 30 | - close_sinks 31 | - comment_references 32 | - constant_identifier_names 33 | - control_flow_in_finally 34 | - directives_ordering 35 | - empty_catches 36 | - empty_constructor_bodies 37 | - empty_statements 38 | - hash_and_equals 39 | - implementation_imports 40 | - invariant_booleans 41 | - iterable_contains_unrelated_type 42 | - join_return_with_assignment 43 | - library_names 44 | - library_prefixes 45 | - list_remove_unrelated_type 46 | - literal_only_boolean_expressions 47 | - no_duplicate_case_values 48 | - non_constant_identifier_names 49 | - null_closures 50 | - package_api_docs 51 | - package_names 52 | - package_prefixed_library_names 53 | - parameter_assignments 54 | - prefer_adjacent_string_concatenation 55 | - prefer_asserts_in_initializer_lists 56 | - prefer_collection_literals 57 | - prefer_conditional_assignment 58 | - prefer_const_constructors 59 | - prefer_const_constructors_in_immutables 60 | - prefer_const_declarations 61 | - prefer_const_literals_to_create_immutables 62 | - prefer_constructors_over_static_methods 63 | - prefer_contains 64 | - prefer_equal_for_default_values 65 | - prefer_final_fields 66 | - prefer_final_locals 67 | - prefer_foreach 68 | - prefer_generic_function_type_aliases 69 | - prefer_interpolation_to_compose_strings 70 | - prefer_is_empty 71 | - prefer_is_not_empty 72 | - prefer_iterable_whereType 73 | - prefer_typing_uninitialized_variables 74 | - recursive_getters 75 | - slash_for_doc_comments 76 | - sort_constructors_first 77 | - sort_unnamed_constructors_first 78 | - test_types_in_equals 79 | - throw_in_finally 80 | - type_annotate_public_apis 81 | - type_init_formals 82 | - unawaited_futures 83 | - unnecessary_const 84 | - unnecessary_getters_setters 85 | - unnecessary_lambdas 86 | - unnecessary_new 87 | - unnecessary_null_aware_assignments 88 | - unnecessary_null_in_if_null_operators 89 | - unnecessary_overrides 90 | - unnecessary_parenthesis 91 | - unnecessary_statements 92 | - unnecessary_this 93 | - unrelated_type_equality_checks 94 | - use_rethrow_when_possible 95 | - use_string_buffers 96 | - use_to_and_as_if_applicable 97 | - valid_regexps 98 | - void_checks -------------------------------------------------------------------------------- /lib/src/controllers/users.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class UsersController extends ResourceController { 4 | UsersController(this._db); 5 | 6 | final Db _db; 7 | 8 | final String _collection = 'users'; 9 | 10 | @Operation.get('id') 11 | Future getUserInformation( 12 | @requiredBinding @Bind.path('id') String id) async { 13 | try { 14 | if (id?.isEmpty ?? true) 15 | return Response.badRequest(body: { 16 | 'status': false, 17 | 'message': 'User id is required!', 18 | }); 19 | 20 | final pipeline = AggregationPipelineBuilder() 21 | .addStage(Match( 22 | where.id(ObjectId.parse(id)).map['\$query'], 23 | )) 24 | .addStage(Project({ 25 | 'password': 0, 26 | 'salt': 0, 27 | 'created_at': 0, 28 | 'updated_at': 0, 29 | })) 30 | .build(); 31 | 32 | final user = await _db 33 | .collection(_collection) 34 | .aggregateToStream(pipeline) 35 | .toList(); 36 | 37 | if (user != null) { 38 | return Response.ok({ 39 | 'status': true, 40 | 'message': 'User found successfully', 41 | 'data': user.first, 42 | }); 43 | } else 44 | return Response.notFound(body: { 45 | 'status': false, 46 | 'message': 'User not found!', 47 | }); 48 | } catch (e) { 49 | return Response.serverError(body: { 50 | 'status': false, 51 | 'message': 'User not found!', 52 | }); 53 | } 54 | } 55 | 56 | @Operation.put('id') 57 | Future updateUserInformation( 58 | @requiredBinding @Bind.path('id') String id, 59 | @requiredBinding @Bind.body() Map user, 60 | ) async { 61 | try { 62 | if (id?.isEmpty ?? true) 63 | return Response.badRequest(body: { 64 | 'status': false, 65 | 'message': 'User id is required!', 66 | }); 67 | 68 | await _db.collection(_collection).update( 69 | { 70 | '_id': ObjectId.parse(id), 71 | }, 72 | { 73 | '\$set': { 74 | 'first_name': user['first_name'], 75 | 'last_name': user['last_name'], 76 | 'email': user['email'], 77 | 'updated_at': DateTime.now().toString(), 78 | } 79 | }, 80 | ); 81 | 82 | return Response.ok({ 83 | 'status': true, 84 | 'message': 'User updated successfully', 85 | }); 86 | } catch (e) { 87 | return Response.serverError(body: { 88 | 'status': false, 89 | 'message': 'User not found!', 90 | }); 91 | } 92 | } 93 | 94 | @Operation.delete() 95 | Future deleteUserInformation( 96 | @Bind.body() Map user) async { 97 | try { 98 | if (!user.containsKey('user_id')) 99 | return Response.badRequest(body: { 100 | 'status': false, 101 | 'message': 'User id is required!', 102 | }); 103 | 104 | await _db.collection(_collection).remove( 105 | { 106 | '_id': ObjectId.parse(user['user_id'] as String), 107 | }, 108 | ); 109 | 110 | return Response.ok({ 111 | 'status': true, 112 | 'message': 'User deleted successfully', 113 | }); 114 | } catch (e) { 115 | return Response.serverError(body: { 116 | 'status': false, 117 | 'message': 'User not found!', 118 | }); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/controllers/favorites.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class FavoritesController extends ResourceController { 4 | FavoritesController(this._db); 5 | final Db _db; 6 | 7 | final String _collection = 'favorites'; 8 | 9 | @Operation.post() 10 | Future createFavorite( 11 | @Bind.body() Map favorite) async { 12 | if (!favorite.containsKey('user_id')) 13 | return Response.badRequest(body: { 14 | 'status': false, 15 | 'message': 'User id is Required', 16 | }); 17 | 18 | if (!favorite.containsKey('article_id')) 19 | return Response.badRequest(body: { 20 | 'status': false, 21 | 'message': 'Article id is Required', 22 | }); 23 | 24 | try { 25 | final createdFavorite = await _db.collection(_collection).insert({ 26 | 'user_id': ObjectId.parse(favorite['user_id'] as String), 27 | 'article_id': ObjectId.parse(favorite['article_id'] as String), 28 | 'created_at': DateTime.now().toString(), 29 | }); 30 | if (createdFavorite != null) 31 | return Response.created('', body: { 32 | 'status': true, 33 | 'message': 'Favorite created successfully', 34 | }); 35 | else 36 | return Response.serverError(body: { 37 | 'status': false, 38 | 'message': 'Favorite created failed!', 39 | }); 40 | } catch (e) { 41 | print(e); 42 | return Response.serverError(body: { 43 | 'status': false, 44 | 'message': 'Favorite already exist!', 45 | }); 46 | } 47 | } 48 | 49 | @Operation.get('userId') 50 | Future getFavorites(@Bind.path('userId') String userId) async { 51 | try { 52 | final pipeline = AggregationPipelineBuilder() 53 | .addStage(Match( 54 | where.eq('user_id', ObjectId.parse(userId)).map['\$query'], 55 | )) 56 | .addStage(Lookup( 57 | from: 'articles', 58 | localField: 'article_id', 59 | foreignField: '_id', 60 | as: 'article', 61 | )) 62 | .addStage(Project( 63 | { 64 | 'article': 1, 65 | }, 66 | )) 67 | .addStage(Unwind( 68 | const Field('article'), 69 | preserveNullAndEmptyArrays: true, 70 | )) 71 | .build(); 72 | 73 | final favorites = await _db 74 | .collection(_collection) 75 | .aggregateToStream(pipeline) 76 | .toList(); 77 | 78 | if (favorites != null) { 79 | return Response.ok({ 80 | 'status': true, 81 | 'message': 'Favorites found successfully', 82 | 'data': favorites, 83 | }); 84 | } else 85 | return Response.notFound(body: { 86 | 'status': false, 87 | 'message': 'Favorites not found!', 88 | }); 89 | } catch (e) { 90 | print(e); 91 | return Response.serverError(body: { 92 | 'status': false, 93 | 'message': 'Favorites not found!', 94 | }); 95 | } 96 | } 97 | 98 | @Operation.delete() 99 | Future deleteFavorite( 100 | @Bind.body() Map body) async { 101 | try { 102 | if (!body.containsKey('article_id')) 103 | return Response.badRequest(body: { 104 | 'status': false, 105 | 'message': 'Article id is required!', 106 | }); 107 | 108 | if (!body.containsKey('user_id')) 109 | return Response.badRequest(body: { 110 | 'status': false, 111 | 'message': 'User id is required!', 112 | }); 113 | 114 | await _db.collection(_collection).remove( 115 | { 116 | 'article_id': ObjectId.parse(body['article_id'] as String), 117 | 'user_id': ObjectId.parse(body['user_id'] as String), 118 | }, 119 | ); 120 | 121 | return Response.ok({ 122 | 'status': true, 123 | 'message': 'Favorite deleted successfully', 124 | }); 125 | } catch (e) { 126 | print(e); 127 | return Response.serverError(body: { 128 | 'status': false, 129 | 'message': 'Favorite not found!', 130 | }); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/src/controllers/articles.dart: -------------------------------------------------------------------------------- 1 | import '../../hello_aqueduct.dart'; 2 | 3 | class ArticlesController extends ResourceController { 4 | ArticlesController(this._db) { 5 | acceptedContentTypes = [ContentType('multipart', 'form-data')]; 6 | } 7 | final Db _db; 8 | 9 | final String _collection = 'articles'; 10 | 11 | // TODO support emoji content 12 | @Operation.post() 13 | Future createArticle() async { 14 | final multipartsUtils = MultipartsUtils(request: request); 15 | await multipartsUtils.parse(); 16 | 17 | if (!multipartsUtils.containsKey('user_id')) 18 | return Response.badRequest(body: { 19 | 'status': false, 20 | 'message': 'User id is Required', 21 | }); 22 | 23 | if (!multipartsUtils.containsKey('title')) 24 | return Response.badRequest(body: { 25 | 'status': false, 26 | 'message': 'Title is Required', 27 | }); 28 | if (!multipartsUtils.containsKey('content')) 29 | return Response.badRequest(body: { 30 | 'status': false, 31 | 'message': 'Content is Required', 32 | }); 33 | if (!multipartsUtils.containsFiles()) 34 | return Response.badRequest(body: { 35 | 'status': false, 36 | 'message': 'Image is Required', 37 | }); 38 | 39 | final articleId = ObjectId(); 40 | 41 | final imagesPath = await multipartsUtils.saveFiles(articleId.toHexString()); 42 | 43 | try { 44 | await _db.collection(_collection).insert({ 45 | '_id': articleId, 46 | 'title': await multipartsUtils.getValue('title'), 47 | 'content': await multipartsUtils.getValue('content'), 48 | 'user_id': ObjectId.parse( 49 | await multipartsUtils.getValue('user_id'), 50 | ), 51 | 'category_id': multipartsUtils.containsKey('category_id') 52 | ? ObjectId.parse( 53 | await multipartsUtils.getValue('category_id'), 54 | ) 55 | : null, 56 | 'images': imagesPath, 57 | 'created_at': DateTime.now().toString(), 58 | }); 59 | 60 | return Response.created('', body: { 61 | 'status': true, 62 | 'message': 'Article created successfully', 63 | }); 64 | } catch (e) { 65 | print(e); 66 | return Response.serverError(body: { 67 | 'status': false, 68 | 'message': 'Article created failed!', 69 | }); 70 | } 71 | } 72 | 73 | @Operation.get() 74 | Future getArticles({@Bind.query('query') String query}) async { 75 | try { 76 | final selector = where.sortBy('created_at', descending: true); 77 | 78 | // Search by title 79 | if (query?.isNotEmpty ?? false) 80 | selector.match( 81 | 'title', 82 | query, 83 | caseInsensitive: true, 84 | ); 85 | 86 | final articles = 87 | await _db.collection(_collection).find(selector).toList(); 88 | 89 | if (articles != null) { 90 | return Response.ok({ 91 | 'status': true, 92 | 'message': 'Articles found successfully', 93 | 'data': articles, 94 | }); 95 | } else 96 | return Response.notFound(body: { 97 | 'status': false, 98 | 'message': 'Articles not found!', 99 | }); 100 | } catch (e) { 101 | return Response.serverError(body: { 102 | 'status': false, 103 | 'message': 'Articles not found!', 104 | }); 105 | } 106 | } 107 | 108 | @Operation.get('id') 109 | Future getArticleById( 110 | @requiredBinding @Bind.path('id') String id, { 111 | @Bind.query('userId') String userId, 112 | }) async { 113 | try { 114 | if (id?.isEmpty ?? true) 115 | return Response.badRequest(body: { 116 | 'status': false, 117 | 'message': 'Article id is required!', 118 | }); 119 | 120 | final pipeline = AggregationPipelineBuilder() 121 | .addStage(Match( 122 | where.id(ObjectId.parse(id)).map['\$query'], 123 | )) 124 | .addStage(Lookup.withPipeline( 125 | from: 'favorites', 126 | let: {}, 127 | pipeline: [ 128 | Match( 129 | where 130 | .eq( 131 | 'user_id', 132 | userId != null ? ObjectId.parse(userId) : null, 133 | ) 134 | .map['\$query'], 135 | ), 136 | Match( 137 | where.eq('article_id', ObjectId.parse(id)).map['\$query'], 138 | ), 139 | ], 140 | as: 'isFavorite', 141 | )) 142 | .addStage(Project( 143 | { 144 | 'title': 1, 145 | 'content': 1, 146 | 'images': 1, 147 | 'created_at': 1, 148 | 'user_id': 1, 149 | 'category_id': 1, 150 | 'isFavorite': Gt(Size('\$isFavorite'), 0), 151 | }, 152 | )) 153 | .build(); 154 | 155 | final article = await _db 156 | .collection(_collection) 157 | .aggregateToStream(pipeline) 158 | .toList(); 159 | 160 | if (article != null) { 161 | return Response.ok({ 162 | 'status': true, 163 | 'message': 'Article found successfully', 164 | 'data': article.first, 165 | }); 166 | } else 167 | return Response.notFound(body: { 168 | 'status': false, 169 | 'message': 'Article not found!', 170 | }); 171 | } catch (e) { 172 | print(e); 173 | return Response.serverError(body: { 174 | 'status': false, 175 | 'message': 'Article not found!', 176 | }); 177 | } 178 | } 179 | 180 | @Operation.get('categoryId') 181 | Future getArticleByCategory( 182 | @requiredBinding @Bind.path('categoryId') String categoryId) async { 183 | try { 184 | if (categoryId?.isEmpty ?? true) 185 | return Response.badRequest(body: { 186 | 'status': false, 187 | 'message': 'Category id is required!', 188 | }); 189 | 190 | final article = await _db.collection(_collection).find( 191 | {'category_id': ObjectId.parse(categoryId)}, 192 | ).toList(); 193 | 194 | if (article != null) { 195 | return Response.ok({ 196 | 'status': true, 197 | 'message': 'Articles found successfully', 198 | 'data': article, 199 | }); 200 | } else 201 | return Response.notFound(body: { 202 | 'status': false, 203 | 'message': 'Articles not found!', 204 | }); 205 | } catch (e) { 206 | print(e); 207 | return Response.serverError(body: { 208 | 'status': false, 209 | 'message': 'Articles not found!', 210 | }); 211 | } 212 | } 213 | 214 | @Operation.get('userId') 215 | Future getArticleByUser( 216 | @requiredBinding @Bind.path('userId') String userId) async { 217 | try { 218 | if (userId?.isEmpty ?? true) 219 | return Response.badRequest(body: { 220 | 'status': false, 221 | 'message': 'User id is required!', 222 | }); 223 | 224 | final article = await _db.collection(_collection).find( 225 | {'user_id': ObjectId.parse(userId)}, 226 | ).toList(); 227 | 228 | if (article != null) { 229 | return Response.ok({ 230 | 'status': true, 231 | 'message': 'Articles found successfully', 232 | 'data': article, 233 | }); 234 | } else 235 | return Response.notFound(body: { 236 | 'status': false, 237 | 'message': 'Articles not found!', 238 | }); 239 | } catch (e) { 240 | print(e); 241 | return Response.serverError(body: { 242 | 'status': false, 243 | 'message': 'Articles not found!', 244 | }); 245 | } 246 | } 247 | 248 | @Operation.delete() 249 | Future deleteArticle( 250 | @Bind.body() Map article) async { 251 | try { 252 | if (!article.containsKey('article_id')) 253 | return Response.badRequest(body: { 254 | 'status': false, 255 | 'message': 'Article id is required!', 256 | }); 257 | 258 | await _db.collection(_collection).remove( 259 | { 260 | '_id': ObjectId.parse(article['article_id'] as String), 261 | }, 262 | ); 263 | 264 | await MultipartsUtils.deleteFiles(article['article_id'] as String); 265 | 266 | return Response.ok({ 267 | 'status': true, 268 | 'message': 'Article deleted successfully', 269 | }); 270 | } catch (e) { 271 | print(e); 272 | return Response.serverError(body: { 273 | 'status': false, 274 | 'message': 'Article not found!', 275 | }); 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.35.4" 11 | aqueduct: 12 | dependency: "direct main" 13 | description: 14 | name: aqueduct 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "3.3.0+1" 18 | aqueduct_test: 19 | dependency: "direct dev" 20 | description: 21 | name: aqueduct_test 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.0.1" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.6.0" 32 | asn1lib: 33 | dependency: transitive 34 | description: 35 | name: asn1lib 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "0.6.5" 39 | async: 40 | dependency: transitive 41 | description: 42 | name: async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "2.4.2" 46 | auth_header: 47 | dependency: transitive 48 | description: 49 | name: auth_header 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.4" 53 | basic_utils: 54 | dependency: transitive 55 | description: 56 | name: basic_utils 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.6.3" 60 | boolean_selector: 61 | dependency: transitive 62 | description: 63 | name: boolean_selector 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.0.5" 67 | bson: 68 | dependency: transitive 69 | description: 70 | name: bson 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.3.3" 74 | charcode: 75 | dependency: transitive 76 | description: 77 | name: charcode 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.1.3" 81 | codable: 82 | dependency: transitive 83 | description: 84 | name: codable 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.0.0" 88 | collection: 89 | dependency: transitive 90 | description: 91 | name: collection 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.14.13" 95 | convert: 96 | dependency: transitive 97 | description: 98 | name: convert 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "2.1.1" 102 | crypto: 103 | dependency: "direct main" 104 | description: 105 | name: crypto 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "2.1.5" 109 | fixnum: 110 | dependency: transitive 111 | description: 112 | name: fixnum 113 | url: "https://pub.dartlang.org" 114 | source: hosted 115 | version: "0.10.11" 116 | front_end: 117 | dependency: transitive 118 | description: 119 | name: front_end 120 | url: "https://pub.dartlang.org" 121 | source: hosted 122 | version: "0.1.14" 123 | glob: 124 | dependency: transitive 125 | description: 126 | name: glob 127 | url: "https://pub.dartlang.org" 128 | source: hosted 129 | version: "1.2.0" 130 | http: 131 | dependency: transitive 132 | description: 133 | name: http 134 | url: "https://pub.dartlang.org" 135 | source: hosted 136 | version: "0.12.2" 137 | http_multi_server: 138 | dependency: transitive 139 | description: 140 | name: http_multi_server 141 | url: "https://pub.dartlang.org" 142 | source: hosted 143 | version: "2.2.0" 144 | http_parser: 145 | dependency: transitive 146 | description: 147 | name: http_parser 148 | url: "https://pub.dartlang.org" 149 | source: hosted 150 | version: "3.1.4" 151 | http_server: 152 | dependency: "direct main" 153 | description: 154 | name: http_server 155 | url: "https://pub.dartlang.org" 156 | source: hosted 157 | version: "0.9.8+3" 158 | io: 159 | dependency: transitive 160 | description: 161 | name: io 162 | url: "https://pub.dartlang.org" 163 | source: hosted 164 | version: "0.3.4" 165 | isolate_executor: 166 | dependency: transitive 167 | description: 168 | name: isolate_executor 169 | url: "https://pub.dartlang.org" 170 | source: hosted 171 | version: "2.0.2+3" 172 | jaguar_jwt: 173 | dependency: "direct main" 174 | description: 175 | name: jaguar_jwt 176 | url: "https://pub.dartlang.org" 177 | source: hosted 178 | version: "2.1.6" 179 | js: 180 | dependency: transitive 181 | description: 182 | name: js 183 | url: "https://pub.dartlang.org" 184 | source: hosted 185 | version: "0.6.2" 186 | json_annotation: 187 | dependency: transitive 188 | description: 189 | name: json_annotation 190 | url: "https://pub.dartlang.org" 191 | source: hosted 192 | version: "3.0.1" 193 | json_rpc_2: 194 | dependency: transitive 195 | description: 196 | name: json_rpc_2 197 | url: "https://pub.dartlang.org" 198 | source: hosted 199 | version: "2.2.2" 200 | kernel: 201 | dependency: transitive 202 | description: 203 | name: kernel 204 | url: "https://pub.dartlang.org" 205 | source: hosted 206 | version: "0.3.14" 207 | logging: 208 | dependency: transitive 209 | description: 210 | name: logging 211 | url: "https://pub.dartlang.org" 212 | source: hosted 213 | version: "0.11.4" 214 | matcher: 215 | dependency: transitive 216 | description: 217 | name: matcher 218 | url: "https://pub.dartlang.org" 219 | source: hosted 220 | version: "0.12.5" 221 | meta: 222 | dependency: transitive 223 | description: 224 | name: meta 225 | url: "https://pub.dartlang.org" 226 | source: hosted 227 | version: "1.2.4" 228 | mime: 229 | dependency: "direct main" 230 | description: 231 | name: mime 232 | url: "https://pub.dartlang.org" 233 | source: hosted 234 | version: "0.9.7" 235 | mongo_dart: 236 | dependency: "direct main" 237 | description: 238 | name: mongo_dart 239 | url: "https://pub.dartlang.org" 240 | source: hosted 241 | version: "0.4.4" 242 | mongo_dart_query: 243 | dependency: transitive 244 | description: 245 | name: mongo_dart_query 246 | url: "https://pub.dartlang.org" 247 | source: hosted 248 | version: "0.4.2" 249 | more: 250 | dependency: transitive 251 | description: 252 | name: more 253 | url: "https://pub.dartlang.org" 254 | source: hosted 255 | version: "1.18.2" 256 | multi_server_socket: 257 | dependency: transitive 258 | description: 259 | name: multi_server_socket 260 | url: "https://pub.dartlang.org" 261 | source: hosted 262 | version: "1.0.2" 263 | node_interop: 264 | dependency: transitive 265 | description: 266 | name: node_interop 267 | url: "https://pub.dartlang.org" 268 | source: hosted 269 | version: "1.1.1" 270 | node_io: 271 | dependency: transitive 272 | description: 273 | name: node_io 274 | url: "https://pub.dartlang.org" 275 | source: hosted 276 | version: "1.1.1" 277 | node_preamble: 278 | dependency: transitive 279 | description: 280 | name: node_preamble 281 | url: "https://pub.dartlang.org" 282 | source: hosted 283 | version: "1.4.12" 284 | open_api: 285 | dependency: transitive 286 | description: 287 | name: open_api 288 | url: "https://pub.dartlang.org" 289 | source: hosted 290 | version: "2.0.1" 291 | package_config: 292 | dependency: transitive 293 | description: 294 | name: package_config 295 | url: "https://pub.dartlang.org" 296 | source: hosted 297 | version: "1.9.3" 298 | package_resolver: 299 | dependency: transitive 300 | description: 301 | name: package_resolver 302 | url: "https://pub.dartlang.org" 303 | source: hosted 304 | version: "1.0.10" 305 | password_hash: 306 | dependency: transitive 307 | description: 308 | name: password_hash 309 | url: "https://pub.dartlang.org" 310 | source: hosted 311 | version: "2.0.0" 312 | path: 313 | dependency: transitive 314 | description: 315 | name: path 316 | url: "https://pub.dartlang.org" 317 | source: hosted 318 | version: "1.7.0" 319 | pedantic: 320 | dependency: transitive 321 | description: 322 | name: pedantic 323 | url: "https://pub.dartlang.org" 324 | source: hosted 325 | version: "1.9.2" 326 | pointycastle: 327 | dependency: transitive 328 | description: 329 | name: pointycastle 330 | url: "https://pub.dartlang.org" 331 | source: hosted 332 | version: "1.0.2" 333 | pool: 334 | dependency: transitive 335 | description: 336 | name: pool 337 | url: "https://pub.dartlang.org" 338 | source: hosted 339 | version: "1.4.0" 340 | postgres: 341 | dependency: transitive 342 | description: 343 | name: postgres 344 | url: "https://pub.dartlang.org" 345 | source: hosted 346 | version: "1.0.2" 347 | pub_cache: 348 | dependency: transitive 349 | description: 350 | name: pub_cache 351 | url: "https://pub.dartlang.org" 352 | source: hosted 353 | version: "0.2.3" 354 | pub_semver: 355 | dependency: transitive 356 | description: 357 | name: pub_semver 358 | url: "https://pub.dartlang.org" 359 | source: hosted 360 | version: "1.4.4" 361 | safe_config: 362 | dependency: transitive 363 | description: 364 | name: safe_config 365 | url: "https://pub.dartlang.org" 366 | source: hosted 367 | version: "2.0.2" 368 | shelf: 369 | dependency: transitive 370 | description: 371 | name: shelf 372 | url: "https://pub.dartlang.org" 373 | source: hosted 374 | version: "0.7.9" 375 | shelf_packages_handler: 376 | dependency: transitive 377 | description: 378 | name: shelf_packages_handler 379 | url: "https://pub.dartlang.org" 380 | source: hosted 381 | version: "1.0.4" 382 | shelf_static: 383 | dependency: transitive 384 | description: 385 | name: shelf_static 386 | url: "https://pub.dartlang.org" 387 | source: hosted 388 | version: "0.2.9+1" 389 | shelf_web_socket: 390 | dependency: transitive 391 | description: 392 | name: shelf_web_socket 393 | url: "https://pub.dartlang.org" 394 | source: hosted 395 | version: "0.2.3" 396 | source_map_stack_trace: 397 | dependency: transitive 398 | description: 399 | name: source_map_stack_trace 400 | url: "https://pub.dartlang.org" 401 | source: hosted 402 | version: "1.1.5" 403 | source_maps: 404 | dependency: transitive 405 | description: 406 | name: source_maps 407 | url: "https://pub.dartlang.org" 408 | source: hosted 409 | version: "0.10.9" 410 | source_span: 411 | dependency: transitive 412 | description: 413 | name: source_span 414 | url: "https://pub.dartlang.org" 415 | source: hosted 416 | version: "1.7.0" 417 | stack_trace: 418 | dependency: transitive 419 | description: 420 | name: stack_trace 421 | url: "https://pub.dartlang.org" 422 | source: hosted 423 | version: "1.9.6" 424 | stream_channel: 425 | dependency: transitive 426 | description: 427 | name: stream_channel 428 | url: "https://pub.dartlang.org" 429 | source: hosted 430 | version: "2.0.0" 431 | string_scanner: 432 | dependency: transitive 433 | description: 434 | name: string_scanner 435 | url: "https://pub.dartlang.org" 436 | source: hosted 437 | version: "1.0.5" 438 | term_glyph: 439 | dependency: transitive 440 | description: 441 | name: term_glyph 442 | url: "https://pub.dartlang.org" 443 | source: hosted 444 | version: "1.1.0" 445 | test: 446 | dependency: "direct dev" 447 | description: 448 | name: test 449 | url: "https://pub.dartlang.org" 450 | source: hosted 451 | version: "1.6.3" 452 | test_api: 453 | dependency: transitive 454 | description: 455 | name: test_api 456 | url: "https://pub.dartlang.org" 457 | source: hosted 458 | version: "0.2.5" 459 | test_core: 460 | dependency: transitive 461 | description: 462 | name: test_core 463 | url: "https://pub.dartlang.org" 464 | source: hosted 465 | version: "0.2.5" 466 | typed_data: 467 | dependency: transitive 468 | description: 469 | name: typed_data 470 | url: "https://pub.dartlang.org" 471 | source: hosted 472 | version: "1.2.0" 473 | uuid: 474 | dependency: transitive 475 | description: 476 | name: uuid 477 | url: "https://pub.dartlang.org" 478 | source: hosted 479 | version: "2.2.2" 480 | vm_service_client: 481 | dependency: transitive 482 | description: 483 | name: vm_service_client 484 | url: "https://pub.dartlang.org" 485 | source: hosted 486 | version: "0.2.6+3" 487 | watcher: 488 | dependency: transitive 489 | description: 490 | name: watcher 491 | url: "https://pub.dartlang.org" 492 | source: hosted 493 | version: "0.9.7+15" 494 | web_socket_channel: 495 | dependency: transitive 496 | description: 497 | name: web_socket_channel 498 | url: "https://pub.dartlang.org" 499 | source: hosted 500 | version: "1.1.0" 501 | yaml: 502 | dependency: transitive 503 | description: 504 | name: yaml 505 | url: "https://pub.dartlang.org" 506 | source: hosted 507 | version: "2.2.1" 508 | sdks: 509 | dart: ">=2.7.0 <3.0.0" 510 | --------------------------------------------------------------------------------