Skip to content

Commit

Permalink
Merge pull request #200 from hypercerts-org/feat/order_filters
Browse files Browse the repository at this point in the history
feat(orders): migrate order fetchin to kysely flow
  • Loading branch information
bitbeckers authored Dec 3, 2024
2 parents da7274e + 12f4e1f commit c1c6e7c
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 125 deletions.
35 changes: 13 additions & 22 deletions src/controllers/MarketplaceController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import {
addressesByNetwork,
HypercertExchangeClient,
utils,
} from "@hypercerts-org/marketplace-sdk";
import { ethers, verifyTypedData } from "ethers";
import {
Body,
Controller,
Expand All @@ -8,21 +14,15 @@ import {
SuccessResponse,
Tags,
} from "tsoa";
import { ApiResponse } from "../types/api.js";
import {
addressesByNetwork,
HypercertExchangeClient,
utils,
} from "@hypercerts-org/marketplace-sdk";
import { ethers, verifyTypedData } from "ethers";
import { z } from "zod";
import { ApiResponse } from "../types/api.js";

import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { isAddress, verifyMessage } from "viem";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { getFractionsById } from "../utils/getFractionsById.js";
import { getHypercertTokenId } from "../utils/tokenIds.js";
import { getRpcUrl } from "../utils/getRpcUrl.js";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { getHypercertTokenId } from "../utils/tokenIds.js";

export interface CreateOrderRequest {
signature: string;
Expand Down Expand Up @@ -435,15 +435,15 @@ export class MarketplaceController extends Controller {
const { orderId, signature } = parsedQuery.data;

const supabase = new SupabaseDataService();
const orderRes = await supabase.getOrders({
const orders = await supabase.getOrders({
where: {
id: {
eq: orderId,
},
},
});

if (!orderRes.data?.length) {
if (!orders.data?.length) {
this.setStatus(404);
return {
success: false,
Expand All @@ -452,16 +452,7 @@ export class MarketplaceController extends Controller {
};
}

if (orderRes.error) {
this.setStatus(500);
return {
success: false,
message: "Could not fetch order",
data: null,
};
}

const signerAddress = orderRes.data[0].signer;
const signerAddress = orders.data[0].signer;

const signatureCorrect = await verifyMessage({
message: `Delete listing ${orderId}`,
Expand Down
27 changes: 27 additions & 0 deletions src/graphql/schemas/resolvers/baseTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,33 @@ export function createBaseResolver<T extends ClassType>(
}
}

getOrders(args: GetOrdersArgs, single: boolean = false) {
console.debug(`[${entityFieldName}Resolver::getOrders] Fetching orders`);

try {
const queries = this.supabaseDataService.getOrders(args);
if (single) {
return queries.data.executeTakeFirst();
}

return this.supabaseDataService.db
.transaction()
.execute(async (transaction) => {
const dataRes = await transaction.executeQuery(queries.data);
const countRes = await transaction.executeQuery(queries.count);
return {
data: dataRes.rows,
count: countRes.rows[0].count,
};
});
} catch (e) {
const error = e as Error;
throw new Error(
`[${entityFieldName}Resolver::getOrders] Error fetching orders: ${error.message}`,
);
}
}

parseAttestation(item: { [K in keyof T]: T[K] }) {
const decodedData = item?.data;
// TODO cleaner handling of bigints in created attestations
Expand Down
36 changes: 11 additions & 25 deletions src/graphql/schemas/resolvers/hypercertResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class HypercertResolver extends HypercertBaseResolver {
};

try {
console.log("Getting fractions for hypercert", hypercert.hypercert_id);
const { data: fractionsRes } = await this.getFractions({
where: { hypercert_id: { eq: hypercert.hypercert_id } },
});
Expand All @@ -105,39 +104,21 @@ class HypercertResolver extends HypercertBaseResolver {
return defaultValue;
}

console.log(
`[HypercertResolver::orders] Fetching orders for ${hypercert.hypercert_id}`,
);

const ordersRes = await this.supabaseDataService.getOrders({
const orders = await this.getOrders({
where: { hypercert_id: { eq: hypercert.hypercert_id } },
});

if (!ordersRes) {
if (!orders) {
console.warn(
`[HypercertResolver::orders] Error fetching orders for ${hypercert.hypercert_id}`,
ordersRes,
);
return defaultValue;
}

const {
data: ordersData,
error: ordersError,
count: ordersCount,
} = ordersRes;

if (ordersError) {
console.error(
`[HypercertResolver::orders] Error fetching orders for ${hypercert.hypercert_id}: `,
ordersError,
orders,
);
return defaultValue;
}

const validOrders = ordersData.filter((order) => !order.invalidated);
const { data: ordersData, count: ordersCount } = orders;

const ordersByFraction = _.groupBy(validOrders, (order) =>
const ordersByFraction = _.groupBy(ordersData, (order) =>
order.itemIds[0].toString(),
);

Expand All @@ -149,10 +130,15 @@ class HypercertResolver extends HypercertBaseResolver {
priceInUSD: string;
pricePerPercentInUSD: string;
})[] = [];

const activeOrders = ordersData.filter((order) => !order.invalidated);
const activeOrdersByFraction = _.groupBy(activeOrders, (order) =>
order.itemIds[0].toString(),
);
// For each fraction, find all orders and find the max units for sale for that fraction
const totalUnitsForSale = (
await Promise.all(
Object.keys(ordersByFraction).map(async (tokenId) => {
Object.keys(activeOrdersByFraction).map(async (tokenId) => {
const fractionId = `${chainId}-${contractAddress}-${tokenId}`;
const fraction = fractionsRes.find(
(fraction) => fraction.fraction_id === fractionId,
Expand Down
72 changes: 12 additions & 60 deletions src/graphql/schemas/resolvers/orderResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ import { Order } from "../typeDefs/orderTypeDefs.js";
import { GetOrdersArgs } from "../args/orderArgs.js";
import { getHypercertTokenId } from "../../../utils/tokenIds.js";
import { getAddress } from "viem";
import { HypercertExchangeClient } from "@hypercerts-org/marketplace-sdk";
import { ethers } from "ethers";
import { getRpcUrl } from "../../../utils/getRpcUrl.js";
import { addPriceInUsdToOrder } from "../../../utils/addPriceInUSDToOrder.js";
import _ from "lodash";
import { createBaseResolver, DataResponse } from "./baseTypes.js";
Expand All @@ -25,29 +22,20 @@ const OrderBaseResolver = createBaseResolver("order");
@Resolver(() => Order)
class OrderResolver extends OrderBaseResolver {
@Query(() => GetOrdersResponse)
async orders(@Args() args: GetOrdersArgs) {
async orders(@Args() args: GetOrdersArgs, single: boolean = false) {
try {
const res = await this.supabaseDataService.getOrders(args);
const ordersRes = await this.getOrders(args, single);

const { data: orders, error, count } = res;

if (error) {
console.warn(`[OrderResolver::orders] Error fetching orders: `, error);
return { orders };
if (!ordersRes) {
return {
data: [],
count: 0,
};
}

const groupedOrders = orders.reduce(
(acc, order) => {
if (!acc[order.chainId]) {
acc[order.chainId] = [];
}
acc[order.chainId].push(order);
return acc;
},
{} as Record<string, (typeof orders)[number][]>,
);
const { data, count } = ordersRes;

const allHypercertIds = _.uniq(orders.map((order) => order.hypercert_id));
const allHypercertIds = _.uniq(data.map((order) => order.hypercert_id));
// TODO: Update this once array filters are available
const allHypercerts = await Promise.all(
allHypercertIds.map(async (hypercertId) => {
Expand All @@ -69,44 +57,8 @@ class OrderResolver extends OrderBaseResolver {
),
);

const ordersAfterCheckingValidity = await Promise.all(
Object.entries(groupedOrders).map(async ([chainId, ordersForChain]) => {
const chainIdParsed = parseInt(chainId);
const hypercertExchangeClient = new HypercertExchangeClient(
chainIdParsed,
// @ts-expect-error - TODO: fix these types
new ethers.JsonRpcProvider(getRpcUrl(chainIdParsed)),
);

const validityResults =
await hypercertExchangeClient.checkOrdersValidity(
ordersForChain.filter((order) => !order.invalidated),
);
const tokenIdsWithInvalidOrder = validityResults
.filter((result) => !result.valid)
.map((result) => BigInt(result.order.itemIds[0]));
if (tokenIdsWithInvalidOrder.length) {
console.log(
"[OrderResolver::orders]:: Found invalid orders",
tokenIdsWithInvalidOrder,
);
// Fire off the validation but don't wait for it to finish
this.supabaseDataService.validateOrdersByTokenIds({
tokenIds: tokenIdsWithInvalidOrder.map((id) => id.toString()),
chainId: chainIdParsed,
});
}
return ordersForChain.map((order) => {
if (tokenIdsWithInvalidOrder.includes(BigInt(order.itemIds[0]))) {
return { ...order, invalidated: true };
}
return order;
});
}),
).then((res) => res.flat());

const ordersWithPrices = await Promise.all(
orders.map(async (order) => {
data.map(async (order) => {
const hypercert = allHypercerts[order.hypercert_id.toLowerCase()];
if (!hypercert?.units) {
console.warn(
Expand All @@ -120,11 +72,11 @@ class OrderResolver extends OrderBaseResolver {

return {
data: ordersWithPrices,
count: count ? count : ordersAfterCheckingValidity?.length,
count: count ? count : ordersWithPrices?.length,
};
} catch (e) {
throw new Error(
`[ContractResolver::orders] Error fetching orders: ${(e as Error).message}`,
`[OrderResolver::orders] Error fetching orders: ${(e as Error).message}`,
);
}
}
Expand Down
41 changes: 23 additions & 18 deletions src/services/SupabaseDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,10 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
}

getOrders(args: GetOrdersArgs) {
let query = this.supabaseData.from("marketplace_orders").select("*");

const { where, sort, offset, first } = args;

query = applyFilters({ query, where });
query = applySorting({ query, sort });
query = applyPagination({ query, pagination: { first, offset } });

return query;
return {
data: this.handleGetData("marketplace_orders", args),
count: this.handleGetCount("marketplace_orders", args),
};
}

getOrdersByTokenId({
Expand Down Expand Up @@ -674,11 +669,14 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
>(tableName: T, args: BaseArgs<A>) {
switch (tableName) {
case "users":
return this.db.selectFrom("users").selectAll();
case "blueprints_with_admins":
case "blueprints":
return this.db.selectFrom("blueprints_with_admins").selectAll();
case "orders":
case "marketplace_orders":
return this.db.selectFrom("marketplace_orders").selectAll();
case "users":
return this.db.selectFrom("users").selectAll();
default:
throw new Error(`Table ${tableName.toString()} not found`);
}
Expand All @@ -691,21 +689,28 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
// eslint-disable-next-line @typescript-eslint/no-unused-vars
>(tableName: T, args: BaseArgs<A>) {
switch (tableName) {
case "users":
return this.db.selectFrom("users").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "blueprints_with_admins":
case "blueprints":
return this.db
.selectFrom("blueprints_with_admins")
.select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "hyperboards":
return this.db.selectFrom("hyperboards").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "blueprints_with_admins":
case "blueprints":
case "orders":
case "marketplace_orders":
return this.db
.selectFrom("blueprints_with_admins")
.selectFrom("marketplace_orders")
.select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
case "users":
return this.db.selectFrom("users").select((expressionBuilder) => {
return expressionBuilder.fn.countAll().as("count");
});
default:
throw new Error(`Table ${tableName.toString()} not found`);
}
Expand Down

0 comments on commit c1c6e7c

Please sign in to comment.