Exemple #1
0
 private async void ReloadTimeEntry()
 {
     await Task.Run(() =>
     {
         var param = new NameValueCollection
         {
             { Redmine.Net.Api.RedmineKeys.USER_ID, $"=me" },
             { Redmine.Net.Api.RedmineKeys.SPENT_ON, $"={DateTimePicker.Value:yyyy-MM-dd}" },
             { Redmine.Net.Api.RedmineKeys.SORT, "project" }
         };
         var timeEntryList = timeEntryManager.GetList(param);
         if (timeEntryList is null)
         {
             timeEntryList = new List <TimeEntry>();
         }
         TimeEntryModelList = new List <TimeEntryModel>();
         timeEntryList.ForEach(row =>
         {
             var issue          = issueManager.Get(row.Issue.Id.ToString(), new NameValueCollection());
             TimeEntryModel tEM = new TimeEntryModel()
             {
                 Id          = row.Id,
                 ProjectName = row.Project.Name.Substring((row.Project.Name.IndexOf('】') + 1)),
                 SubjectId   = issue.Id,
                 Subject     = issue.Subject,
                 Hours       = row.Hours,
                 Percent     = issue.DoneRatio,
                 Comments    = row.Comments,
             };
             TimeEntryModelList.Add(tEM);
         });
         Invoke((EventHandler) delegate
         {
             DataGridViewTimeEntry.DataSource = new List <TimeEntryModel>(TimeEntryModelList.Select(row => (TimeEntryModel)row.Clone()));
             LogOutputTextBox.Text            = LogOutputRefresh();
             ChangePanelState();
             Timer_GetIssue_Tick(null, new EventArgs());
         });
     });
 }
        public ActionResult GetCreatePopup(int itemId)
        {
            GeneratePopupModel model = new GeneratePopupModel();

            model.GeminiDateFormat = CurrentUser.GeminiDateFormat;
            model.Item             = IssueManager.Get(itemId);

            return(Json(new JsonResponse()
            {
                Success = true,
                Message = "",
                Result = new { Html = RenderPartialViewToString(this, AppManager.Instance.GetAppUrl(AppGuid, "views/_GeneratePopup.cshtml"), model) }
            }));
        }
