|
1 | | -import { MongoClient } from "mongodb"; |
| 1 | +import clientPromise from "@/utils/mongo"; |
| 2 | +import { |
| 3 | + buildHistoryFilter, |
| 4 | + fetchPriceHistory, |
| 5 | + mergeLegacyHistory, |
| 6 | + recordPriceHistoryEntry, |
| 7 | +} from "@/utils/priceHistoryStore"; |
2 | 8 |
|
3 | 9 | export default async function handler( req, res ) { |
4 | 10 | const { cardId, set, number, rarity, edition } = req.query; |
5 | | - |
6 | 11 | if ( !cardId || !set || !number || !rarity || !edition ) { |
7 | | - return res.status( 400 ).json( { error: "Missing parameters: cardId, set, number, rarity, edition" } ); |
| 12 | + return res |
| 13 | + .status( 400 ) |
| 14 | + .json( { error: "Missing parameters: cardId, set, number, rarity, edition" } ); |
8 | 15 | } |
9 | 16 |
|
10 | 17 | try { |
11 | | - const client = new MongoClient( process.env.MONGODB_URI ); |
12 | | - await client.connect(); |
13 | | - const db = client.db( "cardPriceApp" ); |
14 | | - |
15 | | - // Fetch user-specific price history from "myCollection" |
16 | | - const userDoc = await db.collection( "myCollection" ).findOne( |
17 | | - { setName: { $eq: set }, number: { $eq: number }, rarity: { $eq: rarity }, printing: { $eq: edition } }, |
18 | | - { projection: { priceHistory: 1, _id: 0 } } |
19 | | - ); |
20 | | - |
21 | | - // Fetch global price history from "priceHistory" |
22 | | - const globalDoc = await db.collection( "priceHistory" ).findOne( |
23 | | - { cardId: { $eq: cardId }, setName: { $eq: set }, number: { $eq: number }, rarity: { $eq: rarity }, edition: { $eq: edition } }, |
24 | | - { projection: { history: 1, _id: 0 } } |
25 | | - ); |
26 | | - |
27 | | - // Extract history arrays |
28 | | - const userHistory = userDoc?.priceHistory || []; |
29 | | - const globalHistory = globalDoc?.history || []; |
30 | | - |
31 | | - // Combine and sort history |
32 | | - let combinedHistory = [ ...userHistory, ...globalHistory ] |
33 | | - .map( entry => ( { |
34 | | - date: new Date( entry.date ).toISOString().split( "T" )[ 0 ], // Standardize format |
35 | | - price: parseFloat( entry.price ) |
36 | | - } ) ) |
37 | | - .sort( ( a, b ) => new Date( a.date ) - new Date( b.date ) ); |
38 | | - |
39 | | - // ✅ If no history exists, fetch price from YGOPRODeck API |
40 | | - if ( combinedHistory.length === 0 ) { |
41 | | - console.log( `🔍 No price history found for ${ cardId }. Fetching initial price...` ); |
| 18 | + const db = ( await clientPromise ).db( "cardPriceApp" ); |
| 19 | + const filter = buildHistoryFilter( { |
| 20 | + cardId, |
| 21 | + setName: set, |
| 22 | + number, |
| 23 | + rarity, |
| 24 | + edition, |
| 25 | + } ); |
| 26 | + |
| 27 | + // Prefer dedicated collection |
| 28 | + let history = await fetchPriceHistory( filter ); |
| 29 | + |
| 30 | + // One-time merge from legacy inline history if present |
| 31 | + if ( !history.length ) { |
| 32 | + const legacy = await db.collection( "myCollection" ).findOne( |
| 33 | + { setName: set, number, rarity, printing: edition }, |
| 34 | + { projection: { priceHistory: 1, _id: 0 } } |
| 35 | + ); |
| 36 | + if ( legacy?.priceHistory?.length ) { |
| 37 | + await mergeLegacyHistory( { filter, entries: legacy.priceHistory } ); |
| 38 | + history = await fetchPriceHistory( filter ); |
| 39 | + } |
| 40 | + } |
42 | 41 |
|
43 | | - const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?id=${ encodeURIComponent( cardId ) }&tcgplayer_data=true`; |
| 42 | + // Bootstrap with external price if still empty |
| 43 | + if ( !history.length ) { |
| 44 | + const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?id=${ encodeURIComponent( |
| 45 | + cardId |
| 46 | + ) }&tcgplayer_data=true`; |
44 | 47 | const response = await fetch( url ); |
45 | 48 | const data = await response.json(); |
46 | 49 |
|
47 | | - if ( !data?.data || data?.data.length === 0 ) { |
48 | | - await client.close(); |
49 | | - return res.status( 404 ).json( { error: "Card not found in external API" } ); |
50 | | - } |
51 | | - |
52 | | - const card = data.data[ 0 ]; |
| 50 | + const card = data?.data?.[ 0 ]; |
53 | 51 | const matchingSet = card?.card_sets?.find( |
54 | | - ( s ) => s.set_name === set && s.set_code === number && s.set_rarity === rarity && s.set_edition === edition |
| 52 | + ( s ) => |
| 53 | + s.set_name === set && |
| 54 | + s.set_code === number && |
| 55 | + s.set_rarity === rarity && |
| 56 | + s.set_edition === edition |
55 | 57 | ); |
56 | 58 |
|
57 | | - if ( !matchingSet || !matchingSet.set_price ) { |
58 | | - await client.close(); |
59 | | - return res.status( 404 ).json( { error: "Set price not available" } ); |
60 | | - } |
61 | | - |
62 | | - const initialPrice = parseFloat( matchingSet.set_price ); |
63 | | - |
64 | | - if ( isNaN( initialPrice ) ) { |
65 | | - console.error( "❌ Invalid price detected." ); |
66 | | - await client.close(); |
67 | | - return res.status( 500 ).json( { error: "Invalid price data" } ); |
68 | | - } |
69 | | - |
70 | | - // ✅ Create initial price history entry |
71 | | - const newEntry = { date: new Date().toISOString(), price: initialPrice }; |
72 | | - |
73 | | - await db.collection( "priceHistory" ).insertOne( { |
74 | | - cardId, |
75 | | - setName: set, |
76 | | - number, |
77 | | - rarity, |
78 | | - edition, |
79 | | - history: [ newEntry ] |
80 | | - } ); |
81 | | - |
82 | | - combinedHistory = [ newEntry ]; // Update history for response |
83 | | - } else { |
84 | | - // ✅ Fetch the latest price if the last recorded date is not today |
85 | | - const lastEntry = combinedHistory[ combinedHistory.length - 1 ]; |
86 | | - const lastEntryDate = new Date( lastEntry.date ).toISOString().split( "T" )[ 0 ]; |
87 | | - const todayDate = new Date().toISOString().split( "T" )[ 0 ]; |
88 | | - |
89 | | - if ( lastEntryDate !== todayDate ) { |
90 | | - console.log( `🔍 Fetching latest price for ${ cardId }...` ); |
91 | | - |
92 | | - const url = `https://db.ygoprodeck.com/api/v7/cardinfo.php?id=${ encodeURIComponent( cardId ) }&tcgplayer_data=true`; |
93 | | - const response = await fetch( url ); |
94 | | - const data = await response.json(); |
95 | | - |
96 | | - if ( data?.data?.length > 0 ) { |
97 | | - const card = data.data[ 0 ]; |
98 | | - const matchingSet = card?.card_sets?.find( |
99 | | - ( s ) => s.set_name === set && s.set_code === number && s.set_rarity === rarity && s.set_edition === edition |
100 | | - ); |
101 | | - |
102 | | - if ( matchingSet?.set_price ) { |
103 | | - const latestPrice = parseFloat( matchingSet.set_price ); |
104 | | - |
105 | | - if ( !isNaN( latestPrice ) ) { |
106 | | - console.log( `✅ Adding new price entry for ${ todayDate }: $${ latestPrice }` ); |
107 | | - |
108 | | - await db.collection( "priceHistory" ).updateOne( |
109 | | - { cardId: { $eq: cardId }, setName: { $eq: set }, number: { $eq: number }, rarity: { $eq: rarity }, edition: { $eq: edition } }, |
110 | | - { $push: { history: { date: new Date().toISOString(), price: latestPrice } } }, |
111 | | - { upsert: true } |
112 | | - ); |
113 | | - |
114 | | - combinedHistory.push( { date: todayDate, price: latestPrice } ); |
115 | | - } |
116 | | - } |
117 | | - } |
| 59 | + const initialPrice = matchingSet?.set_price ? parseFloat( matchingSet.set_price ) : null; |
| 60 | + if ( Number.isFinite( initialPrice ) ) { |
| 61 | + await recordPriceHistoryEntry( { |
| 62 | + cardId, |
| 63 | + setName: set, |
| 64 | + number, |
| 65 | + rarity, |
| 66 | + edition, |
| 67 | + price: initialPrice, |
| 68 | + } ); |
| 69 | + history = await fetchPriceHistory( filter ); |
118 | 70 | } |
119 | 71 | } |
120 | 72 |
|
121 | | - await client.close(); |
122 | | - res.status( 200 ).json( { priceHistory: combinedHistory } ); |
| 73 | + return res.status( 200 ).json( { priceHistory: history } ); |
123 | 74 | } catch ( error ) { |
124 | | - console.error( "❌ Database Error:", error ); |
125 | | - res.status( 500 ).json( { error: "Internal Server Error" } ); |
| 75 | + console.error( "price-history error:", error ); |
| 76 | + return res.status( 500 ).json( { error: "Internal Server Error" } ); |
126 | 77 | } |
127 | 78 | } |
0 commit comments