diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a9c1ce..2d09490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.1.3 + +- Implemented mail + ## 0.1.2 - Implemented multi-isolate server diff --git a/lib/src/application.dart b/lib/src/application.dart index ede3a7d..b1be725 100644 --- a/lib/src/application.dart +++ b/lib/src/application.dart @@ -21,7 +21,7 @@ class Application extends Container { server = BaseHttpServer(config: config); - if (config['isolate']) { + if (config['isolate'] != null && config['isolate']) { server.spawnIsolates(config['isolateCount'] ?? 1); } else { server.startServer(); diff --git a/lib/src/mail/content.dart b/lib/src/mail/content.dart new file mode 100644 index 0000000..4d0baaf --- /dev/null +++ b/lib/src/mail/content.dart @@ -0,0 +1,12 @@ +class Content { + /// The Blade view that represents the text version of the message. + final String? text; + + /// The Blade view that should be rendered for the mailable. + final String? html; + + const Content({ + this.text, + this.html, + }); +} diff --git a/lib/src/mail/envelope.dart b/lib/src/mail/envelope.dart new file mode 100644 index 0000000..227c483 --- /dev/null +++ b/lib/src/mail/envelope.dart @@ -0,0 +1,26 @@ +import 'package:mailer/mailer.dart'; + +class Envelope { + ///The address sending the message. + final Address? from; + + /// The recipients of the message. + final List
to; + + /// The recipients receiving a copy of the message. + final List? cc; + + /// The recipients receiving a blind copy of the message. + final List? bcc; + + /// The subject of the message. + final String subject; + + Envelope({ + this.from, + required this.to, + required this.subject, + this.cc, + this.bcc, + }); +} diff --git a/lib/src/mail/mail.dart b/lib/src/mail/mail.dart new file mode 100644 index 0000000..5df3079 --- /dev/null +++ b/lib/src/mail/mail.dart @@ -0,0 +1,11 @@ +import 'package:mailer/mailer.dart'; + +import 'content.dart'; +import 'envelope.dart'; + +abstract class Mail { + const Mail(); + Content content(); + Envelope envelope(); + List? attachments(); +} diff --git a/lib/src/mail/mailable.dart b/lib/src/mail/mailable.dart new file mode 100644 index 0000000..e4d7bb2 --- /dev/null +++ b/lib/src/mail/mailable.dart @@ -0,0 +1,101 @@ +import 'package:mailer/mailer.dart' as mailer; +import 'package:mailer/smtp_server.dart'; +import 'package:meta/meta.dart'; +import 'package:vania/src/mail/mail.dart'; +import 'package:vania/vania.dart'; + +@immutable +class Mailable implements Mail { + const Mailable(); + + SmtpServer _setupSmtpServer() { + switch (Config().get('mail')['driver']) { + case 'gmail': + return gmail( + Config().get('mail')['username'], Config().get('mail')['password']); + case 'gmailSaslXoauth2': + return gmailSaslXoauth2(Config().get('mail')['username'], + Config().get('mail')['accessToken']); + case 'gmailRelaySaslXoauth2': + return gmail(Config().get('mail')['username'], + Config().get('mail')['accessToken']); + case 'hotmail': + return hotmail( + Config().get('mail')['username'], Config().get('mail')['password']); + case 'mailgun': + return mailgun( + Config().get('mail')['username'], Config().get('mail')['password']); + case 'qq': + return qq( + Config().get('mail')['username'], Config().get('mail')['password']); + case 'yahoo': + return yahoo( + Config().get('mail')['username'], Config().get('mail')['password']); + case 'yandex': + return yandex( + Config().get('mail')['username'], Config().get('mail')['password']); + default: + return SmtpServer( + Config().get('mail')['host'] ?? '', + username: Config().get('mail')['username'] ?? '', + password: Config().get('mail')['password'] ?? '', + port: Config().get('mail')['port'], + ssl: Config().get('mail')['encryption'] == 'ssl', + ignoreBadCertificate: + Config().get('mail')['ignoreBadCertificate'] ?? true, + ); + } + } + + Future send() async { + final message = mailer.Message(); + + message.from = envelope().from ?? + Address( + Config().get('mail')['from_address'], + Config().get('mail')['from_name'], + ); + message.recipients.addAll(envelope().to); + + if (envelope().cc != null) { + message.ccRecipients.addAll(envelope().cc!); + } + + if (envelope().bcc != null) { + message.ccRecipients.addAll(envelope().bcc!); + } + + message.subject = envelope().subject; + message.text = content().text; + message.html = content().html; + + if (attachments() != null) { + message.attachments.addAll(attachments()!); + } + + try { + await mailer.send(message, _setupSmtpServer()); + } catch (e) { + print('Failed to send email: $e'); + rethrow; + } + } + + @mustBeOverridden + @override + List? attachments() { + throw UnimplementedError(); + } + + @mustBeOverridden + @override + Content content() { + throw UnimplementedError(); + } + + @mustBeOverridden + @override + Envelope envelope() { + throw UnimplementedError(); + } +} diff --git a/lib/src/route/router.dart b/lib/src/route/router.dart index 83cab39..5b4d4be 100644 --- a/lib/src/route/router.dart +++ b/lib/src/route/router.dart @@ -58,11 +58,11 @@ class Router { return this; } - Router prefix([String? prifix]) { - if (prifix != null) { + Router prefix([String? prefix]) { + if (prefix != null) { String basePath = _routes.last.path; _routes.last.path = - prifix.endsWith("/") ? "$prifix$basePath" : "$prifix/$basePath"; + prifix.endsWith("/") ? "$prefix$basePath" : "$prefix/$basePath"; } return this; } diff --git a/lib/src/storage/local_storage.dart b/lib/src/storage/local_storage.dart index 83116d9..3df7d30 100644 --- a/lib/src/storage/local_storage.dart +++ b/lib/src/storage/local_storage.dart @@ -44,4 +44,7 @@ class LocalStorage implements StorageDriver { await file.writeAsBytes(bytes); return file.path.replaceFirst(storagePath, ''); } + + @override + String fullPath(String file) => "$storagePath/$file"; } diff --git a/lib/src/storage/storage_driver.dart b/lib/src/storage/storage_driver.dart index 736fa7c..011c113 100644 --- a/lib/src/storage/storage_driver.dart +++ b/lib/src/storage/storage_driver.dart @@ -2,13 +2,15 @@ import 'dart:typed_data'; abstract class StorageDriver { Future put( - String filePath, + String fileName, List bytes, ); - Future get(String filepath); + Future get(String fileName); - Future exists(String filepath); + String fullPath(String file); - Future delete(String filepath); + Future exists(String fileName); + + Future delete(String fileName); } diff --git a/lib/src/utils/helper.dart b/lib/src/utils/helper.dart new file mode 100644 index 0000000..a554238 --- /dev/null +++ b/lib/src/utils/helper.dart @@ -0,0 +1,5 @@ +import 'dart:io'; + +String storagePath(String file) => '${Directory.current.path}/storage/$file'; + +String publicPath(String file) => '${Directory.current.path}/public/$file'; diff --git a/lib/vania.dart b/lib/vania.dart index 8c7c6cc..3dac533 100644 --- a/lib/vania.dart +++ b/lib/vania.dart @@ -42,3 +42,11 @@ export 'package:eloquent/src/query/query_builder.dart'; export 'src/authentication/authentication.dart'; export 'src/authentication/authenticate.dart'; export 'src/cryptographic/hash.dart'; + +export 'src/mail/mailable.dart'; +export 'src/mail/content.dart'; +export 'src/mail/envelope.dart'; +export 'package:mailer/src/entities/address.dart'; +export 'package:mailer/src/entities/attachment.dart'; + +export 'src/utils/helper.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index ff67064..5f6d7f7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: vania description: Fast, simple, and powerful backend framework for Dart built with ❤️ -version: 0.1.2 +version: 0.1.3 homepage: https://vdart.dev repository: https://github.com/vania-dart/framework issue_tracker: https://github.com/vania-dart/framework/issues @@ -19,6 +19,7 @@ dependencies: crypto: ^3.0.3 dart_jsonwebtoken: ^2.13.0 eloquent: ^3.0.0 + mailer: ^6.1.0 meta: ^1.12.0 mime: ^1.0.5 path: ^1.9.0 @@ -29,5 +30,5 @@ dependencies: dev_dependencies: lints: ^3.0.0 test: ^1.25.2 - http: ^1.2.0 + http: ^1.2.1 http_parser: ^4.0.2