Ejemplo n.º 1
0
        public async Task <Shared.Store> AddAsync(Uri uri)
        {
            var absoluteUrl = uri.AbsoluteUri;
            var storedStore = storesRepository.Find(store => store.Url == absoluteUrl).FirstOrDefault();

            if (storedStore == null)
            {
                var dateTime = DateTime.Now;
                var store    = new Store
                {
                    Name      = "Unknown Store",
                    IsEnabled = true,
                    Url       = UriHelper.EnsureStoreUrl(absoluteUrl),
                    Added     = dateTime,
                    Updated   = dateTime,
                    Read      = dateTime
                };

                var transaction = PolicyHelper.WaitAndRetry().Execute(
                    () => storesRepository.BeginTransaction(IsolationLevel.Serializable));

                storesRepository.Add(store);
                await storesRepository.SaveChangesAsync();

                await transaction.CommitAsync();

                return(store.ToDataTransferObject(true));
            }

            return(storedStore?.ToDataTransferObject());
        }
Ejemplo n.º 2
0
        public async Task <Shared.Store> AddAsync(Uri uri)
        {
            var absoluteUrl = uri.AbsoluteUri;
            var storedStore = this.storesRepository.Find(store => store.Url == absoluteUrl).FirstOrDefault();

            if (storedStore == null)
            {
                var dateTime = DateTime.Now;
                var store    = await this.entityScrapper.GetAsync(absoluteUrl);

                if (store != null)
                {
                    store.Added   = dateTime;
                    store.Updated = dateTime;
                    store.Read    = dateTime;

                    var transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => this.storesRepository.BeginTransaction(IsolationLevel.Serializable));

                    this.storesRepository.Add(store);
                    await this.storesRepository.SaveChangesAsync();

                    await transaction.CommitAsync();

                    return(store.ToDataTransferObject(true));
                }
            }

            return(storedStore?.ToDataTransferObject());
        }
Ejemplo n.º 3
0
        public async Task <ActionResult <Product> > Add(
            [FromServices] IRepository <Models.Product, int> productRepository,
            [FromServices] IEntityScrapper <Models.Product> entityScrapper,
            [FromBody] Uri uri)
        {
            var absoluteUrl = uri.AbsoluteUri;

            var storedProduct = productRepository.Find(product => product.Url == absoluteUrl).FirstOrDefault();

            if (storedProduct == null)
            {
                var dateTime = DateTime.Now;
                var product  = await entityScrapper.GetAsync(absoluteUrl);

                if (product != null)
                {
                    product.Added   = dateTime;
                    product.Updated = dateTime;
                    product.Read    = dateTime;
                    var transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => productRepository.BeginTransaction(IsolationLevel.Serializable));

                    productRepository.Add(product);
                    await productRepository.SaveChangesAsync();

                    await transaction.CommitAsync();

                    return(product.ToDataTransferObject(true));
                }
            }

            return(storedProduct?.ToDataTransferObject());
        }
Ejemplo n.º 4
0
        public async Task <ActionResult <Department> > Add(
            [FromServices] IRepository <Models.Department, int> departmentRepository,
            [FromServices] IEntityScraper <Models.Department> departmentScraper,
            [FromBody] Uri uri)
        {
            var absoluteUrl      = uri.AbsoluteUri;
            var storedDepartment =
                departmentRepository.Find(department => department.Url == absoluteUrl).FirstOrDefault();

            if (storedDepartment == null)
            {
                var dateTime   = DateTime.Now;
                var department = new Models.Department
                {
                    Name      = "Unknown Department",
                    IsEnabled = true,
                    Url       = UriHelper.EnsureDepartmentUrl(absoluteUrl),
                    Added     = dateTime,
                    Updated   = dateTime,
                    Read      = dateTime
                };

                var transaction = PolicyHelper.WaitAndRetry().Execute(
                    () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));
                departmentRepository.Add(department);
                await departmentRepository.SaveChangesAsync();

                await transaction.CommitAsync();

                return(department.ToDataTransferObject(true));
            }


            return(storedDepartment?.ToDataTransferObject());
        }
