using FluentValidation.Results;
using Rv = Raven.Client;
namespace Mixtape.Raven;
public partial class RavenOperations : IRavenOperations
{
///
public virtual Task> Create(T model, Func> validate = null, Action onAfterStore = null) where T : MixtapeIdEntity, new() => Save(model, validate, onAfterStore);
///
public virtual Task> Update(T model, Func> validate = null, Action onAfterStore = null) where T : MixtapeIdEntity, new() => Save(model, validate, onAfterStore, true);
///
protected virtual async Task> Save(T model, Func> validate = null, Action onAfterStore = null, bool update = false) where T : MixtapeIdEntity, new()
{
if (model == null)
{
return Result.Fail("@errors.onsave.empty");
}
T previousModel = null;
// check if the Id for a model already exists
if (!model.Id.IsNullOrEmpty())
{
using (IAsyncDocumentSession session = Store.Session(options: new Rv.Documents.Session.SessionOptions() { NoCaching = true }))
{
previousModel = await session.LoadAsync(model.Id);
}
if (update && previousModel == null)
{
return Result.Fail("@errors.onsave.noidmatch");
}
else if (!update && previousModel != null)
{
return Result.Fail("@errors.oncreate.idmismatch");
}
}
// validate flavor
if (model is ISupportsFlavors flavorModel && !flavorModel.Flavor.IsNullOrEmpty())
{
if (!Flavors.Exists(flavorModel.Flavor))
{
return Result.Fail("@errors.onsave.flavornotfound");
}
}
// prepare model
PrepareForSave(model);
// run validator
if (validate != null)
{
ValidationResult validation = await validate(model);
if (!validation.IsValid)
{
return Result.Fail(validation);
}
}
// create ID before-hand so interceptors can use it
if (!update)
{
model.Id = await GenerateId(model);
}
// run interceptor
InterceptorInstruction instruction = update ? Interceptors.ForUpdate(model, previousModel) : Interceptors.ForCreate(model);
if (InterceptorBlocker == null && !await instruction.Start(this))
{
return instruction.Result;
}
// store our model
await Session.StoreAsync(model);
onAfterStore?.Invoke(Session);
await Session.SaveChangesAsync();
if (InterceptorBlocker == null)
{
await instruction.Complete();
}
await Session.SaveChangesAsync();
return Result.Success(model);
}
///
public async Task>> Sort(string[] sortedIds) where T : MixtapeIdEntity, ISupportsSorting, new()
{
Dictionary items = await Load(sortedIds);
uint index = 10;
// contains multiple parents, therefore fail
if (typeof(ISupportsTrees).IsAssignableFrom(typeof(T)) && items.Select(x => (x.Value as ISupportsTrees)?.ParentId).Distinct().Count() > 1)
{
return Result>.Fail("@errors.treeentity.sortingmultipleparents");
}
foreach (var item in items)
{
item.Value.Sort = index;
index += 10;
await Update(item.Value);
}
return Result>.Success(items.Select(x => x.Value).OrderByDescending(x => x.Sort));
}
///
public virtual async Task>> CreateAll(IEnumerable models) where T : MixtapeIdEntity, new()
{
using var bulkInsert = Store.Raven.BulkInsert();
foreach (T model in models)
{
// prepare model
PrepareForSave(model);
// create ID before-hand so interceptors can use it
model.Id = await GenerateId(model);
// run interceptor
InterceptorInstruction instruction = Interceptors.ForCreate(model);
if (InterceptorBlocker == null && !await instruction.Start(this))
{
await bulkInsert.AbortAsync();
return instruction.Result.ConvertTo>(null);
}
// store our model
await bulkInsert.StoreAsync(model);
if (InterceptorBlocker == null)
{
await instruction.Complete();
}
}
return Result>.Success(models);
}
}