Exemple #3
0
        public ActionResult GetIssueRow(int issueId)
        {
            var issue = IssueManager.Get(issueId);

            ItemsGridModel model = new ItemsGridModel();

            var visibility = GetChangelogFields(issue.Entity.ProjectId);

            List <int> projectIds = new List <int>();

            projectIds.Add(issue.Entity.ProjectId);

            var properties = GridManager.GetDisplayProperties(MetaManager.TypeGetAll(new List <ProjectDto>()
            {
                UserContext.Project
            }), visibility, projectIds);

            model.AllowSequencing = false;

            model.ShowSequencing = false;

            model.GroupDependencies = false;

            model.Issues.Add(issue);

            model.Columns = GridManager.DescribeGridColumns(properties);

            model.DisplayData = GridManager.GetDisplayData(model.Issues, model.Columns);

            model.Options = GridManager.DescribeGridOptions();

            JsonResponse response = new JsonResponse();

            response.Success = true;

            response.Result = new
            {
                Html = RenderPartialViewToString(this, "~/Views/Shared/DisplayTemplates/IssueDtoRow.cshtml", model),
            };

            string data = response.ToJson().Replace("<", "\\u003c").Replace(">", "\\u003e").Replace("&", "\\u0026");

            return(Content(data, Request.Files.Count == 0 ? "application/json" : "text/html"));
        }
        public ActionResult Create(string startDate, string endDate, int itemId)
        {
            DateTime?startDateTime = ParseDateString.GetDateForString(startDate);
            DateTime?endDateTime   = ParseDateString.GetDateForString(endDate);

            if (startDateTime == null || endDateTime == null || endDateTime < startDateTime)
            {
                return(JsonError("Make sure Start Date and End Date are valid dates"));
            }

            //If selection range is bigger than 3 years set the last date to max 3 years
            if (((endDateTime.Value - DateTime.Today).TotalDays / 365) > 3)
            {
                endDateTime = DateTime.Today.AddYears(3);

                if (startDateTime > endDateTime)
                {
                    startDateTime = endDateTime;
                }
            }

            var closedStatuses = MetaManager.StatusGetClosed();

            List <IssueLinkType> linkTypes        = IssueManager.GeminiContext.Meta.LinkTypeGet();
            IssueLinkType        repeatedLinkType = linkTypes.Find(t => string.Compare(t.Label, "Repeated", true) == 0);

            if (repeatedLinkType == null && linkTypes.Count > 0)
            {
                repeatedLinkType = linkTypes[0];
            }

            var issue = IssueManager.Get(itemId);

            RepeatParser repeat = new RepeatParser(issue.Repeated);

            List <IssueDto> repeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString());

            if (repeatedIssues.Count > 0)
            {
                var previousItemsToDelete = repeatedIssues.FindAll(c => c.Created.Date() >= startDateTime.Value && c.Created.Date() <= endDateTime.Value && !closedStatuses.Contains(c.Entity.StatusId));

                foreach (var item in previousItemsToDelete)
                {
                    IssueManager.Delete(item.Entity.Id);
                }
            }



            for (DateTime date = startDateTime.Value; date <= endDateTime.Value; date = date.AddDays(1))
            {
                repeat.CurrentDateTime = date.Date();

                IssueDto lastRepeated = IssueManager.GetLastCreatedIssueForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString());

                DateTime lastRepeatedDate = issue.Created;

                if (lastRepeated != null && lastRepeated.Entity.IsExisting)
                {
                    lastRepeatedDate = lastRepeated.Created;
                }

                if (repeat.MaximumRepeats > 0)
                {
                    repeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString());

                    if (repeatedIssues != null && repeatedIssues.Count >= repeat.MaximumRepeats)
                    {
                        continue;
                    }
                }

                //If last item was created into the future do this
                if (lastRepeatedDate > date.Date())
                {
                    List <IssueDto> tmpRepeatedIssues = IssueManager.GetItemsForOriginator(IssueOriginatorType.Repeat, issue.Id.ToString());

                    List <IssueDto> ItemsBeforeStartDate = tmpRepeatedIssues.FindAll(i => i.Created < date.Date());

                    if (ItemsBeforeStartDate.Count == 0)
                    {
                        lastRepeatedDate = issue.Created;
                    }
                    else
                    {
                        lastRepeatedDate = ItemsBeforeStartDate.OrderBy("Created").Last().Created;
                    }
                }

                if (repeat.NeedsToRepeat(lastRepeatedDate))
                {
                    var customFields = issue.CustomFields;

                    issue.Attachments = new List <IssueAttachmentDto>();

                    issue.Entity.Repeated = string.Empty;

                    issue.Entity.OriginatorData = issue.Entity.Id.ToString();

                    issue.Entity.OriginatorType = IssueOriginatorType.Repeat;

                    issue.Entity.ParentIssueId = null;
                    issue.Entity.IsParent      = false;

                    issue.Entity.StatusId = 0;

                    issue.Entity.ResolutionId = 0;

                    if (issue.Entity.StartDate.HasValue && issue.Entity.DueDate.HasValue &&
                        issue.Entity.StartDate != new DateTime() && issue.Entity.DueDate != new DateTime())
                    {
                        TimeSpan tsDates = issue.Entity.DueDate.Value - issue.Entity.StartDate.Value;

                        issue.Entity.DueDate = date.AddDays(tsDates.TotalDays);

                        issue.Entity.StartDate = date.Date();
                    }
                    else
                    {
                        issue.Entity.StartDate = null;

                        issue.Entity.DueDate = null;
                    }

                    int issueId = issue.Id;

                    issue.Entity.Created = date;

                    IssueDto repeated = IssueManager.Create(issue.Entity);

                    if (repeated.Entity.Id > 0)
                    {
                        string statment = "update gemini_issues set created = @created where issueid = @issueid";

                        SQLService.Instance.ExecuteQuery(statment, new { created = new DateTime(date.Year, date.Month, date.Day, 8, 0, 0).ToUtc(UserContext.User.TimeZone), issueid = repeated.Entity.Id });
                    }

                    if (customFields != null && customFields.Count > 0)
                    {
                        foreach (var field in customFields)
                        {
                            try
                            {
                                //Find the existing ID to 'replace', if exists.
                                var existingCF = repeated.CustomFields
                                                 .SingleOrDefault(s => s.Entity.CustomFieldId == field.Entity.CustomFieldId);
                                field.Entity.Id = existingCF == null ? 0 : existingCF.Entity.Id;

                                field.Entity.IssueId = repeated.Entity.Id;

                                field.Entity.ProjectId = repeated.Entity.ProjectId;

                                CustomFieldManager.Update(new CustomFieldData(field.Entity));
                            }
                            catch (Exception ex)
                            {
                                LogException(ex);
                            }
                        }
                    }

                    if (repeatedLinkType != null)
                    {
                        IssueManager.IssueLinkCreate(repeated.Entity.Id, issueId, repeatedLinkType.Id);
                    }
                }
            }

            return(JsonSuccess());
        }
