2021-12-03 08:57:24 +00:00
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
using MDP.SID.Scripting.Grpc;
|
|
|
|
using MDP.SID.Scripting.Shared.Http;
|
|
|
|
using MDP.SID.Shared.Public.Events;
|
|
|
|
using MDP.SID.Shared.Public.Readers;
|
|
|
|
using Event = MDP.SID.Scripting.Grpc.Event;
|
2021-12-06 15:52:05 +00:00
|
|
|
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
public class Tenant
|
|
|
|
{
|
|
|
|
public string Name { get; set; }
|
|
|
|
public int Id { get; set; }
|
|
|
|
public int CurrentOccupancy { get; set; }
|
|
|
|
public int MaxOccupancy { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
internal class ScriptConfiguration
|
|
|
|
{
|
|
|
|
public int AccessLevelId { get; set; }
|
|
|
|
public int[] EntryReaderIds { get; set; }
|
|
|
|
public int[] ExitReaderIds { get; set; }
|
|
|
|
public int RefreshInterval { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
public class Update
|
|
|
|
{
|
|
|
|
public int NewOccupancyValue { get; set; }
|
|
|
|
public int MaxOccupancyValue { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
public class AccessLevel
|
|
|
|
{
|
|
|
|
public int Id { get; set; }
|
|
|
|
public string Name { get; set; }
|
|
|
|
public bool IsEntry { get; set; }
|
|
|
|
}
|
|
|
|
|
2021-12-06 15:52:05 +00:00
|
|
|
private readonly SemaphoreSlim _syncLock = new(1, 1);
|
2021-12-03 08:57:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
Context.OnEventReceived += OnEventReceived;
|
|
|
|
|
|
|
|
Configuration = await ReadConfiguration();
|
|
|
|
|
|
|
|
// setup script HTTP API endpoints
|
|
|
|
SetupHttpEndpoints();
|
|
|
|
|
|
|
|
// run script till it receives stop signal
|
|
|
|
CancellationToken.WaitHandle.WaitOne();
|
|
|
|
|
|
|
|
|
|
|
|
private async Task<ScriptConfiguration> ReadConfiguration()
|
|
|
|
{
|
|
|
|
if (!await Database.ContainsAsync<ScriptConfiguration>("Configuration"))
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.InsertAsync<ScriptConfiguration>("Configuration", new ScriptConfiguration()
|
|
|
|
{
|
|
|
|
AccessLevelId = 3
|
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
return await Database.GetAsync<ScriptConfiguration>("Configuration");
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
private Task<int> CreateCompanyFilterAsync(int companyId)
|
|
|
|
{
|
|
|
|
return Context.CreateFilterAsync(new Filter()
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
Name = Guid.NewGuid().ToString(),
|
|
|
|
Companies = { companyId }
|
|
|
|
});
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
public ScriptConfiguration Configuration { get; set; }
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
private void SetupHttpEndpoints()
|
|
|
|
{
|
|
|
|
Http.MapPost("/sync", async _ => { await SyncTenantsAsync(); });
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapGet("/tenants", async context =>
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
// avoid retrieving during sync
|
|
|
|
await _syncLock.WaitAsync();
|
|
|
|
|
|
|
|
try
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
var tenants = await Database.GetAllAsync<Tenant>();
|
|
|
|
return tenants;
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
2021-12-03 08:57:24 +00:00
|
|
|
finally
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
_syncLock.Release();
|
|
|
|
}
|
|
|
|
});
|
2021-12-06 15:52:05 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapGet("/ping", async _ => "pong");
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapPost("/reset", async context =>
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.ClearAsync<Tenant>();
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
var scriptConfiguration = new ScriptConfiguration()
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
AccessLevelId = 0,
|
|
|
|
EntryReaderIds = null,
|
|
|
|
ExitReaderIds = null
|
|
|
|
};
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.UpdateAsync("Configuration", scriptConfiguration);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Configuration = scriptConfiguration;
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await SyncTenantsAsync();
|
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapGet("/access-levels", async context =>
|
|
|
|
{
|
|
|
|
var accessLevels = await Context.GetAccessLevelsAsync();
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-06 15:52:05 +00:00
|
|
|
return accessLevels.Where(e => !e.BuiltIn).Select(e => new AccessLevel()
|
2021-12-03 08:57:24 +00:00
|
|
|
{
|
|
|
|
Id = e.Id,
|
2021-12-06 15:52:05 +00:00
|
|
|
Name = $"{e.Name} ({e.LocationName})"
|
2021-12-03 08:21:15 +00:00
|
|
|
});
|
2021-12-03 08:57:24 +00:00
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapGet("/readers", async context =>
|
|
|
|
{
|
|
|
|
var doorReaders = await Context.GetDoorReadersAsync();
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
return doorReaders
|
|
|
|
.Where(e => e.DeviceType == (int)DoorDeviceType.EntryReader ||
|
|
|
|
e.DeviceType == (int)DoorDeviceType.ExitReader).Select(e => new AccessLevel
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
|
|
|
Id = e.Id,
|
2021-12-03 08:57:24 +00:00
|
|
|
Name = $"{e.DoorName}",
|
|
|
|
IsEntry = e.DeviceType == (int)DoorDeviceType.EntryReader
|
2021-12-03 08:21:15 +00:00
|
|
|
});
|
2021-12-03 08:57:24 +00:00
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapPut("/settings", async context =>
|
|
|
|
{
|
|
|
|
var settings = await context.GetBodyAsync<ScriptConfiguration>();
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Configuration = settings;
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.UpdateAsync("Configuration", settings);
|
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapGet("/settings", async _ => Configuration);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.MapPut("/tenants/{id}/set-occupancy", async context =>
|
|
|
|
{
|
|
|
|
var id = context.Get("id");
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
var value = await context.GetBodyAsync<Update>();
|
|
|
|
var tenant = await Database.GetAsync<Tenant>(int.Parse(id));
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
if (value == null)
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
context.FailResponse();
|
|
|
|
return;
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await EvaluateOccupancyAsync(tenant, value.NewOccupancyValue, value.MaxOccupancyValue, true);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.UpdateAsync(tenant.Id, tenant);
|
|
|
|
});
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
Http.Run();
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
/// <summary>
|
|
|
|
/// Gets called when system receives event
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="received"></param>
|
|
|
|
private async void OnEventReceived(Event received)
|
|
|
|
{
|
|
|
|
Guid typeUid = Guid.Parse(received.TypeUid);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
if (Configuration.EntryReaderIds == null || Configuration.ExitReaderIds == null)
|
|
|
|
{
|
|
|
|
return;
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
if (typeUid == EventTypes.AccessGranted && received.UserId.HasValue)
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
var user = await Context.GetUserAsync(received.UserId.Value);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
if (!user.CompanyId.HasValue || !received.ReaderId.HasValue ||
|
|
|
|
!Configuration.EntryReaderIds.Contains(received.ReaderId.Value) &&
|
|
|
|
!Configuration.ExitReaderIds.Contains(received.ReaderId.Value))
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
// event without user or reader
|
2021-12-03 08:21:15 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
var isEntry = Configuration.EntryReaderIds.Contains(received.ReaderId.Value);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
var tenant = await Database.GetAsync<Tenant>(user.CompanyId.Value);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await EvaluateOccupancyAsync(tenant, tenant.CurrentOccupancy + (isEntry ? 1 : -1),
|
|
|
|
tenant.MaxOccupancy);
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
2021-12-03 08:57:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Validate tenant state and act based on occupancy values
|
|
|
|
/// NOTE: this function is optimized and will trigger al addition/removal only when upper/lower limit is reached unless
|
|
|
|
/// explicitly set to force update
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="tenant"></param>
|
|
|
|
/// <param name="newOccupancyValue"></param>
|
|
|
|
/// <param name="newMaxOccupancyValue"></param>
|
|
|
|
/// <param name="forceAccessLevelUpdate">Force update relevant user access levels</param>
|
|
|
|
private async Task EvaluateOccupancyAsync(Tenant tenant, int newOccupancyValue, int newMaxOccupancyValue,
|
|
|
|
bool forceAccessLevelUpdate = false)
|
|
|
|
{
|
|
|
|
newOccupancyValue = Math.Clamp(newOccupancyValue, 0, int.MaxValue);
|
2021-12-06 15:52:05 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
if (newOccupancyValue >= newMaxOccupancyValue)
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
await AddRemoveOccupancyAccessLevelAsync(tenant.Id, true);
|
|
|
|
}
|
2021-12-06 15:52:05 +00:00
|
|
|
else if (newOccupancyValue < newMaxOccupancyValue &&
|
|
|
|
(tenant.CurrentOccupancy >= newMaxOccupancyValue || forceAccessLevelUpdate))
|
2021-12-03 08:57:24 +00:00
|
|
|
{
|
|
|
|
await AddRemoveOccupancyAccessLevelAsync(tenant.Id, false);
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
// normalize
|
|
|
|
tenant.CurrentOccupancy = Math.Min(tenant.MaxOccupancy, newOccupancyValue);
|
2021-12-06 15:52:05 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
tenant.MaxOccupancy = newMaxOccupancyValue;
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.UpdateAsync(tenant.Id, tenant);
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
private async Task AddRemoveOccupancyAccessLevelAsync(int tenantId, bool remove)
|
|
|
|
{
|
2021-12-06 11:32:25 +00:00
|
|
|
int filterId = await CreateCompanyFilterAsync(tenantId);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2022-01-11 15:23:50 +00:00
|
|
|
if (filterId <= 0)
|
|
|
|
{
|
|
|
|
await Context.LogErrorAsync("Invalid filter id");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tenantId <= 0)
|
|
|
|
{
|
|
|
|
await Context.LogErrorAsync("Invalid tenant id");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
try
|
|
|
|
{
|
2021-12-06 11:48:15 +00:00
|
|
|
IEnumerable<MDP.SID.Scripting.Grpc.AccessLevel> als = await Context.GetAccessLevelsAsync();
|
2021-12-06 15:52:05 +00:00
|
|
|
|
|
|
|
List<EntityUpdate> toUpdate = als.Where(e => e.Id != Configuration.AccessLevelId && !e.BuiltIn).Select(
|
|
|
|
e =>
|
|
|
|
new EntityUpdate()
|
|
|
|
{
|
|
|
|
Id = e.Id,
|
|
|
|
Remove = false,
|
|
|
|
Update = false
|
|
|
|
}).ToList();
|
2021-12-06 11:32:25 +00:00
|
|
|
|
|
|
|
toUpdate.Add(new EntityUpdate
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-06 11:32:25 +00:00
|
|
|
Id = Configuration.AccessLevelId,
|
|
|
|
Remove = remove,
|
|
|
|
Update = true
|
2021-12-03 08:57:24 +00:00
|
|
|
}
|
2021-12-06 11:32:25 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
var bulkUpdateUserRequest = new BulkUpdateUser();
|
|
|
|
bulkUpdateUserRequest.AccessLevels.AddRange(toUpdate);
|
|
|
|
|
2021-12-06 11:48:15 +00:00
|
|
|
await Context.BulkUpdateUsersAsync(0, 1000, Array.Empty<int>(), bulkUpdateUserRequest, filterId);
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
2021-12-03 08:57:24 +00:00
|
|
|
finally
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
await Context.DeleteFilterAsync(filterId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Synchronize tenants/companies with script storage and return up to date list
|
|
|
|
/// </summary>
|
|
|
|
/// <returns></returns>
|
|
|
|
private async Task SyncTenantsAsync()
|
|
|
|
{
|
|
|
|
// this request can't happen twice at the same time as there might state where database
|
|
|
|
await _syncLock.WaitAsync();
|
2021-12-06 15:52:05 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
try
|
|
|
|
{
|
|
|
|
var existing = (await Database.GetAllAsync<Tenant>()).ToDictionary(e => e.Id);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
List<Company> companies = await Context.GetCompaniesAsync();
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
foreach (Company company in companies)
|
|
|
|
{
|
|
|
|
if (!existing.TryGetValue(company.Id, out var existingCompany))
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-06 15:52:05 +00:00
|
|
|
var tenant = new Tenant
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
Id = company.Id,
|
|
|
|
Name = company.Name
|
|
|
|
};
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.InsertAsync(tenant.Id, tenant);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var tenant = new Tenant
|
2021-12-03 08:21:15 +00:00
|
|
|
{
|
2021-12-03 08:57:24 +00:00
|
|
|
Name = company.Name,
|
|
|
|
Id = company.Id,
|
|
|
|
CurrentOccupancy = existingCompany.CurrentOccupancy,
|
|
|
|
MaxOccupancy = existingCompany.MaxOccupancy
|
|
|
|
};
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
await Database.UpdateAsync(tenant.Id, tenant);
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
existing.Remove(company.Id);
|
|
|
|
}
|
2021-12-03 08:21:15 +00:00
|
|
|
|
2021-12-03 08:57:24 +00:00
|
|
|
foreach (int key in existing.Keys)
|
|
|
|
{
|
|
|
|
await Database.RemoveAsync<Tenant>(key);
|
2021-12-03 08:21:15 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-03 08:57:24 +00:00
|
|
|
}
|
|
|
|
finally
|
|
|
|
{
|
|
|
|
_syncLock.Release();
|
|
|
|
}
|
2022-01-11 15:23:50 +00:00
|
|
|
}
|