using FluentValidation; using FluentValidation.Results; using Raven.Client; using Raven.Client.Documents.Linq; using Raven.Client.Documents.Session; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using zero.Core.Api; using zero.Core.Attributes; using zero.Core.Database; using zero.Core.Entities; using zero.Core.Extensions; using zero.Core.Utils; namespace zero.Core.Backoffice { public abstract class BackofficeService : IBackofficeService where T : IZeroEntity { /// public string Database { get; set; } /// /// Zero context /// protected readonly IZeroContext Context; /// /// Document store /// protected readonly IZeroStore Store; /// /// The validator /// protected readonly IValidator Validator; public BackofficeService(IZeroContext context, IValidator validator = null) { Context = context; Store = context.Store; Validator = validator; } /// /// Create an an async document session /// protected virtual IAsyncDocumentSession Session() { return Database.IsNullOrWhiteSpace() ? Store.OpenAsyncSession() : Store.OpenAsyncSession(Database); } /// public virtual async Task GetById(string id) { if (id.IsNullOrWhiteSpace()) { return default; } using IAsyncDocumentSession session = Session(); return await session.LoadAsync(id); } /// public virtual async Task> GetByIds(params string[] ids) { using IAsyncDocumentSession session = Session(); Dictionary models = await session.LoadAsync(ids); Dictionary result = new Dictionary(); foreach (string id in ids) { models.TryGetValue(id, out T model); result.Add(id, model); } return result; } /// public virtual async Task> GetByQuery(ListQuery query) { using IAsyncDocumentSession session = Session(); return await session.Query().ToQueriedListAsync(query); } /// public virtual IAsyncEnumerable Stream() => Stream(null); /// public virtual async IAsyncEnumerable Stream(Func, IRavenQueryable> expression) { using IAsyncDocumentSession session = Session(); IRavenQueryable query = session.Query(); if (expression != null) { query = expression(query); } var stream = await session.Advanced.StreamAsync(query); while (await stream.MoveNextAsync()) { yield return stream.Current.Document; } } /// public async Task> Save(T model) { bool isCreate = false; // run validator if (Validator != null) { ValidationResult validation = await Validator.ValidateAsync(model); if (!validation.IsValid) { return EntityResult.Fail(validation); } } // find all Raven Ids List> ravenIds = ObjectTraverser.FindAttribute(model); // set unset Raven Ids foreach (ObjectTraverser.Result item in ravenIds) { string id = item.Property.GetValue(item.Parent, null) as string; if (String.IsNullOrWhiteSpace(id)) { item.Property.SetValue(item.Parent, item.Item.Length.HasValue ? IdGenerator.Create(item.Item.Length.Value) : IdGenerator.Create()); } } // get current user string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId); // set default properties if (model.Id.IsNullOrEmpty()) { isCreate = true; model.CreatedDate = DateTimeOffset.Now; model.CreatedById = userId; if (model is ILanguageAwareEntity) { (model as ILanguageAwareEntity).LanguageId = "languages.1-A"; // TODO correct language id } } // update name alias and last modified model.Alias = Safenames.Alias(model.Name); model.LastModifiedById = userId; model.LastModifiedDate = DateTimeOffset.Now; model.CreatedById ??= userId; model.Hash ??= IdGenerator.Create(); using IAsyncDocumentSession session = Session(); session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false); await session.StoreAsync(model); //await Backoffice.Messages.Publish(new EntitySavedMessage() //{ // Id = model.Id, // IsCreate = isCreate, // IsDelete = false, // Model = model, // Session = session //}); await session.SaveChangesAsync(); return EntityResult.Success(model); } /// public virtual async Task> Delete(T model) => await DeleteById(model?.Id); /// public virtual async Task> DeleteById(string id) { using IAsyncDocumentSession session = Session(); session.Advanced.WaitForIndexesAfterSaveChanges(throwOnTimeout: false); T entity = await session.LoadAsync(id); if (entity == null) { return EntityResult.Fail("@errors.ondelete.idnotfound"); } session.Delete(entity); await session.SaveChangesAsync(); return EntityResult.Success(); } /// public virtual async Task Delete(params T[] models) => await DeleteByIds(models.Select(x => x.Id).ToArray()); /// public virtual async Task DeleteByIds(params string[] ids) { int successCount = 0; foreach (string id in ids) { EntityResult result = await DeleteById(id); successCount += result.IsSuccess ? 1 : 0; } return successCount; } /// public virtual async Task> Purge(string querySuffix = null, Parameters parameters = null) { await Store.PurgeAsync(Database, querySuffix, parameters); return EntityResult.Success(); } } public interface IBackofficeService where T : IZeroEntity { /// /// The database to operate on. /// Is null by default, which uses the database from the resolved application. /// string Database { get; set; } /// /// Get an entity by Id /// Task GetById(string id); /// /// Get entities by ids /// Task> GetByIds(params string[] ids); /// /// Get entities by query /// Task> GetByQuery(ListQuery query); /// /// Stream the collection /// IAsyncEnumerable Stream(); /// /// Stream the collection /// IAsyncEnumerable Stream(Func, IRavenQueryable> expression); /// /// Updates or creates an entity with an optional validator /// Task> Save(T model); /// /// Deletes an entity /// Task> Delete(T model); /// /// Deletes entities /// Task Delete(params T[] models); /// /// Deletes an entity by Id /// Task> DeleteById(string id); /// /// Deletes entities by Id /// Task DeleteByIds(params string[] ids); /// /// Delete a whole collection (with an optional query suffix, i.e. a where statement) /// Task> Purge(string querySuffix = null, Parameters parameters = null); } }