From 992fac1328585d5daa5bfda541485aaa146d6ef4 Mon Sep 17 00:00:00 2001
From: Kyle Spearrin <kyle.spearrin@gmail.com>
Date: Thu, 14 Dec 2017 17:23:46 -0500
Subject: [PATCH] index table entity events

---
 src/Core/Models/Data/EventTableEntity.cs      | 134 ++++++++----------
 .../Repositories/SqlServer/EventRepository.cs |   8 +-
 src/EventsProcessor/Functions.cs              |  14 +-
 3 files changed, 75 insertions(+), 81 deletions(-)

diff --git a/src/Core/Models/Data/EventTableEntity.cs b/src/Core/Models/Data/EventTableEntity.cs
index 7b9e3a1cfa..8ba9f4cb3b 100644
--- a/src/Core/Models/Data/EventTableEntity.cs
+++ b/src/Core/Models/Data/EventTableEntity.cs
@@ -11,7 +11,7 @@ namespace Bit.Core.Models.Data
     {
         public EventTableEntity() { }
 
-        public EventTableEntity(IEvent e)
+        private EventTableEntity(IEvent e)
         {
             Date = e.Date;
             Type = e.Type;
@@ -22,80 +22,6 @@ namespace Bit.Core.Models.Data
             GroupId = e.GroupId;
             OrganizationUserId = e.OrganizationUserId;
             ActingUserId = e.ActingUserId;
-
-            switch(e.Type)
-            {
-                case EventType.User_LoggedIn:
-                case EventType.User_ChangedPassword:
-                case EventType.User_Enabled2fa:
-                case EventType.User_Disabled2fa:
-                case EventType.User_Recovered2fa:
-                case EventType.User_FailedLogIn:
-                case EventType.User_FailedLogIn2fa:
-                    if(e.OrganizationId.HasValue)
-                    {
-                        PartitionKey = $"OrganizationId={OrganizationId}";
-                        RowKey = string.Format("Date={0}__UserId={1}__Type={2}",
-                            CoreHelpers.DateTimeToTableStorageKey(Date), UserId, (int)Type);
-                    }
-                    else
-                    {
-                        PartitionKey = $"UserId={UserId}";
-                        RowKey = string.Format("Date={0}__Type={1}",
-                            CoreHelpers.DateTimeToTableStorageKey(Date), (int)Type);
-                    }
-                    break;
-                case EventType.Cipher_Created:
-                case EventType.Cipher_Updated:
-                case EventType.Cipher_Deleted:
-                case EventType.Cipher_AttachmentCreated:
-                case EventType.Cipher_AttachmentDeleted:
-                case EventType.Cipher_Shared:
-                case EventType.Cipher_UpdatedCollections:
-                    if(OrganizationId.HasValue)
-                    {
-                        PartitionKey = $"OrganizationId={OrganizationId}";
-                        RowKey = string.Format("Date={0}__CipherId={1}__ActingUserId={2}__Type={3}",
-                            CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, ActingUserId, (int)Type);
-                    }
-                    else
-                    {
-                        PartitionKey = $"UserId={UserId}";
-                        RowKey = string.Format("Date={0}__CipherId={1}__Type={2}",
-                            CoreHelpers.DateTimeToTableStorageKey(Date), CipherId, (int)Type);
-                    }
-                    break;
-                case EventType.Collection_Created:
-                case EventType.Collection_Updated:
-                case EventType.Collection_Deleted:
-                    PartitionKey = $"OrganizationId={OrganizationId}";
-                    RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
-                        CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, (int)Type);
-                    break;
-                case EventType.Group_Created:
-                case EventType.Group_Updated:
-                case EventType.Group_Deleted:
-                    PartitionKey = $"OrganizationId={OrganizationId}";
-                    RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
-                        CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, (int)Type);
-                    break;
-                case EventType.OrganizationUser_Invited:
-                case EventType.OrganizationUser_Confirmed:
-                case EventType.OrganizationUser_Updated:
-                case EventType.OrganizationUser_Removed:
-                case EventType.OrganizationUser_UpdatedGroups:
-                    PartitionKey = $"OrganizationId={OrganizationId}";
-                    RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
-                        CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, (int)Type);
-                    break;
-                case EventType.Organization_Updated:
-                    PartitionKey = $"OrganizationId={OrganizationId}";
-                    RowKey = string.Format("Date={0}__ActingUserId={1}__Type={2}",
-                        CoreHelpers.DateTimeToTableStorageKey(Date), ActingUserId, (int)Type);
-                    break;
-                default:
-                    break;
-            }
         }
 
         public DateTime Date { get; set; }
