From 84f5ae540394653498966dc260d3ccc8af87539c Mon Sep 17 00:00:00 2001 From: Tobe O Date: Fri, 11 Jan 2019 19:44:28 -0500 Subject: [PATCH] orm boilerplate 2.x --- bin/migrate.dart | 16 ++ config/default.yaml | 4 + lib/models.dart | 1 + lib/src/config/plugins/orm.dart | 31 ++++ lib/src/config/plugins/plugins.dart | 2 + lib/src/models/greeting.dart | 13 ++ lib/src/models/greeting.g.dart | 233 ++++++++++++++++++++++++++++ lib/src/routes/routes.dart | 28 ++++ pubspec.yaml | 17 ++ 9 files changed, 345 insertions(+) create mode 100644 bin/migrate.dart create mode 100644 lib/models.dart create mode 100644 lib/src/config/plugins/orm.dart create mode 100644 lib/src/models/greeting.dart create mode 100644 lib/src/models/greeting.g.dart diff --git a/bin/migrate.dart b/bin/migrate.dart new file mode 100644 index 0000000..49ed6f9 --- /dev/null +++ b/bin/migrate.dart @@ -0,0 +1,16 @@ +import 'package:angel/src/config/plugins/orm.dart'; +import 'package:angel/models.dart'; +import 'package:angel_configuration/angel_configuration.dart'; +import 'package:angel_migration_runner/angel_migration_runner.dart'; +import 'package:angel_migration_runner/postgres.dart'; +import 'package:file/local.dart'; + +main(List args) async { + var fs = LocalFileSystem(); + var configuration = await loadStandaloneConfiguration(fs); + var connection = await connectToPostgres(configuration); + var migrationRunner = PostgresMigrationRunner(connection, migrations: [ + GreetingMigration(), + ]); + return await runMigrations(migrationRunner, args); +} diff --git a/config/default.yaml b/config/default.yaml index 1406625..c33077d 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -3,3 +3,7 @@ jwt_secret: INSECURE_DEFAULT_SECRET host: 127.0.0.1 mongo_db: mongodb://localhost:27017/angel port: 3000 +postgres: + # database_name: angel + username: postgres + password: postgres diff --git a/lib/models.dart b/lib/models.dart new file mode 100644 index 0000000..52af91a --- /dev/null +++ b/lib/models.dart @@ -0,0 +1 @@ +export 'src/models/greeting.dart'; \ No newline at end of file diff --git a/lib/src/config/plugins/orm.dart b/lib/src/config/plugins/orm.dart new file mode 100644 index 0000000..3e616b9 --- /dev/null +++ b/lib/src/config/plugins/orm.dart @@ -0,0 +1,31 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_orm/angel_orm.dart'; +import 'package:angel_orm_postgres/angel_orm_postgres.dart'; +import 'package:postgres/postgres.dart'; + +Future configureServer(Angel app) async { + var connection = await connectToPostgres(app.configuration); + await connection.open(); + + app + ..container.registerSingleton(PostgreSQLExecutor(connection)) + ..shutdownHooks.add((_) => connection.close()); +} + +Future connectToPostgres(Map configuration) async { + var postgresConfig = configuration['postgres'] as Map ?? {}; + var connection = PostgreSQLConnection( + postgresConfig['host'] as String ?? 'localhost', + postgresConfig['port'] as int ?? 5432, + postgresConfig['database_name'] as String ?? + Platform.environment['USER'] ?? + Platform.environment['USERNAME'], + username: postgresConfig['username'] as String, + password: postgresConfig['password'] as String, + timeZone: postgresConfig['time_zone'] as String ?? 'UTC', + timeoutInSeconds: postgresConfig['timeout_in_seconds'] as int ?? 30, + useSSL: postgresConfig['use_ssl'] as bool ?? false); + return connection; +} diff --git a/lib/src/config/plugins/plugins.dart b/lib/src/config/plugins/plugins.dart index b025b21..844edd5 100644 --- a/lib/src/config/plugins/plugins.dart +++ b/lib/src/config/plugins/plugins.dart @@ -3,7 +3,9 @@ library angel.src.config.plugins; import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; +import 'orm.dart' as orm; Future configureServer(Angel app) async { // Include any plugins you have made here. + await app.configure(orm.configureServer); } diff --git a/lib/src/models/greeting.dart b/lib/src/models/greeting.dart new file mode 100644 index 0000000..4676fce --- /dev/null +++ b/lib/src/models/greeting.dart @@ -0,0 +1,13 @@ +import 'package:angel_migration/angel_migration.dart'; +import 'package:angel_model/angel_model.dart'; +import 'package:angel_serialize/angel_serialize.dart'; +import 'package:angel_orm/angel_orm.dart'; +import 'package:meta/meta.dart'; +part 'greeting.g.dart'; + +@serializable +@orm +abstract class _Greeting extends Model { + @SerializableField(isNullable: false) + String get message; +} diff --git a/lib/src/models/greeting.g.dart b/lib/src/models/greeting.g.dart new file mode 100644 index 0000000..8850094 --- /dev/null +++ b/lib/src/models/greeting.g.dart @@ -0,0 +1,233 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'greeting.dart'; + +// ************************************************************************** +// MigrationGenerator +// ************************************************************************** + +class GreetingMigration extends Migration { + @override + up(Schema schema) { + schema.create('greetings', (table) { + table.serial('id')..primaryKey(); + table.varChar('message'); + table.timeStamp('created_at'); + table.timeStamp('updated_at'); + }); + } + + @override + down(Schema schema) { + schema.drop('greetings'); + } +} + +// ************************************************************************** +// OrmGenerator +// ************************************************************************** + +class GreetingQuery extends Query { + GreetingQuery() { + _where = new GreetingQueryWhere(this); + } + + @override + final GreetingQueryValues values = new GreetingQueryValues(); + + GreetingQueryWhere _where; + + @override + get tableName { + return 'greetings'; + } + + @override + get fields { + return const ['id', 'message', 'created_at', 'updated_at']; + } + + @override + GreetingQueryWhere get where { + return _where; + } + + @override + GreetingQueryWhere newWhereClause() { + return new GreetingQueryWhere(this); + } + + static Greeting parseRow(List row) { + if (row.every((x) => x == null)) return null; + var model = new Greeting( + id: row[0].toString(), + message: (row[1] as String), + createdAt: (row[2] as DateTime), + updatedAt: (row[3] as DateTime)); + return model; + } + + @override + deserialize(List row) { + return parseRow(row); + } +} + +class GreetingQueryWhere extends QueryWhere { + GreetingQueryWhere(GreetingQuery query) + : id = new NumericSqlExpressionBuilder(query, 'id'), + message = new StringSqlExpressionBuilder(query, 'message'), + createdAt = new DateTimeSqlExpressionBuilder(query, 'created_at'), + updatedAt = new DateTimeSqlExpressionBuilder(query, 'updated_at'); + + final NumericSqlExpressionBuilder id; + + final StringSqlExpressionBuilder message; + + final DateTimeSqlExpressionBuilder createdAt; + + final DateTimeSqlExpressionBuilder updatedAt; + + @override + get expressionBuilders { + return [id, message, createdAt, updatedAt]; + } +} + +class GreetingQueryValues extends MapQueryValues { + int get id { + return (values['id'] as int); + } + + set id(int value) => values['id'] = value; + String get message { + return (values['message'] as String); + } + + set message(String value) => values['message'] = value; + DateTime get createdAt { + return (values['created_at'] as DateTime); + } + + set createdAt(DateTime value) => values['created_at'] = value; + DateTime get updatedAt { + return (values['updated_at'] as DateTime); + } + + set updatedAt(DateTime value) => values['updated_at'] = value; + void copyFrom(Greeting model) { + values.addAll({ + 'message': model.message, + 'created_at': model.createdAt, + 'updated_at': model.updatedAt + }); + } +} + +// ************************************************************************** +// JsonModelGenerator +// ************************************************************************** + +@generatedSerializable +class Greeting extends _Greeting { + Greeting({this.id, @required this.message, this.createdAt, this.updatedAt}); + + @override + final String id; + + @override + final String message; + + @override + final DateTime createdAt; + + @override + final DateTime updatedAt; + + Greeting copyWith( + {String id, String message, DateTime createdAt, DateTime updatedAt}) { + return new Greeting( + id: id ?? this.id, + message: message ?? this.message, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt); + } + + bool operator ==(other) { + return other is _Greeting && + other.id == id && + other.message == message && + other.createdAt == createdAt && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return hashObjects([id, message, createdAt, updatedAt]); + } + + Map toJson() { + return GreetingSerializer.toMap(this); + } +} + +// ************************************************************************** +// SerializerGenerator +// ************************************************************************** + +abstract class GreetingSerializer { + static Greeting fromMap(Map map) { + if (map['message'] == null) { + throw new FormatException( + "Missing required field 'message' on Greeting."); + } + + return new Greeting( + id: map['id'] as String, + message: map['message'] as String, + createdAt: map['created_at'] != null + ? (map['created_at'] is DateTime + ? (map['created_at'] as DateTime) + : DateTime.parse(map['created_at'].toString())) + : null, + updatedAt: map['updated_at'] != null + ? (map['updated_at'] is DateTime + ? (map['updated_at'] as DateTime) + : DateTime.parse(map['updated_at'].toString())) + : null); + } + + static Map toMap(_Greeting model) { + if (model == null) { + return null; + } + if (model.message == null) { + throw new FormatException( + "Missing required field 'message' on Greeting."); + } + + return { + 'id': model.id, + 'message': model.message, + 'created_at': model.createdAt?.toIso8601String(), + 'updated_at': model.updatedAt?.toIso8601String() + }; + } +} + +abstract class GreetingFields { + static const List allFields = const [ + id, + message, + createdAt, + updatedAt + ]; + + static const String id = 'id'; + + static const String message = 'message'; + + static const String createdAt = 'created_at'; + + static const String updatedAt = 'updated_at'; +} diff --git a/lib/src/routes/routes.dart b/lib/src/routes/routes.dart index a75a12d..74d159a 100644 --- a/lib/src/routes/routes.dart +++ b/lib/src/routes/routes.dart @@ -2,9 +2,11 @@ library angel.src.routes; import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_orm/angel_orm.dart'; import 'package:angel_static/angel_static.dart'; import 'package:file/file.dart'; import 'controllers/controllers.dart' as controllers; +import '../models/greeting.dart'; /// Put your app routes here! /// @@ -19,6 +21,32 @@ AngelConfigurer configureServer(FileSystem fileSystem) { // Render `views/hello.jl` when a user visits the application root. app.get('/', (req, res) => res.render('hello')); + app.get('/greetings', (req, res) { + var executor = req.container.make(); + var query = GreetingQuery(); + return query.get(executor); + }); + + app.post('/greetings', (req, res) async { + await req.parseBody(); + + if (!req.bodyAsMap.containsKey('message')) { + throw AngelHttpException.badRequest(message: 'Missing "message".'); + } else { + var executor = req.container.make(); + var message = req.bodyAsMap['message'].toString(); + var query = GreetingQuery()..values.message = message; + return await query.insert(executor); + } + }); + + app.get('/greetings/:message', (req, res) { + var message = req.params['message'] as String; + var executor = req.container.make(); + var query = GreetingQuery()..where.message.equals(message); + return query.get(executor); + }); + // Mount static server at web in development. // The `CachingVirtualDirectory` variant of `VirtualDirectory` also sends `Cache-Control` headers. // diff --git a/pubspec.yaml b/pubspec.yaml index ab6215d..98919c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,12 +9,29 @@ dependencies: angel_configuration: ^2.0.0 # Loads application configuration, along with support for .env files. angel_framework: ^2.0.0-alpha # The core server library. angel_jael: ^2.0.0 # Server-side templating engine + angel_migration: ^2.0.0-alpha # Migration runtime support + angel_orm: ^2.0.0-dev # Migration runtime support + angel_orm_postgres: ^1.0.0-dev # PostgreSQL support for Angel ORM + angel_serialize: ^2.0.0 # Serialization runtime support angel_production: ^1.0.0-alpha angel_static: ^2.0.0-alpha # Static file server angel_validate: ^2.0.0-alpha # Allows for validation of input data dev_dependencies: angel_hot: ^2.0.0 # Hot-reloading support. :) + angel_migration_runner: ^2.0.0-alpha # Runs migrations + angel_orm_generator: ^2.0.0-dev # Generates ORM libraries + angel_serialize_generator: ^2.0.0 # Generates model serialization code angel_test: ^2.0.0-alpha # Utilities for testing Angel servers. + build_runner: ^1.0.0 # Runs builds io: ^0.3.2 pedantic: ^1.0.0 test: ^1.0.0 +dependency_overrides: + angel_orm: + git: + url: https://github.com/angel-dart/orm.git + path: angel_orm + angel_orm_generator: + git: + url: https://github.com/angel-dart/orm.git + path: angel_orm_generator \ No newline at end of file