Ejemplo n.º 5
0
        public static async Task <(bool, Product)> TryRegisterProductAsync(this IRepository <Product, int> productRepository, string absoluteUrl)
        {
            Log.Information("Try to register product with Url '{Url}'", absoluteUrl);

            var product    = productRepository.Find(p => p.Url == absoluteUrl).FirstOrDefault();
            var registered = false;

            if (product == null)
            {
                var dateTime = DateTime.Now;
                product = new Product();
                {
                    product.Name      = "Unknown Product";
                    product.Url       = UriHelper.EnsureProductUrl(absoluteUrl);
                    product.Added     = dateTime;
                    product.Updated   = dateTime;
                    product.Read      = dateTime;
                    product.IsEnabled = true;
                    var transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => productRepository.BeginTransaction(IsolationLevel.Serializable));

                    productRepository.Add(product);
                    await productRepository.SaveChangesAsync();

                    await transaction.CommitAsync();

                    registered = true;
                }
            }

            Log.Information("Product with Url '{Url}' is registered as {Name}", absoluteUrl, product.Name);

            return(registered, product);
        }
        public async Task Delete([FromServices] IRepository <Models.Department, int> departmentRepository, int id)
        {
            var transaction = PolicyHelper.WaitAndRetry()
                              .Execute(() => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

            departmentRepository.Delete(department => department.Id == id);
            await departmentRepository.SaveChangesAsync();

            await transaction.CommitAsync();
        }
Ejemplo n.º 7
0
        public async Task Delete([FromServices] IRepository <Product, int> productRepository, int id)
        {
            var transaction = PolicyHelper.WaitAndRetry()
                              .Execute(() => productRepository.BeginTransaction(IsolationLevel.Serializable));

            productRepository.Delete(product => product.Id == id);
            await productRepository.SaveChangesAsync();

            await transaction.CommitAsync();
        }
Ejemplo n.º 8
0
        public async Task ExecuteAsync(
            IRepository <User, int> userRepository,
            ITelegramBotClient telegramBotClient = null)
        {
            if (telegramBotClient == null)
            {
                Log.Information("TelegramBotClient is not registered.");

                return;
            }

            Log.Information("Synchronizing users from Telegram.");

            Update[] updates = null;
            try
            {
                updates = await telegramBotClient.GetUpdatesAsync(-1);
            }
            catch (Exception e)
            {
                Log.Error(e, "Error retrieving updates from telegram");
            }

            var users = updates
                        ?.Select(
                update => new User
            {
                ChatId = update?.Message?.Chat?.Id ?? -1,
                Name   = update?.Message?.Chat?.Username ?? string.Empty
            }).Where(user => !string.IsNullOrWhiteSpace(user.Name))
                        /*.Distinct(new UserEqualityComparer())*/.ToList();

            if (users != null)
            {
                foreach (var user in users)
                {
                    var storedUser = userRepository.Find(u => u.Name == user.Name).FirstOrDefault();
                    if (storedUser != null)
                    {
                        user.Id = storedUser.Id;
                    }

                    var transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => userRepository.BeginTransaction(IsolationLevel.Serializable));

                    userRepository.TryAddOrUpdate(user, nameof(User.IsEnable));
                    await userRepository.SaveChangesAsync();

                    await transaction.CommitAsync();
                }
            }

            Log.Information("User synchronization from telegram completed");
        }
Ejemplo n.º 9
0
        public async Task Enable([FromServices] IRepository <Product, int> productRepository, int id)
        {
            var transaction = PolicyHelper.WaitAndRetry()
                              .Execute(() => productRepository.BeginTransaction(IsolationLevel.Serializable));

            var product = productRepository.Find(p => p.Id == id).FirstOrDefault();

            if (product != null)
            {
                product.IsEnabled = true;
            }

            await productRepository.SaveChangesAsync();

            await transaction.CommitAsync();
        }
Ejemplo n.º 10
0
        public async Task <IEnumerable <Store> > Get([FromServices] IRepository <Models.Store, int> storeRepository)
        {
            var stores = new List <Store>();

            foreach (var storedStore in storeRepository.All())
            {
                var hasChanged = storedStore.Read < storedStore.Updated;

                var transaction = PolicyHelper.WaitAndRetry()
                                  .Execute(() => storeRepository.BeginTransaction(IsolationLevel.Serializable));

                storedStore.Read = DateTime.Now;
                stores.Add(storedStore.ToDataTransferObject(hasChanged));
                await storeRepository.SaveChangesAsync();

                await transaction.CommitAsync();
            }

            return(stores);
        }
