1
0
mirror of https://github.com/bitwarden/server.git synced 2025-01-06 19:28:08 +01:00

event paging

This commit is contained in:
Kyle Spearrin 2017-12-15 15:23:57 -05:00
parent 79d46578b0
commit 125eab11dc
8 changed files with 114 additions and 58 deletions

View File

@ -8,6 +8,7 @@ using Bit.Core.Exceptions;
using Bit.Core.Models.Api; using Bit.Core.Models.Api;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core; using Bit.Core;
using Bit.Core.Models.Data;
namespace Bit.Api.Controllers namespace Bit.Api.Controllers
{ {
@ -31,18 +32,19 @@ namespace Bit.Api.Controllers
[HttpGet("")] [HttpGet("")]
public async Task<ListResponseModel<EventResponseModel>> GetUser( public async Task<ListResponseModel<EventResponseModel>> GetUser(
[FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null) [FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null, [FromQuery]string continuationToken = null)
{ {
var dateRange = GetDateRange(start, end); var dateRange = GetDateRange(start, end);
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var events = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2); var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
var responses = events.Select(e => new EventResponseModel(e)); new PageOptions { ContinuationToken = continuationToken });
return new ListResponseModel<EventResponseModel>(responses); var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
} }
[HttpGet("~/organizations/{id}/events")] [HttpGet("~/organizations/{id}/events")]
public async Task<ListResponseModel<EventResponseModel>> GetOrganization(string id, public async Task<ListResponseModel<EventResponseModel>> GetOrganization(string id,
[FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null) [FromQuery]DateTime? start = null, [FromQuery]DateTime? end = null, [FromQuery]string continuationToken = null)
{ {
var orgId = new Guid(id); var orgId = new Guid(id);
if(!_currentContext.OrganizationAdmin(orgId)) if(!_currentContext.OrganizationAdmin(orgId))
@ -51,9 +53,10 @@ namespace Bit.Api.Controllers
} }
var dateRange = GetDateRange(start, end); var dateRange = GetDateRange(start, end);
var events = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2); var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
var responses = events.Select(e => new EventResponseModel(e)); new PageOptions { ContinuationToken = continuationToken });
return new ListResponseModel<EventResponseModel>(responses); var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
} }
private Tuple<DateTime, DateTime> GetDateRange(DateTime? start, DateTime? end) private Tuple<DateTime, DateTime> GetDateRange(DateTime? start, DateTime? end)

View File

@ -5,12 +5,14 @@ namespace Bit.Core.Models.Api
{ {
public class ListResponseModel<T> : ResponseModel where T : ResponseModel public class ListResponseModel<T> : ResponseModel where T : ResponseModel
{ {
public ListResponseModel(IEnumerable<T> data) public ListResponseModel(IEnumerable<T> data, string continuationToken = null)
: base("list") : base("list")
{ {
Data = data; Data = data;
ContinuationToken = continuationToken;
} }
public IEnumerable<T> Data { get; set; } public IEnumerable<T> Data { get; set; }
public string ContinuationToken { get; set; }
} }
} }

View File

