Skip to content

Commit

Permalink
Add tickets support (#153)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmirgaleev authored Sep 1, 2023
1 parent 790091c commit 2c813d8
Show file tree
Hide file tree
Showing 99 changed files with 12,037 additions and 89 deletions.
17 changes: 9 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ jobs:
runs-on: windows-latest
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
steps:
- name: Set up JDK 11
uses: actions/setup-java@v1
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 1.11
- uses: actions/checkout@v2
java-version: 17
distribution: 'zulu' # Alternative distribution options are available.
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Cache SonarCloud packages
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: ~\sonar\cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache SonarCloud scanner
id: cache-sonar-scanner
uses: actions/cache@v1
uses: actions/cache@v3
with:
path: .\.sonar\scanner
key: ${{ runner.os }}-sonar-scanner
Expand All @@ -44,6 +45,6 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: powershell
run: |
.\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
.\.sonar\scanner\dotnet-sonarscanner begin /k:"baking-bad_tzkt" /o:"baking-bad" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
dotnet build
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}"
.\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,7 @@ mumbai-stop:
docker-compose -f docker-compose.mumbai.yml down

mumbai-db-start:
docker-compose -f docker-compose.mumbai.yml up -d mumbai-db
reset:
docker-compose -f docker-compose.mumbai.yml down --volumes
docker-compose -f docker-compose.mumbai.yml up -d mumbai-db
309 changes: 309 additions & 0 deletions Tzkt.Api/Controllers/TicketsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
using Microsoft.AspNetCore.Mvc;
using Tzkt.Api.Models;
using Tzkt.Api.Repositories;
using Tzkt.Api.Services;
using Tzkt.Api.Services.Cache;

namespace Tzkt.Api.Controllers
{
[ApiController]
[Route("v1/tickets")]
public class TicketsController : ControllerBase
{
readonly TicketsRepository Tickets;
readonly StateCache State;
readonly ResponseCacheService ResponseCache;

public TicketsController(TicketsRepository tickets, StateCache state, ResponseCacheService responseCache)
{
Tickets = tickets;
State = state;
ResponseCache = responseCache;
}

#region tickets
/// <summary>
/// Get tickets count
/// </summary>
/// <remarks>
/// Returns a total number of tickets.
/// </remarks>
/// <param name="filter">Filter</param>
/// <returns></returns>
[HttpGet("count")]
public async Task<ActionResult<int>> GetTicketsCount([FromQuery] TicketFilter filter)
{
if (filter.Empty)
return Ok(State.Current.TicketsCount);

var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

var res = await Tickets.GetTicketsCount(filter);
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}

/// <summary>
/// Get tickets
/// </summary>
/// <remarks>
/// Returns a list of tickets.
/// </remarks>
/// <param name="filter">Filter</param>
/// <param name="pagination">Pagination</param>
/// <param name="selection">Selection</param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult<IEnumerable<Ticket>>> GetTickets(
[FromQuery] TicketFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection)
{
var query = ResponseCacheService.BuildKey(Request.Path.Value,
("filter", filter), ("pagination", pagination), ("selection", selection));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

object res;
if (selection.select == null)
{
res = await Tickets.GetTickets(filter, pagination);
}
else
{
res = new SelectionResponse
{
Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
Rows = await Tickets.GetTickets(filter, pagination, selection.select.Fields ?? selection.select.Values)
};
}
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}
#endregion

#region ticket balances
/// <summary>
/// Get ticket balances count
/// </summary>
/// <remarks>
/// Returns a total number of ticket balances.
/// </remarks>
/// <param name="filter">Filter</param>
/// <returns></returns>
[HttpGet("balances/count")]
public async Task<ActionResult<int>> GetTicketBalancesCount([FromQuery] TicketBalanceFilter filter)
{
if (filter.Empty)
return Ok(State.Current.TicketBalancesCount);

#region optimizations
if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
return Ok(0);
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

var res = await Tickets.GetTicketBalancesCount(filter);
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}

/// <summary>
/// Get ticket balances
/// </summary>
/// <remarks>
/// Returns a list of ticket balances.
/// </remarks>
/// <param name="filter">Filter</param>
/// <param name="pagination">Pagination</param>
/// <param name="selection">Selection</param>
/// <returns></returns>
[HttpGet("balances")]
public async Task<ActionResult<IEnumerable<TicketBalance>>> GetTicketBalances(
[FromQuery] TicketBalanceFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection)
{
#region optimizations
if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
return Ok(Enumerable.Empty<TicketBalance>());
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("filter", filter), ("pagination", pagination), ("selection", selection));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

object res;
if (selection.select == null)
{
res = await Tickets.GetTicketBalances(filter, pagination);
}
else
{
res = new SelectionResponse
{
Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
Rows = await Tickets.GetTicketBalances(filter, pagination, selection.select.Fields ?? selection.select.Values)
};
}
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}
#endregion

