diff --git a/src/server/loaders.js b/src/server/loaders.js index f017eef..4fd01ef 100644 --- a/src/server/loaders.js +++ b/src/server/loaders.js @@ -535,55 +535,6 @@ const buildItemAllOccupiedZonesLoader = (db) => }); }); -const buildItemTradeCountsLoader = (db) => - new DataLoader( - async (itemIdOwnedPairs) => { - const qs = itemIdOwnedPairs - .map((_) => "(closet_hangers.item_id = ? AND closet_hangers.owned = ?)") - .join(" OR "); - const values = itemIdOwnedPairs - .map(({ itemId, isOwned }) => [itemId, isOwned]) - .flat(); - const [rows, _] = await db.execute( - ` - SELECT - closet_hangers.item_id AS item_id, closet_hangers.owned AS is_owned, - count(DISTINCT closet_hangers.user_id) AS users_count - FROM closet_hangers - INNER JOIN users ON users.id = closet_hangers.user_id - LEFT JOIN closet_lists ON closet_lists.id = closet_hangers.list_id - WHERE ( - (${qs}) - AND ( - (closet_hangers.list_id IS NOT NULL AND closet_lists.visibility >= 2) - OR ( - closet_hangers.list_id IS NULL AND closet_hangers.owned = 1 - AND users.owned_closet_hangers_visibility >= 2 - ) - OR ( - closet_hangers.list_id IS NULL AND closet_hangers.owned = 0 - AND users.wanted_closet_hangers_visibility >= 2 - ) - ) - ) - GROUP BY closet_hangers.item_id, closet_hangers.owned; - `, - values - ); - - const entities = rows.map(normalizeRow); - - return itemIdOwnedPairs.map(({ itemId, isOwned }) => { - // NOTE: There may be no matching row, if there are 0 such trades. - const entity = entities.find( - (e) => e.itemId === itemId && Boolean(e.isOwned) === isOwned - ); - return entity ? entity.usersCount : 0; - }); - }, - { cacheKeyFn: ({ itemId, isOwned }) => `${itemId}-${isOwned}` } - ); - const buildItemTradesLoader = (db, loaders) => new DataLoader( async (itemIdOwnedPairs) => { @@ -1261,7 +1212,6 @@ function buildLoaders(db) { db ); loaders.itemAllOccupiedZonesLoader = buildItemAllOccupiedZonesLoader(db); - loaders.itemTradeCountsLoader = buildItemTradeCountsLoader(db); loaders.itemTradesLoader = buildItemTradesLoader(db, loaders); loaders.petTypeLoader = buildPetTypeLoader(db, loaders); loaders.petTypeBySpeciesAndColorLoader = buildPetTypeBySpeciesAndColorLoader( diff --git a/src/server/types/Item.js b/src/server/types/Item.js index 8f55824..e4a4605 100644 --- a/src/server/types/Item.js +++ b/src/server/types/Item.js @@ -29,6 +29,7 @@ const typeDefs = gql` currentUserWantsThis: Boolean! @cacheControl(scope: PRIVATE) # How many users are offering/seeking this in their public trade lists. + # Excludes users that seem relatively inactive. numUsersOfferingThis: Int! @cacheControl(maxAge: ${oneHour}) numUsersSeekingThis: Int! @cacheControl(maxAge: ${oneHour}) @@ -268,19 +269,55 @@ const resolvers = { return closetHangers.some((h) => h.itemId === id && !h.owned); }, - numUsersOfferingThis: async ({ id }, _, { itemTradeCountsLoader }) => { - const count = await itemTradeCountsLoader.load({ + numUsersOfferingThis: async ( + { id }, + _, + { itemTradesLoader, userLastTradeActivityLoader } + ) => { + // First, get the trades themselves. TODO: Optimize into one query? + const trades = await itemTradesLoader.load({ itemId: id, isOwned: true, }); - return count; + + // Then, get the last active dates for those users. + const userIds = trades.map((t) => t.user.id); + const lastActiveDates = await userLastTradeActivityLoader.loadMany( + userIds + ); + + // Finally, count how many of those dates were in the last 6 months. + // Those trades get to be in the count! + const sixMonthsAgo = new Date(); + sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); + const numTrades = lastActiveDates.filter((d) => d > sixMonthsAgo).length; + + return numTrades; }, - numUsersSeekingThis: async ({ id }, _, { itemTradeCountsLoader }) => { - const count = await itemTradeCountsLoader.load({ + numUsersSeekingThis: async ( + { id }, + _, + { itemTradesLoader, userLastTradeActivityLoader } + ) => { + // First, get the trades themselves. TODO: Optimize into one query? + const trades = await itemTradesLoader.load({ itemId: id, isOwned: false, }); - return count; + + // Then, get the last active dates for those users. + const userIds = trades.map((t) => t.user.id); + const lastActiveDates = await userLastTradeActivityLoader.loadMany( + userIds + ); + + // Finally, count how many of those dates were in the last 6 months. + // Those trades get to be in the count! + const sixMonthsAgo = new Date(); + sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6); + const numTrades = lastActiveDates.filter((d) => d > sixMonthsAgo).length; + + return numTrades; }, tradesOffering: async ({ id }, _, { itemTradesLoader }) => {