using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Oqtane.Extensions; using Oqtane.Interfaces; using Oqtane.Models; using Oqtane.Repository; using Oqtane.Services; using Oqtane.Shared; namespace Oqtane.Infrastructure { public class SearchIndexJob : HostedServiceBase { private const string SearchLastIndexedOnSetting = "Search_LastIndexedOn"; private const string SearchEnabledSetting = "Search_Enabled"; private const string SearchIgnorePagesSetting = "Search_IgnorePages"; private const string SearchIgnoreEntitiesSetting = "Search_IgnoreEntities"; public SearchIndexJob(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) { Name = "Search Index Job"; Frequency = "m"; // run every minute. Interval = 1; IsEnabled = true; } public override async Task ExecuteJobAsync(IServiceProvider provider) { string log = ""; // get services var siteRepository = provider.GetRequiredService(); var settingRepository = provider.GetRequiredService(); var tenantManager = provider.GetRequiredService(); var pageRepository = provider.GetRequiredService(); var pageModuleRepository = provider.GetRequiredService(); var searchService = provider.GetRequiredService(); var sites = siteRepository.GetSites().ToList(); foreach (var site in sites) { log += $"Indexing Site: {site.Name}
"; // initialize var siteSettings = settingRepository.GetSettings(EntityNames.Site, site.SiteId).ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); if (!Convert.ToBoolean(siteSettings.GetValue(SearchEnabledSetting, "true"))) { log += $"Indexing Disabled
"; continue; } var tenantId = tenantManager.GetTenant().TenantId; tenantManager.SetAlias(tenantId, site.SiteId); var currentTime = DateTime.UtcNow; var lastIndexedOn = Convert.ToDateTime(siteSettings.GetValue(SearchLastIndexedOnSetting, DateTime.MinValue.ToString())); if (lastIndexedOn == DateTime.MinValue) { // reset index log += $"*Site Index Reset*
"; await searchService.DeleteSearchContentsAsync(site.SiteId); } var ignorePages = siteSettings.GetValue(SearchIgnorePagesSetting, "").Split(','); var ignoreEntities = siteSettings.GetValue(SearchIgnoreEntitiesSetting, "File").Split(','); var pages = pageRepository.GetPages(site.SiteId); var pageModules = pageModuleRepository.GetPageModules(site.SiteId); var searchContents = new List(); // index pages foreach (var page in pages) { if (!string.IsNullOrEmpty(page.Path) && (Constants.InternalPagePaths.Contains(page.Path) || ignorePages.Contains(page.Path))) { continue; } bool changed = false; bool removed = false; if (page.ModifiedOn >= lastIndexedOn && !ignoreEntities.Contains(EntityNames.Page)) { changed = true; removed = page.IsDeleted || !Utilities.IsEffectiveAndNotExpired(page.EffectiveDate, page.ExpiryDate); var searchContent = new SearchContent { SiteId = page.SiteId, EntityName = EntityNames.Page, EntityId = page.PageId.ToString(), Title = !string.IsNullOrEmpty(page.Title) ? page.Title : page.Name, Description = string.Empty, Body = string.Empty, Url = $"{(!string.IsNullOrEmpty(page.Path) && !page.Path.StartsWith("/") ? "/" : "")}{page.Path}", Permissions = $"{EntityNames.Page}:{page.PageId}", ContentModifiedBy = page.ModifiedBy, ContentModifiedOn = page.ModifiedOn, AdditionalContent = string.Empty, CreatedOn = DateTime.UtcNow, IsDeleted = removed, TenantId = tenantId }; searchContents.Add(searchContent); } // index modules foreach (var pageModule in pageModules.Where(item => item.PageId == page.PageId)) { if (pageModule.ModifiedOn >= lastIndexedOn && !changed) { changed = true; } var searchable = false; if (pageModule.Module.ModuleDefinition != null && pageModule.Module.ModuleDefinition.ServerManagerType != "") { Type type = Type.GetType(pageModule.Module.ModuleDefinition.ServerManagerType); if (type?.GetInterface(nameof(ISearchable)) != null) { try { searchable = true; // determine if reindexing is necessary var lastindexedon = (changed) ? DateTime.MinValue : lastIndexedOn; // index module content var serverManager = (ISearchable)ActivatorUtilities.CreateInstance(provider, type); var searchcontents = await serverManager.GetSearchContentsAsync(pageModule, lastindexedon); if (searchcontents != null) { foreach (var searchContent in searchcontents) { if (!ignoreEntities.Contains(searchContent.EntityName)) { ValidateSearchContent(searchContent, pageModule, tenantId, removed); searchContents.Add(searchContent); } } } } catch (Exception ex) { log += $"Error Indexing Module {pageModule.Module.ModuleDefinition.Name} - {ex.Message}
"; } } } if (!searchable && changed && !ignoreEntities.Contains(EntityNames.Module)) { // module does not implement ISearchable var searchContent = new SearchContent(); ValidateSearchContent(searchContent, pageModule, tenantId, removed); searchContents.Add(searchContent); } } } // save search contents log += await searchService.SaveSearchContentsAsync(searchContents, siteSettings); log += $"Items Indexed: {searchContents.Count}
"; // update last indexed on SaveSearchLastIndexedOn(settingRepository, site.SiteId, currentTime); } return log; } private void ValidateSearchContent(SearchContent searchContent, PageModule pageModule, int tenantId, bool removed) { // set default values searchContent.SiteId = pageModule.Module.SiteId; searchContent.TenantId = tenantId; searchContent.CreatedOn = DateTime.UtcNow; if (string.IsNullOrEmpty(searchContent.EntityName)) { searchContent.EntityName = EntityNames.Module; } if (string.IsNullOrEmpty(searchContent.EntityId)) { searchContent.EntityId = pageModule.ModuleId.ToString(); } if (string.IsNullOrEmpty(searchContent.Title)) { if (pageModule.Page != null) { searchContent.Title = !string.IsNullOrEmpty(pageModule.Page.Title) ? pageModule.Page.Title : pageModule.Page.Name; } else { searchContent.Title = pageModule.Title; } } if (searchContent.Description == null) { searchContent.Description = (searchContent.Title != pageModule.Title) ? pageModule.Title : string.Empty; } if (searchContent.Body == null) { searchContent.Body = string.Empty; } if (string.IsNullOrEmpty(searchContent.Url)) { searchContent.Url = string.Empty; if (pageModule.Page != null) { searchContent.Url = $"{(!string.IsNullOrEmpty(pageModule.Page.Path) && !pageModule.Page.Path.StartsWith("/") ? "/" : "")}{pageModule.Page.Path}"; } } if (string.IsNullOrEmpty(searchContent.Permissions)) { searchContent.Permissions = $"{EntityNames.Module}:{pageModule.ModuleId},{EntityNames.Page}:{pageModule.PageId}"; } if (string.IsNullOrEmpty(searchContent.ContentModifiedBy)) { searchContent.ContentModifiedBy = pageModule.ModifiedBy; } if (searchContent.ContentModifiedOn == DateTime.MinValue) { searchContent.ContentModifiedOn = pageModule.ModifiedOn; } if (string.IsNullOrEmpty(searchContent.AdditionalContent)) { searchContent.AdditionalContent = string.Empty; } if (removed || pageModule.IsDeleted || !Utilities.IsEffectiveAndNotExpired(pageModule.EffectiveDate, pageModule.ExpiryDate)) { searchContent.IsDeleted = true; } } private void SaveSearchLastIndexedOn(ISettingRepository settingRepository, int siteId, DateTime lastIndexedOn) { var setting = settingRepository.GetSetting(EntityNames.Site, siteId, SearchLastIndexedOnSetting); if (setting == null) { setting = new Setting { EntityName = EntityNames.Site, EntityId = siteId, SettingName = SearchLastIndexedOnSetting, SettingValue = Convert.ToString(lastIndexedOn), }; settingRepository.AddSetting(setting); } else { setting.SettingValue = Convert.ToString(lastIndexedOn); settingRepository.UpdateSetting(setting); } } } }