diff --git a/.env.example b/.env.example index 3494d27..74d818c 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,4 @@ githubrepo=github_repo youtube=youtube_token openweather=openweather_token conversatorkey=chatgpt_token +isproduction=0_or_1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f4c53a6..840f8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ doc/api/ .env .idea +logs.txt assets/swearwords/swearwords.*.json !assets/swearwords/swearwords.sample.json diff --git a/bin/main.dart b/bin/main.dart index 704b176..b5418b3 100644 --- a/bin/main.dart +++ b/bin/main.dart @@ -2,9 +2,12 @@ import 'dart:async'; import 'package:dotenv/dotenv.dart'; import 'package:postgres/postgres.dart'; import 'package:weather/weather.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/globals/chat_platform.dart'; import 'package:weather/src/utils/migrations_manager.dart'; +import 'package:weather/src/utils/logger.dart'; +// TODO: move to database.dart Future getDatabaseConnection(DotEnv env) async { final username = env['dbuser']!; final password = env['dbpassword']!; @@ -20,28 +23,34 @@ Future runMigrations(Pool dbConnection) async { await migrationsManager.runMigrations(); } -ChatPlatform getPlatform(String envPlatform) { +ChatPlatform getPlatform(String? envPlatform) { if (envPlatform == 'telegram') return ChatPlatform.telegram; if (envPlatform == 'discord') return ChatPlatform.discord; throw Exception('Invalid platform $envPlatform'); } +bool getIsProductionMode(String? envIsProduction) { + return int.parse(envIsProduction ?? '0') == 1; +} + void main(List args) async { var env = DotEnv(includePlatformEnvironment: true)..load(); final dbConnection = await getDatabaseConnection(env); + // TODO: move to core config module + final token = env['bottoken']!; + final adminId = env['adminid']!; + final repoUrl = env['githubrepo']!; + final youtubeKey = env['youtube']!; + final openweatherKey = env['openweather']!; + final conversatorKey = env['conversatorkey']!; + final platformName = getPlatform(env['platform']); + final isProduction = getIsProductionMode(env['isproduction']); + setupInjection(isProduction); await runMigrations(dbConnection); runZonedGuarded(() { - final token = env['bottoken']!; - final adminId = env['adminid']!; - final repoUrl = env['githubrepo']!; - final youtubeKey = env['youtube']!; - final openweatherKey = env['openweather']!; - final conversatorKey = env['conversatorkey']!; - final platformName = getPlatform(env['platform']!); - Bot( platformName: platformName, botToken: token, @@ -53,10 +62,8 @@ void main(List args) async { dbConnection: dbConnection, ).startBot(); }, (error, stack) { - var now = DateTime.now(); + var logger = getIt(); - print('[$now]: Error caught'); - print(error); - print(stack); + logger.e('Uncaught error', error); }); } diff --git a/lib/src/core/access.dart b/lib/src/core/access.dart index 8b37889..16b1230 100644 --- a/lib/src/core/access.dart +++ b/lib/src/core/access.dart @@ -1,8 +1,10 @@ import 'package:weather/src/globals/message_event.dart'; import 'package:weather/src/globals/access_level.dart'; -import 'event_bus.dart'; +import 'package:weather/src/injector/injection.dart'; +import 'package:weather/src/utils/logger.dart'; import 'events/access_events.dart'; import 'database.dart'; +import 'event_bus.dart'; typedef OnSuccessCallback = void Function(MessageEvent event); typedef OnFailureCallback = Future Function(MessageEvent event); @@ -11,8 +13,9 @@ class Access { final Database db; final EventBus eventBus; final String adminId; + final Logger _logger; - Access({required this.db, required this.eventBus, required this.adminId}); + Access({required this.db, required this.eventBus, required this.adminId}) : _logger = getIt(); void execute( {required MessageEvent event, @@ -23,31 +26,19 @@ class Access { var user = await db.user.getSingleUserForChat(event.chatId, event.userId); if (user == null || user.banned) { - onFailure(event); - - return; - } - - if (user.id == adminId) { - eventBus.fire(AccessEvent(chatId: event.chatId, user: user, command: command)); - onSuccess(event); - - return; + return onFailure(event); } - if (accessLevel == AccessLevel.admin) { - onFailure(event); + var canExecuteAsAdmin = user.id == adminId; + var canExecuteAsModerator = accessLevel == AccessLevel.moderator && user.moderator; - return; - } - - if (accessLevel == AccessLevel.moderator && !user.moderator) { - onFailure(event); + if (canExecuteAsAdmin || canExecuteAsModerator) { + _logger.i('Executing /$command with event: $event'); + eventBus.fire(AccessEvent(chatId: event.chatId, user: user, command: command)); - return; + return onSuccess(event); } - eventBus.fire(AccessEvent(chatId: event.chatId, user: user, command: command)); - onSuccess(event); + return onFailure(event); } } diff --git a/lib/src/core/conversator.dart b/lib/src/core/conversator.dart index 6468070..06c6a9c 100644 --- a/lib/src/core/conversator.dart +++ b/lib/src/core/conversator.dart @@ -2,6 +2,8 @@ import 'dart:convert'; import 'package:cron/cron.dart'; import 'package:http/http.dart' as http; import 'package:weather/src/globals/module_exception.dart'; +import 'package:weather/src/injector/injection.dart'; +import 'package:weather/src/utils/logger.dart'; import 'database.dart'; const String _converstorApiURL = 'https://api.openai.com/v1/chat/completions'; @@ -39,11 +41,12 @@ class ConversatorUser { class Conversator { final Database db; + final Logger _logger; final String conversatorApiKey; final String adminId; final String _apiBaseUrl = _converstorApiURL; - Conversator({required this.db, required this.conversatorApiKey, required this.adminId}); + Conversator({required this.db, required this.conversatorApiKey, required this.adminId}) : _logger = getIt(); void initialize() { _startResetDailyInvocationsUsageJob(); @@ -130,9 +133,9 @@ class Conversator { var result = await db.conversatorUser.resetDailyInvocations(); if (result == 0) { - print('Something went wrong with resetting conversator daily invocations usage'); + _logger.w('Something went wrong with resetting conversator daily invocations usage'); } else { - print('Reset conversator daily invocation usage for $result rows'); + _logger.i('Reset conversator daily invocation usage for $result rows'); } }); } diff --git a/lib/src/core/dadjokes.dart b/lib/src/core/dadjokes.dart index 892a6eb..40f6fcd 100644 --- a/lib/src/core/dadjokes.dart +++ b/lib/src/core/dadjokes.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +const _dadjokesApiBase = 'https://icanhazdadjoke.com/'; + class DadJokesJoke { final String joke; @@ -10,7 +12,7 @@ class DadJokesJoke { } class DadJokes { - final String _apiBaseUrl = 'https://icanhazdadjoke.com/'; + final String _apiBaseUrl = _dadjokesApiBase; Future getJoke() async { var response = await http.get(Uri.parse(_apiBaseUrl), headers: {'Accept': 'application/json'}); diff --git a/lib/src/core/general.dart b/lib/src/core/general.dart index b2a31a2..95f3653 100644 --- a/lib/src/core/general.dart +++ b/lib/src/core/general.dart @@ -2,10 +2,12 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'chat.dart'; +const _githubApiBase = 'https://api.github.com'; + class General { final Chat chat; final String repositoryUrl; - final String _baseGithubApiUrl = 'https://api.github.com'; + final String _baseGithubApiUrl = _githubApiBase; General({required this.chat, required this.repositoryUrl}); diff --git a/lib/src/core/repositories/repository.dart b/lib/src/core/repositories/repository.dart index c4bb3d3..ece8a42 100644 --- a/lib/src/core/repositories/repository.dart +++ b/lib/src/core/repositories/repository.dart @@ -1,18 +1,21 @@ import 'dart:io'; import 'package:meta/meta.dart'; import 'package:postgres/postgres.dart'; +import 'package:weather/src/injector/injection.dart'; +import 'package:weather/src/utils/logger.dart'; const String _pathToQueries = 'assets/db/queries'; class Repository { final String repositoryName; final Pool dbConnection; + final Logger _logger; final String _queriesDirectory = _pathToQueries; @protected final Map queriesMap = {}; - Repository({required this.repositoryName, required this.dbConnection}); + Repository({required this.repositoryName, required this.dbConnection}) : _logger = getIt(); initRepository() async { var queriesLocation = Directory('$_queriesDirectory/$repositoryName'); @@ -30,7 +33,7 @@ class Repository { @protected Future executeQuery(String? query, [Map? parameters]) async { if (query == null) { - print('Wrong query $query'); + _logger.e('Wrong query: $query'); return null; } @@ -41,7 +44,7 @@ class Repository { @protected Future executeTransaction(String? query, [Map? parameters]) async { if (query == null) { - print('Wrong query $query'); + _logger.e('Wrong query: $query'); return 0; } @@ -51,8 +54,7 @@ class Repository { return queryResult.affectedRows; }).catchError((error) { - print('DB transaction error'); - print(error); + _logger.e('DB transaction error: $error'); return 0; }); diff --git a/lib/src/core/repositories/reputation_repository.dart b/lib/src/core/repositories/reputation_repository.dart index be69833..245a757 100644 --- a/lib/src/core/repositories/reputation_repository.dart +++ b/lib/src/core/repositories/reputation_repository.dart @@ -24,12 +24,6 @@ class ReputationRepository extends Repository { return null; } - if (data.length != 1) { - print('One piece of reputation data data was expected, got ${data.length} instead'); - - return null; - } - var reputationData = data[0].toColumnMap(); return SingleReputationData( diff --git a/lib/src/core/repositories/weather_repository.dart b/lib/src/core/repositories/weather_repository.dart index e255458..944164e 100644 --- a/lib/src/core/repositories/weather_repository.dart +++ b/lib/src/core/repositories/weather_repository.dart @@ -38,12 +38,6 @@ class WeatherRepository extends Repository { return []; } - if (data.length != 1) { - print('One piece of cities data was expected, got ${data.length} instead'); - - return null; - } - var citiesData = data[0].toColumnMap(); return citiesData['cities']?.split(','); diff --git a/lib/src/core/reputation.dart b/lib/src/core/reputation.dart index 47f4781..adc7666 100644 --- a/lib/src/core/reputation.dart +++ b/lib/src/core/reputation.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:cron/cron.dart'; import 'package:weather/src/globals/module_exception.dart'; +import 'package:weather/src/injector/injection.dart'; +import 'package:weather/src/utils/logger.dart'; import 'events/accordion_poll_events.dart'; import 'database.dart'; import 'event_bus.dart'; @@ -32,8 +34,9 @@ class ChatReputationData { class Reputation { final Database db; final EventBus eventBus; + final Logger _logger; - Reputation({required this.db, required this.eventBus}); + Reputation({required this.db, required this.eventBus}) : _logger = getIt(); void initialize() { _startResetVotesJob(); @@ -137,9 +140,9 @@ class Reputation { var result = await db.reputation.resetChangeOptions(numberOfVoteOptions); if (result == 0) { - print('Something went wrong with resetting reputation change options'); + _logger.w('Something went wrong with resetting reputation change options'); } else { - print('Reset reputation change options for $result rows'); + _logger.i('Reset reputation change options for $result rows'); } }); } diff --git a/lib/src/core/weather.dart b/lib/src/core/weather.dart index ce9f8df..c16075c 100644 --- a/lib/src/core/weather.dart +++ b/lib/src/core/weather.dart @@ -4,6 +4,8 @@ import 'package:http/http.dart' as http; import 'package:cron/cron.dart'; import 'database.dart'; +const _weatherApiBase = 'https://api.openweathermap.org/data/2.5'; + class OpenWeatherData { final String city; final num temp; @@ -28,7 +30,7 @@ class ChatNotificationHour { class Weather { final Database db; final String openweatherKey; - final String _apiBaseUrl = 'https://api.openweathermap.org/data/2.5'; + final String _apiBaseUrl = _weatherApiBase; late StreamController _weatherStreamController; List _weatherCronTasks = []; @@ -83,15 +85,7 @@ class Weather { } Future getWeatherForCity(String city) async { - try { - var weatherData = await _getCurrentWeather(city); - - return weatherData.temp; - } catch (err) { - print(err); - - return null; - } + return _getCurrentWeather(city).then((weatherData) => weatherData.temp); } Future setNotificationHour(String chatId, int notificationHour) async { @@ -107,21 +101,12 @@ class Weather { } Future> getWeatherForCities(List cities) async { - List result = []; - - await Future.forEach(cities, (city) async { - try { - var weather = await _getCurrentWeather(city); - - result.add(OpenWeatherData(weather.city, weather.temp)); - - await Future.delayed(Duration(milliseconds: 500)); - } catch (err) { - print("Can't get weather for city $city"); - } - }); + return Future.wait(cities.map((city) async { + var weather = await _getCurrentWeather(city); + await Future.delayed(Duration(milliseconds: 500)); - return result; + return OpenWeatherData(weather.city, weather.temp); + })); } Future _getCurrentWeather(String city) async { diff --git a/lib/src/core/youtube.dart b/lib/src/core/youtube.dart index 1932aa9..b308fb2 100644 --- a/lib/src/core/youtube.dart +++ b/lib/src/core/youtube.dart @@ -1,9 +1,12 @@ import 'dart:convert'; import 'package:http/http.dart' as http; +const _youtubeApiBase = 'https://www.googleapis.com/youtube/v3/search'; +const _youtubeVideoUrlBase = 'https://www.youtube.com/watch'; + class Youtube { final String apiKey; - final String apiBaseUrl = 'https://www.googleapis.com/youtube/v3/search'; + final String apiBaseUrl = _youtubeApiBase; Youtube(this.apiKey); @@ -16,7 +19,7 @@ class Youtube { var videoId = response['items'][0]['id']['videoId']; - return 'https://www.youtube.com/watch?v=$videoId'; + return '$_youtubeVideoUrlBase?v=$videoId'; } Future getRawYoutubeSearchResults(String query) async { diff --git a/lib/src/globals/message_event.dart b/lib/src/globals/message_event.dart index 817ad42..b2b3e4d 100644 --- a/lib/src/globals/message_event.dart +++ b/lib/src/globals/message_event.dart @@ -17,4 +17,9 @@ class MessageEvent { required this.otherUserIds, required this.parameters, required this.rawMessage}); + + @override + String toString() { + return '{ platform: $platform, chatId: $chatId, userId: $userId, otherUserIds: $otherUserIds, parameters: $parameters, isBot: $isBot, rawMessage: $rawMessage }'; + } } diff --git a/lib/src/injector/injection.config.dart b/lib/src/injector/injection.config.dart new file mode 100644 index 0000000..82ff847 --- /dev/null +++ b/lib/src/injector/injection.config.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:get_it/get_it.dart' as _i1; +import 'package:injectable/injectable.dart' as _i2; + +import '../utils/logger.dart' as _i3; + +const String _dev = 'dev'; +const String _prod = 'prod'; + +extension GetItInjectableX on _i1.GetIt { +// initializes the registration of main-scope dependencies inside of GetIt + _i1.GetIt init({ + String? environment, + _i2.EnvironmentFilter? environmentFilter, + }) { + final gh = _i2.GetItHelper( + this, + environment, + environmentFilter, + ); + final loggerModule = _$LoggerModule(); + gh.factory<_i3.Logger>( + () => loggerModule.devLogger, + registerFor: {_dev}, + ); + gh.factory<_i3.Logger>( + () => loggerModule.prodLogger, + registerFor: {_prod}, + ); + return this; + } +} + +class _$LoggerModule extends _i3.LoggerModule {} diff --git a/lib/src/injector/injection.dart b/lib/src/injector/injection.dart new file mode 100644 index 0000000..e23c80d --- /dev/null +++ b/lib/src/injector/injection.dart @@ -0,0 +1,8 @@ +import 'package:get_it/get_it.dart'; +import 'package:injectable/injectable.dart'; +import 'injection.config.dart'; + +final GetIt getIt = GetIt.instance; + +@InjectableInit(preferRelativeImports: true) +void setupInjection(bool isProd) => getIt.init(environment: isProd ? Environment.prod : Environment.dev); diff --git a/lib/src/modules/check_reminder_manager.dart b/lib/src/modules/check_reminder_manager.dart index f655806..346ff84 100644 --- a/lib/src/modules/check_reminder_manager.dart +++ b/lib/src/modules/check_reminder_manager.dart @@ -3,7 +3,9 @@ import 'package:weather/src/core/chat.dart'; import 'package:weather/src/core/check_reminder.dart'; import 'package:weather/src/core/user.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; import 'utils.dart'; class CheckReminderManager { @@ -11,10 +13,12 @@ class CheckReminderManager { final Database db; final Chat chat; final User user; + final Logger _logger; final CheckReminder _checkReminder; CheckReminderManager({required this.platform, required this.db, required this.chat, required this.user}) - : _checkReminder = CheckReminder(db: db); + : _logger = getIt(), + _checkReminder = CheckReminder(db: db); void initialize() { _checkReminder.initialize(); @@ -46,7 +50,7 @@ class CheckReminderManager { sendOperationMessage(checkReminder.chatId, platform: platform, operationResult: true, successfulMessage: checkReminderMessage); } else { - print("Could not send reminder, user not found. chatId: ${checkReminder.chatId}, userId: ${checkReminder.userId}"); + _logger.e('Failed to send reminder: $checkReminder'); } }); } diff --git a/lib/src/modules/command_statistics_manager.dart b/lib/src/modules/command_statistics_manager.dart index e447ca3..f850e3e 100644 --- a/lib/src/modules/command_statistics_manager.dart +++ b/lib/src/modules/command_statistics_manager.dart @@ -3,7 +3,9 @@ import 'package:weather/src/core/command_statistics.dart'; import 'package:weather/src/core/database.dart'; import 'package:weather/src/core/event_bus.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; import 'utils.dart'; class CommandStatisticsManager { @@ -11,10 +13,12 @@ class CommandStatisticsManager { final Database db; final EventBus eventBus; final Chat chat; + final Logger _logger; final CommandStatistics _commandStatistics; CommandStatisticsManager({required this.platform, required this.db, required this.eventBus, required this.chat}) - : _commandStatistics = CommandStatistics(db: db, eventBus: eventBus, chat: chat, chatPlatform: platform.chatPlatform); + : _logger = getIt(), + _commandStatistics = CommandStatistics(db: db, eventBus: eventBus, chat: chat, chatPlatform: platform.chatPlatform); void initialize() { _commandStatistics.initialize(); @@ -49,6 +53,8 @@ class CommandStatisticsManager { void _subscribeToChatReports() { _commandStatistics.chatReportStream.listen((chatReport) async { + _logger.i('Handling chat report data: $chatReport'); + var chatId = chatReport.chatId; var successfulMessage = ''; diff --git a/lib/src/modules/panorama_manager.dart b/lib/src/modules/panorama_manager.dart index 59838e0..aa8bf08 100644 --- a/lib/src/modules/panorama_manager.dart +++ b/lib/src/modules/panorama_manager.dart @@ -3,16 +3,21 @@ import 'package:weather/src/core/database.dart'; import 'package:weather/src/core/panorama.dart'; import 'package:weather/src/globals/chat_platform.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; import 'package:weather/src/modules/utils.dart'; +import 'package:weather/src/utils/logger.dart'; class PanoramaManager { final Platform platform; final Chat chat; final Database db; + final Logger _logger; final PanoramaNews _panoramaNews; - PanoramaManager({required this.platform, required this.chat, required this.db}) : _panoramaNews = PanoramaNews(db: db); + PanoramaManager({required this.platform, required this.chat, required this.db}) + : _logger = getIt(), + _panoramaNews = PanoramaNews(db: db); void initialize() { _panoramaNews.initialize(); @@ -33,9 +38,9 @@ class PanoramaManager { return; } - var panoramaStream = _panoramaNews.panoramaStream; + _panoramaNews.panoramaStream.listen((event) async { + _logger.i('Handling Panorama stream data: $event'); - panoramaStream.listen((event) async { var allChats = await chat.getAllChatIdsForPlatform(ChatPlatform.telegram); allChats.forEach((chatId) { diff --git a/lib/src/modules/user_manager.dart b/lib/src/modules/user_manager.dart index cad9d0b..4241c76 100644 --- a/lib/src/modules/user_manager.dart +++ b/lib/src/modules/user_manager.dart @@ -1,15 +1,18 @@ import 'package:weather/src/core/user.dart'; import 'package:weather/src/core/chat.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; import 'package:weather/src/modules/utils.dart'; +import 'package:weather/src/utils/logger.dart'; class UserManager { final Platform platform; final Chat chat; final User user; + final Logger _logger; - UserManager({required this.platform, required this.chat, required this.user}); + UserManager({required this.platform, required this.chat, required this.user}) : _logger = getIt(); void initialize() { _subscribeToUserUpdates(); @@ -57,7 +60,7 @@ class UserManager { var platformUserPremiumStatus = await platform.getUserPremiumStatus(chatId, chatUser.id); if (chatUser.isPremium != platformUserPremiumStatus) { - print('Updating premium status for ${chatUser.id}'); + _logger.i('Updating premium status for ${chatUser.id}'); await user.updatePremiumStatus(chatUser.id, platformUserPremiumStatus); } diff --git a/lib/src/modules/utils.dart b/lib/src/modules/utils.dart index 05d5c34..5868e12 100644 --- a/lib/src/modules/utils.dart +++ b/lib/src/modules/utils.dart @@ -1,6 +1,8 @@ import 'package:collection/collection.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; bool messageEventParametersCheck(Platform platform, MessageEvent event, [int numberOfParameters = 1]) { if (event.parameters.whereNot((parameter) => parameter.isEmpty).length < numberOfParameters) { @@ -31,6 +33,8 @@ void sendOperationMessage(String chatId, {required Platform platform, required b } void handleException(error, String chatId, Platform platform) { - var errorMessage = error is CustomException ? error.toString() : 'general.something_went_wrong'; + getIt().e('Handling module exception: $error'); + + var errorMessage = CustomException != dynamic && error is CustomException ? error.toString() : 'general.something_went_wrong'; platform.sendMessage(chatId, translation: errorMessage); } diff --git a/lib/src/modules/weather_manager.dart b/lib/src/modules/weather_manager.dart index dec1759..bc05b1e 100644 --- a/lib/src/modules/weather_manager.dart +++ b/lib/src/modules/weather_manager.dart @@ -2,7 +2,9 @@ import 'package:weather/src/core/database.dart'; import 'package:weather/src/core/weather.dart'; import 'package:weather/src/core/chat.dart'; import 'package:weather/src/globals/message_event.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; import 'utils.dart'; class WeatherManager { @@ -10,15 +12,17 @@ class WeatherManager { final Chat chat; final Database db; final String openweatherKey; + final Logger _logger; final Weather _weather; WeatherManager({required this.platform, required this.chat, required this.db, required this.openweatherKey}) - : _weather = Weather(db: db, openweatherKey: openweatherKey); + : _logger = getIt(), + _weather = Weather(db: db, openweatherKey: openweatherKey); void initialize() { _weather.initialize(); - _subscribeToWeatherUpdates(); + _subscribeToWeatherNotifications(); } void addCity(MessageEvent event) async { @@ -56,11 +60,14 @@ class WeatherManager { var chatId = event.chatId; var city = event.parameters[0]; - var temperature = await _weather.getWeatherForCity(city); - var result = temperature != null; - var successfulMessage = chat.getText(chatId, 'weather.cities.temperature', {'city': city, 'temperature': temperature.toString()}); - sendOperationMessage(chatId, platform: platform, operationResult: result, successfulMessage: successfulMessage); + _weather + .getWeatherForCity(city) + .then((result) => sendOperationMessage(chatId, + platform: platform, + operationResult: true, + successfulMessage: chat.getText(chatId, 'weather.cities.temperature', {'city': city, 'temperature': result.toString()}))) + .catchError((error) => handleException(error, chatId, platform)); } void setWeatherNotificationHour(MessageEvent event) async { @@ -84,19 +91,23 @@ class WeatherManager { void getWatchlistWeather(MessageEvent event) async { var chatId = event.chatId; - var watchlistCities = await _weather.getWatchList(chatId); - var weatherData = await _weather.getWeatherForCities(watchlistCities); - var weatherMessage = _buildWatchlistWeatherMessage(weatherData); - sendOperationMessage(chatId, platform: platform, operationResult: weatherMessage.isNotEmpty, successfulMessage: weatherMessage); + _weather + .getWatchList(chatId) + .then((watchlistCities) => _weather.getWeatherForCities(watchlistCities)) + .then((weatherData) => sendOperationMessage(chatId, + platform: platform, operationResult: true, successfulMessage: _buildWatchlistWeatherMessage(weatherData))) + .catchError((error) => handleException(error, chatId, platform)); } String _buildWatchlistWeatherMessage(List weatherData) { return weatherData.map((data) => 'In city: ${data.city} the temperature is ${data.temp}\n\n').join(); } - void _subscribeToWeatherUpdates() { + void _subscribeToWeatherNotifications() { _weather.weatherStream.listen((weatherData) async { + _logger.i('Handling weather notification data: $weatherData'); + var chatData = await chat.getSingleChat(chatId: weatherData.chatId); if (chatData?.platform != platform.chatPlatform) { diff --git a/lib/src/modules/youtube_manager.dart b/lib/src/modules/youtube_manager.dart index d8a3411..352716c 100644 --- a/lib/src/modules/youtube_manager.dart +++ b/lib/src/modules/youtube_manager.dart @@ -6,7 +6,6 @@ import 'utils.dart'; class YoutubeManager { final Platform platform; final String apiKey; - final Youtube _youtube; YoutubeManager({required this.platform, required this.apiKey}) : _youtube = Youtube(apiKey); diff --git a/lib/src/platform/discord_platform.dart b/lib/src/platform/discord_platform.dart index cf0637c..0a26ab5 100644 --- a/lib/src/platform/discord_platform.dart +++ b/lib/src/platform/discord_platform.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'package:collection/collection.dart'; -import 'package:nyxx/nyxx.dart'; +import 'package:nyxx/nyxx.dart' hide Logger; import 'package:nyxx_commands/nyxx_commands.dart'; import 'package:uuid/uuid.dart'; import 'package:cron/cron.dart'; @@ -15,9 +15,12 @@ import 'package:weather/src/globals/chat_platform.dart'; import 'package:weather/src/globals/bot_command.dart'; import 'package:weather/src/globals/message_event.dart'; import 'package:weather/src/globals/access_level.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; + const uuid = Uuid(); const emptyCharacter = 'ㅤ'; @@ -30,6 +33,7 @@ class DiscordPlatform implements Platform { final Access access; final Chat chat; final weather.User user; + final Logger _logger; final List _commands = []; final Map> _usersOnlineStatus = {}; @@ -43,11 +47,12 @@ class DiscordPlatform implements Platform { required this.eventBus, required this.access, required this.chat, - required this.user}); + required this.user}) + : _logger = getIt(); @override Future initialize() async { - print('No initialize script for Discord'); + _logger.i('No initialize script for Discord'); } @override @@ -60,7 +65,7 @@ class DiscordPlatform implements Platform { _startHeroCheckJob(); _watchUsersStatusUpdate(); - print('Discord platform has been started!'); + _logger.i('Discord platform has been started!'); } @override @@ -234,7 +239,7 @@ class DiscordPlatform implements Platform { await Future.forEach(authorizedChats, (chatId) async { var chatOnlineUsers = _usersOnlineStatus[chatId]; if (chatOnlineUsers == null) { - print('Attempt to get online users for empty chat $chatId'); + _logger.w('Attempt to get online users for empty chat $chatId'); return null; } diff --git a/lib/src/platform/telegram_platform.dart b/lib/src/platform/telegram_platform.dart index 9220fd2..20fcef4 100644 --- a/lib/src/platform/telegram_platform.dart +++ b/lib/src/platform/telegram_platform.dart @@ -16,9 +16,12 @@ import 'package:weather/src/globals/chat_platform.dart'; import 'package:weather/src/globals/message_event.dart'; import 'package:weather/src/globals/bot_command.dart'; import 'package:weather/src/globals/accordion_poll.dart'; +import 'package:weather/src/injector/injection.dart'; import 'package:weather/src/platform/platform.dart'; +import 'package:weather/src/utils/logger.dart'; + class TelegramPlatform implements Platform { @override late ChatPlatform chatPlatform; @@ -28,6 +31,7 @@ class TelegramPlatform implements Platform { final Access access; final Chat chat; final User user; + final Logger _logger; // final Debouncer _debouncer = Debouncer(Duration(seconds: 1), initialValue: null); @@ -41,7 +45,8 @@ class TelegramPlatform implements Platform { required this.eventBus, required this.access, required this.chat, - required this.user}); + required this.user}) + : _logger = getIt(); @override Future initialize() async { @@ -54,12 +59,12 @@ class TelegramPlatform implements Platform { _bot.start(); - print('Telegram platform has been started!'); + _logger.i('Telegram platform has been started!'); } @override Future postStart() async { - print('No post-start script for Telegram'); + _logger.i('No post-start script for Telegram'); } @override diff --git a/lib/src/utils/logger.dart b/lib/src/utils/logger.dart index abfad5e..d7a04f2 100644 --- a/lib/src/utils/logger.dart +++ b/lib/src/utils/logger.dart @@ -1 +1,45 @@ -// TODO: add a logger +import 'dart:io'; +import 'package:injectable/injectable.dart'; +import 'package:logger/logger.dart' as lg; + +final logFile = File('logs.txt'); + +class Logger { + final bool isProduction; + late final lg.Logger _logger; + static Logger? _instance; + + Logger._internal(this.isProduction) { + var printer = isProduction ? lg.SimplePrinter(printTime: true, colors: false) : lg.PrettyPrinter(printTime: true); + var output = isProduction ? lg.MultiOutput([lg.FileOutput(file: logFile), lg.ConsoleOutput()]) : lg.ConsoleOutput(); + + _logger = lg.Logger(printer: printer, output: output); + _logger.i('Logger initialized in ${isProduction ? 'prod' : 'dev'} mode'); + } + + factory Logger({required bool isProduction}) { + _instance ??= Logger._internal(isProduction); + return _instance!; + } + + void i(message) { + _logger.i(message); + } + + void w(message) { + _logger.w(message); + } + + void e(message, [Object? error]) { + _logger.e(message, error: error); + } +} + +@module +abstract class LoggerModule { + @dev + Logger get devLogger => Logger(isProduction: false); + + @prod + Logger get prodLogger => Logger(isProduction: true); +} diff --git a/lib/src/utils/migrations_manager.dart b/lib/src/utils/migrations_manager.dart index 435a4e2..8fe3c44 100644 --- a/lib/src/utils/migrations_manager.dart +++ b/lib/src/utils/migrations_manager.dart @@ -1,14 +1,17 @@ import 'dart:io'; import 'package:postgres/postgres.dart'; +import 'package:weather/src/injector/injection.dart'; +import 'package:weather/src/utils/logger.dart'; const String _pathToMigrations = 'assets/db/migrations'; const String _migrationTableMigrationName = '1677944890_migration.sql'; class MigrationsManager { final Pool dbConnection; + final Logger _logger; final String _migrationsDirectory = _pathToMigrations; - MigrationsManager(this.dbConnection); + MigrationsManager(this.dbConnection) : _logger = getIt(); Future runMigrations() async { var migrationsLocation = Directory(_migrationsDirectory); @@ -26,7 +29,7 @@ class MigrationsManager { var runMigration = await _shouldRunMigration(migrationName); if (runMigration) { - print('Applying migration $migrationName'); + _logger.w('Applying migration $migrationName'); var query = await migration.readAsString(); await _runMigration(query, migrationName); diff --git a/pubspec.yaml b/pubspec.yaml index 8ced707..681dc0a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,10 +21,15 @@ dependencies: meta: ^1.11.0 uuid: ^4.2.1 event_bus: ^2.0.0 + injectable: ^2.3.2 + get_it: ^7.6.7 + logger: ^2.0.2+1 dev_dependencies: analyzer: ^6.3.0 + build_runner: ^2.4.8 docker_process: ^1.3.1 + injectable_generator: ^2.4.1 lints: ^3.0.0 path: ^1.8.3 test: ^1.6.0