@@ -130,5 +56,63 @@ namespace Bit.Core.Models.Data
                 Type = (EventType)properties[nameof(Type)].Int32Value;
             }
         }
+
+        public static List<EventTableEntity> IndexEvent(IEvent e)
+        {
+            if(e.OrganizationId.HasValue)
+            {
+                return IndexOrgEvent(e);
+            }
+            else
+            {
+                return new List<EventTableEntity> { IndexUserEvent(e) };
+            }
+        }
+
+        private static List<EventTableEntity> IndexOrgEvent(IEvent e)
+        {
+            var uniquifier = Guid.NewGuid();
+            var pKey = $"OrganizationId={e.OrganizationId}";
+            var dateKey = CoreHelpers.DateTimeToTableStorageKey(e.Date);
+
+            var entities = new List<EventTableEntity>
+            {
+                new EventTableEntity(e)
+                {
+                    PartitionKey = pKey,
+                    RowKey = string.Format("Date={0}__Uniquifier={1}", dateKey, uniquifier)
+                }
+            };
+
+            if(e.ActingUserId.HasValue)
+            {
+                entities.Add(new EventTableEntity(e)
+                {
+                    PartitionKey = pKey,
+                    RowKey = string.Format("ActingUserId={0}__Date={1}__Uniquifier={2}", e.ActingUserId, dateKey, uniquifier)
+                });
+            }
+
+            if(e.CipherId.HasValue)
+            {
+                entities.Add(new EventTableEntity(e)
+                {
+                    PartitionKey = pKey,
+                    RowKey = string.Format("CipherId={0}__Date={1}__Uniquifier={2}", e.CipherId, dateKey, uniquifier)
+                });
+            }
+
+            return entities;
+        }
+
+        private static EventTableEntity IndexUserEvent(IEvent e)
+        {
+            var uniquifier = Guid.NewGuid();
+            return new EventTableEntity(e)
+            {
+                PartitionKey = $"UserId={e.UserId}",
+                RowKey = string.Format("Date={0}__Uniquifier={1}", CoreHelpers.DateTimeToTableStorageKey(e.Date), uniquifier)
+            };
+        }
     }
 }
diff --git a/src/Core/Repositories/SqlServer/EventRepository.cs b/src/Core/Repositories/SqlServer/EventRepository.cs
index d96d689d73..bbc40bc524 100644
--- a/src/Core/Repositories/SqlServer/EventRepository.cs
+++ b/src/Core/Repositories/SqlServer/EventRepository.cs
@@ -42,11 +42,17 @@ namespace Bit.Core.Repositories.SqlServer
 
         public async Task CreateManyAsync(IList<IEvent> entities)
         {
-            if(!entities.Any())
+            if(!entities?.Any() ?? true)
             {
                 return;
             }
 
+            if(entities.Count == 1)
+            {
+                await CreateAsync(entities.First());
+                return;
+            }
+
             using(var connection = new SqlConnection(ConnectionString))
             {
                 connection.Open();
diff --git a/src/EventsProcessor/Functions.cs b/src/EventsProcessor/Functions.cs
index 77b2af2c8d..81491a38c7 100644
--- a/src/EventsProcessor/Functions.cs
+++ b/src/EventsProcessor/Functions.cs
@@ -39,18 +39,22 @@ namespace Bit.EventsProcessor
 
             try
             {
+                var events = new List<IEvent>();
+
                 var token = JToken.Parse(message);
                 if(token is JArray)
                 {
-                    var events = token.ToObject<List<EventMessage>>()
-                        .Select(e => new EventTableEntity(e) as IEvent).ToList();
-                    await _eventWriteService.CreateManyAsync(events);
+                    var indexedEntities = token.ToObject<List<EventMessage>>()
+                        .SelectMany(e => EventTableEntity.IndexEvent(e));
+                    events.AddRange(indexedEntities);
                 }
                 else if(token is JObject)
                 {
-                    var e = token.ToObject<EventMessage>();
-                    await _eventWriteService.CreateAsync(new EventTableEntity(e));
+                    var eventMessage = token.ToObject<EventMessage>();
+                    events.AddRange(EventTableEntity.IndexEvent(eventMessage));
                 }
+
+                await _eventWriteService.CreateManyAsync(events);
             }
             catch(JsonReaderException)
             {