Exemple #5
0
        public string Commit(CommitObject commits, [FromUri] string token)
        {
            string apikey = string.Empty;
            string result = string.Empty;

            CurrentUser = new UserDto(new User()
            {
                ProjectGroups = new List <ProjectGroupMembership>()
                {
                    new ProjectGroupMembership()
                    {
                        ProjectGroupId = Countersoft.Gemini.Commons.Constants.GlobalGroupAdministrators, UserId = 0
                    }
                }
            });
            UserContext.User               = CurrentUser;
            PermissionsManager             = PermissionsManager.Copy(CurrentUser);
            UserContext.PermissionsManager = PermissionsManager;

            if (Request.Headers.Authorization != null && Request.Headers.Authorization.Parameter != null)
            {
                var authDetails = Encoding.Default.GetString(Convert.FromBase64String(Request.Headers.Authorization.Parameter)).Split(':');
                if (authDetails.Length == 2)
                {
                    apikey = authDetails[0];
                }
            }
            else if (token != null)
            {
                apikey = token;
            }

            if (apikey.Length == 0 || GeminiApp.Config.ApiKey.Length == 0 || !apikey.StartsWith(GeminiApp.Config.ApiKey, StringComparison.InvariantCultureIgnoreCase))
            {
                string error;

                if (GeminiApp.Config.ApiKey.Length == 0)
                {
                    error = "Web.config is missing API key";
                }
                else
                {
                    error = "Wrong API key: " + apikey;
                }

                GeminiApp.LogException(new Exception(error)
                {
                    Source = SourceControlProvider.GitHub.ToString()
                }, false);

                return(error);
            }

            if (commits == null)
            {
                try
                {
                    var body = Request.Content.ReadAsStringAsync();
                    body.Wait();
                    GeminiApp.LogException("Null CodeCommit", string.Concat("Null CodeCommit - ", body.Result), false);
                }
                catch
                {
                    try
                    {
                        GeminiApp.LogException("Null CodeCommit", "Null CodeCommit - Empty!", false);
                    }
                    catch
                    {
                    }
                }
                return(string.Empty);
            }

            foreach (var commit in commits.commits)
            {
                Regex           ex      = new Regex("GEM:(?<issueid>[0-9]+)", RegexOptions.IgnoreCase);
                MatchCollection matches = ex.Matches(commit.message);

                if (matches.Count > 0)
                {
                    var baseUrl = commits.repository.url.ReplaceIgnoreCase("https://github.com/", "https://api.github.com/");

                    List <string> filesModified = new List <string>();

                    var commitIndex = commit.url.IndexOf("commit");
                    if (commitIndex != -1)
                    {
                        var url = string.Concat(commit.url.Remove(commitIndex, commit.url.Length - commitIndex), "blob/master/");

                        for (int i = 0; i < commit.added.Length; i++)
                        {
                            filesModified.Add(string.Concat("{\"Filename\":\"", commit.added[i], "\", \"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\" }"));
                        }

                        for (int i = 0; i < commit.modified.Length; i++)
                        {
                            filesModified.Add(string.Concat("{\"Filename\":\"", commit.modified[i], "\",\"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\"}"));
                        }

                        for (int i = 0; i < commit.removed.Length; i++)
                        {
                            filesModified.Add(string.Concat("{\"Filename\":\"", commit.removed[i], "\",\"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\"}"));
                        }
                    }

                    CodeCommit codeCommit = new CodeCommit();
                    codeCommit.Provider = SourceControlProvider.GitHub;
                    codeCommit.Comment  = commit.message;
                    codeCommit.Fullname = commit.author.name;
                    codeCommit.Data     = string.Concat("{\"RevisionId\":\"", commit.id, "\",\"PreviousRevisionId\":\"", string.Empty, "\",\"Files\":[", string.Join(",", filesModified.ToArray()), "],\"RepositoryName\":\"", commits.repository.name, "\",\"RepositoryUrl\":\"", baseUrl, "\",\"IsPrivate\":\"", commits.repository.PRIVATE, "\"}");

                    commit.message = ex.Replace(commit.message, string.Empty);

                    List <int> issuesAlreadyProcessed = new List <int>();

                    foreach (Match match in matches)
                    {
                        var issue = IssueManager.Get(match.ToString().Remove(0, 4).ToInt());

                        if (issue != null && !issuesAlreadyProcessed.Contains(issue.Id))
                        {
                            codeCommit.IssueId = issue.Id;
                            GeminiContext.CodeCommits.Create(codeCommit);
                            issuesAlreadyProcessed.Add(issue.Id);

                            try
                            {
                                if (match.Index + match.Length + 1 + 5 <= codeCommit.Comment.Length)
                                {
                                    var time   = codeCommit.Comment.Substring(match.Index + match.Length + 1, 5);
                                    var timeEx = new System.Text.RegularExpressions.Regex("[0-9][0-9]:[0-9][0-9]");
                                    var m      = timeEx.Match(time);
                                    if (m.Success)
                                    {
                                        // Okay, log time!
                                        var timeTypes = MetaManager.TimeTypeGetAll(issue.Project.TemplateId);
                                        if (timeTypes.Count > 0)
                                        {
                                            // Let's try and find the user
                                            var user = commit.author.email.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                         u.Email.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                         u.Fullname.Equals(commit.author.email, StringComparison.InvariantCultureIgnoreCase)) : null;

                                            if (user == null)
                                            {
                                                user = commit.author.name.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                        u.Email.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                        u.Fullname.Equals(commit.author.name, StringComparison.InvariantCultureIgnoreCase)) : null;
                                            }
                                            var timeEntry = new IssueTimeTracking();
                                            timeEntry.IssueId    = issue.Id;
                                            timeEntry.ProjectId  = issue.Entity.ProjectId;
                                            timeEntry.Comment    = codeCommit.Comment.ToMax(1990);
                                            timeEntry.EntryDate  = DateTime.Now;
                                            timeEntry.Hours      = m.Value.Substring(0, 2).ToInt();
                                            timeEntry.Minutes    = m.Value.Substring(3, 2).ToInt();
                                            timeEntry.TimeTypeId = timeTypes[0].Entity.Id;
                                            timeEntry.UserId     = user == null ? Countersoft.Gemini.Commons.Constants.SystemAccountUserId : user.Id;
                                            TimeTrackingManager.Create(timeEntry);
                                        }
                                    }
                                }
                            }
                            catch (Exception timeEx)
                            {
                                LogManager.LogError(timeEx, "GitHub - Time log");
                            }
                        }
                    }
                }
            }

            return(result);
        }
            public List <CodeCommit> CodeCommit(string auth, string payload = "")
            {
                if (auth.IsEmpty())
                {
                    GeminiApp.LogException(new UnauthorizedAccessException()
                    {
                        Source = SourceControlProvider.Bitbucket.ToString()
                    }, false);

                    return(null);
                }

                var authDetails = Encoding.Default.GetString(Convert.FromBase64String(auth)).Split(':');

                string apikey = string.Empty;

                if (authDetails.Length == 2)
                {
                    apikey = authDetails[0];
                }

                CurrentUser = new UserDto(new User()
                {
                    ProjectGroups = new List <ProjectGroupMembership>()
                    {
                        new ProjectGroupMembership()
                        {
                            ProjectGroupId = Countersoft.Gemini.Commons.Constants.GlobalGroupAdministrators, UserId = 0
                        }
                    }
                });
                UserContext.User               = CurrentUser;
                PermissionsManager             = PermissionsManager.Copy(CurrentUser);
                UserContext.PermissionsManager = PermissionsManager;

                if (apikey.Length == 0 || GeminiApp.Config.ApiKey.Length == 0 || !apikey.StartsWith(GeminiApp.Config.ApiKey, StringComparison.InvariantCultureIgnoreCase))
                {
                    string error;

                    if (GeminiApp.Config.ApiKey.Length == 0)
                    {
                        error = "Web.config is missing API key.";
                    }
                    else
                    {
                        error = string.Format("Wrong API key: {0}.", apikey);
                    }

                    GeminiApp.LogException(new Exception(error)
                    {
                        Source = SourceControlProvider.Bitbucket.ToString()
                    }, false);

                    return(null);
                }


                try
                {
                    var body = Request.Content.ReadAsStringAsync();
                    body.Wait();
                    //GeminiApp.LogException("Null CodeCommit", string.Concat("Null CodeCommit - ", body.Result), false);
                }
                catch
                {
                    try
                    {
                        GeminiApp.LogException("Null CodeCommit", "Null CodeCommit - Empty!", false);
                    }
                    catch
                    {
                    }
                }


                Payload commits = System.Web.HttpContext.Current.Request.Form["payload"].FromJson <Payload>();

                if (commits == null)
                {
                    return(null);
                }

                List <CodeCommit> allCommits = new List <CodeCommit>();

                foreach (var commit in commits.commits)
                {
                    Regex           ex         = new Regex("GEM:(?<issueid>[0-9]+)", RegexOptions.IgnoreCase);
                    MatchCollection matches    = ex.Matches(commit.message);
                    List <int>      issueAdded = new List <int>();

                    List <string> filesModified = new List <string>();

                    foreach (var file in commit.files)
                    {
                        FileCommitType type = FileCommitType.Created;

                        if (file.type.Equals("modified", StringComparison.InvariantCultureIgnoreCase))
                        {
                            type = FileCommitType.Modified;
                        }
                        else if (file.type.Equals("removed", StringComparison.InvariantCultureIgnoreCase))
                        {
                            type = FileCommitType.Deleted;
                        }

                        filesModified.Add(string.Concat("{\"Filename\":\"", file.file, "\", \"FileId\":\"", string.Empty, "\",\"PreviousFileRevisionId\":\"", string.Empty, "\", \"Type\":\"", type.ToString(), "\" }"));
                    }

                    var data = commit.ToJson();

                    if (matches.Count > 0)
                    {
                        foreach (Match match in matches)
                        {
                            IssueDto issue = IssueManager.Get(match.ToString().Remove(0, 4).ToInt());
                            if (issue != null)
                            {
                                if (!issueAdded.Contains(issue.Id))
                                {
                                    CodeCommit newCodeCommit = new CodeCommit();
                                    newCodeCommit.Provider = SourceControlProvider.Bitbucket;
                                    newCodeCommit.Comment  = commit.message;
                                    newCodeCommit.Fullname = commit.author;
                                    newCodeCommit.Data     = string.Concat("{\"RevisionId\":\"", commit.raw_node, "\",\"PreviousRevisionId\":\"", commit.parents[0], "\",\"Files\":[", string.Join(",", filesModified.ToArray()), "],\"RepositoryName\":\"", commits.repository.name, "\",\"RepositoryUrl\":\"", String.Concat(commits.canon_url, commits.repository.absolute_url), "\",\"IsPrivate\":\"", commits.repository.is_private, "\"}");;
                                    newCodeCommit.IssueId  = issue.Id;

                                    allCommits.Add(GeminiContext.CodeCommits.Create(newCodeCommit));

                                    issueAdded.Add(issue.Id);

                                    try
                                    {
                                        if (match.Index + match.Length + 1 + 5 <= commit.message.Length)
                                        {
                                            var time   = commit.message.Substring(match.Index + match.Length + 1, 5);
                                            var timeEx = new System.Text.RegularExpressions.Regex("[0-9][0-9]:[0-9][0-9]");
                                            var m      = timeEx.Match(time);
                                            if (m.Success)
                                            {
                                                // Okay, log time!
                                                var timeTypes = MetaManager.TimeTypeGetAll(issue.Project.TemplateId);
                                                if (timeTypes.Count > 0)
                                                {
                                                    // Let's try and find the user
                                                    var user = commit.author.HasValue() ? Cache.Users.Find(u => u.Username.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                           u.Email.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase) ||
                                                                                                           u.Fullname.Equals(commit.author, StringComparison.InvariantCultureIgnoreCase)) : null;
                                                    var timeEntry = new IssueTimeTracking();
                                                    timeEntry.IssueId    = issue.Id;
                                                    timeEntry.ProjectId  = issue.Entity.ProjectId;
                                                    timeEntry.Comment    = commit.message.ToMax(1990);
                                                    timeEntry.EntryDate  = DateTime.Now;
                                                    timeEntry.Hours      = m.Value.Substring(0, 2).ToInt();
                                                    timeEntry.Minutes    = m.Value.Substring(3, 2).ToInt();
                                                    timeEntry.TimeTypeId = timeTypes[0].Entity.Id;
                                                    timeEntry.UserId     = user == null ? Countersoft.Gemini.Commons.Constants.SystemAccountUserId : user.Id;
                                                    TimeTrackingManager.Create(timeEntry);
                                                }
                                            }
                                        }
                                    }
                                    catch (Exception timeEx)
                                    {
                                        LogManager.LogError(timeEx, "BitBucket - Time log");
                                    }
                                }
                            }
                            else
                            {
                                GeminiApp.LogException(new Exception(string.Concat("Item ID ", match.ToString().Remove(0, 4).ToInt(), " could not be found."))
                                {
                                    Source = "Commit Failed"
                                }, false);
                            }
                        }
                    }
                }

                return(allCommits);
            }
