Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/refactoring #63

Merged
merged 3 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Argon.Api/Features/Captcha/CloudflareCaptcha.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Argon.Api.Features.Captcha;

using Argon.Features;
using Extensions;
using Flurl.Http;
using Microsoft.Extensions.Options;
Expand Down
13 changes: 13 additions & 0 deletions src/Argon.Api/Features/EF/ColourConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Argon.Api.Features.EF;

using System.Drawing;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

public class ColourConverter() : ValueConverter<Color, int>(x => ToInt(x), x => ToColor(x))
{
private static Color ToColor(int val)
=> Color.FromArgb(val);

private static int ToInt(Color val)
=> val.ToArgb();
}
65 changes: 65 additions & 0 deletions src/Argon.Api/Features/EF/TimeStampAndSoftDeleteInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace Argon.Api.Features.EF;

using Contracts.Models;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore;

public class TimeStampAndSoftDeleteInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
HandleSoftDelete(eventData.Context);
SetTimestamps(eventData.Context);
return base.SavingChanges(eventData, result);
}

public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
HandleSoftDelete(eventData.Context);
SetTimestamps(eventData.Context);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}

private void SetTimestamps(DbContext? context)
{
if (context == null) return;

var entries = context.ChangeTracker
.Entries()
.Where(e => e is { Entity: ArgonEntity, State: EntityState.Added or EntityState.Modified });

foreach (var entry in entries)
{
var entity = (ArgonEntity)entry.Entity;

if (entry.State == EntityState.Added)
entity.CreatedAt = DateTime.UtcNow;

entity.UpdatedAt = DateTime.UtcNow;
}
}

private void HandleSoftDelete(DbContext? context)
{
if (context == null) return;

var entries = context.ChangeTracker
.Entries()
.Where(e => e is { Entity: ArgonEntity, State: EntityState.Deleted });

foreach (var entry in entries)
{
var entity = (ArgonEntity)entry.Entity;

entity.DeletedAt = DateTime.UtcNow;
entity.IsDeleted = true;

entry.State = EntityState.Modified;
}
}
}
106 changes: 106 additions & 0 deletions src/Argon.Api/Features/Repositories/IServerRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
namespace Argon.Api.Features.Repositories;

using System.Diagnostics;
using Contracts.Models;
using Contracts.Models.ArchetypeModel;
using Entities;
using Grains.Interfaces;
using Microsoft.EntityFrameworkCore;

public static class TemplateFeature
{
public static IServiceCollection AddEfRepositories(this WebApplicationBuilder builder)
{
builder.Services.AddScoped<IServerRepository, ServerRepository>();
return builder.Services;
}
}

public interface IServerRepository
{
ValueTask<Server> CreateAsync(Guid serverId, ServerInput data, Guid initiator);
}

public class ServerRepository(ApplicationDbContext context) : IServerRepository
{
public async ValueTask<Server> CreateAsync(Guid serverId, ServerInput data, Guid initiator)
{
var strategy = context.Database.CreateExecutionStrategy();
return await strategy.ExecuteAsync(async () =>
{
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
var server = new Server()
{
Id = serverId,
AvatarFileId = data.AvatarUrl,
CreatorId = initiator,
Description = data.Description,
Name = data.Name!
};

var e = await context.Servers.AddAsync(server);

await context.SaveChangesAsync();

var sm = new ServerMember()
{
Id = initiator,
ServerId = serverId,
UserId = initiator,
CreatorId = initiator,
};

await context.UsersToServerRelations.AddAsync(sm);

await context.SaveChangesAsync();

await CloneArchetypesAsync(e.Entity.Id, sm.Id);

await transaction.CommitAsync();

return e.Entity;
}
catch (Exception)
{
await transaction.RollbackAsync();
throw;
}
});
}


