Example #1
0
        public T Process <T>(EActionType type, EActionScope scope, Mutator mutator, T current, T source) where T : Data <T>
        {
            // This is the main Moderation control flow. Let's start by setting up some helper classes:

            var moderationSetup          = ModerationHelper.Setup <T>();
            var customModerationPipeline = moderationSetup.CustomModerationPipeline;
            var allowedActions           = customModerationPipeline?.GetModerationActions(type, scope, mutator, current, source) ?? moderationSetup.ModerationActions;
            var currentPerson            = App.Current.Orchestrator.Person?.Locator;

            //Log.Info<T>($"MOD {type} | {currentPerson}");
            //Log.Info<T>($"MOD {type} | {allowedActions.ToJson()}");
            //Log.Info<T>($"MOD {type} | {current.ToReference()}");

            var currentKey = current.GetDataKey();

            // What can this user do?

            // Before anything, check if the target entry is marked as Imported. In that case the whole Moderation pipeline is sidestepped.

            var importModelMember = moderationSetup.Setup.ImportModelMember;

            if (importModelMember != null)
            {
                if (!current.HasMember(importModelMember))
                {
                    var msg = $"Moderation Pipeline: Invalid configuration - missing [{importModelMember}] ImportModelMember";
                    Base.Current.Log.Warn <T>(msg);
                    throw new InvalidOperationException(msg);
                }

                switch (type)
                {
                case EActionType.Insert:
                    var status = current.GetMemberValue <bool>(importModelMember);
                    if (status)
                    {
                        return(current);            // Imported record, nothing to be done here.
                    }
                    break;

                case EActionType.Update:
                    var newStatus = current.GetMemberValue <bool>(importModelMember);
                    var oldStatus = source.GetMemberValue <bool>(importModelMember);

                    if (source != null)
                    {
                        if (newStatus != oldStatus)
                        {
                            throw new ArgumentException($"Moderation Pipeline: Import flag [{importModelMember}] cannot be changed.");
                        }
                    }

                    if (newStatus)
                    {
                        return(current);
                    }
                    break;
                }
            }

            // Let's determine the Creator of this record.

            string creatorLocator = null;

            var creatorLocatorModelMember = moderationSetup.Setup.CreatorLocatorModelMember;

            if (creatorLocatorModelMember != null)
            {
                if (!current.HasMember(creatorLocatorModelMember))
                {
                    var msg = $"Moderation Pipeline: Invalid configuration - missing [{creatorLocatorModelMember}] CreatorLocatorModelMember";
                    Base.Current.Log.Warn <T>(msg);
                    throw new InvalidOperationException(msg);
                }

                creatorLocator = current.GetMemberValue <string>(creatorLocatorModelMember);
            }

            var    isCreator  = creatorLocator == currentPerson;
            string oRationale = null;

            // There are two kinds of Moderation action that this pipeline can handle. One is a Moderation Task....

            var controlPayload = ModerationHelper.GetModerationControlPayload();

            if (controlPayload != null)
            {
                // This is a Moderation Task operation - say, like an User trying to delete their own entry,
                // or a Mod changing things. We can use some extra data to help with the process.

                var oTaskId = controlPayload?.___ModerationTaskId;
                oRationale = controlPayload?.___ModerationRationale;

                if (oTaskId != null)
                {
                    var oTask = ModerationTask <T> .Get(oTaskId);

                    switch (type)
                    {
                    case EActionType.Insert:
                    case EActionType.Update:
                        // Let's change the payload to the new content.

                        var _os = oTask.Entry.ToJson();
                        var _ns = current.ToJson();

                        if (_ns != _os)
                        {
                            oTask.Entry = current;
                            oTask.Save();

                            new ModerationLog <T>
                            {
                                SourceId = currentKey,
                                Entry    = current,
                                Action   = type.ToString()
                            }.Save();

                            Base.Current.Log.Info <T>($"Moderation Pipeline: PATCH {oTaskId}");
                        }

                        // Moderator/Whitelisted? Accept it.

                        if (allowedActions.Moderate || allowedActions.Whitelisted || moderationSetup.Setup.CreatorCanWithdraw && isCreator)
                        {
                            ModerationTask <T> .Approve(oTaskId);
                        }

                        break;

                    case EActionType.Remove:
                        if (allowedActions.Moderate || allowedActions.Whitelisted)
                        {
                            ModerationTask <T> .Withdraw(oTaskId);
                        }
                        else
                        {
                            ModerationTask <T> .Reject(oTaskId);
                        }
                        break;
                    }
                }
            }

            // The second kind is regular models posted by users. Let's determine their fate:

            var mustCreateTask = false;
            var clearModel     = false;

            if (!(allowedActions.Author || allowedActions.Whitelisted))
            {
                throw new InvalidOperationException("Moderation Pipeline: Not authorized.");
            }

            switch (type)
            {
            case EActionType.Insert:
            case EActionType.Update:

                if (!(allowedActions.Moderate || allowedActions.Whitelisted))
                {
                    mustCreateTask = true;
                }

                if (allowedActions.Whitelisted)     // User is Whitelisted
                {
                    if (type == EActionType.Update) // If it's an update,
                    {
                        if (!isCreator)             // Whitelisted user isn't the creator
                        {
                            mustCreateTask = true;
                        }
                    }
                }

                break;

            case EActionType.Remove:
                if (allowedActions.Moderate)
                {
                    break;
                }
                if (allowedActions.Whitelisted && isCreator)
                {
                    break;
                }
                if (moderationSetup.Setup.CreatorCanWithdraw && isCreator)
                {
                    break;
                }

                mustCreateTask = true;

                break;

            default: throw new ArgumentOutOfRangeException(nameof(type), type, null);
            }

            if (mustCreateTask) //
            {
                var statusModelMember = moderationSetup.Setup.StatusModelMember;
                if (statusModelMember != null)
                {
                    if (current.HasMember(statusModelMember))
                    {
                        if (!current.SetMemberValue(statusModelMember, null)) // Mark as 'Waiting Moderation'.
                        {
                            Base.Current.Log.Warn <T>($"Could NOT change moderation status on [{statusModelMember}] | {type}, entry {{{currentKey}}}");
                        }
                    }
                }

                var moderationTask = new ModerationTask <T>
                {
                    SourceId      = currentKey,
                    Entry         = current,
                    Action        = type.ToString(),
                    AuthorLocator = currentPerson,
                    Rationale     = oRationale
                };

                moderationTask.Save();

                clearModel = true;
            }

            new ModerationLog <T>
            {
                SourceId      = currentKey,
                Entry         = current,
                Action        = type.ToString(),
                AuthorLocator = currentPerson,
                Result        = mustCreateTask ? States.EResult.TaskCreated : States.EResult.Approved
            }.Save();

            if (moderationSetup.Setup.NotifyChanges)
            {
                var ePer = new Email();

                var emailContent =
                    "A new entry has been submitted.<br/><br/>" +
                    "Entry: " + current.ToReference() + "<br/>" +
                    "Creator: " + App.Current.Orchestrator.Person + "<br/><br/>";

                ePer.AddTo(App.Current.Orchestrator.Application.GetGroup("CUR"));
                ePer.AddCc(App.Current.Orchestrator.Application.GetGroup("MOD"));

                ePer.Title = $"{currentPerson}: {type} {Info<T>.Settings.FriendlyName}";

                emailContent += !mustCreateTask
                    ? "User was whitelisted: Action was automatically approved."
                    : "Action was submitted for moderation.";

                ePer.Content = emailContent;
                ePer.Send();
            }

            if (clearModel)
            {
                current = null;
            }
            return(current);
        }