mirror of
https://github.com/bitwarden/server.git
synced 2025-01-04 19:07:50 +01:00
event paging
This commit is contained in:
parent
79d46578b0
commit
125eab11dc
@ -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)
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
8
src/Core/Models/Data/PageOptions.cs
Normal file
8
src/Core/Models/Data/PageOptions.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Bit.Core.Models.Data
|
||||
{
|
||||
public class PageOptions
|
||||
{
|
||||
public string ContinuationToken { get; set; }
|
||||
public int PageSize { get; set; } = 100;
|
||||
}
|
||||
}
|
10
src/Core/Models/Data/PagedResult.cs
Normal file
10
src/Core/Models/Data/PagedResult.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user