private async ValueTask CloneArchetypesAsync(Guid serverId, Guid initiator)
{
var everyone = await context.Archetypes.FindAsync(Archetype.DefaultArchetype_Everyone);
var owner = await context.Archetypes.FindAsync(Archetype.DefaultArchetype_Owner);

owner!.Id = Guid.NewGuid();
owner.CreatorId = initiator;
owner.Server = null!;
owner.ServerId = serverId;
owner.ServerMemberRoles = new List<ServerMemberArchetype>();

everyone!.Id = Guid.NewGuid();
everyone.CreatorId = initiator;
everyone.ServerId = serverId;
everyone.Server = null!;
everyone.ServerMemberRoles = new List<ServerMemberArchetype>();

await context.Archetypes.AddAsync(everyone);
await context.Archetypes.AddAsync(owner);

Debug.Assert(await context.SaveChangesAsync() == 2);

var e = new ServerMemberArchetype()
{
ArchetypeId = owner.Id,
ServerMemberId = initiator
};

await context.ServerMemberArchetypes.AddAsync(e);

Debug.Assert(await context.SaveChangesAsync() == 1);
}
}
15 changes: 15 additions & 0 deletions src/Argon.Api/Features/Utils/AsyncContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Argon.Features;

public class AsyncContainer<T>(Func<Task<T>> taskFactory)
where T : class
{
private readonly Task<T> _lazyTask = taskFactory();
private volatile T? _value;

public T Value => _value ?? throw new Exception($"Not created");

public bool IsValueCreated => _value is not null;

public async ValueTask DoCreateAsync()
=> Interlocked.Exchange(ref _value, await _lazyTask);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Argon.Api.Extensions;
namespace Argon.Features;

using System.Security.Claims;

Expand Down
54 changes: 54 additions & 0 deletions src/Argon.Api/Features/Utils/ObjDiff.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
namespace Argon.Features;

using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Reflection;
using ActualLab.Collections;
using ActualLab.Text;

public static class ObjDiff
{
private static readonly ConcurrentDictionary<Type, object> comparer_cache = new();


private static bool IsAreEqual(Type t, object t1, object t2)
{
var comparerType = typeof(EqualityComparer<>).MakeGenericType(t);

if (comparer_cache.TryGetValue(t, out var cmp))
{
var equalsMethod = comparerType.GetMethod("Equals", [t, t])!;
return (bool)equalsMethod.Invoke(cmp, new[] { t1, t2 })!;
}
else
{
var defaultComparer = comparerType.GetProperty("Default")!.GetValue(null)!;

comparer_cache.TryAdd(t, defaultComparer);

var equalsMethod = comparerType.GetMethod("Equals", [t, t])!;
return (bool)equalsMethod.Invoke(defaultComparer, new[] { t1, t2 })!;
}
}


public static PropertyBag Compare<T>(T prev, T next)
{
if (EqualityComparer<T>.Default.Equals(prev, next))
return PropertyBag.Empty;

var props = PropertyBag.Empty;
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach (var prop in properties)
{
var prevValue = prop.GetValue(prev)!;
var updatedValue = prop.GetValue(next)!;

if (!IsAreEqual(prop.PropertyType, prevValue, updatedValue))
props.Set(new Symbol(prop.Name), updatedValue);
}

return props;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Argon.Api.Extensions;

using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

public static class SwaggerExtension
{
Expand Down Expand Up @@ -29,8 +30,28 @@ public static WebApplicationBuilder AddSwaggerWithAuthHeader(this WebApplication
[]
}
});
c.OperationFilter<AddHeaderParameterOperationFilter>();
}).AddEndpointsApiExplorer();

return builder;
}
}

public class AddHeaderParameterOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
operation.Parameters ??= new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "X-Host-Name",
In = ParameterLocation.Header,
Description = "Host Name",
Required = true,
Schema = new OpenApiSchema
{
Type = "string"
}
});
}
}
34 changes: 0 additions & 34 deletions src/Argon.Api/Grains.Interfaces/IServerGrain.cs

This file was deleted.

10 changes: 0 additions & 10 deletions src/Argon.Api/Grains.Persistence.States/ChannelGrainState.cs

This file was deleted.

2 changes: 2 additions & 0 deletions src/Argon.Api/Grains/AuthorizationGrain.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
namespace Argon.Api.Grains;

using Contracts;
using Contracts.Models;
using Entities;
using Features.Otp;
using Interfaces;
Expand Down Expand Up @@ -96,6 +97,7 @@ await strategy.ExecuteAsync(async () =>
Username = input.Username,
PasswordDigest = passwordHashingService.HashPassword(input.Password),
PhoneNumber = input.PhoneNumber,
DisplayName = input.DisplayName,
};
await context.Users.AddAsync(user);

Expand Down
Loading