using FluentValidation.Results; using Microsoft.Extensions.DependencyInjection; using Raven.Client; using Raven.Client.Documents.Indexes; using Raven.Client.Documents.Linq; using System.Linq.Expressions; using System.Security.Claims; namespace Finch.Raven; public partial class RavenOperations : IRavenOperations { /// public IAsyncDocumentSession Session => Store.Session(); protected record OperationOptions(bool IncludeInactive); protected IFinchContext Context { get; private set; } protected IInterceptors Interceptors { get; private set; } protected FlavorOptions Flavors { get; private set; } protected IServiceProvider Services { get; private set; } protected IFinchStore Store { get; private set; } protected StoreInterceptorBlocker InterceptorBlocker { get; private set; } public RavenOperations(StoreContext context) { Store = context.Store; Context = context.Context; Interceptors = context.Interceptors; Services = context.Services; Flavors = context.Options.For(); } /// public async Task GenerateId(T model) where T : FinchIdEntity { IAsyncDocumentSession session = Session; return await session.Advanced.DocumentStore.Conventions.GenerateDocumentIdAsync(session.Advanced.DocumentStore.Database, model); } /// public T AutoSetIds(T model) { return IdGenerator.Autofill(model); } /// public T PrepareForSave(T model) where T : FinchIdEntity { // set IDs AutoSetIds(model); if (model is FinchEntity finchModel) { // get current user string userId = null; //string userId = Context.BackofficeUser.FindFirstValue(Constants.Auth.Claims.UserId).Or(Constants.Auth.SystemUser); // set default properties if (finchModel.CreatedDate == default) { finchModel.CreatedDate = DateTimeOffset.Now; } if (finchModel.CreatedById.IsNullOrEmpty()) { finchModel.CreatedById = userId; } // update name alias and last modified finchModel.Alias = Safenames.Alias(finchModel.Name); finchModel.LastModifiedById = userId; finchModel.LastModifiedDate = DateTimeOffset.Now; finchModel.CreatedById ??= userId; finchModel.Hash ??= IdGenerator.Create(); } return model; } /// public async Task Validate(T model) where T : FinchIdEntity, new() { IFinchMergedValidator validator = Services.GetService>(); if (validator == null) { return new(); } return await validator.ValidateAsync(model); } /// public StoreInterceptorBlocker WithoutInterceptors() { return InterceptorBlocker ?? (InterceptorBlocker = new(() => InterceptorBlocker = null)); } /// public virtual T WhenActive(T model) where T : FinchIdEntity, new() { return model; // TODO should we really use this? I tried to get data in a custom backend but couldn't because of this //return model != null && (model is not FinchEntity || (model as FinchEntity).IsActive) && (model is not ISupportsSoftDelete || !(model as ISupportsSoftDelete).IsDeleted) ? model : default; } } public class StoreInterceptorBlocker : IDisposable { Action _onRelease; internal StoreInterceptorBlocker(Action onRelease) { _onRelease = onRelease; } public void Dispose() { _onRelease(); } } public interface IRavenOperations { /// /// Access to the current session /// IAsyncDocumentSession Session { get; } /// /// Get new instance of an entity (with an optional flavor) /// Task Empty(string flavorAlias = null) where T : FinchIdEntity, ISupportsFlavors, new(); /// /// Get new instance of an entity with a specific flavor /// /// Optional alias. If left out the default flavor is used (if configured) Task Empty(string flavorAlias = null) where T : FinchIdEntity, ISupportsFlavors, new() where TFlavor : T, new(); /// /// Generate model Id by using configured document store conventions /// Task GenerateId(T model) where T : FinchIdEntity; /// /// Generate values for all properties (incl. nested) which contain the [GenerateId] attribute /// T AutoSetIds(T model); /// /// Automatically fill base properties of a FinchEntity /// T PrepareForSave(T model) where T : FinchIdEntity; /// /// Check if any items exist in this collection (with optional query) /// Task Any(Func, IQueryable> querySelector = default) where T : FinchIdEntity, new(); /// /// Get an entity by Id /// Task Load(string id, string changeVector = null) where T : FinchIdEntity, new(); /// /// Get entities by ids /// Task> Load(IEnumerable ids) where T : FinchIdEntity, new(); /// /// Get entities by ids /// Task> LoadAsList(IEnumerable ids) where T : FinchIdEntity, new(); /// /// Get entities by query /// Task> Load(int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : FinchIdEntity, new(); /// /// Get entities by query (by using the specified index) /// Task> Load(int pageNumber, int pageSize, Func, IQueryable> expression = default) where T : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new(); /// /// Get entities by query /// Task> Load(Func, IQueryable> expression) where T : FinchIdEntity, new(); /// /// Get entities by query (by using the specified index) /// Task> Load(Func, IQueryable> expression) where T : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new(); /// /// Get entities by query /// Task> Load(Expression> predicate) where T : FinchIdEntity, new(); /// /// Get entities by query (by using the specified index) /// Task> Load(Expression> predicate) where T : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new(); /// /// Get entities by query (by using the specified index) and project into a result /// Task> Load(int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : FinchIdEntity, new() where TProjection : FinchIdEntity, new() where TIndex : AbstractCommonApiForIndexes, new(); /// /// Get all entities from this collection. /// Warning: Don't use this method for large collections. Stream the results instead. /// Task> LoadAll() where T : FinchIdEntity, new(); /// /// Stream the collection /// IAsyncEnumerable Stream(Func, IQueryable> expression) where T : FinchIdEntity, new(); /// /// Get the change vector for a model /// string GetChangeToken(T model) where T : FinchIdEntity, new(); /// /// Validates an entity /// Task Validate(T model) where T : FinchIdEntity, new(); /// /// Do not run interceptors for create/update/delete operations while this disposable is active /// StoreInterceptorBlocker WithoutInterceptors(); /// /// Do only return the model when it is set to active or inactive entities are included with IncludeInactive() /// T WhenActive(T model) where T : FinchIdEntity, new(); /// /// Creates an entity with an optional validator /// Task> Create(T model, Func> validate = null, Action onAfterStore = null) where T : FinchIdEntity, new(); /// /// Updates an entity with an optional validator /// Task> Update(T model, Func> validate = null, Action onAfterStore = null) where T : FinchIdEntity, new(); /// Task>> Sort(string[] sortedIds) where T : FinchIdEntity, ISupportsSorting, new(); /// /// Batch create entities /// Task>> CreateAll(IEnumerable models) where T : FinchIdEntity, new(); /// /// Deletes an entity /// Task> Delete(T model) where T : FinchIdEntity, new(); /// /// Deletes an entity /// Task> Delete(string id) where T : FinchIdEntity, new(); /// /// Deletes the whole collection /// Task Purge(string querySuffix = null, Parameters parameters = null) where T : FinchIdEntity, new(); /// /// Loads all children for an entity /// Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : FinchIdEntity, ISupportsTrees, new(); /// /// Get descendants by query (by using the specified index) /// Task> LoadChildren(string parentId, int pageNumber, int pageSize, Func, IQueryable> querySelector = default) where T : FinchIdEntity, ISupportsTrees, new() where TIndex : AbstractCommonApiForIndexes, new(); /// /// Get tree hierarchy for an entity /// Task GetHierarchy(string id) where T : FinchIdEntity, ISupportsTrees, new() where TIndex : FinchTreeHierarchyIndex, new(); /// /// Move an entity to a new parent /// Task> Move(string id, string newParentId, Func> isParentAllowed = null) where T : FinchIdEntity, ISupportsTrees, new(); /// /// Copies an entity to a new location /// Task> Copy(string id, string newParentId, Func> isParentAllowed = null) where T : FinchIdEntity, ISupportsTrees, new(); /// /// Copies an entity with descendants to a new location /// Task> CopyWithDescendants(string id, string newParentId, Func> isParentAllowed = null) where T : FinchIdEntity, ISupportsTrees, new(); /// /// Deletes an entity with all descendents /// Task> DeleteWithDescendants(T model) where T : FinchIdEntity, ISupportsTrees, new(); }