Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 402: Add filtering and sorting for products #470

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static TradableMaterial Deserialize(BsonDocument doc)
ItemSubType = itemSubType,
ElementalType = doc["ElementalType"].ToEnum<Nekoyume.Model.Elemental.ElementalType>(),
ItemId = HashDigest<SHA256>.FromString(doc["ItemId"].AsString),
RequiredBlockIndex = doc["RequiredBlockIndex"].AsInt64,
RequiredBlockIndex = doc["RequiredBlockIndex"].ToLong(),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using Lib9c.Models.Market;
using Libplanet.Types.Assets;
using Mimir.MongoDB.Bson.Extensions;
using Mimir.MongoDB.Bson.Serialization.Serializers.Libplanet;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;

namespace Mimir.MongoDB.Bson.Serialization.Serializers.Lib9c.Market;

public class ProductSerializer : ClassSerializerBase<Product>
public class ProductSerializer : ClassSerializerBase<Product>, IBsonDocumentSerializer
{
public static readonly ProductSerializer Instance = new();

Expand All @@ -33,9 +36,31 @@ public override Product Deserialize(BsonDeserializationContext context, BsonDese
return Deserialize(doc);
}

public bool TryGetMemberSerializationInfo(string memberName, [UnscopedRef] out BsonSerializationInfo? serializationInfo)
{
switch (memberName)
{
case nameof(Product.Price):
{
serializationInfo = new BsonSerializationInfo(memberName, FungibleAssetValueSerializer.Instance, typeof(FungibleAssetValue));
return true;
}
case nameof(Product.ProductType):
{
serializationInfo = new BsonSerializationInfo(memberName, new EnumSerializer<Nekoyume.Model.Market.ProductType>(BsonType.String), typeof(Nekoyume.Model.Market.ProductType));
Atralupus marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
default:
{
serializationInfo = null;
return false;
}
}
}

// DO NOT OVERRIDE Serialize METHOD: Currently objects will be serialized to Json first.
// public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, ItemBase value)
// {
// base.Serialize(context, args, value);
// }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class DecimalStatSerializer : ClassSerializerBase<DecimalStat>
public static DecimalStat Deserialize(BsonDocument doc) => new()
{
StatType = doc["StatType"].ToEnum<Nekoyume.Model.Stat.StatType>(),
BaseValue = (decimal)doc["BaseValue"].AsDouble,
AdditionalValue = (decimal)doc["AdditionalValue"].AsDouble,
BaseValue = doc["BaseValue"].ToDecimal(),
AdditionalValue = doc["AdditionalValue"].ToDecimal(),
};

public override DecimalStat Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Libplanet.Types.Assets;
using MongoDB.Bson;
Expand All @@ -6,7 +7,7 @@

namespace Mimir.MongoDB.Bson.Serialization.Serializers.Libplanet;

public class FungibleAssetValueSerializer : StructSerializerBase<FungibleAssetValue>
public class FungibleAssetValueSerializer : StructSerializerBase<FungibleAssetValue>, IBsonDocumentSerializer
{
private static class ElementNames
{
Expand All @@ -31,6 +32,23 @@ public override FungibleAssetValue Deserialize(
return Deserialize(doc);
}

public bool TryGetMemberSerializationInfo(string memberName, [UnscopedRef] out BsonSerializationInfo? serializationInfo)
{
switch (memberName)
{
case nameof(FungibleAssetValue.RawValue):
{
serializationInfo = new BsonSerializationInfo(memberName, Int64Serializer.Instance, typeof(long));
return true;
}
default:
{
serializationInfo = null;
return false;
}
}
}

// DO NOT OVERRIDE Serialize METHOD: Currently objects will be serialized to Json first.
// public override void Serialize(
// BsonSerializationContext context,
Expand Down
10 changes: 10 additions & 0 deletions Mimir.MongoDB/Enums/ProductSortBy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Mimir.MongoDB.Enums;

public enum ProductSortBy
{
Cp,
Crystal,
Grade,
Price,
UnitPrice
}
7 changes: 7 additions & 0 deletions Mimir.MongoDB/Enums/SortDirection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Mimir.MongoDB.Enums;

public enum SortDirection
{
Ascending,
Descending
}
143 changes: 139 additions & 4 deletions Mimir.MongoDB/Repositories/ProductRepository.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using Bencodex;
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Data.MongoDb;
using Mimir.MongoDB.Exceptions;
using Mimir.MongoDB.Bson;
using Mimir.MongoDB.Enums;
using Mimir.MongoDB.Services;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
using Nekoyume.Model.Item;
using Nekoyume.Model.Market;

namespace Mimir.MongoDB.Repositories;

Expand All @@ -23,9 +28,19 @@ public ProductRepository(MongoDbService dbService)
_gridFsBucket = dbService.GetGridFs();
}

public IExecutable<ProductDocument> GetAll() => _collection
.Find(Builders<ProductDocument>.Filter.Empty)
.AsExecutable();
public IExecutable<ProductDocument> Get(ProductFilter? productFilter)
Spoomer marked this conversation as resolved.
Show resolved Hide resolved
{
var filter = BuildFilter(productFilter);

var find = _collection.Find(filter);

if (productFilter?.SortBy is not null)
{
return ApplySorting(productFilter, filter, find);
}

return find.AsExecutable();
}

public async Task<ProductDocument> GetByProductIdAsync(Guid productId)
{
Expand All @@ -40,4 +55,124 @@ public async Task<ProductDocument> GetByProductIdAsync(Guid productId)

return productDocument;
}
}

public class ProductFilter
{
public ProductType? ProductType { get; set; }
public ItemType? ItemType { get; set; }
public ItemSubType? ItemSubType { get; set; }
public ProductSortBy? SortBy { get; set; }
public Enums.SortDirection? SortDirection { get; set; } = Enums.SortDirection.Ascending;
}
Comment on lines +59 to +66
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you separate this class to another file?


private static FilterDefinition<ProductDocument> BuildFilter(ProductFilter? productFilter)
{
var filterBuilder = Builders<ProductDocument>.Filter;
var filter = filterBuilder.Empty;

if (productFilter?.ProductType is not null)
{
filter &= filterBuilder.Eq(x => x.Object.ProductType, productFilter.ProductType);
}

if (productFilter?.ItemType is not null)
{
filter &= filterBuilder.Eq("Object.TradableItem.ItemType", productFilter.ItemType.ToString());
}

if (productFilter?.ItemSubType is not null)
{
filter &= filterBuilder.Eq("Object.TradableItem.ItemSubType", productFilter.ItemSubType.ToString());
}

return filter;
}

private IExecutable<ProductDocument> ApplySorting(ProductFilter productFilter, FilterDefinition<ProductDocument> filter,
IFindFluent<ProductDocument, ProductDocument> find)
{
productFilter.SortDirection ??= Enums.SortDirection.Ascending;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Ascending is the default value, how about settings ProductFilter.SortDirection's default value to it?


switch (productFilter.SortBy)
{
case ProductSortBy.Cp:
break;
case ProductSortBy.Crystal:
break;
Comment on lines +98 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend removing sort criteria from the ProductSortBy enumeration that are not yet supported: Cp, Crystal

case ProductSortBy.Grade:
return SortByGrade(productFilter, find);
case ProductSortBy.Price:
return SortByPrice(filter, productFilter);
case ProductSortBy.UnitPrice:
return SortByUnitPrice(productFilter, filter, find);
}

return find.AsExecutable();
}

private static MongoDbFindFluentExecutable<ProductDocument> SortByGrade(ProductFilter productFilter,
IFindFluent<ProductDocument, ProductDocument> find)
Comment on lines +113 to +114
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just accept the SortDirection type instead of the ProductFilter type argument?

{
var sortBuilder = Builders<ProductDocument>.Sort;
find = productFilter.SortDirection == Enums.SortDirection.Ascending
? find.Sort(sortBuilder.Ascending("Object.TradableItem.Grade"))
: find.Sort(sortBuilder.Descending("Object.TradableItem.Grade"));

return find.AsExecutable();
}

private MongoDbAggregateFluentExecutable<ProductDocument> SortByPrice(FilterDefinition<ProductDocument> filter,
ProductFilter productFilter)
Comment on lines +124 to +125
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just accept the SortDirection type instead of the ProductFilter type argument?

{
var convertStage = new BsonDocument("$addFields", new BsonDocument
{
{ "convertedPrice", new BsonDocument("$toLong", "$Object.Price.MajorUnit") },
});
var sortStage = new BsonDocument("$sort", new BsonDocument("convertedPrice",
productFilter.SortDirection == Enums.SortDirection.Ascending ? 1 : -1));

return _collection.Aggregate()
.Match(filter)
.AppendStage<ProductDocument>(convertStage)
.AppendStage<ProductDocument>(sortStage)
.AsExecutable();
}

private IExecutable<ProductDocument> SortByUnitPrice(ProductFilter productFilter, FilterDefinition<ProductDocument> filter, IFindFluent<ProductDocument, ProductDocument> find)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just accept the ProductType and SortDirection type instead of the ProductFilter type argument?

{
// ProductDocument.UnitPrice is null for FungibleAssetValue
if (productFilter.ProductType == ProductType.FungibleAssetValue)
{
return SortFungibleAssetValueByUnitPrice(productFilter, filter);
}

find = productFilter.SortDirection == Enums.SortDirection.Ascending
? find.SortBy(x => x.UnitPrice)
: find.SortByDescending(x => x.UnitPrice);

return find.AsExecutable();
}

private MongoDbAggregateFluentExecutable<ProductDocument> SortFungibleAssetValueByUnitPrice(ProductFilter productFilter,
FilterDefinition<ProductDocument> filter)
Comment on lines +156 to +157
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about just accept the SortDirection type instead of the ProductFilter type argument?

{
var convertStage = new BsonDocument("$addFields", new BsonDocument
{
{ "convertedPrice", new BsonDocument("$toLong", "$Object.Price.MajorUnit") },
{ "convertedQty", new BsonDocument("$toLong", "$Object.Asset.MajorUnit") },
});
var calcStage = new BsonDocument("$addFields", new BsonDocument
{
{ "calcUnitPrice", new BsonDocument("$divide", new BsonArray { "$convertedPrice", "$convertedQty" }) },
});
var sortStage = new BsonDocument("$sort", new BsonDocument("calcUnitPrice",
productFilter.SortDirection == Enums.SortDirection.Ascending ? 1 : -1));

return _collection.Aggregate()
.Match(filter)
.AppendStage<ProductDocument>(convertStage)
.AppendStage<ProductDocument>(calcStage)
.AppendStage<ProductDocument>(sortStage)
.AsExecutable();
}
}
82 changes: 52 additions & 30 deletions Mimir.Worker/ActionHandler/ProductStateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,45 +143,67 @@ private ProductDocument CreateProductDocumentAsync(
Product product)
{
var productAddress = Nekoyume.Model.Market.Product.DeriveAddress(product.ProductId);
if (product is ItemProduct itemProduct)
switch (product)
{
var unitPrice = CalculateUnitPrice(itemProduct);
// var combatPoint = await CalculateCombatPointAsync(itemProduct);
// var (crystal, crystalPerPrice) = await CalculateCrystalMetricsAsync(itemProduct);
case ItemProduct itemProduct:
{
var unitPrice = CalculateUnitPrice(itemProduct);
// var combatPoint = await CalculateCombatPointAsync(itemProduct);
// var (crystal, crystalPerPrice) = await CalculateCrystalMetricsAsync(itemProduct);

// return new ProductDocument(
// productAddress,
// avatarAddress,
// productsStateAddress,
// product,
// unitPrice,
// combatPoint,
// crystal,
// crystalPerPrice
// );
return new ProductDocument(
productAddress,
avatarAddress,
productsStateAddress,
product,
unitPrice,
null,
null,
null);
}
// return new ProductDocument(
// productAddress,
// avatarAddress,
// productsStateAddress,
// product,
// unitPrice,
// combatPoint,
// crystal,
// crystalPerPrice
// );
return new ProductDocument(
productAddress,
avatarAddress,
productsStateAddress,
product,
unitPrice,
null,
null,
null);
}
case FavProduct favProduct:
{
var unitPrice = CalculateUnitPrice(favProduct);

return new ProductDocument(
productAddress,
avatarAddress,
productsStateAddress,
product);
return new ProductDocument(
productAddress,
avatarAddress,
productsStateAddress,
product,
unitPrice,
null,
null,
null);
}
default:
return new ProductDocument(
productAddress,
avatarAddress,
productsStateAddress,
product);
}
}

private static decimal CalculateUnitPrice(ItemProduct itemProduct)
{
return decimal.Parse(itemProduct.Price.GetQuantityString()) / itemProduct.ItemCount;
}


private static decimal CalculateUnitPrice(FavProduct favProduct)
{
return decimal.Parse(favProduct.Price.GetQuantityString()) / decimal.Parse(favProduct.Asset.GetQuantityString());
}

// private async Task<int?> CalculateCombatPointAsync(ItemProduct itemProduct)
// {
// try
Expand Down
Loading
Loading