Skip to content

Commit

Permalink
Add filtering contracts by tags
Browse files Browse the repository at this point in the history
  • Loading branch information
Groxan committed Apr 17, 2022
1 parent 8ad6af7 commit a30dff8
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 39 deletions.
34 changes: 18 additions & 16 deletions Tzkt.Api/Controllers/ContractsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public ContractsController(AccountRepository accounts, BigMapsRepository bigMaps
/// <remarks>
/// Returns a list of contract accounts.
/// </remarks>
/// <param name="kind">Contract kind to filter by (`delegator_contract` or `smart_contract`)</param>
/// <param name="kind">Contract kind to filter by (`delegator_contract`, `smart_contract`, or `asset`)</param>
/// <param name="tzips">Filters by tzips (`fa1`, `fa12`, or `fa2`)</param>
/// <param name="creator">Filters contracts by creator. Allowed fields for `.eqx` mode: `manager`, `delegate`.</param>
/// <param name="manager">Filters contracts by manager. Allowed fields for `.eqx` mode: `creator`, `delegate`.</param>
/// <param name="delegate">Filters contracts by delegate. Allowed fields for `.eqx` mode: `manager`, `creator`.</param>
Expand All @@ -49,6 +50,7 @@ public ContractsController(AccountRepository accounts, BigMapsRepository bigMaps
[HttpGet]
public async Task<ActionResult<IEnumerable<Contract>>> Get(
ContractKindParameter kind,
ContractTagsParameter tzips,
AccountParameter creator,
AccountParameter manager,
AccountParameter @delegate,
Expand Down Expand Up @@ -104,25 +106,25 @@ public async Task<ActionResult<IEnumerable<Contract>>> Get(
#endregion

if (select == null)
return Ok(await Accounts.GetContracts(kind, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, includeStorage));
return Ok(await Accounts.GetContracts(kind, tzips, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, includeStorage));

if (select.Values != null)
{
if (select.Values.Length == 1)
return Ok(await Accounts.GetContracts(kind, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Values[0], includeStorage));
return Ok(await Accounts.GetContracts(kind, tzips, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Values[0], includeStorage));
else
return Ok(await Accounts.GetContracts(kind, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Values, includeStorage));
return Ok(await Accounts.GetContracts(kind, tzips, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Values, includeStorage));
}
else
{
if (select.Fields.Length == 1)
return Ok(await Accounts.GetContracts(kind, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Fields[0], includeStorage));
return Ok(await Accounts.GetContracts(kind, tzips, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Fields[0], includeStorage));
else
{
return Ok(new SelectionResponse
{
Cols = select.Fields,
Rows = await Accounts.GetContracts(kind, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Fields, includeStorage)
Rows = await Accounts.GetContracts(kind, tzips, creator, manager, @delegate, balance, lastActivity, typeHash, codeHash, sort, offset, limit, select.Fields, includeStorage)
});
}
}
Expand Down Expand Up @@ -191,25 +193,25 @@ public async Task<ActionResult<IEnumerable<Contract>>> GetSame(
var codeHash = new Int32Parameter { Eq = contract.CodeHash };

if (select == null)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, codeHash, sort, offset, limit, includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, null, codeHash, sort, offset, limit, includeStorage));

if (select.Values != null)
{
if (select.Values.Length == 1)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Values[0], includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Values[0], includeStorage));
else
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Values, includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Values, includeStorage));
}
else
{
if (select.Fields.Length == 1)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Fields[0], includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Fields[0], includeStorage));
else
{
return Ok(new SelectionResponse
{
Cols = select.Fields,
Rows = await Accounts.GetContracts(null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Fields, includeStorage)
Rows = await Accounts.GetContracts(null, null, null, null, null, null, null, null, codeHash, sort, offset, limit, select.Fields, includeStorage)
});
}
}
Expand Down Expand Up @@ -250,25 +252,25 @@ public async Task<ActionResult<IEnumerable<Contract>>> GetSimilar(
var typeHash = new Int32Parameter { Eq = contract.TypeHash };

if (select == null)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, typeHash, null, sort, offset, limit, includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, typeHash, null, sort, offset, limit, includeStorage));

if (select.Values != null)
{
if (select.Values.Length == 1)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Values[0], includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Values[0], includeStorage));
else
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Values, includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Values, includeStorage));
}
else
{
if (select.Fields.Length == 1)
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Fields[0], includeStorage));
return Ok(await Accounts.GetContracts(null, null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Fields[0], includeStorage));
else
{
return Ok(new SelectionResponse
{
Cols = select.Fields,
Rows = await Accounts.GetContracts(null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Fields, includeStorage)
Rows = await Accounts.GetContracts(null, null, null, null, null, null, null, typeHash, null, sort, offset, limit, select.Fields, includeStorage)
});
}
}
Expand Down
39 changes: 39 additions & 0 deletions Tzkt.Api/Extensions/ModelBindingContextExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,45 @@ public static bool TryGetBigMapActionList(this ModelBindingContext bindingContex
return true;
}

