Skip to content

Commit

Permalink
impl paginating
Browse files Browse the repository at this point in the history
  • Loading branch information
kinosang committed Apr 17, 2024
1 parent 51fb22f commit 758981f
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Schemata.Abstractions.Exceptions;

public class InvalidArgumentException : HttpException
{
public InvalidArgumentException(
int status = 400,
string? message = "An error occurred while processing your request.") : base(status, message) { }
}
1 change: 0 additions & 1 deletion src/Schemata.Entity.Repository/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Schemata.Abstractions.Advices;

namespace Schemata.Entity.Repository;

Expand Down
1 change: 0 additions & 1 deletion src/Schemata.Entity.Repository/IRepository`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Schemata.Abstractions.Advices;

namespace Schemata.Entity.Repository;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Linq;
using System.Threading.Tasks;
using Humanizer;
using Microsoft.AspNetCore.Authorization;
Expand Down Expand Up @@ -38,6 +39,6 @@ public static class HttpContextExtensions
return AuthorizationResult.Success();
}

throw new AuthorizationException();
throw new AuthorizationException(401, result.Failure?.FailureReasons.FirstOrDefault()?.Message ?? "");
}
}
11 changes: 11 additions & 0 deletions src/Schemata.Resource.Foundation/Extensions/QueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Schemata.Abstractions.Entities;
using Schemata.Resource.Foundation.Grammars;
using Schemata.Resource.Foundation.Grammars.Terms;
using Schemata.Resource.Foundation.Models;

// ReSharper disable once CheckNamespace
namespace System.Linq;
Expand Down Expand Up @@ -57,6 +58,16 @@ public static class QueryableExtensions
return query;
}

public static Func<IQueryable<T>, IQueryable<T>> ApplyPaginating<T>(
this Func<IQueryable<T>, IQueryable<T>> query,
PageToken token) {
var build = query;

query = q => build(q).Skip(token.Skip).Take(token.PageSize);

return query;
}

public static IOrderedQueryable<T> ApplyOrdering<T>(
this IQueryable<T> source,
Expression<Func<T, object>> select,
Expand Down
7 changes: 0 additions & 7 deletions src/Schemata.Resource.Foundation/Models/ListResponse.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Schemata.Resource.Foundation.Converters;

namespace Schemata.Resource.Foundation.Models;

public class ListResponse<TSummary>
{
[JsonConverter(typeof(ListResponseJsonConverter))]
public virtual IEnumerable<TSummary>? Entities { get; set; }

public virtual long? TotalSize { get; set; }

public virtual int? PageSize { get; set; }

public virtual int? Skip { get; set; }

public virtual string? NextPageToken { get; set; }
}
60 changes: 60 additions & 0 deletions src/Schemata.Resource.Foundation/Models/PageToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Schemata.Resource.Foundation.Models;

public class PageToken
{
public virtual string? Filter { get; set; }

public virtual string? OrderBy { get; set; }

public virtual bool? ShowDeleted { get; set; }

public virtual int PageSize { get; set; }

public virtual int Skip { get; set; }

public async Task<string> ToStringAsync() {
var json = JsonSerializer.Serialize(this);
var bytes = Encoding.UTF8.GetBytes(json);

using var ms = new MemoryStream();
await using var gz = new BrotliStream(ms, CompressionLevel.Optimal);
gz.Write(bytes, 0, bytes.Length);
gz.Close();

return Convert.ToBase64String(ms.ToArray()).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}

public static async Task<PageToken?> FromStringAsync(string? token) {
if (string.IsNullOrWhiteSpace(token)) {
return null;
}

var base64 = token.Replace('_', '/').Replace('-', '+');
switch (base64.Length % 4) {
case 2:
base64 += "==";
break;
case 3:
base64 += "=";
break;
}

var bytes = Convert.FromBase64String(base64);

using var ms = new MemoryStream(bytes);
await using var gz = new BrotliStream(ms, CompressionMode.Decompress);

try {
return await JsonSerializer.DeserializeAsync<PageToken>(gz);
} catch {
return null;
}
}
}
52 changes: 50 additions & 2 deletions src/Schemata.Resource.Http/ResourceController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Humanizer;
using Microsoft.AspNetCore.Mvc;
using Schemata.Abstractions.Advices;
using Schemata.Abstractions.Entities;
using Schemata.Abstractions.Exceptions;
using Schemata.Entity.Repository;
using Schemata.Mapping.Skeleton;
using Schemata.Resource.Foundation.Advices;
Expand Down Expand Up @@ -46,6 +48,39 @@ public class ResourceController<TEntity, TRequest, TDetail, TSummary> : Controll
return EmptyResult;
}

var token = await PageToken.FromStringAsync(request.PageToken) ?? new PageToken {
Filter = request.Filter,
OrderBy = request.OrderBy,
ShowDeleted = request.ShowDeleted,
};
if (token.Filter != request.Filter
|| token.OrderBy != request.OrderBy
|| token.ShowDeleted != request.ShowDeleted) {
throw new InvalidArgumentException {
Errors = new() {
[nameof(request.PageToken).Underscore()] = "mismatch",
},
};
}

if (request.PageSize.HasValue) {
token.PageSize = request.PageSize.Value;
}

token.PageSize = token.PageSize switch {
<= 0 => 25,
> 100 => 100,
var _ => token.PageSize,
};

if (request.Skip.HasValue) {
token.Skip += request.Skip.Value;
}

if (token.Skip < 0) {
token.Skip = 0;
}

var repository = Repository.Once();

Func<IQueryable<TEntity>, IQueryable<TEntity>> query = q => q;
Expand All @@ -64,15 +99,28 @@ public class ResourceController<TEntity, TRequest, TDetail, TSummary> : Controll
repository = repository.SuppressQuerySoftDelete();
}

var response = new ListResponse<TSummary> {
TotalSize = await repository.LongCountAsync(q => query(q), HttpContext.RequestAborted),
};

query = query.ApplyPaginating(token);

var entities = await repository.ListAsync(q => query(q), HttpContext.RequestAborted)
.ToListAsync(HttpContext.RequestAborted);

if (!await Advices<IResourceResponsesAdvice<TEntity>>.AdviseAsync(ServiceProvider, ctx, entities, HttpContext, HttpContext.RequestAborted)) {
return EmptyResult;
}

var summaries = Mapper.Map<IEnumerable<TEntity>, IEnumerable<TSummary>>(entities);
return Ok(summaries);
token.Skip += token.PageSize;

if (entities.Count >= token.PageSize) {
response.NextPageToken = await token.ToStringAsync();
}

response.Entities = Mapper.Map<IEnumerable<TEntity>, IEnumerable<TSummary>>(entities);

return Ok(response);
}

[HttpGet("{id}")]
Expand Down

0 comments on commit 758981f

Please sign in to comment.