using FluentValidation.Results; using Microsoft.AspNetCore.Identity; using Newtonsoft.Json; using Raven.Client.Documents; using Raven.Client.Documents.Conventions; using Raven.Client.Documents.Session; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using zero.Core.Entities; using zero.Core.Entities.Setup; using zero.Core.Extensions; using zero.Core.Identity; using zero.Core.Options; using zero.Core.Validation; namespace zero.Core.Api { public class SetupApi : ISetupApi { protected IZeroOptions Options { get; private set; } protected UserManager UserManager { get; private set; } public SetupApi(IZeroOptions options, UserManager userManager) { Options = options; UserManager = userManager; } public async Task> Install(SetupModel model) { ValidationResult validation = await new SetupModelValidator().ValidateAsync(model); if (!validation.IsValid) { return EntityResult.Fail(validation); } DocumentStore raven = null; try { // test read & write permissions on folders // TODO // create temporary instance of database raven = new DocumentStore() { Urls = model.Database.Url.Split(','), Database = model.Database.Name }; raven.Setup(Options).Initialize(); // create application Application app = new Application() { CreatedDate = DateTimeOffset.Now, IsActive = true, Name = model.AppName, Alias = Safenames.Alias(model.AppName) }; // create user BackofficeUser user = new BackofficeUser() { IsSuper = true, CreatedDate = DateTimeOffset.Now, Email = model.User.Email, Name = model.User.Name, IsActive = true, LanguageId = Options.DefaultLanguage, Alias = Safenames.Alias(model.User.Name), IsEmailConfirmed = true }; // create default language Language language = new Language() // TODO get default language selection from setup UI { Name = "English", Alias = Safenames.Alias("English"), CreatedDate = DateTimeOffset.Now, Code = "en-US", IsActive = true, IsDefault = true }; // TODO UserManager uses the DI resolved IDocumentStore instance which should not be available at this point?? IdentityResult result = await UserManager.CreateAsync(user, model.User.Password); // user creation failed if (!result.Succeeded) { EntityResult entityResult = EntityResult.Fail(); foreach (IdentityError error in result.Errors) { entityResult.AddError(error.Code, error.Description); } return entityResult; } // save entities using (IAsyncDocumentSession session = raven.OpenAsyncSession()) { await session.StoreAsync(app); // set app-id for user and store it user.AppId = session.Advanced.GetDocumentId(app); await session.StoreAsync(user); // save default user roles IList roles = GetRoles(model); foreach (BackofficeUserRole role in roles) { await session.StoreAsync(role); } // add admin role to super user user.RoleIds.Add(roles.First(role => role.Name == "Standard").Alias); user.RoleIds.Add(roles.First(role => role.Name == "Administrator").Alias); // create language await session.StoreAsync(language); // set countries using (Raven.Client.Documents.BulkInsert.BulkInsertOperation bulkInsert = raven.BulkInsert()) { foreach (Country country in GetCountries(model, language)) { await bulkInsert.StoreAsync(country); } } // update settings file. if this fails the changes won't be stored UpdateSettingsFile(model); await session.SaveChangesAsync(); } } catch (Exception) { // TODO revert throw; } finally { raven?.Dispose(); } return EntityResult.Success(model); } /// /// Updates the settings file with the new data (database connection and version) /// void UpdateSettingsFile(SetupModel model) { // TODO should we write this into appSettings.json now? // or let the user do it in the code editor? //var filePath = Path.Combine(model.ContentRootPath, "zeroSettings.json"); //string json = File.ReadAllText(filePath); //ZeroConfiguration config = JsonConvert.DeserializeObject(json); //config.Raven = new RavenOptions() //{ // Database = model.Database.Name, // Url = model.Database.Url //}; //config.ZeroVersion = System.Reflection.Assembly.GetEntryAssembly().GetName().Version.ToString(); //json = JsonConvert.SerializeObject(config, Formatting.Indented); //File.WriteAllText(filePath, json); } /// /// Get countries for supported backoffice languages /// IList GetCountries(SetupModel model, Language defaultLanguage) { List countries = new List(); //string[] isoCodes = Config.SupportedLanguages; string[] isoCodes = new string[1] { "en-US" }; foreach (string languageISO in isoCodes) { // country list from: https://github.com/umpirsky/country-list/tree/master/data var filePath = Path.Combine(model.ContentRootPath, "Resources", "Countries", "countries." + languageISO.ToLowerInvariant() + ".json"); string json = File.ReadAllText(filePath); countries.AddRange(JsonConvert.DeserializeObject>(json).Select(country => new Country() { CreatedDate = DateTimeOffset.Now, IsActive = true, //AppId = Constants.Database.SharedAppId, // TODO appx fix Alias = Safenames.Alias(country.Value), //LanguageId = defaultLanguage.Id, Code = country.Key.ToLowerInvariant(), Name = country.Value }).ToList()); } return countries; } /// /// Create default roles /// IList GetRoles(SetupModel model) { string type = Constants.Auth.Claims.Permission; BackofficeUserRole adminRole = new BackofficeUserRole() { Name = "Administrator", Alias = Safenames.Alias("Administrator"), Sort = 0, //AppId = Constants.Database.SharedAppId, // TODO appx fix Icon = "fth-award", CreatedDate = DateTimeOffset.Now, IsActive = true, Claims = new List() { //new UserClaim(type, Permissions.Applications, PermissionsValue.Write), new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True), new UserClaim(type, Permissions.Settings.Applications, PermissionsValue.None), new UserClaim(type, Permissions.Settings.Countries, PermissionsValue.Update), new UserClaim(type, Permissions.Settings.Logging, PermissionsValue.Update), new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.Update), new UserClaim(type, Permissions.Settings.Updates, PermissionsValue.Update), new UserClaim(type, Permissions.Settings.Users, PermissionsValue.Update), }, }; BackofficeUserRole editorRole = new BackofficeUserRole() { Name = "Editor", Alias = Safenames.Alias("Editor"), Sort = 1, Icon = "fth-feather", CreatedDate = DateTimeOffset.Now, IsActive = true, Claims = new List() { new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Spaces, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Pages, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Media, PermissionsValue.True), new UserClaim(type, Permissions.Sections.Settings, PermissionsValue.True), new UserClaim(type, Permissions.Settings.Translations, PermissionsValue.True) } }; BackofficeUserRole defaultRole = new BackofficeUserRole() { Name = "Standard", Alias = Safenames.Alias("Standard"), Sort = 2, Icon = "fth-users", CreatedDate = DateTimeOffset.Now, IsActive = true, Claims = new List() { new UserClaim(type, Permissions.Sections.Dashboard, PermissionsValue.True) } }; return new List() { adminRole, editorRole, defaultRole }; } } public interface ISetupApi { Task> Install(SetupModel model); } }