public static bool TryGetContractTags(this ModelBindingContext bindingContext, string name, ref bool hasValue, out int? result)
{
result = null;
var valueObject = bindingContext.ValueProvider.GetValue(name);

if (valueObject != ValueProviderResult.None)
{
bindingContext.ModelState.SetModelValue(name, valueObject);
if (!string.IsNullOrEmpty(valueObject.FirstValue))
{
var rawValues = valueObject.FirstValue.Split(',', StringSplitOptions.RemoveEmptyEntries);

if (rawValues.Length == 0)
{
bindingContext.ModelState.TryAddModelError(name, "List should contain at least one item.");
return false;
}

hasValue = true;
result = (int)Data.Models.ContractTags.None;

foreach (var rawValue in rawValues)
{
if (!ContractTags.TryParse(rawValue, out var tag))
{
bindingContext.ModelState.TryAddModelError(name, "Invalid contract tags.");
return false;
}
hasValue = true;
result |= tag;
}


}
}

return true;
}

public static bool TryGetBigMapTags(this ModelBindingContext bindingContext, string name, ref bool hasValue, out int? result)
{
result = null;
Expand Down
41 changes: 41 additions & 0 deletions Tzkt.Api/Parameters/Binders/ContractTagsBinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Tzkt.Api
{
public class ContractTagsBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = bindingContext.ModelName;
var hasValue = false;

if (!bindingContext.TryGetContractTags($"{model}", ref hasValue, out var value))
return Task.CompletedTask;

if (!bindingContext.TryGetContractTags($"{model}.eq", ref hasValue, out var eq))
return Task.CompletedTask;

if (!bindingContext.TryGetContractTags($"{model}.any", ref hasValue, out var any))
return Task.CompletedTask;

if (!bindingContext.TryGetContractTags($"{model}.all", ref hasValue, out var all))
return Task.CompletedTask;

if (!hasValue)
{
bindingContext.Result = ModelBindingResult.Success(null);
return Task.CompletedTask;
}

bindingContext.Result = ModelBindingResult.Success(new ContractTagsParameter
{
Eq = value ?? eq,
Any = any,
All = all
});

return Task.CompletedTask;
}
}
}
40 changes: 40 additions & 0 deletions Tzkt.Api/Parameters/ContractTagsParameter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using NJsonSchema.Annotations;

