198 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System.Text.Json;
 | 
						|
using DysonNetwork.Shared.Cache;
 | 
						|
using DysonNetwork.Shared.Models;
 | 
						|
using Microsoft.EntityFrameworkCore;
 | 
						|
using NodaTime;
 | 
						|
 | 
						|
namespace DysonNetwork.Pass.Permission;
 | 
						|
 | 
						|
public class PermissionService(
 | 
						|
    AppDatabase db,
 | 
						|
    ICacheService cache
 | 
						|
)
 | 
						|
{
 | 
						|
    private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1);
 | 
						|
 | 
						|
    private const string PermCacheKeyPrefix = "perm:";
 | 
						|
    private const string PermGroupCacheKeyPrefix = "perm-cg:";
 | 
						|
    private const string PermissionGroupPrefix = "perm-g:";
 | 
						|
 | 
						|
    private static string _GetPermissionCacheKey(string actor, string area, string key) =>
 | 
						|
        PermCacheKeyPrefix + actor + ":" + area + ":" + key;
 | 
						|
 | 
						|
    private static string _GetGroupsCacheKey(string actor) =>
 | 
						|
        PermGroupCacheKeyPrefix + actor;
 | 
						|
 | 
						|
    private static string _GetPermissionGroupKey(string actor) =>
 | 
						|
        PermissionGroupPrefix + actor;
 | 
						|
 | 
						|
    public async Task<bool> HasPermissionAsync(string actor, string area, string key)
 | 
						|
    {
 | 
						|
        var value = await GetPermissionAsync<bool>(actor, area, key);
 | 
						|
        return value;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
 | 
						|
    {
 | 
						|
        var cacheKey = _GetPermissionCacheKey(actor, area, key);
 | 
						|
 | 
						|
        var (hit, cachedValue) = await cache.GetAsyncWithStatus<T>(cacheKey);
 | 
						|
        if (hit)
 | 
						|
            return cachedValue;
 | 
						|
 | 
						|
        var now = SystemClock.Instance.GetCurrentInstant();
 | 
						|
        var groupsKey = _GetGroupsCacheKey(actor);
 | 
						|
 | 
						|
        var groupsId = await cache.GetAsync<List<Guid>>(groupsKey);
 | 
						|
        if (groupsId == null)
 | 
						|
        {
 | 
						|
            groupsId = await db.PermissionGroupMembers
 | 
						|
                .Where(n => n.Actor == actor)
 | 
						|
                .Where(n => n.ExpiredAt == null || n.ExpiredAt > now)
 | 
						|
                .Where(n => n.AffectedAt == null || n.AffectedAt <= now)
 | 
						|
                .Select(e => e.GroupId)
 | 
						|
                .ToListAsync();
 | 
						|
 | 
						|
            await cache.SetWithGroupsAsync(groupsKey, groupsId,
 | 
						|
                [_GetPermissionGroupKey(actor)],
 | 
						|
                CacheExpiration);
 | 
						|
        }
 | 
						|
 | 
						|
        var permission = await db.PermissionNodes
 | 
						|
            .Where(n => (n.GroupId == null && n.Actor == actor) ||
 | 
						|
                        (n.GroupId != null && groupsId.Contains(n.GroupId.Value)))
 | 
						|
            .Where(n => n.Key == key && n.Area == area)
 | 
						|
            .Where(n => n.ExpiredAt == null || n.ExpiredAt > now)
 | 
						|
            .Where(n => n.AffectedAt == null || n.AffectedAt <= now)
 | 
						|
            .FirstOrDefaultAsync();
 | 
						|
 | 
						|
        var result = permission is not null ? _DeserializePermissionValue<T>(permission.Value) : default;
 | 
						|
 | 
						|
        await cache.SetWithGroupsAsync(cacheKey, result,
 | 
						|
            [_GetPermissionGroupKey(actor)],
 | 
						|
            CacheExpiration);
 | 
						|
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<PermissionNode> AddPermissionNode<T>(
 | 
						|
        string actor,
 | 
						|
        string area,
 | 
						|
        string key,
 | 
						|
        T value,
 | 
						|
        Instant? expiredAt = null,
 | 
						|
        Instant? affectedAt = null
 | 
						|
    )
 | 
						|
    {
 | 
						|
        if (value is null) throw new ArgumentNullException(nameof(value));
 | 
						|
 | 
						|
        var node = new PermissionNode
 | 
						|
        {
 | 
						|
            Actor = actor,
 | 
						|
            Key = key,
 | 
						|
            Area = area,
 | 
						|
            Value = _SerializePermissionValue(value),
 | 
						|
            ExpiredAt = expiredAt,
 | 
						|
            AffectedAt = affectedAt
 | 
						|
        };
 | 
						|
 | 
						|
        db.PermissionNodes.Add(node);
 | 
						|
        await db.SaveChangesAsync();
 | 
						|
 | 
						|
        // Invalidate related caches
 | 
						|
        await InvalidatePermissionCacheAsync(actor, area, key);
 | 
						|
 | 
						|
        return node;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<PermissionNode> AddPermissionNodeToGroup<T>(
 | 
						|
        PermissionGroup group,
 | 
						|
        string actor,
 | 
						|
        string area,
 | 
						|
        string key,
 | 
						|
        T value,
 | 
						|
        Instant? expiredAt = null,
 | 
						|
        Instant? affectedAt = null
 | 
						|
    )
 | 
						|
    {
 | 
						|
        if (value is null) throw new ArgumentNullException(nameof(value));
 | 
						|
 | 
						|
        var node = new PermissionNode
 | 
						|
        {
 | 
						|
            Actor = actor,
 | 
						|
            Key = key,
 | 
						|
            Area = area,
 | 
						|
            Value = _SerializePermissionValue(value),
 | 
						|
            ExpiredAt = expiredAt,
 | 
						|
            AffectedAt = affectedAt,
 | 
						|
            Group = group,
 | 
						|
            GroupId = group.Id
 | 
						|
        };
 | 
						|
 | 
						|
        db.PermissionNodes.Add(node);
 | 
						|
        await db.SaveChangesAsync();
 | 
						|
 | 
						|
        // Invalidate related caches
 | 
						|
        await InvalidatePermissionCacheAsync(actor, area, key);
 | 
						|
        await cache.RemoveAsync(_GetGroupsCacheKey(actor));
 | 
						|
        await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor));
 | 
						|
 | 
						|
        return node;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task RemovePermissionNode(string actor, string area, string key)
 | 
						|
    {
 | 
						|
        var node = await db.PermissionNodes
 | 
						|
            .Where(n => n.Actor == actor && n.Area == area && n.Key == key)
 | 
						|
            .FirstOrDefaultAsync();
 | 
						|
        if (node is not null) db.PermissionNodes.Remove(node);
 | 
						|
        await db.SaveChangesAsync();
 | 
						|
 | 
						|
        // Invalidate cache
 | 
						|
        await InvalidatePermissionCacheAsync(actor, area, key);
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task RemovePermissionNodeFromGroup<T>(PermissionGroup group, string actor, string area, string key)
 | 
						|
    {
 | 
						|
        var node = await db.PermissionNodes
 | 
						|
            .Where(n => n.GroupId == group.Id)
 | 
						|
            .Where(n => n.Actor == actor && n.Area == area && n.Key == key)
 | 
						|
            .FirstOrDefaultAsync();
 | 
						|
        if (node is null) return;
 | 
						|
        db.PermissionNodes.Remove(node);
 | 
						|
        await db.SaveChangesAsync();
 | 
						|
 | 
						|
        // Invalidate caches
 | 
						|
        await InvalidatePermissionCacheAsync(actor, area, key);
 | 
						|
        await cache.RemoveAsync(_GetGroupsCacheKey(actor));
 | 
						|
        await cache.RemoveGroupAsync(_GetPermissionGroupKey(actor));
 | 
						|
    }
 | 
						|
 | 
						|
    private async Task InvalidatePermissionCacheAsync(string actor, string area, string key)
 | 
						|
    {
 | 
						|
        var cacheKey = _GetPermissionCacheKey(actor, area, key);
 | 
						|
        await cache.RemoveAsync(cacheKey);
 | 
						|
    }
 | 
						|
 | 
						|
    private static T? _DeserializePermissionValue<T>(JsonDocument json)
 | 
						|
    {
 | 
						|
        return JsonSerializer.Deserialize<T>(json.RootElement.GetRawText());
 | 
						|
    }
 | 
						|
 | 
						|
    private static JsonDocument _SerializePermissionValue<T>(T obj)
 | 
						|
    {
 | 
						|
        var str = JsonSerializer.Serialize(obj);
 | 
						|
        return JsonDocument.Parse(str);
 | 
						|
    }
 | 
						|
 | 
						|
    public static PermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
 | 
						|
    {
 | 
						|
        return new PermissionNode
 | 
						|
        {
 | 
						|
            Actor = actor,
 | 
						|
            Area = area,
 | 
						|
            Key = key,
 | 
						|
            Value = _SerializePermissionValue(value),
 | 
						|
        };
 | 
						|
    }
 | 
						|
} |