Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
using adas_core.Domain.Models.Responses;
namespace adas_core.Domain.Utils;
public sealed class AuthUtils
{
private LoginResponse _loginResponse = new();
static AuthUtils()
{
InternalInstance ??= new AuthUtils();
}
public AuthUtils()
{
InternalInstance = this;
}
public LoginResponse LoginResponse
{
get
{
lock (_loginResponse)
{
return _loginResponse;
}
}
set
{
lock (_loginResponse)
{
_loginResponse = value;
}
}
}
private static AuthUtils InternalInstance { get; set; }
public static AuthUtils Instance => InternalInstance;
public LoginResponse GetLoginResponse()
{
lock (_loginResponse)
{
return _loginResponse;
}
}
}
+26
View File
@@ -0,0 +1,26 @@
using MongoDB.Bson;
namespace adas_core.Domain.Utils;
public static class BsonUtils
{
public static object? ToObject(this BsonValue val)
{
if (val.IsInt32) return val.AsInt32;
if (val.IsInt64) return val.AsInt64;
if (val.IsDouble) return val.AsDouble;
if (val.IsValidDateTime) return val.ToUniversalTime();
if (val.IsString) return val.AsString;
return null;
}
public static BsonValue? Get(this BsonDocument doc, string key)
{
return doc.TryGetValue(key, out var value) ? value : null;
}
}
+365
View File
@@ -0,0 +1,365 @@
using System.Text.RegularExpressions;
using MongoDB.Bson;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
namespace adas_core.Domain.Utils
{
// TTL RESOLVER POR ENTIDAD / CLAVE
/// <summary>
/// Resuelve TTLs a partir de CacheSettings teniendo en cuenta:
/// - El backend seleccionado por entidad (None | Cache | Redis)
/// - El TTL de la entidad para ese backend
/// - Caída a GlobalSeconds del backend si no hay TTL específico
/// - Si el TTL resultante es null o menor o igual a 0 sin expiración
/// </summary>
public static class CacheKeyTtl
{
private static TimeSpan? SecondsOrNull(int? seconds)
=> seconds is > 0
? TimeSpan.FromSeconds(seconds.Value)
: null;
private static int? FromInMemory(Func<TtlSettings, int?> selector, CacheSettings s)
=> selector(s.InMemory.Ttl);
private static int? FromRedis(Func<TtlSettings, int?>? selector, CacheSettings? s)
=> selector?.Invoke(s?.Redis.Ttl ?? new TtlSettings());
private static TimeSpan? GlobalInMemory(CacheSettings? s)
=> SecondsOrNull(s?.InMemory.Ttl.GlobalSeconds);
private static TimeSpan? GlobalRedis(CacheSettings? s)
=> SecondsOrNull(s?.Redis.Ttl.GlobalSeconds);
/// <summary>
/// Resuelve TTL para una entidad concreta, respetando el backend configurado para dicha entidad.
/// </summary>
public static TimeSpan? ResolveForEntity(CacheSettings? settings, CacheEnum.EntityType entity)
{
// Selección del backend según la entidad
if (settings == null) return null;
var mode = entity switch
{
CacheEnum.EntityType.Patients => settings.Patients,
CacheEnum.EntityType.Displays => settings.Displays,
CacheEnum.EntityType.PumpObservations => settings.PumpObservations,
CacheEnum.EntityType.Appointments => settings.Appointments,
CacheEnum.EntityType.PontOfCare => settings.PointOfCares,
CacheEnum.EntityType.GroupedObservations => settings.GroupedObservations,
CacheEnum.EntityType.PatientObservations => settings.PatientObservations,
_ => CacheEnum.Mode.Cache
};
if (!settings.IsEnabled || mode == CacheEnum.Mode.None)
return null;
// Selector de TTL específico por entidad
Func<TtlSettings, int?> selector = entity switch
{
CacheEnum.EntityType.Patients => x => x.PatientsSeconds,
CacheEnum.EntityType.Displays => x => x.DisplaysSeconds,
CacheEnum.EntityType.PumpObservations => x => x.PumpObservationsSeconds,
CacheEnum.EntityType.Appointments => x => x.AppointmentsSeconds,
CacheEnum.EntityType.PontOfCare => x => x.PointOfCaresSeconds,
CacheEnum.EntityType.GroupedObservations => x => x.GroupedObservationsSeconds,
CacheEnum.EntityType.PatientObservations => x => x.PatientObservationsSeconds,
CacheEnum.EntityType.ConfigObservations => x => x.ConfigObservationsSeconds,
_ => x => x.GlobalSeconds
};
// Resolver en función del backend elegido para la entidad
return mode switch
{
CacheEnum.Mode.Cache => SecondsOrNull(FromInMemory(selector, settings))
?? GlobalInMemory(settings),
CacheEnum.Mode.Redis => SecondsOrNull(FromRedis(selector, settings))
?? GlobalRedis(settings),
_ => null
};
}
/// <summary>
/// Dada una clave, clasifica la entidad y resuelve el TTL para esa clave.
/// </summary>
public static TimeSpan? ResolveForKey(CacheSettings settings, string key)
{
var entity = CacheKeyClassifier.Classify(key);
return ResolveForEntity(settings, entity);
}
}
// CACHE KEYS (GENERATION)
/// <summary>
/// Generador centralizado de claves de caché.
/// - Prefijos normalizados para que el CacheDispatcher clasifique el backend.
/// - Overloads "KeyWithTtl" para devolver (key, ttl) en una llamada.
/// </summary>
public static class CacheKeys
{
#region ConfigObservations
public static string ConfigObservationsAll()
=> "configObservations:all";
public static (string Key, TimeSpan? Ttl) ConfigObservationsAllKeyWithTtl(
CacheSettings? settings)
{
// lo cual usará CacheEnum.EntityType.ConfigObservations (debes añadirlo)
var key = ConfigObservationsAll();
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.ConfigObservations);
return (key, ttl);
}
#endregion
#region DisplayConfig
public static string DisplayBase(ObjectId displayId)
=> $"configDisplays:display:{displayId}:base";
public static (string Key, TimeSpan? Ttl) DisplayBaseKeyWithTtl(
CacheSettings? settings,
ObjectId displayId)
{
var key = DisplayBase(displayId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Displays);
return (key, ttl);
}
public static string DisplayWithConfig(ObjectId displayId)
=> $"configDisplays:display:{displayId}:config";
public static (string Key, TimeSpan? Ttl) DisplayWithConfigKeyWithTtl(
CacheSettings? settings,
ObjectId displayId)
{
var key = DisplayWithConfig(displayId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Displays);
return (key, ttl);
}
#endregion
#region PointOfCare
public static string PointOfCareBase(ObjectId pocId)
=> $"pointOfCare:{pocId}:base";
public static (string Key, TimeSpan? Ttl) PointOfCareBaseKeyWithTtl(
CacheSettings? settings, ObjectId pocId)
{
var key = PointOfCareBase(pocId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PontOfCare);
return (key, ttl);
}
public static string PointOfCareWithInfoKeyWithTtl(ObjectId pocId)
=> $"pointOfCare:{pocId}:withInfo";
public static (string Key, TimeSpan? Ttl) PointOfCareWithInfoKeyWithTtl(
CacheSettings? settings, ObjectId pocId)
{
var key = PointOfCareWithInfoKeyWithTtl(pocId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PontOfCare);
return (key, ttl);
}
#endregion
#region Observations
public static string LatestObservations(ObjectId patientId, IEnumerable<string> fieldNames, int? last = null)
=> $"patients:latestObs:{patientId}:{Normalize(fieldNames)}:{last ?? 0}";
public static (string Key, TimeSpan? Ttl) LatestObservationsKeyWithTtl(
CacheSettings? settings, ObjectId patientId, IEnumerable<string> fieldNames, int? last = null)
{
var key = LatestObservations(patientId, fieldNames, last);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PatientObservations);
return (key, ttl);
}
#endregion
#region PumpObservations
public static string LatestPumps(ObjectId patientId, int? last = null)
=> $"pumpObs:latest:{patientId}:{last ?? 0}";
public static (string Key, TimeSpan? Ttl) LatestPumpsKeyWithTtl(
CacheSettings settings, ObjectId patientId, int? last = null)
{
var key = LatestPumps(patientId, last);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PumpObservations);
return (key, ttl);
}
#endregion
#region Appointments
public static string PatientAppointmentsToday(ObjectId patientId)
{
var dateKey = DateTime.UtcNow.ToString("yyyyMMdd");
return $"appointments:patient:{patientId}:{dateKey}";
}
public static (string Key, TimeSpan? Ttl) PatientAppointmentsTodayKeyWithTtl(
CacheSettings? settings,
ObjectId patientId)
{
var key = PatientAppointmentsToday(patientId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.Appointments);
return (key, ttl);
}
public static string PocAppointmentsToday(ObjectId pocId)
{
var dateKey = DateTime.UtcNow.ToString("yyyyMMdd");
return $"appointments:PoC:{pocId}:{dateKey}";
}
public static (string Key, TimeSpan? Ttl) PocAppointmentsTodayKeyWithTtl(
CacheSettings? settings,
ObjectId pocId)
{
var key = PocAppointmentsToday(pocId);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.PontOfCare);
return (key, ttl);
}
#endregion
#region Grouped Observations (claves: "groupedObs:{field}:patient:{id}")
public static string GroupedObs(ObjectId patientId, string groupedFieldName)
=> $"groupedObs:{groupedFieldName}:patient:{patientId}";
public static (string Key, TimeSpan? Ttl) GroupedObsKeyWithTtl(
CacheSettings? settings, ObjectId patientId, string groupedFieldName)
{
var key = GroupedObs(patientId, groupedFieldName);
var ttl = CacheKeyTtl.ResolveForEntity(settings, CacheEnum.EntityType.GroupedObservations);
return (key, ttl);
}
#endregion
//Helpers
public static string Normalize(IEnumerable<string> items)
=> string.Join("|", items
.Where(s => !string.IsNullOrWhiteSpace(s))
.Select(s => s.Trim())
.OrderBy(s => s, StringComparer.Ordinal));
}
// CLASSIFIER
/// <summary>
/// Clasificador de claves basado en prefijos.
/// Permite al CacheDispatcher seleccionar el backend adecuado (Redis, InMemory, None).
/// </summary>
public static class CacheKeyClassifier
{
public static CacheEnum.EntityType Classify(string key)
{
if (key.StartsWith("patients:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.Patients;
if (key.StartsWith("displays:", StringComparison.OrdinalIgnoreCase) ||
key.StartsWith("configDisplays:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.Displays;
if (key.StartsWith("pumpObs:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.PumpObservations;
if (key.StartsWith("appointments:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.Appointments;
if (key.StartsWith("groupedObs:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.GroupedObservations;
if (key.StartsWith("configObservations:", StringComparison.OrdinalIgnoreCase))
return CacheEnum.EntityType.ConfigObservations;
return CacheEnum.EntityType.Unknown;
}
}
// PATTERNS
/// <summary>
/// Patrones para DeleteByPattern (Redis) u operaciones masivas por prefijo.
/// </summary>
public static class CacheKeyPatterns
{
public static string ForEntity(CacheEnum.EntityType type)
=> type switch
{
CacheEnum.EntityType.Patients => "patients:*",
CacheEnum.EntityType.Displays => "displays:*",
CacheEnum.EntityType.PumpObservations => "pumpObs:*",
CacheEnum.EntityType.Appointments => "appointments:*",
CacheEnum.EntityType.GroupedObservations => "groupedObs:*",
CacheEnum.EntityType.ConfigObservations => "configObservation:*",
_ => "*"
};
public static string ByPrefix(string prefix)
=> $"{prefix}:*";
public static string ByPatient(string prefix, string patientId)
=> $"{prefix}:*:{patientId}*";
public static string ByDate(string prefix, string date)
=> $"{prefix}:*:{date}";
}
// INSPECTOR (diagnóstico)
/// <summary>
/// Utilidad para extraer información estructurada útil para trazas.
/// No usada por la lógica de caché, pero sí por logs y diagnósticos.
/// </summary>
public static class CacheKeyInspector
{
private static readonly Regex _patientRegex =
new(@"patients:(?<id>[^:]+)", RegexOptions.Compiled);
private static readonly Regex _groupedRegex =
new(@"groupedObs:(?<field>[^:]+):(?<id>[^:]+)", RegexOptions.Compiled);
private static readonly Regex _appointmentDayRegex =
new(@"appointments:(?<location>.+):(?<date>\d{8})", RegexOptions.Compiled);
public static string? ExtractPatientId(string key)
{
var match = _patientRegex.Match(key);
return match.Success ? match.Groups["id"].Value : null;
}
public static (string? Field, string? PatientId) ExtractGroupedObservationInfo(string key)
{
var match = _groupedRegex.Match(key);
return !match.Success
? (null, null)
: (match.Groups["field"].Value, match.Groups["id"].Value);
}
public static (string? Location, DateTime? Date) ExtractAppointmentInfo(string key)
{
var match = _appointmentDayRegex.Match(key);
if (!match.Success)
return (null, null);
var location = match.Groups["location"].Value;
var dateStr = match.Groups["date"].Value;
if (DateTime.TryParseExact(
dateStr, "yyyyMMdd", null,
System.Globalization.DateTimeStyles.None,
out var date))
return (location, date);
return (location, null);
}
}
}
@@ -0,0 +1,40 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Domain.Utils;
public static class CardConfigExtensions
{
// Método principal para extraer todos los nombres
public static List<string> GetAllObservationNames(this CardConfig config)
{
if (config.Rows == null) return [];
// 1. Usar SelectMany para aplanar la lista de Rows a una lista de Cells.
var allCells = config.Rows.Where(r => r.Cells != null)
.SelectMany(r => r.Cells!);
// 2. Usar SelectMany y el método recursivo para obtener todos los nombres de todas las Cells.
var observationNames = allCells.SelectMany(ExtractObservationNames)
.Distinct() // Opcional: para asegurar que los nombres sean únicos
.ToList();
return observationNames;
}
// Método auxiliar RECURSIVO para extraer nombres de una Cell y sus SubObs
private static IEnumerable<string> ExtractObservationNames(Cell cell)
{
// 1. Si la Cell tiene ObservationName, devolver esos nombres.
if (cell.ObservationName != null)
// Retornar los elementos de la lista ObservationName
foreach (var name in cell.ObservationName)
yield return name;
// 2. Si la Cell tiene SubObs, llamar recursivamente al método para cada SubObs.
if (cell.SubObs == null) yield break;
{
foreach (var name in cell.SubObs.SelectMany(ExtractObservationNames))
yield return name;
}
}
}
@@ -0,0 +1,48 @@
using System.Collections;
namespace adas_core.Domain.Utils;
public static class CollectionsUtils
{
public static bool IsNullOrEmpty<T>(this List<T>? value)
{
return value == null || !value.Any();
}
public static bool IsEmptyOrNull<T>(List<T>? value)
{
return value == null || !value.Any();
}
public static bool HasElements<T>(List<T>? value)
{
return value != null && value.Any();
}
public static bool IsNotEmpty<T>(List<T> value)
{
return !IsEmptyOrNull(value);
}
public static List<T> EmptyIfNull<T>(List<T>? value)
{
return value ?? [];
}
public static List<T>? NullIfEmpty<T>(List<T> value)
{
return IsEmptyOrNull(value) ? null : value;
}
public static Array ToArray(ICollection collection, Type type)
{
var result = Array.CreateInstance(type, collection.Count);
collection.CopyTo(result, 0);
return result;
}
public static List<string> IfEmptyOrNull(List<string> list, List<string> defaultList)
{
return IsEmptyOrNull(list) ? defaultList : list;
}
}
@@ -0,0 +1,20 @@
using System.Text;
namespace adas_core.Domain.Utils;
public class ComparableDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull where TValue : notnull
{
public override int GetHashCode()
{
StringBuilder str = new();
foreach (var item in this)
{
str.Append(item.Key);
str.Append('_');
str.Append(item.Value);
str.Append("%%");
}
return str.ToString().GetHashCode();
}
}
@@ -0,0 +1,314 @@
using System.Collections;
using System.Text.RegularExpressions;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Masters;
using MongoDB.Bson;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
using Newtonsoft.Json;
using JsonConvert = Newtonsoft.Json.JsonConvert;
namespace adas_core.Domain.Utils;
public partial class ComplexObjectValueTypeSerializer : SerializerBase<object>
{
[GeneratedRegex("ObjectId\\((.[a-f0-9]{24}.)\\)")]
private static partial Regex ObjectIdRegex();
public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
try
{
switch (context.Reader.GetCurrentBsonType())
{
case BsonType.Null:
context.Reader.ReadNull();
return false;
case BsonType.Boolean: return context.Reader.ReadBoolean();
case BsonType.Double: return context.Reader.ReadDouble();
case BsonType.String: return context.Reader.ReadString();
case BsonType.Int32: return context.Reader.ReadInt32();
case BsonType.Int64: return context.Reader.ReadInt64();
case BsonType.DateTime: return context.Reader.ReadDateTime();
case BsonType.Decimal128: return context.Reader.ReadDecimal128();
case BsonType.ObjectId: return context.Reader.ReadObjectId();
case BsonType.EndOfDocument:
context.Reader.ReadEndDocument();
return false;
case BsonType.Binary: return context.Reader.ReadBytes();
case BsonType.Undefined:
context.Reader.ReadUndefined();
return false;
case BsonType.RegularExpression: return context.Reader.ReadRegularExpression();
case BsonType.JavaScript: return context.Reader.ReadJavaScript();
case BsonType.Symbol: return context.Reader.ReadSymbol();
case BsonType.JavaScriptWithScope: return context.Reader.ReadJavaScriptWithScope();
case BsonType.Timestamp: return context.Reader.ReadTimestamp();
case BsonType.MinKey:
context.Reader.ReadMinKey();
return false;
case BsonType.MaxKey:
context.Reader.ReadMaxKey();
return false;
case BsonType.Document:
//default:
{
//if (context.Reader.GetCurrentBsonType() != BsonType.Document)
// throw new Exception("Unexpected BsonType: " + context.Reader.GetCurrentBsonType());
var bsonDocument =
BsonSerializer.Deserialize(context.Reader, typeof(BsonDocument)) as BsonDocument ??
throw new Exception("BsonDocument is null");
var t = bsonDocument["_t"].AsString ??
throw new Exception("Can't deserialize object. No type found.");
var isArray = t.EndsWith("]");
string dirtyJson;
if (isArray)
{
if (!bsonDocument.TryGetValue("_v", out var arrayValue))
throw new Exception("Can't deserialize object. No array value found.");
dirtyJson = arrayValue.ToJson();
bsonDocument.Remove("_v");
if (t.Contains("String[]"))
t = "System.String[], mscorlib";
}
else
{
if (!t.Contains('.'))
t = $"adas-core.Domain.Models.{t}";
if (t.Contains(
"adas-core.Models")) //esta comprobación se hace porque al cambiar a clean code en la BBDD se estaba guardando el espacio de nombres antiguo
t = t.Replace("adas-core.Models", "adas-core.Domain.Models");
bsonDocument.Remove("_t");
dirtyJson = bsonDocument.ToJson();
}
var settings = new JsonSerializerSettings
{
Converters = { new ObjectIdConverter() }
};
var cleanJson = ObjectIdRegex().Replace(dirtyJson, m => m.Groups[1].Value);
var type = Type.GetType(t) ?? throw new Exception($"Type not found: {t}");
return JsonConvert.DeserializeObject(cleanJson, type, settings) ??
throw new Exception("Can't deserialize object");
}
case BsonType.Array:
{
var bsonArray = BsonSerializer.Deserialize<BsonArray>(context.Reader);
var dirtyJsonArray = bsonArray.ToJson(new JsonWriterSettings
{
OutputMode = JsonOutputMode.CanonicalExtendedJson
});
// Convertir el BsonArray completo a una cadena JSON (dirtyJsonArray contiene {"$oid": "..."})
//var dirtyJsonArray = bsonArray.ToJson();
// Obtener el tipo de los valores del array (necesitas esto incluso para el caso vacío)
var elementType = GetElementTypeFromBsonArray(bsonArray);
if (bsonArray.Count == 0)
{
// Caso Array Vacío
// Si el tipo se pudo determinar (p. ej., si la información está en metadatos), usa ese tipo.
// Si no, devuelve una List<object> vacía.
if (elementType == null) return new List<object>();
var elementTypeListEmpty = typeof(List<>).MakeGenericType(elementType);
return Activator.CreateInstance(elementTypeListEmpty)!;
}
// El array no está vacío. Ahora revisamos el tipo.
if (elementType == null)
throw new Exception("No se pudo determinar el tipo de los elementos del array.");
// APLICAR LA LIMPIEZA DE ObjectId AL JSON COMPLETO DEL ARRAY
// Esto cambia: {"_id": {"$oid": "..."}} a: {"_id": "..."}
var cleanJsonArray = ObjectIdRegex().Replace(dirtyJsonArray, m => m.Groups[1].Value);
cleanJsonArray = Regex.Replace(
cleanJsonArray,
"\"_id\"\\s*:\\s*\\{\\s*\"\\$oid\"\\s*:\\s*\"([^\"]+)\"\\s*\\}",
"\"id\":\"$1\""
);
var elementTypeList = typeof(List<>).MakeGenericType(elementType);
var settings = new JsonSerializerSettings
{
Converters = { new ObjectIdConverter() }
};
// Usar el JSON limpio para la deserialización
return JsonConvert.DeserializeObject(cleanJsonArray, elementTypeList, settings)
?? throw new Exception("Can't deserialize BSON array.");
}
default:
throw new Exception("Unexpected BsonType: " + context.Reader.GetCurrentBsonType());
}
}
catch (Exception ex)
{
throw new Exception("Unexpected BsonType: " + ex.GetBaseException());
}
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)
{
switch (value)
{
case null:
context.Writer.WriteNull();
return;
case bool b:
BooleanSerializer.Instance.Serialize(context, args, b);
return;
case double b:
DoubleSerializer.Instance.Serialize(context, args, b);
return;
case string b:
StringSerializer.Instance.Serialize(context, args, b);
return;
case int b:
Int32Serializer.Instance.Serialize(context, args, b);
return;
case long b:
Int64Serializer.Instance.Serialize(context, args, b);
return;
case Decimal128 b:
new Decimal128Serializer().Serialize(context, args, b);
return;
case DateTime b:
new DateTimeSerializer().Serialize(context, args, b);
return;
case OptionList optionList:
BsonSerializer.Serialize(context.Writer, optionList.GetType(), optionList);
return;
case IList<OptionList> optionListList:
BsonSerializer.Serialize(context.Writer, optionListList.GetType(), optionListList);
return;
case PatientAllergiesValue pav:
BsonSerializer.Serialize(context.Writer, pav.GetType(), pav);
return;
case IList<PatientAllergiesValue> pavList:
BsonSerializer.Serialize(context.Writer, pavList.GetType(), pavList);
return;
case ICollection i:
var t = i.GetType().GetElementType() ?? typeof(object);
var serializerType = typeof(ArraySerializer<>).MakeGenericType(t);
var ctor = serializerType.GetConstructor([]);
var serializer = ctor?.Invoke([]);
var serializeMethod = serializerType.GetMethod("Serialize");
var array = CollectionsUtils.ToArray(i, t);
serializeMethod?.Invoke(serializer!, [context, args, array]);
return;
default:
var json = JsonConvert.SerializeObject(value);
var document = BsonDocument.Parse(json);
document["_t"] = value.GetType().FullName;
BsonSerializer.Serialize(context.Writer, typeof(BsonDocument), document);
return;
}
}
private Type? GetElementTypeFromBsonArray(BsonArray bsonArray)
{
if (bsonArray.Count == 1 && bsonArray.Values.Any(c => c.IsBsonNull))
return typeof(OptionList);
foreach (var element in bsonArray)
if (element is BsonDocument bsonDocument)
{
if (bsonDocument.Contains("_t"))
{
var t = bsonDocument["_t"].AsString;
if (t.Contains('.'))
{
t = $"adas-core_Domain.Models.{t}";
t = t.Replace("adas-core_Models", "adas-core_Domain.Models");
}
var type = Type.GetType(t);
if (type != null) return type;
}
else
{
var instance = BsonSerializer.Deserialize(bsonDocument, typeof(OptionList));
if (instance != null && bsonDocument.Contains("name")) return typeof(OptionList);
instance = BsonSerializer.Deserialize(bsonDocument, typeof(List<OptionList>));
if (instance != null)
return typeof(List<OptionList>);
instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientAllergiesValue));
if (instance != null)
return typeof(PatientAllergiesValue);
instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientDrainagesValue));
if (instance != null)
return typeof(PatientDrainagesValue);
instance = BsonSerializer.Deserialize(bsonDocument, typeof(PatientIntravenousLinesValue));
if (instance != null)
return typeof(PatientIntravenousLinesValue);
}
}
return null;
}
public class NullIgnoringListSerializer<T> : SerializerBase<List<T>>
{
public override List<T> Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
{
var bsonType = context.Reader.GetCurrentBsonType();
if (bsonType == BsonType.Null)
{
context.Reader.ReadNull();
return [];
}
var list = new List<T>();
context.Reader.ReadStartArray();
while (context.Reader.ReadBsonType() != BsonType.EndOfDocument)
{
var element = BsonSerializer.Deserialize<T>(context.Reader);
if (element != null) list.Add(element);
}
context.Reader.ReadEndArray();
return list;
}
public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, List<T> value)
{
context.Writer.WriteStartArray();
foreach (var item in value) BsonSerializer.Serialize(context.Writer, item?.GetType(), item);
context.Writer.WriteEndArray();
}
}
// public class IgnoreEmptyStringSerializer : SerializerBase<string>
// {
// public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, string value)
// {
// if (string.IsNullOrEmpty(value))
// {
// context.Writer.WriteNull();
// }
// else
// {
// context.Writer.WriteString(value);
// }
// }
//
// public override string Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
// {
// if (string.IsNullOrEmpty(context.Reader.ReadString()))
// {
// return "";
// }
// else
// {
// return context.Reader.ReadString();
// }
// }
// }
}
+52
View File
@@ -0,0 +1,52 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
namespace adas_core.Domain.Utils;
public static class CryptoAdas
{
private static readonly Regex PassRegex = new("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$");
public static string CreateMd5(string input)
{
using var md5 = MD5.Create();
var inputBytes = Encoding.ASCII.GetBytes(input);
var hashBytes = md5.ComputeHash(inputBytes);
var sb = new StringBuilder();
for (var i = hashBytes.Length - 1; i >= 0; i--) sb.Append(hashBytes[i].ToString("X2"));
var res = sb.ToString();
return res;
}
public static string CreateMd5GroupedObs(GroupedField gF, ObjectId patientId)
{
var names = CollectionsUtils.IfEmptyOrNull(gF.Names, [gF.Name ?? string.Empty]);
return CreateMd5(patientId +
string.Join("+", names.Select(c => c.ToString())) +
gF.Max +
gF.Since +
gF.Regularity +
string.Join("+", gF.Result.Select(c => c.ToString())));
}
public static string CreateBCrypt(string input)
{
return BCrypt.Net.BCrypt.HashPassword(input);
}
public static bool VerifyPassword(string loginPass, string passwordHash)
{
return BCrypt.Net.BCrypt.Verify(loginPass, passwordHash);
}
public static bool IsStrongPassword(string password)
{
return PassRegex.IsMatch(password);
}
}
@@ -0,0 +1,53 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Domain.Utils;
public static class DetailConfigExtension
{
// Método principal para iniciar la extracción
public static List<string> GetAllObservationNames(this CardDetailsConfig config)
{
if (config.NurseRows == null) return [];
// Se usa el método auxiliar para recorrer todas las RowDetailsConfig
var observationNames = ExtractNamesFromRows(config.NurseRows)
.Distinct() // Opcional: para nombres únicos
.ToList();
return observationNames;
}
// --- Auxiliar 1: Recorre la anidación de Filas (RowDetailsConfig) ---
private static IEnumerable<string> ExtractNamesFromRows(List<RowDetailsConfig> rows)
{
foreach (var row in rows)
{
// 1. EXTRAER de las CELDAS (Cells)
if (row.Cells != null)
// Usar SelectMany para aplanar los resultados del método recursivo de Celdas
foreach (var name in row.Cells.SelectMany(ExtractNamesFromCells))
yield return name;
// 2. EXTRAER de las FILAS ANIDADAS (Rows)
if (row.Rows != null)
// Llamada recursiva: Volver a este mismo método para procesar las filas anidadas
foreach (var name in ExtractNamesFromRows(row.Rows))
yield return name;
}
}
// --- Auxiliar 2: Recorre la anidación de Celdas (CellDetails) ---
private static IEnumerable<string> ExtractNamesFromCells(CellDetails cell)
{
// 1. EXTRAER nombres del nivel actual
if (cell.ObservationName != null)
foreach (var name in cell.ObservationName)
yield return name;
// 2. EXTRAER de las CELDAS ANIDADAS (Cells)
if (cell.Cells != null)
// Llamada recursiva: Volver a este mismo método para procesar las celdas anidadas
foreach (var name in cell.Cells.SelectMany(ExtractNamesFromCells))
yield return name;
}
}
+24
View File
@@ -0,0 +1,24 @@
namespace adas_core.Domain.Utils;
public static class DictionaryEx
{
public static TV? GetValue<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV? defaultValue = default)
{
return dict.TryGetValue(key, out var value) ? value : defaultValue;
}
public static string ToListString<TK, TV>(this IDictionary<TK, TV> dict, string itemSeparator = ", ",
string keySeparator = ": ")
{
var s = dict.Keys.Select(key => key + keySeparator + dict[key]).ToList();
return string.Join(itemSeparator, s);
}
public static TValue? TryGetAndReturn<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
where TKey : notnull where TValue : class
{
if (!dictionary.TryGetValue(key, out var retValue)) retValue = null;
return retValue;
}
}
+20
View File
@@ -0,0 +1,20 @@
using System.ComponentModel;
using System.Reflection;
namespace adas_core.Domain.Utils;
public static class EnumUtils
{
public static string GetDescription(Enum value)
{
var enumMember = value.GetType().GetMember(value.ToString()).FirstOrDefault();
var descriptionAttribute =
enumMember == null
? null
: enumMember.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute;
return
descriptionAttribute == null
? value.ToString()
: descriptionAttribute.Description;
}
}
@@ -0,0 +1,43 @@
namespace adas_core.Domain.Utils;
public sealed class EquatableDictionary<TKey, TValue>
: Dictionary<TKey, TValue>, IEquatable<ComparableDictionary<TKey, TValue>>
where TKey : notnull where TValue : notnull
{
public bool Equals(ComparableDictionary<TKey, TValue>? other)
{
if (other is null) return false;
if (Count != other.Count) return false;
foreach (var pair in this)
{
if (!other.TryGetValue(pair.Key, out var otherValue)) return false;
if (!EqualityComparer<TValue>.Default.Equals(pair.Value, otherValue)) return false;
}
return true;
}
//private readonly Dictionary<TKey, TValue> dictionary = new();
public override bool Equals(object? other)
{
return Equals(other as ComparableDictionary<TKey, TValue>);
}
public override int GetHashCode()
{
var hash = 0;
foreach (var pair in this)
{
var miniHash = 17;
miniHash = miniHash * 31 +
EqualityComparer<TKey>.Default.GetHashCode(pair.Key);
miniHash = miniHash * 31 +
EqualityComparer<TValue>.Default.GetHashCode(pair.Value);
hash ^= miniHash;
}
return hash;
}
// Implementation of IDictionary<,> which just delegates to the dictionary
}
+15
View File
@@ -0,0 +1,15 @@
namespace adas_core.Domain.Utils;
public static class GlobalData
{
public static Dictionary<string, object> Data = new();
public static void AddData(string key, object value)
{
var exists = Data.ContainsKey(key);
if (exists)
Data[key] = value;
else
Data.Add(key, value);
}
}
+61
View File
@@ -0,0 +1,61 @@
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
namespace adas_core.Domain.Utils;
public static class Hl7Utils
{
/// <summary>
/// Evalúa si se debe permitir el auto ADT basándose en la configuración de la unidad actual y la antigua.
/// </summary>
/// <param name="unitConfig">Configuración de la unidad (Location)</param>
/// <param name="oldUnitConfig">Configuración de la antigua unidad (OldLocation)</param>
/// <param name="logger">Logger para registrar advertencias</param>
/// <param name="type">Tipo de mensaje HL7 que se va a procesar su entrada</param>
/// <returns>true si se permite; false en caso contrario.</returns>
public static bool ManageAutoAdt(Unit? unitConfig, Unit? oldUnitConfig, ILogger logger, string type)
{
// Variables de estado:
var locationKnown = unitConfig != null;
var oldLocationKnown = oldUnitConfig != null;
var locationAutoAdt = unitConfig?.Configuration.AutoAdt ?? false;
var oldLocationAutoAdt = oldUnitConfig?.Configuration.AutoAdt ?? false;
// Creamos una clave que combine ambas informaciones:
// Formato: "LK-LA:OLK-OLA" donde LK = locationKnown (1=si, 0=no) y LA = locationAutoAdt (1=si, 0=no)
var key = $"{(locationKnown ? "1" : "0")}-" +
$"{(locationAutoAdt ? "1" : "0")}/" +
$"{(oldLocationKnown ? "1" : "0")}-" +
$"{(oldLocationAutoAdt ? "1" : "0")}";
// Evaluamos la clave según los escenarios:
// Caso 1: "1-1/1-1" → Ambas conocidas y AutoAdt true → Permitido.
// Caso 2: "1-0/1-1" → Ambas conocidas, pero location.AutoAdt false → No permitido.
// Caso 3: "0-0/1-1" → Location desconocida, oldLocation conocida con AutoAdt true → Permitido.
// Caso 4: "1-1/0-0" → Location conocida con AutoAdt true, oldLocation desconocida → Permitido.
// Caso 5: "1-1/1-0" → Ambas conocidas, pero oldLocation.AutoAdt false → No permitido.
// Caso 6: "1-0/1-0" → Ambas conocidas, ambas con AutoAdt false → No permitido.
// Caso 7: "0-0/1-0" → Location desconocida, pero oldLocation.AutoAdt false → No permitido.
// Caso 8: "1-0/0-0" → Location conocida con AutoAdt false, y oldLocation desconocida → No permitido.
// Caso 9: "0-0/0-0" → Ambas desconocidas → No permitido.
switch (key)
{
case "1-1/1-1": // Caso 1
case "0-0/1-1": // Caso 3
case "1-1/0-0": // Caso 4
return true;
case "1-0/1-1": // Caso 2
case "1-1/1-0": // Caso 5
case "1-0/1-0": // Caso 6
case "0-0/1-0": // Caso 7
case "1-0/0-0": // Caso 8
logger.LogWarning(
"Deny permission for auto {HL7} on Location: {Location}, OldLocation: {OldLocation} check: {Key}",
type, unitConfig?.Name, oldUnitConfig?.Name, key);
return false;
default: // Caso 9 u otro escenario no contemplado
logger.LogWarning("Both units are unknown on Auto {HL7} check: {Key}", type, key);
return false;
}
}
}
@@ -0,0 +1,9 @@
namespace adas_core.Domain.Utils.Interfaces;
public interface IMappingUtils
{
(string type, string name, string group)? SearchByCode(object code, string category);
int GetComplexityValue(string name, double? peso = null);
// void ConvertStringToDoubleInMapping();
}
+16
View File
@@ -0,0 +1,16 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
namespace adas_core.Domain.Utils;
public static class JsonExtensions
{
public static void CopyToBson(string inputPath, string outputPath, FileMode fileMode = FileMode.CreateNew)
{
using var textReader = File.OpenText(inputPath);
using var jsonReader = new JsonTextReader(textReader);
using var oFileStream = new FileStream(outputPath, fileMode);
using var dataWriter = new BsonDataWriter(oFileStream);
dataWriter.WriteToken(jsonReader);
}
}
+70
View File
@@ -0,0 +1,70 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.IdentityModel.Tokens;
namespace adas_core.Domain.Utils;
public abstract class JwtHelper
{
public static JwtSecurityToken GetJwtToken(
string username,
string secret,
string issuer,
string audience,
int expiration,
Claim[]? additionalClaims = null
)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, username),
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.NameIdentifier, username),
// this guarantees the token is unique
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
if (additionalClaims != null)
{
var claimList = new List<Claim>(claims);
claimList.AddRange(additionalClaims);
claims = claimList.ToArray();
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var expireDate = DateTime.UtcNow.AddMinutes(expiration);
return new JwtSecurityToken(
issuer,
audience,
expires: expireDate,
claims: claims,
signingCredentials: creds
);
}
public static string GenerateRefreshToken()
{
var randomNumber = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
/// <summary>
/// return username from claim
/// </summary>
/// <param name="principal"></param>
/// <returns></returns>
public static string? GetUsernameFromPrincipal(ClaimsPrincipal principal)
{
var nameClaim = principal.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier));
return nameClaim?.Value;
}
}
+57
View File
@@ -0,0 +1,57 @@
using System.Dynamic;
using System.Reflection;
namespace adas_core.Domain.Utils;
public static class Mapper<T>
// We can only use reference types
where T : class
{
private static readonly Dictionary<string, PropertyInfo> PropertyMap;
static Mapper()
{
// At this point we can convert each
// property name to lower case so we avoid
// creating a new string more than once.
PropertyMap =
typeof(T)
.GetProperties()
.ToDictionary(
p => p.Name.ToLower(),
p => p
);
}
public static void Map(ExpandoObject source, T destination)
{
// Might as well take care of null references early.
if (source == null) throw new ArgumentNullException(nameof(source));
if (destination == null) throw new ArgumentNullException(nameof(destination));
// By iterating the KeyValuePair<string, object> of
// source we can avoid manually searching the keys of
// source as we see in your original code.
foreach (var kv in source)
if (PropertyMap.TryGetValue(kv.Key.ToLower(), out var p))
{
var propType = p.PropertyType;
if (kv.Value == null)
{
if (propType is { IsByRef: false } && propType.Name != "Nullable`1")
// Throw if type is a value type
// but not Nullable<>
throw new ArgumentException("not nullable");
}
else if (kv.Value.GetType() != propType)
{
// You could make this a bit less strict
// but I don't recommend it.
p.SetValue(destination, Convert.ToDouble(kv.Value), null);
}
p.SetValue(destination, kv.Value, null);
}
}
}
+185
View File
@@ -0,0 +1,185 @@
using System.Globalization;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Utils.Interfaces;
using Microsoft.Extensions.Options;
namespace adas_core.Domain.Utils;
public sealed class MappingUtils : IMappingUtils
{
private readonly List<MappingInterventions>? _cccData;
private bool _isTransformedValue;
public MappingUtils(IOptions<ApiSettings> apiSettings)
{
var cccMappingData = apiSettings.Value.MappingInterventions;
_cccData = cccMappingData;
_isTransformedValue = false;
}
/*
public (string Tipo, string Nombre)? SearchByCode(double code)
{
ConvertStringToDoubleInMapping();
var data = _cccData;
foreach (var dato in data)
{
foreach (var rango in dato.Codes)
{
if (rango.FinalValue == null || rango.FinalValue.Equals("")) // Es un valor unico
{
if ((double)rango.InitialValue == code)
return (dato.Type, dato.Name);
}
else // Es un rango
{
if (code >= (double)rango.InitialValue && code <= (double)rango.FinalValue)
return (dato.Type, dato.Name);
}
}
}
return null; // No se encontro el code
}*/
public (string type, string name, string group)? SearchByCode(object code, string category)
{
// esta funcion conviete a double los string que permitan conversion si no se puede los deja como string
ConvertStringToDoubleInMapping();
var data = _cccData;
if (data == null)
return null;
// Si el parámetro es un double, buscar directamente en los valores numéricos
if (code is double searchValue)
{
foreach (var dato in data)
foreach (var rango in dato.Codes)
if (rango.FinalValue is null or "") // Es un valor unico
{
if (rango.InitialValue is double initialDouble && Math.Abs(initialDouble - searchValue) == 0 &&
category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
}
else // Es un rango
{
if (rango is { InitialValue: double initialDouble, FinalValue: double finalDouble } &&
searchValue >= initialDouble && searchValue <= finalDouble
&& category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
}
}
// Si el parámetro es un string
else if (code is string searchString)
{
// Intentar convertir el string a double para buscar entre valores numericos
if (double.TryParse(searchString, NumberStyles.Float, CultureInfo.InvariantCulture,
out var searchValueAsDouble))
{
foreach (var dato in data)
foreach (var rango in dato.Codes)
if (rango.FinalValue is null or "") // Es un valor unico
{
if (rango.InitialValue is double initialDouble &&
Math.Abs(initialDouble - searchValueAsDouble) == 0 && category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
}
else // Es un rango
{
if (rango is { InitialValue: double initialDouble, FinalValue: double finalDouble } &&
searchValueAsDouble >= initialDouble && searchValueAsDouble <= finalDouble
&& category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
}
}
else
{
// Si no es convertible a double, buscar directamente entre los valores de tipo string
foreach (var dato in data)
foreach (var rango in dato.Codes)
{
if (rango.InitialValue is string initialString && initialString == searchString &&
category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
if (rango.FinalValue is string finalString && finalString == searchString &&
category == dato.Category.ToString())
return (dato.Type, dato.Name, dato.Group)!;
}
}
}
// No se encontro el codigo
return null;
}
public int GetComplexityValue(string name, double? peso = null)
{
ConvertStringToDoubleInMapping();
var data = _cccData;
if (data == null) return 0; // No encontro ningun nombre que conincida con el dado
foreach (var dato in data)
if (name.Equals(dato.Name))
{
if (dato.Value is
{
Count: 1
}) //value es una lista, si contiene un solo elemento retornaremos el initial value de ese elemento
return Convert.ToInt32(dato.Value[0].InitialValue);
if (dato.Value is { Count: > 1 } && peso != null)
foreach (var rango in dato.Value)
if (peso >= (double?)rango.InitialValue && peso <= (double?)rango.FinalValue)
return rango.ValueContributed;
}
return 0; // No encontro ningun nombre que conincida con el dado
}
private void ConvertStringToDoubleInMapping()
{
// esto es para garantizar que el punto (".") sea reconocido como separador decimal
var culture = CultureInfo.InvariantCulture;
if (_isTransformedValue)
{
}
else if (_cccData != null)
{
foreach (var mapping in _cccData)
{
// Recorrer la lista Codes
foreach (var code in mapping.Codes)
{
if (code.InitialValue is string initialValueString &&
double.TryParse(initialValueString, culture, out var initialValueDouble))
code.InitialValue = initialValueDouble; // Convertir y guardar como double
if (code.FinalValue is string finalValueString &&
double.TryParse(finalValueString, culture, out var finalValueDouble))
code.FinalValue = finalValueDouble; // Convertir y guardar como double
}
// Recorrer la lista Value (si no es nula)
if (mapping.Value != null)
foreach (var value in mapping.Value)
{
if (value.InitialValue is string initialValueString &&
double.TryParse(initialValueString, culture, out var initialValueDouble))
value.InitialValue = initialValueDouble; // Convertir y guardar como double
if (value.FinalValue is string finalValueString &&
double.TryParse(finalValueString, culture, out var finalValueDouble))
value.FinalValue = finalValueDouble; // Convertir y guardar como double
}
} //Fin ford
_isTransformedValue = true;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
using File = TagLib.File;
namespace adas_core.Domain.Utils;
public static class MediaUtils
{
public static readonly Dictionary<string, string> Mapping = new(StringComparer.InvariantCultureIgnoreCase)
{
{ ".aac", "audio/aac" },
{ ".abw", "application/x-abiword" },
{ ".apng", "image/apng" },
{ ".arc", "application/x-freearc" },
{ ".avif", "image/avif" },
{ ".avi", "video/x-msvideo" },
{ ".azw", "application/vnd.amazon.ebook" },
{ ".bin", "application/octet-stream" },
{ ".bmp", "image/bmp" },
{ ".bz", "application/x-bzip" },
{ ".bz2", "application/x-bzip2" },
{ ".cda", "application/x-cdf" },
{ ".csh", "application/x-csh" },
{ ".css", "text/css" },
{ ".csv", "text/csv" },
{ ".doc", "application/msword" },
{ ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
{ ".eot", "application/vnd.ms-fontobject" },
{ ".epub", "application/epub+zip" },
{ ".gz", "application/gzip" },
{ ".gif", "image/gif" },
{ ".htm", "text/html" },
{ ".html", "text/html" },
{ ".ico", "image/vnd.microsoft.icon" },
{ ".ics", "text/calendar" },
{ ".jar", "application/java-archive" },
{ ".jpeg", "image/jpeg" },
{ ".jpg", "image/jpeg" },
{ ".js", "text/javascript" },
{ ".json", "application/json" },
{ ".jsonld", "application/ld+json" },
{ ".mid", "audio/midi" },
{ ".midi", "audio/midi" },
{ ".mjs", "text/javascript" },
{ ".mp3", "audio/mpeg" },
{ ".mp4", "video/mp4" },
{ ".mpeg", "video/mpeg" },
{ ".mpkg", "application/vnd.apple.installer+xml" },
{ ".odp", "application/vnd.oasis.opendocument.presentation" },
{ ".ods", "application/vnd.oasis.opendocument.spreadsheet" },
{ ".odt", "application/vnd.oasis.opendocument.text" },
{ ".oga", "audio/ogg" },
{ ".ogv", "video/ogg" },
{ ".ogx", "application/ogg" },
{ ".opus", "audio/ogg" },
{ ".otf", "font/otf" },
{ ".png", "image/png" },
{ ".pdf", "application/pdf" },
{ ".php", "application/x-httpd-php" },
{ ".ppt", "application/vnd.ms-powerpoint" },
{ ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
{ ".rar", "application/vnd.rar" },
{ ".rtf", "application/rtf" },
{ ".sh", "application/x-sh" },
{ ".svg", "image/svg+xml" },
{ ".tar", "application/x-tar" },
{ ".tif", "image/tiff" },
{ ".tiff", "image/tiff" },
{ ".ts", "video/mp2t" },
{ ".ttf", "font/ttf" },
{ ".txt", "text/plain" },
{ ".vsd", "application/vnd.visio" },
{ ".wav", "audio/wav" },
{ ".weba", "audio/webm" },
{ ".webm", "video/webm" },
{ ".webp", "image/webp" },
{ ".woff", "font/woff" },
{ ".woff2", "font/woff2" },
{ ".xhtml", "application/xhtml+xml" },
{ ".xls", "application/vnd.ms-excel" },
{ ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
{ ".xml", "application/xml" },
{ ".xul", "application/vnd.mozilla.xul+xml" },
{ ".zip", "application/zip" },
{ ".3gp", "video/3gpp" },
{ ".3g2", "video/3gpp2" },
{ ".7z", "application/x-7z-compressed" },
{ ".flv", "video/x-flv" },
{ ".m3u8", "application/x-mpegURL" },
{ ".mov", "video/quicktime" },
{ ".wmv", "video/x-ms-wmv" }
};
public static string GetMimeType(string filePath)
{
var extension = Path.GetExtension(filePath).ToLowerInvariant();
return Mapping.TryGetValue(extension, out var mimeType) ? mimeType : "application/octet-stream";
}
public static bool IsVideoFile(string filePath)
{
// Extensiones de video soportadas
var videoExtensions = new[]
{ ".mp4", ".avi", ".mkv", ".mpeg", ".ogv", ".webm", ".3gp", ".3g2", ".mov", ".wmv", ".m3u8" };
return videoExtensions.Contains(Path.GetExtension(filePath), StringComparer.InvariantCultureIgnoreCase);
}
public static long? GetVideoDuration(string videoPath)
{
try
{
var file = File.Create(videoPath);
var duration = file.Properties.Duration;
return (long)duration.TotalMilliseconds;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
return null;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
namespace adas_core.Domain.Utils;
public static class NumberUtils
{
public static bool IsNumber(this object value)
{
return value is sbyte
|| value is byte
|| value is short
|| value is ushort
|| value is int
|| value is uint
|| value is long
|| value is ulong
|| value is float
|| value is double
|| value is decimal;
}
public static double ToDouble(this object value)
{
return Convert.ToDouble(value);
}
}
@@ -0,0 +1,41 @@
using MongoDB.Bson;
using Newtonsoft.Json;
namespace adas_core.Domain.Utils;
public class ObjectIdConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value is ObjectId objectId)
// Serializa el ObjectId como un string
writer.WriteValue(objectId.ToString());
else
writer.WriteNull();
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
var stringValue = reader.Value as string;
// Verifica si el valor es el valor predeterminado de ObjectId
if (!string.IsNullOrEmpty(stringValue) &&
stringValue != "000000000000000000000000" &&
ObjectId.TryParse(stringValue, out var oid))
return oid;
}
// Nullables reciben null, no-null vuelven Empty
if (objectType == typeof(ObjectId))
return ObjectId.Empty;
// Retorna null o ObjectId.Empty para manejar el valor predeterminado de ObjectId
return null;
}
public override bool CanConvert(Type objectType)
{
return typeof(ObjectId).IsAssignableFrom(objectType);
}
}
+25
View File
@@ -0,0 +1,25 @@
using System.Text;
namespace adas_core.Domain.Utils;
public static class ObjectUtils
{
public static string ToStr(this object? obj)
{
if (obj == null) return "[OBJ NULL]";
var type = obj.GetType();
//if (type == null) return "[OBJ TYPE NULL]";
StringBuilder sb = new();
sb.Append(type.Name + "[");
foreach (var property in obj.GetType().GetProperties())
{
var value = obj.GetType().GetProperty(property.Name)?.GetValue(obj);
if (value == null) continue;
sb.Append($"{property.Name}: {value},");
}
sb.Append(']');
return sb.ToString();
}
}
+118
View File
@@ -0,0 +1,118 @@
using System.Net;
using System.Web;
using adas_core.Domain.Models.MongoModels;
using Serilog;
namespace adas_core.Domain.Utils;
public class RelayHelper
{
public static bool GetRelayStatusFromApiRest(Relay relay)
{
UriBuilder builder = new()
{
Scheme = "https",
Host = "localhost",
Port = 7186,
Path = $"{relay.RelayNumber}/status"
};
var query = HttpUtility.ParseQueryString(builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relays"] = "8"; //todo:tendría que recoger el total
builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = builder.Uri,
Method = HttpMethod.Get
};
try
{
using var client = new HttpClient();
var httpResponse = client.SendAsync(request).Result;
if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
{
var responseContent = httpResponse.Content;
var response = responseContent.ReadAsStringAsync().Result;
return Convert.ToBoolean(response);
}
return false;
}
catch (Exception e)
{
Log.Debug("Exception Getting Relay Status From Api Rest: {relay}", e);
return false;
}
}
//TODO son provisionales mientras se añade como nuget Smacs.Divers
public static void PowerOnRelay(Relay relay)
{
UriBuilder builder = new()
{
Scheme = "https",
Host = "localhost",
Port = 7186,
Path = $"{relay.RelayNumber}/powerOn"
};
PowerRelay(relay, builder);
}
public static void PowerOffRelay(Relay relay)
{
UriBuilder builder = new()
{
Scheme = "https",
Host = "localhost",
Port = 7186,
Path = $"{relay.RelayNumber}/powerOff"
};
PowerRelay(relay, builder);
}
private static void PowerRelay(Relay relay, UriBuilder builder)
{
var query = HttpUtility.ParseQueryString(builder.Query);
query["driver"] = relay.Driver;
query["host"] = relay.Ip;
query["port"] = relay.Port.ToString();
query["relays"] = "8"; //todo:tendría que recoger el total
builder.Query = query.ToString();
HttpRequestMessage request = new()
{
RequestUri = builder.Uri,
Method = HttpMethod.Post
};
try
{
using var client = new HttpClient();
var httpResponse = client.SendAsync(request).Result;
if (!httpResponse.StatusCode.Equals(HttpStatusCode.OK)) Log.Information("relay: {relay} powered", relay);
}
catch (Exception e)
{
Log.Debug("Exception {e} powering Relay: {relay}", e.Message, relay);
}
}
public static bool GetRelayStatus(Relay relay)
{
return false;
}
}
+34
View File
@@ -0,0 +1,34 @@
namespace adas_core.Domain.Utils;
public static class StringEx
{
public static bool IsEmpty(this string source)
{
return string.IsNullOrEmpty(source);
}
public static bool IsEmptyOrWhiteSpace(this string source)
{
return string.IsNullOrWhiteSpace(source);
}
public static string SubstringAfter(this string source, string value)
{
if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value)) return source;
var index = source.IndexOf(value, StringComparison.Ordinal);
return index >= 0
? source.Substring(index + value.Length)
: source;
}
public static string SubstringBefore(this string source, string value)
{
if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(value)) return source;
var index = source.IndexOf(value, StringComparison.Ordinal);
return index >= 0
? source.Substring(0, index)
: source;
}
}