@ -0,0 +1,8 @@
namespace Bit.Core.Models.Data
{
public class PageOptions
{
public string ContinuationToken { get; set; }
public int PageSize { get; set; } = 100;
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Bit.Core.Models.Data
{
public class PagedResult<T>
{
public List<T> Data { get; set; } = new List<T>();
public string ContinuationToken { get; set; }
}
}

View File

@ -7,8 +7,10 @@ namespace Bit.Core.Repositories
{ {
public interface IEventRepository public interface IEventRepository
{ {
Task<ICollection<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate); Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
Task<ICollection<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate); PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task CreateAsync(IEvent e); Task CreateAsync(IEvent e);
Task CreateManyAsync(IList<IEvent> e); Task CreateManyAsync(IList<IEvent> e);
} }

View File

@ -19,13 +19,15 @@ namespace Bit.Core.Repositories.SqlServer
: base(connectionString) : base(connectionString)
{ } { }
public Task<ICollection<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate) public Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
PageOptions pageOptions)
{ {
// TODO // TODO
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task<ICollection<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate) public Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -24,63 +24,40 @@ namespace Bit.Core.Repositories.TableStorage
_table = tableClient.GetTableReference("event"); _table = tableClient.GetTableReference("event");
} }
public async Task<ICollection<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate) public async Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
PageOptions pageOptions)
{ {
var start = CoreHelpers.DateTimeToTableStorageKey(startDate); var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate); var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilter($"UserId={userId}", $"Date={start}", $"Date={end}");
var rowFilter = TableQuery.CombineFilters( var query = new TableQuery<EventTableEntity>().Where(filter).Take(pageOptions.PageSize);
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"Date={start}_"), var result = new PagedResult<IEvent>();
TableOperators.And, var continuationToken = DeserializeContinuationToken(pageOptions?.ContinuationToken);
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"Date={end}`"));
var filter = TableQuery.CombineFilters( var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken);
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"UserId={userId}"), result.ContinuationToken = SerializeContinuationToken(queryResults.ContinuationToken);
TableOperators.And, result.Data.AddRange(queryResults.Results);
rowFilter);
var query = new TableQuery<EventTableEntity>().Where(filter); return result;
var results = new List<EventTableEntity>();
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();
} }
public async Task<ICollection<IEvent>> GetManyByOrganizationAsync(Guid organizationId, public async Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
DateTime startDate, DateTime endDate) DateTime startDate, DateTime endDate, PageOptions pageOptions)
{ {
var start = CoreHelpers.DateTimeToTableStorageKey(startDate); var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate); var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilter($"OrganizationId={organizationId}", $"Date={start}", $"Date={end}");
var rowFilter = TableQuery.CombineFilters( var query = new TableQuery<EventTableEntity>().Where(filter).Take(pageOptions.PageSize);
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"Date={start}_"), var result = new PagedResult<IEvent>();
TableOperators.And, var continuationToken = DeserializeContinuationToken(pageOptions?.ContinuationToken);
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"Date={end}`"));
var filter = TableQuery.CombineFilters( var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken);
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"OrganizationId={organizationId}"), result.ContinuationToken = SerializeContinuationToken(queryResults.ContinuationToken);
TableOperators.And, result.Data.AddRange(queryResults.Results);
rowFilter);
var query = new TableQuery<EventTableEntity>().Where(filter); return result;
var results = new List<EventTableEntity>();
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();
} }
public async Task CreateAsync(IEvent e) public async Task CreateAsync(IEvent e)
@ -142,5 +119,51 @@ namespace Bit.Core.Repositories.TableStorage
{ {
await _table.ExecuteAsync(TableOperation.Insert(entity)); await _table.ExecuteAsync(TableOperation.Insert(entity));
} }
private string MakeFilter(string partitionKey, string rowStart, string rowEnd)
{
var rowFilter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"{rowStart}`"),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"{rowEnd}_"));
return TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey),
TableOperators.And,
rowFilter);
}
private string SerializeContinuationToken(TableContinuationToken token)
{
if(token == null)
{
return null;
}
return string.Format("{0}__{1}__{2}__{3}", (int)token.TargetLocation, token.NextTableName,
token.NextPartitionKey, token.NextRowKey);
}
private TableContinuationToken DeserializeContinuationToken(string token)
{
if(string.IsNullOrWhiteSpace(token))
{
return null;
}
var tokenParts = token.Split(new string[] { "__" }, StringSplitOptions.None);
if(tokenParts.Length < 4 || !Enum.TryParse(tokenParts[0], out StorageLocation tLoc))
{
return null;
}
return new TableContinuationToken
{
TargetLocation = tLoc,
NextTableName = string.IsNullOrWhiteSpace(tokenParts[1]) ? null : tokenParts[1],
NextPartitionKey = string.IsNullOrWhiteSpace(tokenParts[2]) ? null : tokenParts[2],
NextRowKey = string.IsNullOrWhiteSpace(tokenParts[3]) ? null : tokenParts[3]
};
}
} }
} }

View File

@ -11,6 +11,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dapper; using Dapper;
using System.Globalization;
namespace Bit.Core.Utilities namespace Bit.Core.Utilities
{ {
@ -18,6 +19,7 @@ namespace Bit.Core.Utilities
{ {
private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks; private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks;
private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly DateTime _max = new DateTime(9999, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static readonly Random _random = new Random(); private static readonly Random _random = new Random();
private static string _version; private static string _version;
private static readonly string _qwertyDvorakMap = "-=qwertyuiop[]asdfghjkl;'zxcvbnm,./_+QWERTYUIO" + private static readonly string _qwertyDvorakMap = "-=qwertyuiop[]asdfghjkl;'zxcvbnm,./_+QWERTYUIO" +
@ -412,12 +414,16 @@ namespace Bit.Core.Utilities
public static string DateTimeToTableStorageKey(DateTime? date = null) public static string DateTimeToTableStorageKey(DateTime? date = null)
{ {
if(date == null) if(date.HasValue)
{
date = date.Value.ToUniversalTime();
}
else
{ {
date = DateTime.UtcNow; date = DateTime.UtcNow;
} }
return date.Value.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fff"); return _max.Subtract(date.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
} }
} }
} }