From adc23bf007445200dca7b236f63c74c0f733f9bc Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 14 Dec 2017 12:33:50 -0500 Subject: [PATCH] stub out API for events --- src/Api/Controllers/EventsController.cs | 85 +++++++++++++++++++ .../Models/Api/Response/EventResponseModel.cs | 38 +++++++++ src/Core/Repositories/IEventRepository.cs | 1 + .../Repositories/SqlServer/EventRepository.cs | 5 ++ .../TableStorage/EventRepository.cs | 36 +++++++- 5 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/Api/Controllers/EventsController.cs create mode 100644 src/Core/Models/Api/Response/EventResponseModel.cs diff --git a/src/Api/Controllers/EventsController.cs b/src/Api/Controllers/EventsController.cs new file mode 100644 index 000000000..7309498f7 --- /dev/null +++ b/src/Api/Controllers/EventsController.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Bit.Core.Repositories; +using Microsoft.AspNetCore.Authorization; +using Bit.Core.Exceptions; +using Bit.Core.Models.Api; +using Bit.Core.Services; +using Bit.Core; + +namespace Bit.Api.Controllers +{ + [Route("events")] + [Authorize("Application")] + public class EventsController : Controller + { + private readonly IUserService _userService; + private readonly IEventRepository _eventRepository; + private readonly CurrentContext _currentContext; + + public EventsController( + IUserService userService, + IEventRepository eventRepository, + CurrentContext currentContext) + { + _userService = userService; + _eventRepository = eventRepository; + _currentContext = currentContext; + } + + [HttpGet("user")] + public async Task> GetUser( + [FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null) + { + var dateRange = GetDateRange(start, end); + var userId = _userService.GetProperUserId(User).Value; + var events = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2); + var responses = events.Select(e => new EventResponseModel(e)); + return new ListResponseModel(responses); + } + + [HttpGet("organization/{id}")] + public async Task> GetOrganization(string id, + [FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null) + { + var orgId = new Guid(id); + if(!_currentContext.OrganizationAdmin(orgId)) + { + throw new NotFoundException(); + } + + var dateRange = GetDateRange(start, end); + var events = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2); + var responses = events.Select(e => new EventResponseModel(e)); + return new ListResponseModel(responses); + } + + private Tuple GetDateRange(DateTime? start, DateTime? end) + { + var endSet = false; + if(!end.HasValue) + { + endSet = true; + end = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1); + } + + if(!start.HasValue) + { + start = end.Value.AddDays(-30); + if(endSet) + { + start = end.Value.AddMilliseconds(1); + } + } + + if(start.Value > end.Value || (end.Value - start.Value) > TimeSpan.FromDays(32)) + { + throw new BadRequestException("Invalid date range."); + } + + return new Tuple(start.Value, end.Value); + } + } +} diff --git a/src/Core/Models/Api/Response/EventResponseModel.cs b/src/Core/Models/Api/Response/EventResponseModel.cs new file mode 100644 index 000000000..aecfae7ba --- /dev/null +++ b/src/Core/Models/Api/Response/EventResponseModel.cs @@ -0,0 +1,38 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Data; + +namespace Bit.Core.Models.Api +{ + public class EventResponseModel : ResponseModel + { + public EventResponseModel(IEvent ev) + : base("event") + { + if(ev == null) + { + throw new ArgumentNullException(nameof(ev)); + } + + Type = ev.Type; + UserId = ev.UserId; + OrganizationId = ev.OrganizationId; + CipherId = ev.CipherId; + CollectionId = ev.CollectionId; + GroupId = ev.GroupId; + OrganizationUserId = ev.OrganizationUserId; + ActingUserId = ev.ActingUserId; + Date = ev.Date; + } + + public EventType Type { get; set; } + public Guid? UserId { get; set; } + public Guid? OrganizationId { get; set; } + public Guid? CipherId { get; set; } + public Guid? CollectionId { get; set; } + public Guid? GroupId { get; set; } + public Guid? OrganizationUserId { get; set; } + public Guid? ActingUserId { get; set; } + public DateTime Date { get; set; } + } +} diff --git a/src/Core/Repositories/IEventRepository.cs b/src/Core/Repositories/IEventRepository.cs index 4baf4eda8..01058b6c8 100644 --- a/src/Core/Repositories/IEventRepository.cs +++ b/src/Core/Repositories/IEventRepository.cs @@ -8,6 +8,7 @@ namespace Bit.Core.Repositories public interface IEventRepository { Task> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate); + Task> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate); Task CreateAsync(IEvent e); Task CreateManyAsync(IList e); } diff --git a/src/Core/Repositories/SqlServer/EventRepository.cs b/src/Core/Repositories/SqlServer/EventRepository.cs index a58123353..d96d689d7 100644 --- a/src/Core/Repositories/SqlServer/EventRepository.cs +++ b/src/Core/Repositories/SqlServer/EventRepository.cs @@ -25,6 +25,11 @@ namespace Bit.Core.Repositories.SqlServer throw new NotImplementedException(); } + public Task> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate) + { + throw new NotImplementedException(); + } + public async Task CreateAsync(IEvent e) { if(!(e is Event ev)) diff --git a/src/Core/Repositories/TableStorage/EventRepository.cs b/src/Core/Repositories/TableStorage/EventRepository.cs index 415257de8..efdafd7ed 100644 --- a/src/Core/Repositories/TableStorage/EventRepository.cs +++ b/src/Core/Repositories/TableStorage/EventRepository.cs @@ -24,8 +24,7 @@ namespace Bit.Core.Repositories.TableStorage _table = tableClient.GetTableReference("event"); } - public async Task> GetManyByUserAsync(Guid userId, - DateTime startDate, DateTime endDate) + public async Task> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate) { var start = CoreHelpers.DateTimeToTableStorageKey(startDate); var end = CoreHelpers.DateTimeToTableStorageKey(endDate); @@ -48,7 +47,38 @@ namespace Bit.Core.Repositories.TableStorage var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken); continuationToken = queryResults.ContinuationToken; results.AddRange(queryResults.Results); - } while(continuationToken != null); + } + while(continuationToken != null); + + return results.Select(r => r as IEvent).ToList(); + } + + public async Task> GetManyByOrganizationAsync(Guid organizationId, + DateTime startDate, DateTime endDate) + { + var start = CoreHelpers.DateTimeToTableStorageKey(startDate); + var end = CoreHelpers.DateTimeToTableStorageKey(endDate); + + var rowFilter = TableQuery.CombineFilters( + TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"{start}_"), + TableOperators.And, + TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"{end}`")); + + var filter = TableQuery.CombineFilters( + TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"OrganizationId={organizationId}"), + TableOperators.And, + rowFilter); + + var query = new TableQuery().Where(filter); + var results = new List(); + TableContinuationToken continuationToken = null; + do + { + var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken); + continuationToken = queryResults.ContinuationToken; + results.AddRange(queryResults.Results); + } + while(continuationToken != null); return results.Select(r => r as IEvent).ToList(); }