Exemple #7
0
        private void ProcessAppNavCardAlerts()
        {
            var navigationCardsManager = new NavigationCardsManager(_issueManager);

            List <NavigationCard> cards = navigationCardsManager.GetPendingAlertCards();

            LogDebugMessage("Email templates loaded: " + _templates.Count);

            LogDebugMessage("Pending card alerts: " + cards.Count);

            // ? We need to store user id and issue id for every email we send out -- avoids dupes?
            List <string> issuesEmailedToUsers = new List <string>(50);

            List <string> individualIssuesEmailedToUsers = new List <string>(50);

            AlertsTemplateHelper alerts = new AlertsTemplateHelper(_templates, GetUrl(_issueManager), _languages);

            UserManager userManager = new UserManager(_issueManager);

            bool refreshCache = false;

            var allOptOuts = navigationCardsManager.GetOptOuts();

            foreach (NavigationCard card in cards)
            {
                List <IssueDto> individualIssues = new List <IssueDto>();

                // Safety checks
                if (!card.UserId.HasValue)
                {
                    continue;
                }

                if (card.CardData.Alerts.Count == 0)
                {
                    continue;
                }

                UserDto recepient = userManager.Get(card.UserId.Value);

                // Safety check
                if (!recepient.Entity.EmailMe || recepient.Entity.Email.IsEmpty())
                {
                    continue;
                }

                DateTime lastChecked = card.CardData.AlertsLastSent.HasValue ? card.CardData.AlertsLastSent.Value : card.Created;

                DateTime lastCheckedLocal = lastChecked.ToLocal(_issueManager.UserContext.User.TimeZone);

                AlertTypeAppNavAlertsTemplateModel model = new AlertTypeAppNavAlertsTemplateModel();

                model.TheRecipient = recepient;

                model.Version = GeminiVersion.Version;

                model.GeminiUrl = alerts.GeminiUrl;

                List <int> issuesToAlert = new List <int>(card.CardData.Alerts);

                foreach (int issueId in issuesToAlert)
                {
                    IssueDto issue = null;
                    try
                    {
                        issue = _issueManager.Get(issueId);
                    }
                    catch (Exception ex)
                    {
                        LogException(ex);
                    }

                    // Safety check
                    if (issue == null || issue.Entity.IsNew)
                    {
                        continue;
                    }

                    // Dupe check
                    string dupeKey = string.Format("{0}-{1}-{2}", recepient.Entity.Id, issueId, card.Id);

                    if (issuesEmailedToUsers.Contains(dupeKey))
                    {
                        continue;
                    }

                    var permissionManager = new PermissionsManager(recepient, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false);

                    if (!permissionManager.CanSeeItem(issue.Project, issue))
                    {
                        continue;
                    }

                    foreach (var comment in issue.Comments)
                    {
                        if (!permissionManager.CanSeeComment(issue, comment))
                        {
                            issue.Comments.RemoveAll(c => !permissionManager.CanSeeComment(issue, c));
                            break;
                        }
                    }

                    // Remove the reported by first entry!
                    if (issue.History.Count > 0 && issue.History[issue.History.Count - 1].Entity.AttributeChanged == ItemAttributeVisibility.ReportedBy)
                    {
                        issue.History.RemoveAt(issue.History.Count - 1);
                    }
                    issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, recepient, lastCheckedLocal);

                    // Populate model for email template
                    if (card.CardData.Subscription.IndividualAlert)
                    {
                        individualIssues.Add(issue);
                    }

                    if (card.CardData.Subscription.Created && issue.Created.ToUtc(_issueManager.UserContext.User.TimeZone) >= lastChecked)
                    {
                        model.TheItemsCreated.Add(issue);
                    }
                    else
                    {
                        List <IssueAuditDto> allChanges = issue.History.FindAll(h => h.Entity.Created.ToUtc(_issueManager.UserContext.User.TimeZone) >= lastChecked);

                        List <IssueAuditDto> commentChanges = allChanges.FindAll(a => !a.Entity.IsCustom && a.Entity.AttributeChanged == ItemAttributeVisibility.AssociatedComments);

                        List <IssueAuditDto> nonCommentChanges = allChanges.FindAll(a => a.Entity.IsCustom || a.Entity.AttributeChanged != ItemAttributeVisibility.AssociatedComments);

                        // Add comments and updates
                        if (card.CardData.Subscription.Updated && nonCommentChanges.Count > 0 || card.CardData.Subscription.Commented && commentChanges.Count > 0 && issue.Comments.Count > 0)
                        {
                            model.TheItemsUpdated.Add(issue);
                        }

                        if (card.CardData.Subscription.Commented && commentChanges.Count > 0 && issue.Comments.Count > 0)
                        {
                            model.TheItemsCommented.Add(issue);
                        }
                    }

                    // Record the fact that we have processed this issue for this recepient (to prevent dupes)
                    issuesEmailedToUsers.Add(dupeKey);
                }

                model.CardTitle = string.Format("{0} {1}", card.Key, card.Title);

                model.CardKey = card.Key;

                model.CardDescription = card.Title;

                model.CardComment = card.CardData.Comment;

                model.CardUrl = string.Concat(model.GeminiUrl, "workspace/", card.Id, '/', card.Url);

                // Safety check!
                if (model.ChangeCount > 0)
                {
                    List <UserDto> subscribers = GetCardSubscribers(card, navigationCardsManager, userManager, recepient);

                    //if (!subscribers.Contains(recepient) && subscribers.Find(u => u.Entity.Id == recepient.Entity.Id) == null) subscribers.Insert(0, recepient);
                    if (card.CardData.Subscription.IndividualAlert)
                    {
                        foreach (var user in subscribers)
                        {
                            if (allOptOuts.Any(s => s.UserId == user.Entity.Id && s.CardId == card.Id && s.OptOutType == OptOutEmails.OptOutTypes.Alert))
                            {
                                continue;
                            }

                            foreach (var issue in individualIssues)
                            {
                                string individualDupeKey = string.Format("{0}-{1}", user.Entity.Id, issue.Entity.Id);

                                if (individualIssuesEmailedToUsers.Contains(individualDupeKey))
                                {
                                    continue;
                                }

                                if (user != recepient)
                                {
                                    var permissionManager = new PermissionsManager(user, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false);

                                    if (!permissionManager.CanSeeItem(issue.Project, issue))
                                    {
                                        continue;
                                    }

                                    issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, user, lastCheckedLocal);
                                }

                                var indModel = new AlertTypeIndividualTemplateModel();

                                indModel.GeminiUrl = model.GeminiUrl;

                                indModel.LinkViewItem = NavigationHelper.GetIssueUrl(_issueManager.UserContext, issue.Entity.ProjectId, issue.EscapedProjectCode, issue.Entity.Id);

                                indModel.TheItem = issue;

                                indModel.TheRecipient = user;

                                indModel.Version = GeminiVersion.Version;

                                indModel.IsNewItem = model.TheItemsCreated.Contains(issue);

                                indModel.CardKey = model.CardKey;

                                indModel.CardDescription = model.CardDescription;

                                indModel.CardComment = model.CardComment;

                                indModel.CardUrl = model.CardUrl;

                                if (!indModel.IsNewItem && issue.ChangeLog.Count == 0)
                                {
                                    continue;
                                }

                                var template = alerts.FindTemplateForProject(
                                    indModel.IsNewItem ? AlertTemplateType.Created : AlertTemplateType.Updated,
                                    issue.Entity.ProjectId, GetLanguageId(user));

                                string html = alerts.GenerateHtml(template, indModel);

                                if (GeminiApp.GeminiLicense.IsFree)
                                {
                                    html = alerts.AddSignature(html);
                                }

                                // Send email
                                string log;

                                string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, indModel, true) : string.Format("[{0}] {1} {2} ({3})", issue.IssueKey, issue.Type, model.TheItemsCreated.Contains(issue) ? "Created" : "Updated", issue.Title, issue.IsClosed ? "Closed" : string.Empty);

                                EmailHelper.Send(_issueManager.UserContext.Config, subject, html, user.Entity.Email, user.Fullname, true, out log);

                                individualIssuesEmailedToUsers.Add(individualDupeKey);
                            }
                        }
                    }
                    else
                    {
                        var cloneCreated = new List <IssueDto>(model.TheItemsCreated);

                        var cloneUpdated = new List <IssueDto>(model.TheItemsUpdated);

                        var cloneCommented = new List <IssueDto>(model.TheItemsCommented);

                        // Find email template to use (for this project or fall back to default template)

                        foreach (var user in subscribers)
                        {
                            AlertTemplate template = alerts.FindTemplateForProject(AlertTemplateType.AppNavAlerts, 0, GetLanguageId(user));


                            if (allOptOuts.Any(s => s.UserId == user.Entity.Id && s.CardId == card.Id && s.OptOutType == OptOutEmails.OptOutTypes.Alert))
                            {
                                continue;
                            }

                            model.TheItemsCreated = new List <IssueDto>(cloneCreated);

                            model.TheItemsUpdated = new List <IssueDto>(cloneUpdated);

                            model.TheItemsCommented = new List <IssueDto>(cloneCommented);

                            if (user != recepient)
                            {
                                var permissionManager = new PermissionsManager(user, _types, _permissionSets, _organizations, _issueManager.UserContext.Config.HelpDeskModeGroup, false);

                                model.TheItemsCreated.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i));

                                model.TheItemsUpdated.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i));

                                model.TheItemsCommented.RemoveAll(i => !permissionManager.CanSeeItem(i.Project, i));

                                foreach (var issue in model.TheItemsCreated.Concat(model.TheItemsUpdated).Concat(model.TheItemsCommented))
                                {
                                    issue.ChangeLog = _issueManager.GetChangeLog(issue, _issueManager.UserContext.User, user, lastCheckedLocal);
                                }
                            }

                            //model.TheItemsCreated.RemoveAll(i => i.ChangeLog.Count == 0);
                            model.TheItemsUpdated.RemoveAll(i => i.ChangeLog.Count == 0);

                            model.TheItemsCommented.RemoveAll(i => i.ChangeLog.Count == 0);

                            if (model.ChangeCount == 0)
                            {
                                continue;
                            }

                            // Generate email template
                            string html = alerts.GenerateHtml(template, model);

                            if (GeminiApp.GeminiLicense.IsFree)
                            {
                                html = alerts.AddSignature(html);
                            }

                            string subject = template.Options.Subject.HasValue() ? alerts.GenerateHtml(template, model, true) : string.Format("{0} {1}", card.Key, card.Title);

                            // Send email
                            string log;

                            EmailHelper.Send(_issueManager.UserContext.Config, subject, html, user.Entity.Email, user.Fullname, true, out log);
                        }
                    }
                }

                // Remove the alert notifications and update the database
                lock (card.CardData.Alerts)
                {
                    card.CardData.Alerts.RemoveAll(a => issuesToAlert.Contains(a));
                }

                card.CardData.AlertsLastSent = DateTime.UtcNow;

                refreshCache = true;

                navigationCardsManager.Update(card, false, false);
            }

            if (refreshCache)
            {
                navigationCardsManager.Cache.NavigationCards.Invalidate();
                var webNodes = GeminiApp.Container.Resolve <IWebNodes>();
                webNodes.AddDataOnAllNodesButMe(new WebNodeData()
                {
                    NodeGuid = GeminiApp.GUID, Key = "cache", Value = navigationCardsManager.Cache.NavigationCards.CacheKey
                });
            }
        }