using Ardalis.Specification; using Ardalis.Specification.EntityFrameworkCore; using DD.Persistence.Database.Entity; using DD.Persistence.Database.Postgres.Helpers; using DD.Persistence.Database.Specifications.ChangeLog; using DD.Persistence.Database.Specifications.Common.DiscriminatorItem; using DD.Persistence.Models; using DD.Persistence.Models.Common; using DD.Persistence.Models.Requests; using DD.Persistence.Repositories; using Mapster; using Microsoft.EntityFrameworkCore; using UuidExtensions; namespace DD.Persistence.Database.Repositories; public class ChangeLogRepository : IChangeLogRepository { private readonly DbContext db; private readonly ObsoleteIsNullSpec obsoleteIsNullSpecification; public ChangeLogRepository(DbContext db) { this.db = db; this.obsoleteIsNullSpecification = new ObsoleteIsNullSpec(); } public async Task AddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) { var entities = new List(); foreach (var dto in dtos) { var entity = CreateEntityFromDto(idAuthor, idDiscriminator, dto); entities.Add(entity); } db.Set().AddRange(entities); var result = await db.SaveChangesAsync(token); return result; } public async Task MarkAsDeleted(Guid idEditor, IEnumerable ids, CancellationToken token) { var containsIdsSpecification = new IdContainsSpec(ids); var query = db.Set() .WithSpecification(containsIdsSpecification) .WithSpecification(obsoleteIsNullSpecification); if (query.Count() != ids.Count()) { throw new ArgumentException("Count of active items not equal count of ids", nameof(ids)); } var entities = await query.ToArrayAsync(token); var result = await MarkAsObsolete(idEditor, entities, token); return result; } public async Task MarkAsDeleted(Guid idEditor, Guid idDiscriminator, CancellationToken token) { var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); var query = db.Set() .WithSpecification(specDiscriminatorEqual) .WithSpecification(obsoleteIsNullSpecification); var entities = await query.ToArrayAsync(token); var result = await MarkAsObsolete(idEditor, entities, token); return result; } private async Task MarkAsObsolete(Guid idEditor, IEnumerable entities, CancellationToken token) { var updateTime = DateTimeOffset.UtcNow; foreach (var entity in entities) { entity.Obsolete = updateTime; entity.IdEditor = idEditor; } return await db.SaveChangesAsync(token); } public async Task ClearAndAddRange(Guid idAuthor, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) { var result = 0; using var transaction = await db.Database.BeginTransactionAsync(token); result += await MarkAsDeleted(idAuthor, idDiscriminator, token); result += await AddRange(idAuthor, idDiscriminator, dtos, token); await transaction.CommitAsync(token); return result; } public async Task UpdateRange(Guid idEditor, IEnumerable dtos, CancellationToken token) { var dbSet = db.Set(); var updatedIds = dtos.Select(d => d.Id); var containsIdsSpecification = new IdContainsSpec(updatedIds); var updatedEntities = dbSet .WithSpecification(containsIdsSpecification) .ToDictionary(s => s.Id); var result = 0; using var transaction = await db.Database.BeginTransactionAsync(token); foreach (var dto in dtos) { var updatedEntity = updatedEntities.GetValueOrDefault(dto.Id); if (updatedEntity is null) { throw new ArgumentException($"Entity with id = {dto.Id} doesn't exist in Db", nameof(dto)); } var newEntity = CreateEntityFromDto(idEditor, updatedEntity.DiscriminatorId, dto); dbSet.Add(newEntity); updatedEntity.IdNext = newEntity.Id; updatedEntity.Obsolete = DateTimeOffset.UtcNow; updatedEntity.IdEditor = idEditor; } result = await db.SaveChangesAsync(token); await transaction.CommitAsync(token); return result; } public async Task> GetByDate( Guid idDiscriminator, DateTimeOffset momentUtc, PaginationRequest paginationRequest, CancellationToken token) { var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); var query = db.Set() .WithSpecification(specDiscriminatorEqual); query = query.Apply(momentUtc); var result = await query.ApplyPagination(paginationRequest, Convert, token); return result; } public async Task> GetChangeLogForInterval(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) { var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); var query = db.Set() .WithSpecification(specDiscriminatorEqual); var min = new DateTimeOffset(dateBegin.ToUniversalTime().Date, TimeSpan.Zero); var max = new DateTimeOffset(dateEnd.ToUniversalTime().Date, TimeSpan.Zero); var specCreatedByDateRange = new FromCreationDateRangeSpec(min, max); var specHistoryByDateRange = new FromObsoleteDateRangeSpece(min, max); var createdQuery = query.WithSpecification(specCreatedByDateRange); var editedQuery = query.WithSpecification(specHistoryByDateRange); query = createdQuery.Union(editedQuery); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(e => e.Adapt()); return dtos; } public async Task> GetDatesChange(Guid idDiscriminator, CancellationToken token) { var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); var specObsoleteNotNull = new ObsoleteNotNullSpec(); var query = db.Set() .WithSpecification(specDiscriminatorEqual); var datesCreateQuery = query .Select(e => e.Creation) .Distinct(); var datesCreate = await datesCreateQuery.ToArrayAsync(token); var datesUpdateQuery = query .WithSpecification(specObsoleteNotNull) .Select(e => e.Obsolete!.Value) .Distinct(); var datesUpdate = await datesUpdateQuery.ToArrayAsync(token); var dates = datesCreate.Concat(datesUpdate); var datesOnly = dates .Select(d => new DateOnly(d.Year, d.Month, d.Day)) .Distinct() .OrderBy(d => d); return datesOnly; } private static ChangeLog CreateEntityFromDto(Guid idAuthor, Guid idDiscriminator, ChangeLogValuesDto dto) { var entity = new ChangeLog() { Id = Uuid7.Guid(), Creation = DateTimeOffset.UtcNow, IdAuthor = idAuthor, DiscriminatorId = idDiscriminator, IdEditor = idAuthor, Value = dto.Value }; return entity; } public async Task> GetGtDate(Guid idDiscriminator, DateTimeOffset dateBegin, CancellationToken token) { var date = dateBegin.ToUniversalTime(); var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); var specCreatedOrUpdatedGeDate = new CreatedOrUpdatedGeDateSpec(date); var query = db.Set() .WithSpecification(specDiscriminatorEqual) .WithSpecification(specCreatedOrUpdatedGeDate); var entities = await query.ToArrayAsync(token); var dtos = entities.Select(Convert); return dtos; } public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) { return null; //var specDiscriminatorEqual = new DiscriminatorEqualSpec(idDiscriminator); //var evaluator = // SpecificationEvaluator.Default //var query = db.Set() // //.Where(e => e.IdDiscriminator == idDiscriminator) // .GroupBy(e => 1) // .WithSpecification(specDiscriminatorEqual) // .Select(group => new // { // Min = group.Min(e => e.Creation), // Max = group.Max(e => e.Obsolete.HasValue && e.Obsolete > e.Creation // ? e.Obsolete.Value // : e.Creation), // }); //var values = await query.FirstOrDefaultAsync(token); //if (values is null) //{ // return null; //} //return new DatesRangeDto //{ // From = values.Min, // To = values.Max, //}; } private ChangeLogValuesDto Convert(ChangeLog entity) => entity.Adapt(); }