#region ticket transfers
/// <summary>
/// Get ticket transfers count
/// </summary>
/// <remarks>
/// Returns the total number of ticket transfers.
/// </remarks>
/// <param name="filter">Filter</param>
/// <returns></returns>
[HttpGet("transfers/count")]
public async Task<ActionResult<int>> GetTicketTransfersCount([FromQuery] TicketTransferFilter filter)
{
if (filter.Empty)
return Ok(State.Current.TicketTransfersCount);

#region optimizations
if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull))
return Ok(0);

if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull))
return Ok(0);

if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull))
return Ok(0);
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value, ("filter", filter));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

var res = await Tickets.GetTicketTransfersCount(filter);
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}

/// <summary>
/// Get ticket transfers
/// </summary>
/// <remarks>
/// Returns a list of ticket transfers.
/// </remarks>
/// <param name="filter">Filter</param>
/// <param name="pagination">Pagination</param>
/// <param name="selection">Selection</param>
/// <returns></returns>
[HttpGet("transfers")]
public async Task<ActionResult<IEnumerable<TicketTransfer>>> GetTicketTransfers(
[FromQuery] TicketTransferFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection)
{
#region optimizations
if (filter.from != null && (filter.from.Eq == -1 || filter.from.In?.Count == 0 && !filter.from.InHasNull))
return Ok(Enumerable.Empty<TicketTransfer>());

if (filter.to != null && (filter.to.Eq == -1 || filter.to.In?.Count == 0 && !filter.to.InHasNull))
return Ok(Enumerable.Empty<TicketTransfer>());

if (filter.anyof != null && (filter.anyof.Eq == -1 || filter.anyof.In?.Count == 0 && !filter.anyof.InHasNull))
return Ok(Enumerable.Empty<TicketTransfer>());
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("filter", filter), ("pagination", pagination), ("selection", selection));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

object res;
if (selection.select == null)
{
res = await Tickets.GetTicketTransfers(filter, pagination);
}
else
{
res = new SelectionResponse
{
Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
Rows = await Tickets.GetTicketTransfers(filter, pagination, selection.select.Fields ?? selection.select.Values)
};
}
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}
#endregion

#region historical balances
/// <summary>
/// Get historical ticket balances
/// </summary>
/// <remarks>
/// Returns a list of ticket balances at the end of the specified block.
/// Note, this endpoint is quite heavy, therefore at least one of the filters
/// (`account`, `ticket.id`, `ticket.ticketer`) must be specified.
/// </remarks>
/// <param name="level">Level of the block at the end of which historical balances must be calculated</param>
/// <param name="filter">Filter</param>
/// <param name="pagination">Pagination</param>
/// <param name="selection">Selection</param>
/// <returns></returns>
[HttpGet("historical_balances/{level:int}")]
public async Task<ActionResult<IEnumerable<TicketBalanceShort>>> GetHistoricalTicketBalances(int level,
[FromQuery] TicketBalanceShortFilter filter,
[FromQuery] Pagination pagination,
[FromQuery] Selection selection)
{

if (filter.account?.Eq == null &&
filter.account?.In == null &&
filter.ticket.id?.Eq == null &&
filter.ticket.id?.In == null &&
filter.ticket.ticketer?.Eq == null &&
filter.ticket.ticketer?.In == null)
return new BadRequest("query", "At least one of the filters (`account`, `ticket.id`, `ticket.ticketer`) must be specified");

#region optimizations
if (filter.account != null && (filter.account.Eq == -1 || filter.account.In?.Count == 0 && !filter.account.InHasNull))
return Ok(Enumerable.Empty<TicketBalanceShort>());
#endregion

var query = ResponseCacheService.BuildKey(Request.Path.Value,
("filter", filter), ("pagination", pagination), ("selection", selection));

if (ResponseCache.TryGet(query, out var cached))
return this.Bytes(cached);

object res;
if (selection.select == null)
{
res = await Tickets.GetHistoricalTicketBalances(level, filter, pagination);
}
else
{
res = new SelectionResponse
{
Cols = selection.select.Fields?.Select(x => x.Alias).ToArray(),
Rows = await Tickets.GetHistoricalTicketBalances(level, filter, pagination, selection.select.Fields ?? selection.select.Values)
};
}
cached = ResponseCache.Set(query, res);
return this.Bytes(cached);
}
#endregion
}
}
Loading

0 comments on commit 2c813d8

Please sign in to comment.