Ejemplo n.º 11
0
        public async Task <IEnumerable <Shared.Product> > Get([FromServices] IRepository <Product, int> productRepository)
        {
            var products = new List <Shared.Product>();

            foreach (var storedProduct in productRepository.All())
            {
                var hasChanged  = storedProduct.Read < storedProduct.Updated;
                var transaction = PolicyHelper.WaitAndRetry().Execute(
                    () => productRepository.BeginTransaction(IsolationLevel.Serializable));

                storedProduct.Read = DateTime.Now;
                await productRepository.SaveChangesAsync();

                await transaction.CommitAsync();

                products.Add(storedProduct.ToDataTransferObject(hasChanged));
            }

            return(products);
        }
        public async Task <IEnumerable <Department> > Get(
            [FromServices] IRepository <Models.Department, int> departmentRepository)
        {
            var departments = new List <Department>();

            foreach (var storedDepartment in departmentRepository.All())
            {
                var hasChanged = storedDepartment.Read < storedDepartment.Updated;

                var transaction = PolicyHelper.WaitAndRetry().Execute(
                    () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

                storedDepartment.Read = DateTime.Now;
                await departmentRepository.SaveChangesAsync();

                await transaction.CommitAsync();

                departments.Add(storedDepartment.ToDataTransferObject(hasChanged));
            }

            return(departments);
        }
        public async Task <ActionResult <Department> > Add(
            [FromServices] IRepository <Models.Department, int> departmentRepository,
            [FromServices] IEntityScrapper <Models.Department> departmentScrapper,
            [FromBody] Uri uri)
        {
            var absoluteUrl      = uri.AbsoluteUri;
            var storedDepartment =
                departmentRepository.Find(department => department.Url == absoluteUrl).FirstOrDefault();

            if (storedDepartment == null)
            {
                var department = await departmentScrapper.GetAsync(absoluteUrl);

                if (department != null)
                {
                    var dateTime = DateTime.Now;
                    department.Added   = dateTime;
                    department.Updated = dateTime;
                    department.Read    = dateTime;

                    var transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));
                    departmentRepository.Add(department);
                    await departmentRepository.SaveChangesAsync();

                    await transaction.CommitAsync();

                    return(department.ToDataTransferObject(true));
                }
            }

            if (storedDepartment == null)
            {
                return(this.NotFound());
            }

            return(storedDepartment?.ToDataTransferObject());
        }
