1
0
mirror of https://github.com/bitwarden/server.git synced 2025-01-04 19:07:50 +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.Services;
using Bit.Core;
using Bit.Core.Models.Data;
namespace Bit.Api.Controllers
{
@ -31,18 +32,19 @@ namespace Bit.Api.Controllers
[HttpGet("")]
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 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<EventResponseModel>(responses);
var result = await _eventRepository.GetManyByUserAsync(userId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
var responses = result.Data.Select(e => new EventResponseModel(e));
return new ListResponseModel<EventResponseModel>(responses, result.ContinuationToken);
}
[HttpGet("~/organizations/{id}/events")]
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);
if(!_currentContext.OrganizationAdmin(orgId))
@ -51,9 +53,10 @@ namespace Bit.Api.Controllers
}
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<EventResponseModel>(responses);
var result = await _eventRepository.GetManyByOrganizationAsync(orgId, dateRange.Item1, dateRange.Item2,
new PageOptions { ContinuationToken = continuationToken });
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)

View File

@ -5,12 +5,14 @@ namespace Bit.Core.Models.Api
{
public class ListResponseModel<T> : ResponseModel where T : ResponseModel
{
public ListResponseModel(IEnumerable<T> data)
public ListResponseModel(IEnumerable<T> data, string continuationToken = null)
: base("list")
{
Data = data;
ContinuationToken = continuationToken;
}
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
{
Task<ICollection<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate);
Task<ICollection<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate);
Task<PagedResult<IEvent>> GetManyByUserAsync(Guid userId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId, DateTime startDate, DateTime endDate,
PageOptions pageOptions);
Task CreateAsync(IEvent e);
Task CreateManyAsync(IList<IEvent> e);
}

View File

@ -19,13 +19,15 @@ namespace Bit.Core.Repositories.SqlServer
: 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
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();
}

View File

@ -24,63 +24,40 @@ namespace Bit.Core.Repositories.TableStorage
_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 end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilter($"UserId={userId}", $"Date={start}", $"Date={end}");
var rowFilter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"Date={start}_"),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"Date={end}`"));
var query = new TableQuery<EventTableEntity>().Where(filter).Take(pageOptions.PageSize);
var result = new PagedResult<IEvent>();
var continuationToken = DeserializeContinuationToken(pageOptions?.ContinuationToken);
var filter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"UserId={userId}"),
TableOperators.And,
rowFilter);
var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken);
result.ContinuationToken = SerializeContinuationToken(queryResults.ContinuationToken);
result.Data.AddRange(queryResults.Results);
var query = new TableQuery<EventTableEntity>().Where(filter);
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();
return result;
}
public async Task<ICollection<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
DateTime startDate, DateTime endDate)
public async Task<PagedResult<IEvent>> GetManyByOrganizationAsync(Guid organizationId,
DateTime startDate, DateTime endDate, PageOptions pageOptions)
{
var start = CoreHelpers.DateTimeToTableStorageKey(startDate);
var end = CoreHelpers.DateTimeToTableStorageKey(endDate);
var filter = MakeFilter($"OrganizationId={organizationId}", $"Date={start}", $"Date={end}");
var rowFilter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, $"Date={start}_"),
TableOperators.And,
TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThanOrEqual, $"Date={end}`"));
var query = new TableQuery<EventTableEntity>().Where(filter).Take(pageOptions.PageSize);
var result = new PagedResult<IEvent>();
var continuationToken = DeserializeContinuationToken(pageOptions?.ContinuationToken);
var filter = TableQuery.CombineFilters(
TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, $"OrganizationId={organizationId}"),
TableOperators.And,
rowFilter);
var queryResults = await _table.ExecuteQuerySegmentedAsync(query, continuationToken);
result.ContinuationToken = SerializeContinuationToken(queryResults.ContinuationToken);
result.Data.AddRange(queryResults.Results);
var query = new TableQuery<EventTableEntity>().Where(filter);
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();
return result;
}
public async Task CreateAsync(IEvent e)
@ -142,5 +119,51 @@ namespace Bit.Core.Repositories.TableStorage
{
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.RegularExpressions;
using Dapper;
using System.Globalization;
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 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 string _version;
private static readonly string _qwertyDvorakMap = "-=qwertyuiop[]asdfghjkl;'zxcvbnm,./_+QWERTYUIO" +
@ -412,12 +414,16 @@ namespace Bit.Core.Utilities
public static string DateTimeToTableStorageKey(DateTime? date = null)
{
if(date == null)
if(date.HasValue)
{
date = date.Value.ToUniversalTime();
}
else
{
date = DateTime.UtcNow;
}
return date.Value.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fff");
return _max.Subtract(date.Value).TotalMilliseconds.ToString(CultureInfo.InvariantCulture);
}
}
}