diff --git a/README.md b/README.md index 3e7ed2f..33d16ab 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,28 @@ # ORM Starter Application for Angel3 framework -This is an ORM starter application for [Angel3 framework](https://angel3-framework.web.app) which is a full-stack Web framework in Dart. The default database is `postgresql`. `mysql` support is still in active development. +This is an ORM starter application for [Angel3 framework](https://angel3-framework.web.app) which is a full-stack Web framework in Dart. The default database is MariaDB. MySQL support is still in active development. ## Installation & Setup 1. Download and install [Dart](https://dart.dev/get-dart). -2. Install `postgresql` version 10, 11, 12 and 13 -3. Create a new user and database in postgres using `psql` cli. For example: +2. Install `MariaDB` 10.2.x or later +3. Create a new user and database using MySQL Client. For example: ```sql - postgres=# create database appdb; - postgres=# create user appuser with encrypted password 'App1970#'; - postgres=# grant all privileges on database appdb to appuser; + MariaDB [(none)]> CREATE DATABASE appdb; + MariaDB [(none)]> CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'App1970#'; + MariaDB [(none)]> GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost'; ``` -4. Update the `postgres` section in the `config/default.yaml` file with the newly created user and database name. +4. Update the `mariadb` section in the `config/default.yaml` file with the newly created user and database name. ```yaml - postgres: + mariadb: host: localhost - port: 5432 + port: 3306 database_name: appdb username: appuser password: App1970# - useSSL: false - time_zone: UTC ``` 5. Run the migration to generate `migrations` and `greetings` tables in the database. @@ -57,8 +55,8 @@ This is an ORM starter application for [Angel3 framework](https://angel3-framewo 4. Query DB: - ``` - http://localhost:3000/greetings/ + ```bash + curl http://localhost:3000/greetings/ ``` ### Production @@ -71,6 +69,14 @@ This is an ORM starter application for [Angel3 framework](https://angel3-framewo 2. Run as docker. Edit and run the provided `Dockerfile` to build the image. +### Building ORM Model + +1. Run the followig command: + + ```bash + dart run build_runner build + ``` + ## Resources Visit the [Developer Guide](https://angel3-docs.dukefirehawk.com/guides) for dozens of guides and resources, including video tutorials, to get up and running as quickly as possible with Angel3. diff --git a/bin/migrate.dart b/bin/migrate.dart index 0474245..05b3ae0 100644 --- a/bin/migrate.dart +++ b/bin/migrate.dart @@ -2,7 +2,7 @@ import 'package:angel/src/config/plugins/orm.dart'; import 'package:angel/models.dart'; import 'package:angel3_configuration/angel3_configuration.dart'; import 'package:angel3_migration_runner/angel3_migration_runner.dart'; -import 'package:angel3_migration_runner/postgres.dart'; +import 'package:angel3_migration_runner/mariadb.dart'; import 'package:file/local.dart'; import 'package:logging/logging.dart'; @@ -20,9 +20,12 @@ void main(List args) async { var fs = LocalFileSystem(); var configuration = await loadStandaloneConfiguration(fs); - var connection = await connectToPostgres(configuration); - var migrationRunner = PostgresMigrationRunner(connection, migrations: [ + + // MariaDB database + var connection = await connectToMariaDb(configuration); + var migrationRunner = MariaDbMigrationRunner(connection, migrations: [ GreetingMigration(), ]); - return await runMigrations(migrationRunner, args); + + await runMigrations(migrationRunner, args); } diff --git a/config/default.yaml b/config/default.yaml index fed19e9..c9ac4d5 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -2,12 +2,12 @@ jwt_secret: INSECURE_DEFAULT_SECRET host: 127.0.0.1 port: 3000 -postgres: +mariadb: host: localhost - port: 5432 + port: 3306 database_name: appdb username: appuser password: App1970# - use_ssl: false - time_zone: UTC + + diff --git a/lib/src/config/plugins/orm.dart b/lib/src/config/plugins/orm.dart index 66d66d5..72ec12f 100644 --- a/lib/src/config/plugins/orm.dart +++ b/lib/src/config/plugins/orm.dart @@ -2,37 +2,38 @@ import 'dart:async'; import 'dart:io'; import 'package:angel3_framework/angel3_framework.dart'; import 'package:angel3_orm/angel3_orm.dart'; -import 'package:angel3_orm_postgres/angel3_orm_postgres.dart'; -import 'package:postgres/postgres.dart'; +import 'package:angel3_orm_mysql/angel3_orm_mysql.dart'; +import 'package:mysql1/mysql1.dart'; +// For MariaDb Future configureServer(Angel app) async { try { - var connection = await connectToPostgres(app.configuration); - await connection.open(); - - var executor = PostgreSqlExecutor(connection, logger: app.logger); + var connection = await connectToMariaDb(app.configuration); + var executor = MariaDbExecutor(connection, logger: app.logger); app ..container.registerSingleton(executor) ..shutdownHooks.add((_) => connection.close()); } catch (e) { - app.logger.severe("Failed to connect to PostgreSQL. ORM disabled.", e); + app.logger.severe("Failed to connect to MariaDB. ORM disabled.", e); } } -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? ?? +Future connectToMariaDb(Map configuration) async { + var mariaDbConfig = configuration['mariadb'] as Map? ?? {}; + var settings = ConnectionSettings( + host: mariaDbConfig['host'] as String? ?? 'localhost', + port: mariaDbConfig['port'] as int? ?? 5432, + db: mariaDbConfig['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); + user: mariaDbConfig['username'] as String?, + password: mariaDbConfig['password'] as String?, + timeout: Duration( + seconds: mariaDbConfig['timeout_in_seconds'] as int? ?? 30000), + useSSL: mariaDbConfig['use_ssl'] as bool? ?? false); + + var connection = await MySqlConnection.connect(settings); return connection; } diff --git a/lib/src/models/greeting.g.dart b/lib/src/models/greeting.g.dart index 82c1295..26ffd5e 100644 --- a/lib/src/models/greeting.g.dart +++ b/lib/src/models/greeting.g.dart @@ -11,9 +11,9 @@ class GreetingMigration extends Migration { void up(Schema schema) { schema.create('greetings', (table) { table.serial('id').primaryKey(); - table.varChar('message'); table.timeStamp('created_at'); table.timeStamp('updated_at'); + table.varChar('message', length: 255); }); } @@ -28,7 +28,8 @@ class GreetingMigration extends Migration { // ************************************************************************** class GreetingQuery extends Query { - GreetingQuery({Set? trampoline}) { + GreetingQuery({Query? parent, Set? trampoline}) + : super(parent: parent) { trampoline ??= {}; trampoline.add(tableName); _where = GreetingQueryWhere(this); @@ -37,6 +38,8 @@ class GreetingQuery extends Query { @override final GreetingQueryValues values = GreetingQueryValues(); + List _selectedFields = []; + GreetingQueryWhere? _where; @override @@ -51,7 +54,15 @@ class GreetingQuery extends Query { @override List get fields { - return const ['id', 'message', 'created_at', 'updated_at']; + const _fields = ['id', 'created_at', 'updated_at', 'message']; + return _selectedFields.isEmpty + ? _fields + : _fields.where((field) => _selectedFields.contains(field)).toList(); + } + + GreetingQuery select(List selectedFields) { + _selectedFields = selectedFields; + return this; } @override @@ -64,40 +75,42 @@ class GreetingQuery extends Query { return GreetingQueryWhere(this); } - static Greeting? parseRow(List row) { - if (row.every((x) => x == null)) return null; + Optional parseRow(List row) { + if (row.every((x) => x == null)) { + return Optional.empty(); + } var model = Greeting( - id: row[0].toString(), - message: (row[1] as String?), - createdAt: (row[2] as DateTime?), - updatedAt: (row[3] as DateTime?)); - return model; + id: fields.contains('id') ? row[0].toString() : null, + createdAt: fields.contains('created_at') ? (row[1] as DateTime?) : null, + updatedAt: fields.contains('updated_at') ? (row[2] as DateTime?) : null, + message: fields.contains('message') ? (row[3] as String?) : null); + return Optional.of(model); } @override Optional deserialize(List row) { - return Optional.ofNullable(parseRow(row)); + return parseRow(row); } } class GreetingQueryWhere extends QueryWhere { GreetingQueryWhere(GreetingQuery query) : id = NumericSqlExpressionBuilder(query, 'id'), - message = StringSqlExpressionBuilder(query, 'message'), createdAt = DateTimeSqlExpressionBuilder(query, 'created_at'), - updatedAt = DateTimeSqlExpressionBuilder(query, 'updated_at'); + updatedAt = DateTimeSqlExpressionBuilder(query, 'updated_at'), + message = StringSqlExpressionBuilder(query, 'message'); final NumericSqlExpressionBuilder id; - final StringSqlExpressionBuilder message; - final DateTimeSqlExpressionBuilder createdAt; final DateTimeSqlExpressionBuilder updatedAt; + final StringSqlExpressionBuilder message; + @override List get expressionBuilders { - return [id, message, createdAt, updatedAt]; + return [id, createdAt, updatedAt, message]; } } @@ -112,11 +125,6 @@ class GreetingQueryValues extends MapQueryValues { } set id(String? 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?); } @@ -127,10 +135,15 @@ class GreetingQueryValues extends MapQueryValues { } set updatedAt(DateTime? value) => values['updated_at'] = value; + String? get message { + return (values['message'] as String?); + } + + set message(String? value) => values['message'] = value; void copyFrom(Greeting model) { - message = model.message; createdAt = model.createdAt; updatedAt = model.updatedAt; + message = model.message; } } @@ -140,46 +153,49 @@ class GreetingQueryValues extends MapQueryValues { @generatedSerializable class Greeting extends _Greeting { - Greeting({this.id, required this.message, this.createdAt, this.updatedAt}); + Greeting({this.id, this.createdAt, this.updatedAt, required this.message}); + + /// A unique identifier corresponding to this item. + @override + String? id; + + /// The time at which this item was created. + @override + DateTime? createdAt; + + /// The last time at which this item was updated. + @override + DateTime? updatedAt; @override - final String? id; - - @override - final String? message; - - @override - final DateTime? createdAt; - - @override - final DateTime? updatedAt; + String? message; Greeting copyWith( - {String? id, String? message, DateTime? createdAt, DateTime? updatedAt}) { + {String? id, DateTime? createdAt, DateTime? updatedAt, String? message}) { return Greeting( id: id ?? this.id, - message: message ?? this.message, createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt); + updatedAt: updatedAt ?? this.updatedAt, + message: message ?? this.message); } @override bool operator ==(other) { return other is _Greeting && other.id == id && - other.message == message && other.createdAt == createdAt && - other.updatedAt == updatedAt; + other.updatedAt == updatedAt && + other.message == message; } @override int get hashCode { - return hashObjects([id, message, createdAt, updatedAt]); + return hashObjects([id, createdAt, updatedAt, message]); } @override String toString() { - return 'Greeting(id=$id, message=$message, createdAt=$createdAt, updatedAt=$updatedAt)'; + return 'Greeting(id=$id, createdAt=$createdAt, updatedAt=$updatedAt, message=$message)'; } Map toJson() { @@ -212,7 +228,6 @@ class GreetingSerializer extends Codec { @override GreetingEncoder get encoder => const GreetingEncoder(); - @override GreetingDecoder get decoder => const GreetingDecoder(); static Greeting fromMap(Map map) { @@ -222,29 +237,28 @@ class GreetingSerializer extends Codec { return 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?) + ? (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?) + ? (map['updated_at'] as DateTime) : DateTime.parse(map['updated_at'].toString())) - : null); + : null, + message: map['message'] as String?); } - static Map toMap(_Greeting model) { - if (model.message == null) { - throw FormatException("Missing required field 'message' on Greeting."); + static Map toMap(_Greeting? model) { + if (model == null) { + throw FormatException("Required field [model] cannot be null"); } - return { 'id': model.id, - 'message': model.message, 'created_at': model.createdAt?.toIso8601String(), - 'updated_at': model.updatedAt?.toIso8601String() + 'updated_at': model.updatedAt?.toIso8601String(), + 'message': model.message }; } } @@ -252,16 +266,16 @@ class GreetingSerializer extends Codec { abstract class GreetingFields { static const List allFields = [ id, - message, createdAt, - updatedAt + updatedAt, + message ]; static const String id = 'id'; - static const String message = 'message'; - static const String createdAt = 'created_at'; static const String updatedAt = 'updated_at'; + + static const String message = 'message'; } diff --git a/pubspec.yaml b/pubspec.yaml index 46c9338..44d204e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: angel3_jael: ^6.0.0 angel3_migration: ^6.0.0 angel3_orm: ^6.0.0 - angel3_orm_postgres: ^6.0.0 + angel3_orm_mysql: ^6.0.0-beta.1 angel3_serialize: ^6.0.0 angel3_production: ^6.0.0 angel3_static: ^6.0.0 @@ -29,3 +29,6 @@ dev_dependencies: io: ^1.0.0 test: ^1.21.0 lints: ^1.0.0 +dependency_overrides: + angel3_migration_runner: + path: ../belatuk/packages/orm/angel_migration_runner