Ejemplo n.º 14
0
        public async Task Execute(
            IRepository <Department, int> globalDepartmentRepository,
            IRepository <Product, int> globalProductRepository,
            IServiceProvider serviceProvider,
            IHubContext <MessagesHub> messageHubContext,
            ITelegramBotClient telegramBotClient = null)
        {
            Log.Information("Running {Source} Monitor.", AlertSource.Departments);

            var sourceChanged = false;

            // TODO: Improve this.
            var storedDepartments = globalDepartmentRepository.Find(s => s.IsEnabled).ToList();
            var disabledProducts  = globalProductRepository.Find(p => !p.IsEnabled).Select(p => p.Url)
                                    .ToImmutableSortedSet();

            await storedDepartments.ParallelForEachAsync(
                async storedDepartment =>
            {
                var serviceScope = serviceProvider.CreateScope();
                var serviceScopeServiceProvider = serviceScope.ServiceProvider;
                var departmentRepository        =
                    serviceScopeServiceProvider.GetService <IRepository <Department, int> >();
                var userRepository     = serviceScopeServiceProvider.GetService <IRepository <User, int> >();
                var departmentScrapper = serviceProvider.GetService <IEntityScraper <Department> >();

                // var storedDepartmentStore = storedDepartment.Store;
                // var storedDepartmentName = storedDepartment.Name;
                // var disabledProducts = productRepository.Find(p => p.Store == storedDepartmentStore && p.Department == storedDepartmentName && !p.IsEnabled)
                // .Select(p => p.Url).ToImmutableSortedSet();
                var dateTime   = DateTime.Now;
                var department = await departmentScrapper.GetAsync(
                    storedDepartment.Url,
                    true,
                    disabledProducts);
                IDbContextTransaction transaction = null;
                Log.Information("Updating scrapped department '{url}'", storedDepartment.Url);
                if (department == null)
                {
                    department = storedDepartment;
                    if (department.IsAvailable)
                    {
                        transaction = PolicyHelper.WaitAndRetry().Execute(
                            () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

                        department.IsAvailable = false;
                        department.Updated     = dateTime;
                        department.Sha256      = JsonSerializer.Serialize(department).ComputeSha256();

                        sourceChanged = true;
                        Log.Information(
                            "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
                            department.Name,
                            department.Store,
                            department.IsAvailable);
                    }
                }
                else if (department.Sha256 != storedDepartment.Sha256)
                {
                    transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

                    department.Id      = storedDepartment.Id;
                    department.Updated = dateTime;
                    departmentRepository.TryAddOrUpdate(
                        department,
                        nameof(Department.Added),
                        nameof(Department.Read));
                    sourceChanged = true;

                    Log.Information(
                        "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
                        department.Name,
                        department.Store,
                        department.IsAvailable);
                }

                if (transaction != null)
                {
                    await departmentRepository.SaveChangesAsync();
                    await transaction.CommitAsync();

                    // var repository = serviceProvider.GetService<IRepository<Product, int>>();
                    // foreach (var departmentProduct in department.Products.Values)
                    // {
                    // var departmentProductUrl = departmentProduct.Url;
                    // repository.TryAddOrUpdate(departmentProduct);
                    // }
                    var departmentDataTransferObject = department.ToDataTransferObject(true);
                    var message = JsonSerializer.Serialize(departmentDataTransferObject);
                    await messageHubContext.Clients.All.SendAsync(
                        ClientMethods.EntityChanged,
                        AlertSource.Departments,
                        message);

                    using var httpClient = new HttpClient();
                    foreach (var departmentProduct in department.Products.Values)
                    {
                        var imageStream = await httpClient.GetStreamAsync(departmentProduct.ImageUrl);

                        var storeSlug = UriHelper.GetStoreSlug(department.Url);
                        if (!Directory.Exists($"logs/products/{storeSlug}"))
                        {
                            Directory.CreateDirectory($"logs/products/{storeSlug}");
                        }

                        var baseFilePath = $"logs/products/{storeSlug}/{departmentProduct.Name.ComputeSha256()}";

                        var imageFilePath = $"{baseFilePath}.jpg";
                        try
                        {
                            await using var fileStream = File.Create(imageFilePath);
                            await imageStream.CopyToAsync(fileStream);
                            await fileStream.FlushAsync();
                        }
                        catch (Exception e)
                        {
                            Log.Warning(e, "Error saving image.");
                        }

                        var textFilePath = $"{baseFilePath}.txt";
                        try
                        {
                            var builder = new StringBuilder();
                            builder.AppendLine(departmentProduct.Name);
                            builder.AppendLine($"{departmentProduct.Price} {departmentProduct.Currency}");
                            await File.WriteAllTextAsync(textFilePath, builder.ToString());
                        }
                        catch (Exception e)
                        {
                            Log.Warning(e, "Error saving description");
                        }
                    }

                    if (telegramBotClient != null && department.IsAvailable)
                    {
                        var messageStringBuilder = new StringBuilder();
                        messageStringBuilder.AppendLine("*Product Set Changed*");
                        messageStringBuilder.AppendLine($"*Name:* _{departmentDataTransferObject.Name}_");
                        messageStringBuilder.AppendLine(
                            $"*Category:* _{departmentDataTransferObject.Category}_");
                        messageStringBuilder.AppendLine(
                            $"*Products Count:* _{departmentDataTransferObject.ProductsCount}_");
                        messageStringBuilder.AppendLine(
                            $"*Is Available:* _{departmentDataTransferObject.IsAvailable}_");

                        if (departmentDataTransferObject.ProductsCount > 0)
                        {
                            messageStringBuilder.AppendLine(
                                $"*Link:* [{departmentDataTransferObject.Url}]({departmentDataTransferObject.Url})");
                        }

                        // TODO: Use the DTO instead.
                        foreach (var keyValuePair in department.Products)
                        {
                            var product = keyValuePair.Value;
                            messageStringBuilder.AppendLine("------------------------------");
                            messageStringBuilder.AppendLine($"*Product Name:* {product.Name}");
                            messageStringBuilder.AppendLine(
                                $"*Product Price:* _{product.Price:C} {product.Currency}_");
                            messageStringBuilder.AppendLine($"*Product Is Available:* {product.IsAvailable}");
                            messageStringBuilder.AppendLine($"*Product Is In Cart:* {product.IsInCart}");
                            if (product.IsAvailable)
                            {
                                // TODO: Improve this to send the image.
                                messageStringBuilder.AppendLine(
                                    $"*Product Link:* [{product.Url}]({product.Url})");
                                messageStringBuilder.AppendLine(
                                    $"*Product Image:* [{product.ImageUrl}]({product.ImageUrl})");
                            }

                            messageStringBuilder.AppendLine("------------------------------");
                        }

                        messageStringBuilder.AppendLine($"*Store:* _{departmentDataTransferObject.Store}_");
                        var markdownMessage = messageStringBuilder.ToString();

                        var users = userRepository.Find(user => user.IsEnable).ToList();
                        foreach (var user in users)
                        {
                            try
                            {
                                await telegramBotClient.SendTextMessageAsync(
                                    user.ChatId,
                                    markdownMessage,
                                    ParseMode.Markdown);
                            }
                            catch (Exception e)
                            {
                                Log.Warning(
                                    e,
                                    "Error sending notification messages via telegram to {UserName}",
                                    user.Name);
                            }

                            using var client = new HttpClient();
                            foreach (var departmentProduct in department.Products.Values)
                            {
                                var storeSlug     = UriHelper.GetStoreSlug(department.Url);
                                var baseFilePath  = $"logs/products/{storeSlug}/{departmentProduct.Name.ComputeSha256()}";
                                var imageFilePath = $"{baseFilePath}.jpg";
                                if (File.Exists(imageFilePath))
                                {
                                    try
                                    {
                                        await telegramBotClient.SendPhotoAsync(
                                            user.ChatId,
                                            new InputMedia(
                                                new FileStream(imageFilePath, FileMode.Open),
                                                "photo.jpg"),
                                            departmentProduct.Name);
                                    }
                                    catch (Exception e)
                                    {
                                        Log.Warning(
                                            e,
                                            "Error sending detailed messages via telegram to {UserName}",
                                            user.Name);
                                    }
                                }
                            }
                        }
                    }

                    Log.Information("Entity changed at source {Source}.", AlertSource.Departments);
                }
                else
                {
                    Log.Information("No change detected for department '{url}'", storedDepartment.Url);
                }
            });

            Log.Information(
                sourceChanged ? "{Source} changes detected" : "No {Source} changes detected",
                AlertSource.Departments);
        }
Ejemplo n.º 15
0
        public async Task Execute(
            IRepository <Product, int> globalProductRepository,
            IServiceProvider serviceProvider,
            IHubContext <MessagesHub> messageHubContext,
            ITelegramBotClient telegramBotClient = null)
        {
            Log.Information("Running {Source} Monitor.", AlertSource.Products);

            var sourceChanged = false;

            var storedProducts = globalProductRepository.Find(product => product.IsEnabled).ToList();
            await storedProducts.ParallelForEachAsync(
                async storedProduct =>
            {
                var serviceScope = serviceProvider.CreateScope();
                var serviceScopeServiceProvider = serviceScope.ServiceProvider;
                var productRepository           = serviceScopeServiceProvider.GetService <IRepository <Product, int> >();
                var productScrapper             = serviceProvider.GetService <IEntityScrapper <Product> >();
                var userRepository = serviceScopeServiceProvider.GetService <IRepository <User, int> >();

                var dateTime = DateTime.Now;
                var product  = await productScrapper.GetAsync(storedProduct.Url, true);
                IDbContextTransaction transaction = null;
                Log.Information("Updating scrapped product '{url}'", storedProduct.Url);
                if (product == null)
                {
                    product = storedProduct;
                    if (product.IsAvailable)
                    {
                        transaction = PolicyHelper.WaitAndRetry().Execute(
                            () => productRepository.BeginTransaction(IsolationLevel.Serializable));

                        product.IsAvailable = false;
                        product.Updated     = dateTime;
                        product.Sha256      = JsonSerializer.Serialize(storedProduct).ComputeSHA256();
                        sourceChanged       = true;

                        Log.Information(
                            "Product {Product} from {Store} has changed. Is Available: {IsAvailable}",
                            storedProduct.Name,
                            storedProduct.Store,
                            storedProduct.IsAvailable);
                    }
                }
                else if (product.Sha256 != storedProduct.Sha256)
                {
                    transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => productRepository.BeginTransaction(IsolationLevel.Serializable));

                    product.Id      = storedProduct.Id;
                    product.Updated = dateTime;
                    productRepository.TryAddOrUpdate(product, nameof(Product.Added), nameof(Product.Read));
                    sourceChanged = true;

                    Log.Information(
                        "Product {Product} from {Store} has changed. Is Available: {IsAvailable}",
                        product.Name,
                        product.Store,
                        product.IsAvailable);
                }

                if (transaction != null)
                {
                    await productRepository.SaveChangesAsync();
                    await transaction.CommitAsync();

                    var productDataTransferObject = product.ToDataTransferObject(true);
                    var message = JsonSerializer.Serialize(productDataTransferObject);
                    await messageHubContext.Clients.All.SendAsync(
                        ClientMethods.EntityChanged,
                        AlertSource.Products,
                        message);

                    Log.Information("Entity changed at source {Source}.", AlertSource.Products);

                    if (telegramBotClient != null)
                    {
                        var messageStringBuilder = new StringBuilder();
                        messageStringBuilder.AppendLine("*Product Changed*");
                        messageStringBuilder.AppendLine($"*Name:* _{productDataTransferObject.Name}_");
                        messageStringBuilder.AppendLine(
                            $"*Price:* _{productDataTransferObject.Price.ToString("C")} {productDataTransferObject.Currency}_");
                        messageStringBuilder.AppendLine(
                            $"*Is Available:* _{productDataTransferObject.IsAvailable}_");
                        messageStringBuilder.AppendLine(
                            $"*Is In Cart:* _{productDataTransferObject.IsInCart}_");
                        if (productDataTransferObject.IsAvailable)
                        {
                            messageStringBuilder.AppendLine(
                                $"*Link:* [{productDataTransferObject.Url}]({productDataTransferObject.Url})");
                        }

                        messageStringBuilder.AppendLine($"*Store:* _{productDataTransferObject.Store}_");
                        messageStringBuilder.AppendLine(
                            $"*Department:* _{productDataTransferObject.Department}_");
                        messageStringBuilder.AppendLine(
                            $"*Category:* _{productDataTransferObject.DepartmentCategory}_");

                        var markdownMessage = messageStringBuilder.ToString();

                        var users = userRepository.Find(user => user.IsEnable).ToList();
                        foreach (var user in users)
                        {
                            try
                            {
                                await telegramBotClient.SendTextMessageAsync(
                                    user.ChatId,
                                    markdownMessage,
                                    ParseMode.Markdown);
                            }
                            catch (Exception e)
                            {
                                Log.Error(e, "Error sending message via telegram to {UserName}", user.Name);
                            }
                        }
                    }
                }
            });

            Log.Information(
                sourceChanged ? "{Source} changes detected" : "No {Source} changes detected",
                AlertSource.Products);
        }
