mirror of
https://github.com/bitwarden/server.git
synced 2025-01-06 19:28:08 +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.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)
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user