|
@@ -0,0 +1,283 @@
|
|
|
+using Comal.Classes;
|
|
|
+using Expressive;
|
|
|
+using InABox.Core;
|
|
|
+using InABox.Database;
|
|
|
+using System;
|
|
|
+using System.Collections;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+
|
|
|
+namespace PRS.Shared.Events;
|
|
|
+
|
|
|
+public class EventData<T, TDataModel>
|
|
|
+ where T : IEvent<TDataModel>
|
|
|
+ where TDataModel : IEventDataModel
|
|
|
+{
|
|
|
+ public T Event { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A list of triggers for this event. If any of the triggers match, the event runs.
|
|
|
+ /// </summary>
|
|
|
+ public List<ITrigger<T, TDataModel>> Triggers { get; set; }
|
|
|
+
|
|
|
+ public List<IEventAction<T>> Actions { get; set; }
|
|
|
+
|
|
|
+ public EventData(T eventData)
|
|
|
+ {
|
|
|
+ Event = eventData;
|
|
|
+ Triggers = new List<ITrigger<T, TDataModel>>();
|
|
|
+ Actions = new List<IEventAction<T>>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Notification GenerateNotification(TDataModel model) => Event.GenerateNotification(model);
|
|
|
+}
|
|
|
+
|
|
|
+public static class EventUtils
|
|
|
+{
|
|
|
+ private static bool Check<T, TDataModel>(EventData<T, TDataModel> ev, TDataModel dataModel)
|
|
|
+ where T : IEvent<TDataModel>
|
|
|
+ where TDataModel : IEventDataModel
|
|
|
+ {
|
|
|
+ return ev.Triggers.Any(x => x.Check(dataModel));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void Run<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
|
|
|
+ where T : IEvent<TDataModel>
|
|
|
+ where TDataModel : IEventDataModel
|
|
|
+ {
|
|
|
+ if (!Check(evData, dataModel)) return;
|
|
|
+
|
|
|
+ foreach(var action in evData.Actions)
|
|
|
+ {
|
|
|
+ IEvent.RunAction(evData.Event, dataModel, action);
|
|
|
+ }
|
|
|
+
|
|
|
+ NotifySubscribers(store, ev, evData, dataModel);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void NotifySubscribers<T, TDataModel>(IStore store, Event ev, EventData<T, TDataModel> evData, TDataModel dataModel)
|
|
|
+ where T : IEvent<TDataModel>
|
|
|
+ where TDataModel : IEventDataModel
|
|
|
+ {
|
|
|
+ string? description;
|
|
|
+ if (ev.NotificationExpression.IsNullOrWhiteSpace())
|
|
|
+ {
|
|
|
+ description = null;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ var descriptionExpr = new CoreExpression(ev.NotificationExpression, typeof(string));
|
|
|
+ if(!descriptionExpr.TryEvaluate<string>(dataModel).Get(out description, out var error))
|
|
|
+ {
|
|
|
+ CoreUtils.LogException(store.UserID, error, extra: "Error notifying subscribers", store.Logger);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var subscribers = store.Provider.Query(
|
|
|
+ new Filter<EventSubscriber>(x => x.Event.ID).IsEqualTo(ev.ID),
|
|
|
+ Columns.None<EventSubscriber>().Add(x => x.Employee.ID))
|
|
|
+ .ToArray<EventSubscriber>();
|
|
|
+
|
|
|
+ var notifications = new List<Notification>();
|
|
|
+ foreach(var subscriber in subscribers)
|
|
|
+ {
|
|
|
+ var notification = evData.GenerateNotification(dataModel);
|
|
|
+ notification.Employee.CopyFrom(subscriber.Employee);
|
|
|
+ if(description is not null)
|
|
|
+ {
|
|
|
+ notification.Description = description;
|
|
|
+ }
|
|
|
+ notifications.Add(notification);
|
|
|
+ }
|
|
|
+ store.Provider.Save(notifications);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#region DataModel Definition
|
|
|
+
|
|
|
+public interface IEventVariable
|
|
|
+{
|
|
|
+ string Name { get; set; }
|
|
|
+
|
|
|
+ Type Type { get; set; }
|
|
|
+}
|
|
|
+
|
|
|
+public class StandardEventVariable : IEventVariable
|
|
|
+{
|
|
|
+ public string Name { get; set; }
|
|
|
+
|
|
|
+ public Type Type { get; set; }
|
|
|
+
|
|
|
+ public StandardEventVariable(string name, Type type)
|
|
|
+ {
|
|
|
+ Name = name;
|
|
|
+ Type = type;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public class ListEventVariable : IEventVariable
|
|
|
+{
|
|
|
+ public string Name { get; set; }
|
|
|
+
|
|
|
+ public Type Type { get; set; }
|
|
|
+
|
|
|
+ public ListEventVariable(string name, Type type)
|
|
|
+ {
|
|
|
+ Name = name;
|
|
|
+ Type = type;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public interface IEventDataModelDefinition
|
|
|
+{
|
|
|
+ IEnumerable<IEventVariable> GetVariables();
|
|
|
+
|
|
|
+ IEventVariable? GetVariable(string name);
|
|
|
+}
|
|
|
+
|
|
|
+#endregion
|
|
|
+
|
|
|
+#region DataModel
|
|
|
+
|
|
|
+public interface IEventDataModel : IVariableProvider
|
|
|
+{
|
|
|
+ bool TryGetVariable(string name, out object? value);
|
|
|
+
|
|
|
+ bool IVariableProvider.TryGetValue(string variableName, out object? value)
|
|
|
+ {
|
|
|
+ return TryGetVariable(variableName, out value);
|
|
|
+ }
|
|
|
+
|
|
|
+ T RootModel<T>()
|
|
|
+ where T : IEventDataModel
|
|
|
+ {
|
|
|
+ if(this is IChildEventDataModel child)
|
|
|
+ {
|
|
|
+ return child.Parent.RootModel<T>();
|
|
|
+ }
|
|
|
+ else if(this is T root)
|
|
|
+ {
|
|
|
+ return root;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ throw new Exception($"Root model of wrong type; expected {typeof(T).FullName}; got {GetType().FullName}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public interface ITypedEventDataModel : IEventDataModel
|
|
|
+{
|
|
|
+ public Type EntityType { get; }
|
|
|
+}
|
|
|
+
|
|
|
+public interface IChildEventDataModel : IEventDataModel
|
|
|
+{
|
|
|
+ IEventDataModel Parent { get; }
|
|
|
+}
|
|
|
+
|
|
|
+public class ChildEventDataModel : IChildEventDataModel
|
|
|
+{
|
|
|
+ public IEventDataModel Parent { get; set; }
|
|
|
+
|
|
|
+ public Dictionary<string, object?> Values { get; set; } = new Dictionary<string, object?>();
|
|
|
+
|
|
|
+ public ChildEventDataModel(IEventDataModel parent)
|
|
|
+ {
|
|
|
+ Parent = parent;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool TryGetVariable(string name, out object? value)
|
|
|
+ {
|
|
|
+ return Values.TryGetValue(name, out value)
|
|
|
+ || Parent.TryGetVariable(name, out value);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#endregion
|
|
|
+
|
|
|
+public interface IEvent
|
|
|
+{
|
|
|
+ IEventDataModelDefinition DataModelDefinition();
|
|
|
+
|
|
|
+ public static void RunAction(IEvent ev, IEventDataModel model, IEventAction action)
|
|
|
+ {
|
|
|
+ var dataModelDef = ev.DataModelDefinition();
|
|
|
+
|
|
|
+ var values = new List<(string name, object? value)[]>() { Array.Empty<(string, object?)>() };
|
|
|
+
|
|
|
+ var vars = action.ReferencedVariables();
|
|
|
+ foreach(var variable in vars)
|
|
|
+ {
|
|
|
+ var varDef = dataModelDef.GetVariable(variable);
|
|
|
+ if(varDef is ListEventVariable)
|
|
|
+ {
|
|
|
+ if (model.TryGetVariable(varDef.Name, out var list) && list is IEnumerable enumerable)
|
|
|
+ {
|
|
|
+ var oldValues = values;
|
|
|
+ values = new List<(string name, object? value)[]>();
|
|
|
+ foreach(var item in enumerable)
|
|
|
+ {
|
|
|
+ foreach(var valueList in oldValues)
|
|
|
+ {
|
|
|
+ values.Add(valueList.Concatenate(new (string name, object? value)[]
|
|
|
+ {
|
|
|
+ (varDef.Name, item)
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ values.Clear();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(values.Count > 0 && (values.Count > 1 || values[0].Length > 0))
|
|
|
+ {
|
|
|
+ var subModel = new ChildEventDataModel(model);
|
|
|
+ foreach(var valueSet in values)
|
|
|
+ {
|
|
|
+ subModel.Values.Clear();
|
|
|
+ foreach(var (name, value) in valueSet)
|
|
|
+ {
|
|
|
+ subModel.Values[name] = value;
|
|
|
+ }
|
|
|
+ action.Execute(subModel);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ action.Execute(model);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public interface IEvent<TDataModel> : IEvent
|
|
|
+{
|
|
|
+ Notification GenerateNotification(TDataModel model);
|
|
|
+}
|
|
|
+
|
|
|
+public interface ITrigger<TEvent, TDataModel>
|
|
|
+ where TEvent : IEvent<TDataModel>
|
|
|
+ where TDataModel : IEventDataModel
|
|
|
+{
|
|
|
+ bool Check(TDataModel dataModel);
|
|
|
+}
|
|
|
+
|
|
|
+public interface IEventAction
|
|
|
+{
|
|
|
+ IEnumerable<string> ReferencedVariables();
|
|
|
+
|
|
|
+ object? Execute(IEventDataModel dataModel);
|
|
|
+}
|
|
|
+
|
|
|
+public interface IEventAction<TEvent> : IEventAction
|
|
|
+ where TEvent : IEvent
|
|
|
+{
|
|
|
+}
|