Ejemplo n.º 16
0
        public async Task Execute(
            IRepository <Department, int> globalDepartmentRepository,
            IRepository <Product, int> globalProductRepository,
            IServiceProvider serviceProvider,
            IHubContext <MessagesHub> messageHubContext,
            ITelegramBotClient telegramBotClient = null)
        {
            Log.Information("Running {Source} Monitor.", AlertSource.Departments);

            var sourceChanged = false;

            // TODO: Improve this.
            var storedDepartments = globalDepartmentRepository.Find(s => s.IsEnabled).ToList();
            var disabledProducts  = globalProductRepository.Find(p => !p.IsEnabled).Select(p => p.Url)
                                    .ToImmutableSortedSet();

            await storedDepartments.ParallelForEachAsync(
                async storedDepartment =>
            {
                var serviceScope = serviceProvider.CreateScope();
                var serviceScopeServiceProvider = serviceScope.ServiceProvider;
                var departmentRepository        =
                    serviceScopeServiceProvider.GetService <IRepository <Department, int> >();
                var userRepository     = serviceScopeServiceProvider.GetService <IRepository <User, int> >();
                var departmentScrapper = serviceProvider.GetService <IEntityScrapper <Department> >();

                // var storedDepartmentStore = storedDepartment.Store;
                // var storedDepartmentName = storedDepartment.Name;
                // var disabledProducts = productRepository.Find(p => p.Store == storedDepartmentStore && p.Department == storedDepartmentName && !p.IsEnabled)
                // .Select(p => p.Url).ToImmutableSortedSet();
                var dateTime   = DateTime.Now;
                var department = await departmentScrapper.GetAsync(
                    storedDepartment.Url,
                    true,
                    disabledProducts);
                IDbContextTransaction transaction = null;
                Log.Information("Updating scrapped department '{url}'", storedDepartment.Url);
                if (department == null)
                {
                    department = storedDepartment;
                    if (department.IsAvailable)
                    {
                        transaction = PolicyHelper.WaitAndRetry().Execute(
                            () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

                        department.IsAvailable = false;
                        department.Updated     = dateTime;
                        department.Sha256      = JsonSerializer.Serialize(department).ComputeSHA256();

                        sourceChanged = true;
                        Log.Information(
                            "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
                            department.Name,
                            department.Store,
                            department.IsAvailable);
                    }
                }
                else if (department.Sha256 != storedDepartment.Sha256)
                {
                    transaction = PolicyHelper.WaitAndRetry().Execute(
                        () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

                    department.Id      = storedDepartment.Id;
                    department.Updated = dateTime;
                    departmentRepository.TryAddOrUpdate(
                        department,
                        nameof(Department.Added),
                        nameof(Department.Read));
                    sourceChanged = true;

                    Log.Information(
                        "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
                        department.Name,
                        department.Store,
                        department.IsAvailable);
                }

                if (transaction != null)
                {
                    await departmentRepository.SaveChangesAsync();
                    await transaction.CommitAsync();

                    var departmentDataTransferObject = department.ToDataTransferObject(true);
                    var message = JsonSerializer.Serialize(departmentDataTransferObject);
                    await messageHubContext.Clients.All.SendAsync(
                        ClientMethods.EntityChanged,
                        AlertSource.Departments,
                        message);

                    if (telegramBotClient != null && department.IsAvailable)
                    {
                        var messageStringBuilder = new StringBuilder();
                        messageStringBuilder.AppendLine("*Product Set Changed*");
                        messageStringBuilder.AppendLine($"*Name:* _{departmentDataTransferObject.Name}_");
                        messageStringBuilder.AppendLine(
                            $"*Category:* _{departmentDataTransferObject.Category}_");
                        messageStringBuilder.AppendLine(
                            $"*Products Count:* _{departmentDataTransferObject.ProductsCount}_");
                        messageStringBuilder.AppendLine(
                            $"*Is Available:* _{departmentDataTransferObject.IsAvailable}_");

                        if (departmentDataTransferObject.ProductsCount > 0)
                        {
                            messageStringBuilder.AppendLine(
                                $"*Link:* [{departmentDataTransferObject.Url}]({departmentDataTransferObject.Url})");
                        }

                        // TODO: Use the DTO instead.
                        foreach (var keyValuePair in department.Products)
                        {
                            var product = keyValuePair.Value;
                            messageStringBuilder.AppendLine("------------------------------");
                            messageStringBuilder.AppendLine($"*Product Name:* {product.Name}");
                            messageStringBuilder.AppendLine(
                                $"*Product Price:* _{product.Price:C} {product.Currency}_");
                            messageStringBuilder.AppendLine($"*Product Is Available:* {product.IsAvailable}");
                            messageStringBuilder.AppendLine($"*Product Is In Cart:* {product.IsInCart}");
                            if (product.IsAvailable)
                            {
                                messageStringBuilder.AppendLine(
                                    $"*Product Link:* [{product.Url}]({product.Url})");
                            }

                            messageStringBuilder.AppendLine("------------------------------");
                        }

                        messageStringBuilder.AppendLine($"*Store:* _{departmentDataTransferObject.Store}_");
                        var markdownMessage = messageStringBuilder.ToString();

                        var users = userRepository.Find(user => user.IsEnable).ToList();
                        foreach (var user in users)
                        {
                            try
                            {
                                await telegramBotClient.SendTextMessageAsync(
                                    user.ChatId,
                                    markdownMessage,
                                    ParseMode.Markdown);
                            }
                            catch (Exception e)
                            {
                                Log.Error(e, "Error sending message via telegram to {UserName}", user.Name);
                            }
                        }
                    }

                    Log.Information("Entity changed at source {Source}.", AlertSource.Departments);
                }
                else
                {
                    Log.Information("No change detected for department '{url}'", storedDepartment.Url);
                }
            });

            // Parallel.ForEach(
            // storedDepartments,
            // async storedDepartment =>
            // {
            // var departmentRepository = serviceProvider.CreateScope().ServiceProvider
            // .GetService<IRepository<Department, int>>();

            // // var storedDepartmentStore = storedDepartment.Store;
            // // var storedDepartmentName = storedDepartment.Name;
            // // var disabledProducts = productRepository.Find(p => p.Store == storedDepartmentStore && p.Department == storedDepartmentName && !p.IsEnabled)
            // // .Select(p => p.Url).ToImmutableSortedSet();
            // var dateTime = DateTime.Now;
            // var department = await departmentScrapper.GetAsync(
            // storedDepartment.Url,
            // true,
            // disabledProducts);
            // IDbContextTransaction transaction = null;
            // Log.Information("Updating scrapped department '{url}'", storedDepartment.Url);
            // if (department == null)
            // {
            // department = storedDepartment;
            // if (department.IsAvailable)
            // {
            // transaction = PolicyHelper.WaitAndRetry().Execute(
            // () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

            // department.IsAvailable = false;
            // department.Updated = dateTime;
            // department.Sha256 = JsonSerializer.Serialize(department).ComputeSHA256();

            // sourceChanged = true;
            // Log.Information(
            // "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
            // department.Name,
            // department.Store,
            // department.IsAvailable);
            // }
            // }
            // else if (department.Sha256 != storedDepartment.Sha256)
            // {
            // transaction = PolicyHelper.WaitAndRetry().Execute(
            // () => departmentRepository.BeginTransaction(IsolationLevel.Serializable));

            // department.Id = storedDepartment.Id;
            // department.Updated = dateTime;
            // departmentRepository.TryAddOrUpdate(
            // department,
            // nameof(Department.Added),
            // nameof(Department.Read));
            // sourceChanged = true;

            // Log.Information(
            // "Department {Department} from {Store} has changed. Is Available: {IsAvailable}",
            // department.Name,
            // department.Store,
            // department.IsAvailable);
            // }

            // if (transaction != null)
            // {
            // await departmentRepository.SaveChangesAsync();
            // await transaction.CommitAsync();

            // var departmentDataTransferObject = department.ToDataTransferObject(true);
            // var message = JsonSerializer.Serialize(departmentDataTransferObject);
            // await messageHubContext.Clients.All.SendAsync(
            // ClientMethods.EntityChanged,
            // AlertSource.Departments,
            // message);

            // if (telegramBotClient != null && department.IsAvailable)
            // {
            // var messageStringBuilder = new StringBuilder();
            // messageStringBuilder.AppendLine("*Product Set Changed*");
            // messageStringBuilder.AppendLine($"*Name:* _{departmentDataTransferObject.Name}_");
            // messageStringBuilder.AppendLine(
            // $"*Category:* _{departmentDataTransferObject.Category}_");
            // messageStringBuilder.AppendLine(
            // $"*Products Count:* _{departmentDataTransferObject.ProductsCount}_");
            // messageStringBuilder.AppendLine(
            // $"*Is Available:* _{departmentDataTransferObject.IsAvailable}_");

            // if (departmentDataTransferObject.ProductsCount > 0)
            // {
            // messageStringBuilder.AppendLine(
            // $"*Link:* [{departmentDataTransferObject.Url}]({departmentDataTransferObject.Url})");
            // }

            // // TODO: Use the DTO instead.
            // foreach (var keyValuePair in department.Products)
            // {
            // var product = keyValuePair.Value;
            // messageStringBuilder.AppendLine("------------------------------");
            // messageStringBuilder.AppendLine($"*Product Name:* {product.Name}");
            // messageStringBuilder.AppendLine(
            // $"*Product Price:* _{product.Price:C} {product.Currency}_");
            // messageStringBuilder.AppendLine($"*Product Is Available:* {product.IsAvailable}");
            // messageStringBuilder.AppendLine($"*Product Is In Cart:* {product.IsInCart}");
            // if (product.IsAvailable)
            // {
            // messageStringBuilder.AppendLine(
            // $"*Product Link:* [{product.Url}]({product.Url})");
            // }

            // messageStringBuilder.AppendLine("------------------------------");
            // }

            // messageStringBuilder.AppendLine($"*Store:* _{departmentDataTransferObject.Store}_");
            // var markdownMessage = messageStringBuilder.ToString();

            // var users = userRepository.Find(user => user.IsEnable).ToList();
            // foreach (var user in users)
            // {
            // try
            // {
            // await telegramBotClient.SendTextMessageAsync(
            // user.ChatId,
            // markdownMessage,
            // ParseMode.Markdown);
            // }
            // catch (Exception e)
            // {
            // Log.Error(e, "Error sending message via telegram to {UserName}", user.Name);
            // }
            // }
            // }

            // Log.Information("Entity changed at source {Source}.", AlertSource.Departments);
            // }
            // else
            // {
            // Log.Information("No change detected for department '{url}'", storedDepartment.Url);
            // }
            // });
            Log.Information(
                sourceChanged ? "{Source} changes detected" : "No {Source} changes detected",
                AlertSource.Departments);
        }