05/31 Fix fetch price issues
This commit is contained in:
@@ -257,6 +257,19 @@ def refresh_prices():
|
|||||||
@investments_bp.route('/api/price/<ticker>')
|
@investments_bp.route('/api/price/<ticker>')
|
||||||
@login_required
|
@login_required
|
||||||
def api_price(ticker):
|
def api_price(ticker):
|
||||||
"""Live price lookup for a ticker — used in the add transaction form."""
|
"""Live price lookup for a ticker — used in the add/edit investment form."""
|
||||||
|
ticker = ticker.upper().strip()
|
||||||
|
error = None
|
||||||
|
price = None
|
||||||
|
try:
|
||||||
price = fetch_price(ticker)
|
price = fetch_price(ticker)
|
||||||
return jsonify({'ticker': ticker.upper(), 'price': price})
|
if price is None:
|
||||||
|
error = f'No data returned for {ticker}. Check the ticker format.'
|
||||||
|
except Exception as e:
|
||||||
|
error = str(e)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'ticker': ticker,
|
||||||
|
'price': price,
|
||||||
|
'error': error,
|
||||||
|
})
|
||||||
|
|||||||
@@ -80,18 +80,38 @@
|
|||||||
document.getElementById('checkTicker').addEventListener('click', function() {
|
document.getElementById('checkTicker').addEventListener('click', function() {
|
||||||
const ticker = document.getElementById('tickerInput').value.trim().toUpperCase();
|
const ticker = document.getElementById('tickerInput').value.trim().toUpperCase();
|
||||||
const result = document.getElementById('tickerResult');
|
const result = document.getElementById('tickerResult');
|
||||||
|
const btn = document.getElementById('checkTicker');
|
||||||
if (!ticker) { result.textContent = ''; return; }
|
if (!ticker) { result.textContent = ''; return; }
|
||||||
result.innerHTML = '<span class="text-muted">Fetching…</span>';
|
|
||||||
fetch('/investments/api/price/' + encodeURIComponent(ticker))
|
result.innerHTML = '<span class="text-muted"><i class="bi bi-hourglass-split me-1"></i>Fetching…</span>';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
// AbortController for 15s timeout
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), 15000);
|
||||||
|
|
||||||
|
fetch('/investments/api/price/' + encodeURIComponent(ticker), { signal: controller.signal })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.price) {
|
clearTimeout(timer);
|
||||||
result.innerHTML = '<span class="text-success"><i class="bi bi-check-circle me-1"></i>' + ticker + ' → {{ current_user.currency_symbol }}' + data.price.toFixed(4) + '</span>';
|
btn.disabled = false;
|
||||||
|
if (data.price != null) {
|
||||||
|
result.innerHTML = '<span class="text-success"><i class="bi bi-check-circle me-1"></i>'
|
||||||
|
+ ticker + ' → {{ current_user.currency_symbol }}' + data.price.toFixed(4) + '</span>';
|
||||||
} else {
|
} else {
|
||||||
result.innerHTML = '<span class="text-danger"><i class="bi bi-x-circle me-1"></i>Ticker not found or no data</span>';
|
const msg = data.error || 'Ticker not found or no data';
|
||||||
|
result.innerHTML = '<span class="text-danger"><i class="bi bi-x-circle me-1"></i>' + msg + '</span>';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => { result.innerHTML = '<span class="text-warning">Could not fetch price</span>'; });
|
.catch(err => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
btn.disabled = false;
|
||||||
|
if (err.name === 'AbortError') {
|
||||||
|
result.innerHTML = '<span class="text-warning"><i class="bi bi-clock me-1"></i>Request timed out — Yahoo Finance may be slow. Try again.</span>';
|
||||||
|
} else {
|
||||||
|
result.innerHTML = '<span class="text-warning"><i class="bi bi-exclamation-triangle me-1"></i>Network error. Check server connectivity.</span>';
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -105,17 +105,33 @@ priceInput.addEventListener('input', updateTotal);
|
|||||||
const fillBtn = document.getElementById('fillPrice');
|
const fillBtn = document.getElementById('fillPrice');
|
||||||
if (fillBtn) {
|
if (fillBtn) {
|
||||||
fillBtn.addEventListener('click', function() {
|
fillBtn.addEventListener('click', function() {
|
||||||
|
fillBtn.disabled = true;
|
||||||
fillBtn.textContent = '…';
|
fillBtn.textContent = '…';
|
||||||
fetch('/investments/api/price/{{ inv.ticker }}')
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timer = setTimeout(() => controller.abort(), 15000);
|
||||||
|
|
||||||
|
fetch('/investments/api/price/{{ inv.ticker }}', { signal: controller.signal })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.price) {
|
clearTimeout(timer);
|
||||||
|
fillBtn.disabled = false;
|
||||||
|
fillBtn.textContent = '↓';
|
||||||
|
if (data.price != null) {
|
||||||
priceInput.value = data.price.toFixed(4);
|
priceInput.value = data.price.toFixed(4);
|
||||||
updateTotal();
|
updateTotal();
|
||||||
|
} else {
|
||||||
|
fillBtn.title = data.error || 'No price available';
|
||||||
|
fillBtn.style.color = '#ef4444';
|
||||||
|
setTimeout(() => { fillBtn.style.color = ''; fillBtn.title = ''; }, 3000);
|
||||||
}
|
}
|
||||||
fillBtn.textContent = '↓';
|
|
||||||
})
|
})
|
||||||
.catch(() => { fillBtn.textContent = '↓'; });
|
.catch(err => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
fillBtn.disabled = false;
|
||||||
|
fillBtn.textContent = '↓';
|
||||||
|
fillBtn.title = err.name === 'AbortError' ? 'Timed out' : 'Network error';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user