namespace Tzkt.Api
{
[ModelBinder(BinderType = typeof(ContractTagsBinder))]
[JsonSchemaExtensionData("x-tzkt-extension", "query-parameter")]
[JsonSchemaExtensionData("x-tzkt-query-parameter", "fa1,fa12,fa2")]
public class ContractTagsParameter
{
/// <summary>
/// **Equal** filter mode (optional, i.e. `param.eq=123` is the same as `param=123`). \
/// Specify a comma-separated list of contract tags to get contracts with exactly the same set of tags.
/// Avoid using this mode and use `.any` or `.all` instead, because it may not work as expected due to internal 'hidden' tags.
///
/// Example: `?tags=fa2` or `?tags=fa1,fa12`.
/// </summary>
[JsonSchemaType(typeof(List<string>))]
public int? Eq { get; set; }

/// <summary>
/// **Has any** filter mode. \
/// Specify a comma-separated list of contract tags to get contracts where at least one of the specified tags is presented.
///
/// Example: `?tags.any=fa2` or `?tags.any=fa1,fa12`.
/// </summary>
[JsonSchemaType(typeof(List<string>))]
public int? Any { get; set; }

/// <summary>
/// **Has all** filter mode. \
/// Specify a comma-separated list of contract tags to get contracts where all of the specified tags are presented.
///
/// Example: `?tags.all=fa2` or `?tags.all=fa1,fa12`.
/// </summary>
[JsonSchemaType(typeof(List<string>))]
public int? All { get; set; }
}
}
29 changes: 10 additions & 19 deletions Tzkt.Api/Repositories/AccountRepository.Contracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public async Task<Contract> GetContract(string address)
Alias = contract.Alias,
Address = contract.Address,
Kind = ContractKinds.ToString(contract.Kind),
Tzips = GetTzips(contract.Tags),
Tzips = ContractTags.ToList((Data.Models.ContractTags)contract.Tags),
Balance = contract.Balance,
Creator = creator == null ? null : new CreatorInfo
{
Expand Down Expand Up @@ -86,6 +86,7 @@ public async Task<int> GetContractsCount(ContractKindParameter kind)

public async Task<IEnumerable<Contract>> GetContracts(
ContractKindParameter kind,
ContractTagsParameter tags,
AccountParameter creator,
AccountParameter manager,
AccountParameter @delegate,
Expand Down Expand Up @@ -113,6 +114,7 @@ LEFT JOIN ""Storages"" AS st
.Filter("ManagerId", manager, x => x == "creator" ? "CreatorId" : "DelegateId")
.Filter("DelegateId", @delegate, x => x == "manager" ? "ManagerId" : "CreatorId")
.Filter("Kind", kind)
.Filter("Tags", tags)
.Filter("Balance", balance)
.Filter("LastLevel", lastActivity)
.Filter("TypeHash", typeHash)
Expand Down Expand Up @@ -145,7 +147,7 @@ LEFT JOIN ""Storages"" AS st
Alias = row.Alias,
Address = row.Address,
Kind = ContractKinds.ToString(row.Kind),
Tzips = GetTzips(row.Tags),
Tzips = ContractTags.ToList((Data.Models.ContractTags)row.Tags),
Balance = row.Balance,
Creator = creator == null ? null : new CreatorInfo
{
Expand Down Expand Up @@ -188,6 +190,7 @@ LEFT JOIN ""Storages"" AS st

public async Task<object[][]> GetContracts(
ContractKindParameter kind,
ContractTagsParameter tags,
AccountParameter creator,
AccountParameter manager,
AccountParameter @delegate,
Expand Down Expand Up @@ -252,6 +255,7 @@ public async Task<object[][]> GetContracts(
.Filter("ManagerId", manager, x => x == "creator" ? "CreatorId" : "DelegateId")
.Filter("DelegateId", @delegate, x => x == "manager" ? "ManagerId" : "CreatorId")
.Filter("Kind", kind)
.Filter("Tags", tags)
.Filter("Balance", balance)
.Filter("LastLevel", lastActivity)
.Filter("TypeHash", typeHash)
Expand Down Expand Up @@ -290,7 +294,7 @@ public async Task<object[][]> GetContracts(
break;
case "tzips":
foreach (var row in rows)
result[j++][i] = GetTzips(row.Tags);
result[j++][i] = ContractTags.ToList((Data.Models.ContractTags)row.Tags);
break;
case "address":
foreach (var row in rows)
Expand Down Expand Up @@ -425,6 +429,7 @@ public async Task<object[][]> GetContracts(

public async Task<object[]> GetContracts(
ContractKindParameter kind,
ContractTagsParameter tags,
AccountParameter creator,
AccountParameter manager,
AccountParameter @delegate,
Expand Down Expand Up @@ -486,6 +491,7 @@ public async Task<object[]> GetContracts(
.Filter("ManagerId", manager, x => x == "creator" ? "CreatorId" : "DelegateId")
.Filter("DelegateId", @delegate, x => x == "manager" ? "ManagerId" : "CreatorId")
.Filter("Kind", kind)
.Filter("Tags", tags)
.Filter("Balance", balance)
.Filter("LastLevel", lastActivity)
.Filter("TypeHash", typeHash)
Expand Down Expand Up @@ -521,7 +527,7 @@ public async Task<object[]> GetContracts(
break;
case "tzips":
foreach (var row in rows)
result[j++] = GetTzips(row.Tags);
result[j++] = ContractTags.ToList((Data.Models.ContractTags)row.Tags);
break;
case "address":
foreach (var row in rows)
Expand Down Expand Up @@ -1430,20 +1436,5 @@ public static async Task<Dictionary<int, object>> GetStorages(IDbConnection db,
})
: null;
}

IEnumerable<string> GetTzips(int tags)
{
if (tags == 0) return null;
var res = new List<string>(1);

if ((tags & (int)Data.Models.ContractTags.FA2) == (int)Data.Models.ContractTags.FA2)
res.Add("fa2");
else if ((tags & (int)Data.Models.ContractTags.FA12) == (int)Data.Models.ContractTags.FA12)
res.Add("fa12");
else if ((tags & (int)Data.Models.ContractTags.FA1) == (int)Data.Models.ContractTags.FA1)
res.Add("fa1");

return res;
}
}
}
8 changes: 4 additions & 4 deletions Tzkt.Api/Repositories/AccountRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public async Task<Account> Get(string address, bool metadata)
Alias = contract.Alias,
Address = contract.Address,
Kind = ContractKinds.ToString(contract.Kind),
Tzips = GetTzips(contract.Tags),
Tzips = ContractTags.ToList((Data.Models.ContractTags)contract.Tags),
Balance = contract.Balance,
Creator = creator == null ? null : new CreatorInfo
{
Expand Down Expand Up @@ -371,7 +371,7 @@ public async Task<IEnumerable<Account>> Get(
Alias = row.Alias,
Address = row.Address,
Kind = ContractKinds.ToString(row.Kind),
Tzips = GetTzips(row.Tags),
Tzips = ContractTags.ToList((Data.Models.ContractTags)row.Tags),
Balance = row.Balance,
Creator = creator == null ? null : new CreatorInfo
{
Expand Down Expand Up @@ -743,7 +743,7 @@ public async Task<object[][]> Get(
break;
case "tzips":
foreach (var row in rows)
result[j++][i] = GetTzips(row.Tags);
result[j++][i] = ContractTags.ToList((Data.Models.ContractTags)row.Tags);
break;
case "creator":
foreach (var row in rows)
Expand Down Expand Up @@ -1079,7 +1079,7 @@ public async Task<object[]> Get(
break;
case "tzips":
foreach (var row in rows)
result[j++] = GetTzips(row.Tags);
result[j++] = ContractTags.ToList((Data.Models.ContractTags)row.Tags);
break;
case "creator":
foreach (var row in rows)
Expand Down
Loading

0 comments on commit a30dff8

Please sign in to comment.