public async Task <ICommandResultBase> UninstallAsync() { var output = new CommandResultBase(); foreach (var descriptor in Descriptors) { // Ensure the feature is enabled var feature = await _featureFacade.GetFeatureByIdAsync(descriptor.ModuleId); if (feature == null) { continue; } var result = await UninstallLabelsAsync(feature); if (!result.Succeeded) { return(output.Failed(result.Errors.ToArray())); } } return(output.Success()); }
async Task <ICommandResultBase> DropIndexesInternalAsync() { // Build result var result = new CommandResultBase(); // Get provided indexes var providedIndexes = _fullTextIndexManager.GetIndexes(); // Drop all provided indexes foreach (var providedIndex in providedIndexes) { var deleteIndex = await _fullTextIndexCommand.DeleteAsync(new SchemaFullTextIndex() { TableName = providedIndex.TableName }); if (!deleteIndex.Succeeded) { return(result.Failed(deleteIndex.Errors.ToArray())); } } return(result.Success()); }
public async Task <ICommandResultBase> InitialMigrationsAsync() { // Our result var output = new CommandResultBase(); // Get all installed features var features = await _shellFeatureStore.SelectFeatures(); // We need features to upgrade if (features == null) { return(output.Failed("No features could be found within the shell features store.")); } // Attempt to update each found feature and compile any errors var errors = new List <CommandError>(); foreach (var feature in features) { var result = await _shellFeatureUpdater.UpdateAsync(feature.ModuleId); if (result.Errors.Any()) { errors.AddRange(result.Errors); } } // Did any feature upgrade encounter errors? if (errors.Count > 0) { return(output.Failed(errors.ToArray())); } // Return success return(output.Success()); }
private async Task <ICommandResultBase> UninstallInternalAsync(IShellSettings shellSettings) { // Our result var result = new CommandResultBase(); var errors = new List <CommandError>(); // ---------------------- // 1. Attempt to delete App_Data/{SiteName} folder // ---------------------- var deleted = true; try { deleted = _shellSettingsManager.DeleteSettings(shellSettings); } catch (Exception e) { errors.Add(new CommandError(e.Message)); } // Report any errors if (errors.Count > 0) { return(result.Failed(errors.ToArray())); } // Ensure we could delete the directory if (deleted == false) { return(result.Failed($"Cannot delete tenant folder with the name \"{shellSettings.Location}\"!")); } // ---------------------- // 2. Attempt to drop all tables and stored procedures with our table prefix // ---------------------- using (var shellContext = _shellContextFactory.CreateMinimalShellContext(shellSettings)) { using (var scope = shellContext.ServiceProvider.CreateScope()) { using (var dbContext = scope.ServiceProvider.GetRequiredService <IDbContext>()) { // update dbContext confirmation dbContext.Configure(options => { options.ConnectionString = shellSettings.ConnectionString; options.DatabaseProvider = shellSettings.DatabaseProvider; options.TablePrefix = shellSettings.TablePrefix; }); var logger = scope.ServiceProvider.GetRequiredService <ILogger <TenantSetUpService> >(); var dbHelper = scope.ServiceProvider.GetRequiredService <IDbHelper>(); try { await dbHelper.ExecuteScalarAsync <int>(UninstallSql, new Dictionary <string, string>() { ["{prefix}"] = shellSettings.TablePrefix }); } catch (Exception e) { errors.Add(new CommandError(e.Message)); } } } } // ---------------------- // 3. Dispose the tenant // ---------------------- // Ensure no errors occurred if (errors.Count == 0) { _platoHost.DisposeShell(shellSettings); } return(errors.Count > 0 ? result.Failed(errors.ToArray()) : result.Success()); }
// ---------- async Task <ICommandResultBase> InstallInternalAsync(IShellFeature feature) { // Validate if (feature == null) { throw new ArgumentNullException(nameof(feature)); } if (string.IsNullOrEmpty(feature.ModuleId)) { throw new ArgumentNullException(nameof(feature.ModuleId)); } var user = await _contextFacade.GetAuthenticatedUserAsync(); // Get all feature tags var labels = await _labelStore.QueryAsync() .Select <LabelQueryParams>(Q => { Q.FeatureId.Equals(feature.Id); }) .ToList(); // Associate every tag with at least 1 entity var output = new CommandResultBase(); if (labels != null) { var entities = await _entityStore.QueryAsync() .Select <EntityQueryParams>(q => { q.FeatureId.Equals(feature.Id); }) .ToList(); var alreadyAdded = new Dictionary <int, Entity>(); foreach (var label in labels?.Data) { var randomEntities = GetRandomEntities(entities?.Data, alreadyAdded); if (randomEntities == null) { return(output.Success()); } foreach (var entity in randomEntities) { var result = await _entityLabelManager.CreateAsync(new EntityLabel() { EntityId = entity.Id, LabelId = label.Id, CreatedUserId = user?.Id ?? 0, CreatedDate = DateTime.UtcNow }); if (!result.Succeeded) { return(output.Failed(result.Errors.ToArray())); } } } } return(output.Success()); }
// -------------------- async Task <ICommandResultBase> ApplyLatestHistoryPoint(Idea entity, IdeaComment reply) { // Get current user var user = await _contextFacade.GetAuthenticatedUserAsync(); // We need to be authenticated to make changes if (user == null) { return(await ResetEditDetails(entity, reply, user)); } // Get newest / most recent history entry var histories = await _entityHistoryStore.QueryAsync() .Take(1, false) .Select <EntityHistoryQueryParams>(q => { q.EntityId.Equals(entity.Id); q.EntityReplyId.Equals(reply?.Id ?? 0); }) .OrderBy("Id", OrderBy.Desc) .ToList(); // No history point, return success if (histories == null) { return(await ResetEditDetails(entity, reply, user)); } // No history point, return success if (histories.Data == null) { return(await ResetEditDetails(entity, reply, user)); } // No history point, return success if (histories.Data.Count == 0) { return(await ResetEditDetails(entity, reply, user)); } var history = histories.Data[0]; // No history available reset edit details if (history == null) { return(await ResetEditDetails(entity, reply, user)); } // Update edit details based on latest history point var result = new CommandResultBase(); if (reply != null) { reply.ModifiedUserId = user.Id; reply.ModifiedDate = DateTimeOffset.UtcNow; reply.EditedUserId = history.CreatedUserId; reply.EditedDate = history.CreatedDate; // Update reply to history point var updateResult = await _entityReplyStore.UpdateAsync(reply); if (updateResult != null) { return(result.Success()); } } else { entity.ModifiedUserId = user.Id; entity.ModifiedDate = DateTimeOffset.UtcNow; entity.EditedUserId = history.CreatedUserId; entity.EditedDate = history.CreatedDate; // Update entity to history point var updateResult = await _entityStore.UpdateAsync(entity); if (updateResult != null) { return(result.Success()); } } return(result.Success()); }
async Task <ICommandResultBase> InstallEntityInternalAsync(SampleDataDescriptor descriptor, IList <User> users, int sortOrder = 0) { // Validate if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } if (string.IsNullOrEmpty(descriptor.ModuleId)) { throw new ArgumentNullException(nameof(descriptor.ModuleId)); } // Our result var result = new CommandResultBase(); // Ensure the feature is enabled var feature = await _featureFacade.GetFeatureByIdAsync(descriptor.ModuleId); if (feature == null) { return(result.Failed($"The feature {descriptor.ModuleId} is not enabled!")); } // Get a random user for the post var randomUser = users[_random.Next(0, users.Count)]; // Capitalize the first character of our entity type var entityTypeCapitalized = char.ToUpper(descriptor.EntityType[0]).ToString() + descriptor.EntityType.Substring(1); // Create the post var entity = new Entity() { Title = $"Example {entityTypeCapitalized} {_random.Next(0, 2000).ToString()}", Message = GetEntityText(descriptor), FeatureId = feature?.Id ?? 0, SortOrder = sortOrder + 1, CreatedUserId = randomUser?.Id ?? 0, CreatedDate = DateTimeOffset.UtcNow }; // Create entity var entityResult = await _entityManager.CreateAsync(entity); if (entityResult.Succeeded) { var lastReplyId = string.Empty; var lastReplyUserName = string.Empty; var lastReplyMessage = string.Empty; // Create entity replies for (var i = 0; i < descriptor.EntityRepliesToCreate; i++) { randomUser = users[_random.Next(0, users.Count - 1)]; var message = GetReplyText(descriptor); message = message.Replace("{replyUserName}", randomUser?.UserName ?? ""); message = message.Replace("{lastReplyId}", lastReplyId ?? ""); message = message.Replace("{lastReplyMessage}", lastReplyMessage ?? ""); message = message.Replace("{lastReplyQuotedMessage}", lastReplyMessage.Replace(System.Environment.NewLine, System.Environment.NewLine + "> ") ?? ""); message = message.Replace("{lastReplyUserName}", lastReplyUserName ?? ""); message = message.Replace("{entityId}", entityResult.Response.Id.ToString() ?? ""); message = message.Replace("{entityTitle}", entityResult.Response.Title ?? ""); message = message.Replace("{entityUserName}", entityResult.Response.CreatedBy.UserName); message = message.Replace("{mentionSample}", BuildMentionSampleMarkUp()); message = message.Replace("{lastReplyUrl}", _contextFacade.GetRouteUrl(new RouteValueDictionary() { ["area"] = descriptor.ModuleId, ["controller"] = "Home", ["action"] = "Reply", ["opts.id"] = entityResult.Response.Id.ToString() ?? "", ["opts.alias"] = entityResult.Response.Alias.ToString() ?? "", ["opts.replyId"] = lastReplyId ?? "" })); // Create reply var reply = new EntityReply() { EntityId = entityResult.Response.Id, Message = message, CreatedUserId = randomUser?.Id ?? 0, CreatedDate = DateTimeOffset.UtcNow }; // Add reply var replyResult = await _entityReplyManager.CreateAsync(reply); if (!replyResult.Succeeded) { return(result.Failed()); } lastReplyId = replyResult.Response.Id.ToString(); lastReplyMessage = replyResult.Response.Message; lastReplyUserName = replyResult.Response.CreatedBy.UserName; } } else { return(result.Failed(result.Errors.ToArray())); } return(result.Success()); }
// ---------- async Task <ICommandResultBase> InstallInternalAsync(IShellFeature feature) { // Validate if (feature == null) { throw new ArgumentNullException(nameof(feature)); } if (string.IsNullOrEmpty(feature.ModuleId)) { throw new ArgumentNullException(nameof(feature.ModuleId)); } var user = await _contextFacade.GetAuthenticatedUserAsync(); // Get all feature tags var categories = await _categoryStore.QueryAsync() .Select <CategoryQueryParams>(Q => { Q.FeatureId.Equals(feature.Id); }) .ToList(); // Associate every category with at least 1 entity var output = new CommandResultBase(); if (categories != null) { var entityFeature = await GetEntityFeatureAsync(feature); if (entityFeature == null) { return(output.Failed($"A feature named {feature.ModuleId.Replace(".Categories", "")} is not enabled!")); } // Get entities for feature var entities = await _entityStore.QueryAsync() .Select <EntityQueryParams>(q => { q.FeatureId.Equals(entityFeature.Id); q.CategoryId.Equals(0); }) .ToList(); // Keeps track of entities already added to categories var alreadyAdded = new Dictionary <int, Entity>(); // Interate categories building random entities // not already added to a category and adding // those random entities to the current category foreach (var category in categories?.Data) { // Get random entities var randomEntities = GetRandomEntities(entities?.Data, alreadyAdded); // Ensure we have some random entities, they may have already al been added if (randomEntities == null) { return(output.Success()); } // Add random entities to category foreach (var randomEntity in randomEntities) { // Get the full entity var entity = await _entityStore.GetByIdAsync(randomEntity.Id); // Update entity.CategoryId = category.Id; entity.ModifiedUserId = user?.Id ?? 0; entity.ModifiedDate = DateTime.UtcNow; // Persist var entityResult = await _entityManager.UpdateAsync(entity); if (entityResult.Succeeded) { // Add entity / category relationship var result = await _entityCategoryManager.CreateAsync(new EntityCategory() { EntityId = entityResult.Response.Id, CategoryId = category.Id, CreatedUserId = user?.Id ?? 0, CreatedDate = DateTime.UtcNow }); if (!result.Succeeded) { return(output.Failed(result.Errors.ToArray())); } } else { return(output.Failed(entityResult.Errors.ToArray())); } } } } return(output.Success()); }
public async Task <ICommandResultBase> UpdateAsync(string moduleId) { var output = new CommandResultBase(); // Get installed feature var feature = await _featureFacade.GetFeatureByIdAsync(moduleId); // Ensure we found the feature if (feature == null) { return(output.Failed( $"A feature named '{moduleId}' could not be found within the ShellFeatures table.")); } // Get available module var module = await _shellDescriptorManager.GetFeatureAsync(moduleId); // Ensure we found the module if (module == null) { return(output.Failed($"A module named '{moduleId}' could not be found on the file system. Please ensure the '{moduleId}' module exists within the root/modules folder.")); } // ------------------------------------------------------------------ // 1. Check to ensure we have a newer version of the module // ------------------------------------------------------------------ var from = feature.Version.ToVersion(); var to = module.Descriptor.Version.ToVersion(); if (from == null) { return(output.Failed( $"Could not convert version for feature {feature.ModuleId} of {feature.Version} to a valid version. Please check the version within the shell features database table.")); } if (to == null) { return(output.Failed( $"Could not convert version for module {module.Descriptor.Id} of {module.Descriptor.Version} to a valid version. Please check the version within the modules manifest file.")); } // No newer version simply return if (from >= to) { return(output.Success()); } // ------------------------------------------------------------------ // 2. Check to ensure the module we are updating is compatible // with the current version of Plato we are running // ------------------------------------------------------------------ var modulePlatoVersion = module.Descriptor.PlatoVersion.ToVersion(); // Does the module have a Plato version defined? if (modulePlatoVersion != null) { // Get current Plato version var currentPlatoVersion = _platoOptions.Value.Version.ToVersion(); if (currentPlatoVersion != null) { // Does the module require a newer version of Plato? if (modulePlatoVersion > currentPlatoVersion) { return(output.Failed( $"{moduleId} {module.Descriptor.Version} requires Plato {modulePlatoVersion.ToString()} whilst you are running Plato {currentPlatoVersion.ToString()}. Please upgrade to Plato {modulePlatoVersion.ToString()} and try updating {moduleId} again.")); } } } // ------------------------------------------------------------------ // 3. Invoke FeatureEventHandlers & database migrations // ------------------------------------------------------------------ var results = await InvokeFeatureEventHandlersAsync( feature, async context => { // The result to return var result = new CommandResultBase(); // Perform migrations from current installed feature version // to latest available version defined via the modules IMigrationProvider // All versions between from and to var versions = from.GetVersionsBetween(to); // Compile versions to search var versionsToSearch = versions != null ? versions?.ToList().Select(v => v.ToString()) : new List <string>(); // Build migrations for feature & versions var migrations = _migrationBuilder.BuildMigrations(moduleId, versionsToSearch.ToArray()); // Apply migrations var migrationResults = await migrations.ApplyMigrationsAsync(); // We may not have migrations if (migrationResults != null) { // Did any errors occur whilst applying the migration? if (migrationResults.Errors.Any()) { var errors = new List <CommandError>(); foreach (var error in migrationResults.Errors) { errors.Add(new CommandError(error.Message)); } return(result.Failed(errors.ToArray())); } } // If we reach this point everything went OK, Migrations applied // successfully and no errors occurred within the features update handlers. // Finally update the features version within the ShellFeatures table to reflect // the new version of the module we've just updated to, also update // shell descriptor to reflect version changes within dictionary store var updateResult = await UpdateShellFeatureVersionAsync(feature, module.Descriptor.Version); if (updateResult.Errors.Any()) { return(result.Failed(updateResult.Errors.ToArray())); } // Return success return(result.Success()); }); // Did any errors occur? var handlerErrors = results .Where(c => c.Value.Errors.Any()) .SelectMany(h => h.Value.Errors) .ToList(); if (handlerErrors.Count > 0) { var errors = new List <CommandError>(); foreach (var error in handlerErrors) { errors.Add(new CommandError(error.Value)); } return(output.Failed(errors.ToArray())); } // No errors, recycle shell context to apply updates RecycleShell(); return(output.Success()); }
public async Task <ICommandResultBase> ValidateCompanyNameAsync(string companyName) { var result = new CommandResultBase(); var error = "The company name is invalid or is already in use. Please try a different company name."; // We always need a company name if (string.IsNullOrEmpty(companyName)) { return(result.Failed("A company name is required!")); } // Validate characters var valid = true; foreach (var c in companyName) { // Not a letter, digit or space if (!c.IsLetterDigitOrSpace()) { valid = false; break; } } // Only allow letters digits and spaces if (valid == false) { return(result.Failed("The company name cannot contain special characters. Only letters, numbers and spaces are allowed. Please try a different company name.")); } // Create the company name alias to compare with our blacklist var companyNameAlias = _aliasCrator.Create(companyName); // We need an alias if (string.IsNullOrEmpty(companyNameAlias)) { return(result.Failed(error)); } // Does the company name appear in the blacklist? var blackList = GetBlackListedCompanyNames(); foreach (var item in blackList) { if (item.Equals(companyName, StringComparison.OrdinalIgnoreCase)) { return(result.Failed(error)); } if (item.Equals(companyNameAlias, StringComparison.OrdinalIgnoreCase)) { return(result.Failed(error)); } } // --------------- // Does the company name already exist? // We need company names to be unique as the // tenant RequestedPrefixUrl uses CompanyNameAlias // --------------- var signUps = await _signUpStore.QueryAsync() .Select <SignUpQueryParams>(q => { q.CompanyName.Or().Equals(companyName); q.CompanyNameAlias.Or().Equals(companyName); }) .ToList(); if (signUps?.Data != null) { return(result.Failed(error)); } return(result.Success()); }