Skip to content

Commit 5b34d63

Browse files
committed
Switch from full database preload to API-based fetching to reduce initial load time
1 parent b786636 commit 5b34d63

2 files changed

Lines changed: 169 additions & 77 deletions

File tree

app.py

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def should_refresh_cache():
5757

5858
@app.route('/', methods=['GET', 'POST'])
5959
def index():
60-
messages = []
6160
timezone_str = request.cookies.get('timezone')
6261
if timezone_str:
6362
try:
@@ -86,13 +85,13 @@ def index():
8685
})
8786

8887
global cached_messages, cache_timestamp
89-
88+
9089
cache_info = {}
9190
if should_refresh_cache():
9291
with lock:
9392
if should_refresh_cache():
94-
messages = list(coles_updates_collection.find().sort("date", -1))
95-
cached_messages = messages
93+
temp_messages = list(coles_updates_collection.find().sort("date", -1))
94+
cached_messages = temp_messages
9695
cache_timestamp = dt.now(utc_tz)
9796
cache_info = {
9897
'status': 'miss',
@@ -103,11 +102,9 @@ def index():
103102
'status': 'hit',
104103
'timestamp': cache_timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')
105104
}
106-
107-
messages = cached_messages
108-
total_messages = len(messages)
109105

110-
for message in messages:
106+
processed_messages = []
107+
for message in cached_messages:
111108
if message.get("date"):
112109
date_obj = message["date"]
113110
if date_obj.tzinfo is None:
@@ -119,6 +116,20 @@ def index():
119116
message["date_formatted_utc"] = date_obj.strftime('%d/%m/%Y %H:%M:%S UTC')
120117
local_date_obj = date_obj.astimezone(user_tz)
121118
message["date_formatted_local"] = local_date_obj.strftime('%d/%m/%Y %I:%M %p %Z')
119+
message["timestamp"] = local_date_obj.timestamp()
120+
121+
if message.get("price_before", 0) != 0:
122+
increase = ((message["price_after"] - message["price_before"]) / message["price_before"] * 100)
123+
else:
124+
increase = float('inf')
125+
message["increase"] = increase
126+
127+
processed_messages.append(message)
128+
129+
processed_messages.sort(key=lambda m: m["timestamp"], reverse=True)
130+
131+
messages = processed_messages[:9]
132+
total_messages = len(processed_messages)
122133

123134
return render_template(
124135
'index.html',
@@ -283,6 +294,92 @@ def item(item_id):
283294
initial_item=item_data
284295
)
285296

297+
@app.route('/api/messages')
298+
def api_messages():
299+
page = int(request.args.get('page', 1))
300+
per_page = int(request.args.get('per_page', 9))
301+
selected_date = request.args.get('date', None)
302+
search_term = request.args.get('search', '').lower()
303+
sort_by = request.args.get('sort', 'date')
304+
305+
timezone_str = request.cookies.get('timezone')
306+
user_tz = utc_tz
307+
if timezone_str:
308+
try:
309+
user_tz = ZoneInfo(timezone_str)
310+
except ZoneInfoNotFoundError:
311+
user_tz = utc_tz
312+
313+
global cached_messages, cache_timestamp
314+
if should_refresh_cache():
315+
with lock:
316+
if should_refresh_cache():
317+
temp_messages = list(coles_updates_collection.find().sort("date", -1))
318+
cached_messages = temp_messages
319+
cache_timestamp = dt.now(utc_tz)
320+
321+
messages = cached_messages
322+
323+
processed_messages = []
324+
for message in messages:
325+
if message.get("date"):
326+
date_obj = message["date"]
327+
if date_obj.tzinfo is None:
328+
date_obj = date_obj.replace(tzinfo=utc_tz)
329+
else:
330+
date_obj = date_obj.astimezone(utc_tz)
331+
332+
message["date_iso"] = date_obj.isoformat()
333+
message["date_formatted_utc"] = date_obj.strftime('%d/%m/%Y %H:%M:%S UTC')
334+
local_date_obj = date_obj.astimezone(user_tz)
335+
message["date_formatted_local"] = local_date_obj.strftime('%d/%m/%Y %I:%M %p %Z')
336+
message["timestamp"] = local_date_obj.timestamp()
337+
338+
if message.get("price_before", 0) != 0:
339+
increase = ((message["price_after"] - message["price_before"]) / message["price_before"] * 100)
340+
else:
341+
increase = float('inf')
342+
message["increase"] = increase
343+
344+
search_text = f"{message.get('item_brand', '')} {message.get('item_name', '')} {message.get('item_id', '')} {message.get('price_before', '')} {message.get('price_after', '')}".lower()
345+
346+
processed_messages.append({
347+
**message,
348+
"search_text": search_text
349+
})
350+
351+
filtered_messages = processed_messages
352+
if search_term:
353+
filtered_messages = [m for m in filtered_messages if search_term in m["search_text"]]
354+
if selected_date:
355+
filtered_messages = [m for m in filtered_messages if selected_date in m["date_formatted_local"]]
356+
357+
if sort_by == 'increase':
358+
filtered_messages.sort(key=lambda m: m["increase"], reverse=True)
359+
else:
360+
filtered_messages.sort(key=lambda m: m["timestamp"], reverse=True)
361+
362+
total_count = len(filtered_messages)
363+
total_pages = (total_count + per_page - 1) // per_page
364+
start = (page - 1) * per_page
365+
end = start + per_page
366+
paginated_messages = filtered_messages[start:end]
367+
368+
serializable_messages = []
369+
for msg in paginated_messages:
370+
msg_dict = dict(msg)
371+
if '_id' in msg_dict:
372+
msg_dict['_id'] = str(msg_dict['_id'])
373+
serializable_messages.append(msg_dict)
374+
375+
return {
376+
"messages": serializable_messages,
377+
"total_count": total_count,
378+
"page": page,
379+
"per_page": per_page,
380+
"total_pages": total_pages
381+
}
382+
286383
@app.errorhandler(404)
287384
def not_found_error(error):
288385
return render_template('error.html',

templates/index.html

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -306,69 +306,63 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
306306
});
307307

308308
const searchInput = document.getElementById('searchInput');
309-
const cards = document.querySelectorAll('.col-lg-4');
310309
const ITEMS_PER_PAGE = 9;
311310
let currentPage = 1;
312311
let selectedDate = null;
313312
let selectedDateLabel = '';
314313
let searchTerm = '';
315314
let currentSort = 'date';
316-
317-
const allProducts = Array.from(cards).map(card => {
318-
const dateText = card.querySelector('.date').textContent.trim();
319-
const rawIncrease = card.querySelector('.card-text h5').dataset.increase;
320-
let increase = Number(rawIncrease);
321-
// If increase isn't a finite number (e.g. 'Infinity' or non-numeric), treat it as Infinity for sorting
322-
if (!isFinite(increase)) {
323-
increase = Infinity;
324-
}
325-
326-
return {
327-
element: card,
328-
searchText: (card.querySelector('.card-title').textContent + ' ' +
329-
card.querySelector('.card-text').textContent).toLowerCase(),
330-
date: dateText,
331-
increase: increase,
332-
timestamp: new Date(dateText.split(' ')[0].split('/').reverse().join('-')).getTime()
333-
};
334-
});
335-
336-
let filteredProducts = [...allProducts];
337-
338-
function filterProducts() {
339-
filteredProducts = allProducts.filter(product => {
340-
const matchesSearch = !searchTerm || product.searchText.includes(searchTerm);
341-
const matchesDate = !selectedDate || product.date.includes(selectedDate);
342-
return matchesSearch && matchesDate;
343-
});
344-
345-
if (currentSort === 'increase') {
346-
filteredProducts.sort((a, b) => b.increase - a.increase);
347-
} else {
348-
filteredProducts.sort((a, b) => b.timestamp - a.timestamp);
349-
}
350-
351-
showPage(1, filteredProducts);
352-
}
353-
354-
function showPage(page, products) {
355-
currentPage = page;
356-
const start = (page - 1) * ITEMS_PER_PAGE;
357-
const end = start + ITEMS_PER_PAGE;
358-
const productsToShow = products.slice(start, end);
359-
360-
allProducts.forEach(product => {
361-
product.element.style.display = 'none';
362-
});
315+
let totalCount = {{ total_messages }};
316+
let totalPages = Math.ceil(totalCount / ITEMS_PER_PAGE);
363317

318+
function renderMessages(messages) {
364319
const container = document.getElementById('cardsContainer');
365-
productsToShow.forEach(product => {
366-
product.element.style.display = '';
367-
container.appendChild(product.element);
320+
container.innerHTML = '';
321+
messages.forEach(message => {
322+
const increase = message.increase;
323+
const increaseText = isFinite(increase) ? `+${increase.toFixed(2)}%` : '+∞%';
324+
const cardHtml = `
325+
<div class="col-lg-4 col-md-6 mb-4 item-container" onclick="loadItem(${message.item_id})">
326+
<div class="card h-100 shadow-sm position-relative" style="cursor: pointer;">
327+
${message.image_url ? `<img src="${message.image_url}" class="card-img-top" alt="${message.item_name}" loading="lazy" onerror="this.onerror=null;this.src='{{ url_for('static', filename='placeholder.png') }}';">` : `<img src="{{ url_for('static', filename='placeholder.png') }}" class="card-img-top" alt="No Image Available" loading="lazy">`}
328+
<div class="card-body d-flex flex-column">
329+
<h5 class="card-title">${message.item_brand} ${message.item_name}</h5>
330+
<div class="card-text">
331+
<h5 class="card-title" data-increase="${increase}">
332+
$${message.price_before} <span>→</span> $${message.price_after} (${increaseText})
333+
</h5>
334+
<strong>Item ID:</strong> ${message.item_id}<br>
335+
<strong>Date:</strong> <span class="date">${message.date_formatted_local}</span>
336+
</div>
337+
</div>
338+
</div>
339+
</div>
340+
`;
341+
container.insertAdjacentHTML('beforeend', cardHtml);
368342
});
343+
}
369344

370-
const totalPages = Math.ceil(products.length / ITEMS_PER_PAGE);
371-
updatePaginationUI(page, totalPages, products.length);
345+
async function fetchMessages(page = 1, date = selectedDate, search = searchTerm, sort = currentSort) {
346+
try {
347+
const params = new URLSearchParams({
348+
page: page,
349+
per_page: ITEMS_PER_PAGE,
350+
sort: sort
351+
});
352+
if (date) params.append('date', date);
353+
if (search) params.append('search', search);
354+
355+
const response = await fetch(`/api/messages?${params}`);
356+
const data = await response.json();
357+
358+
renderMessages(data.messages);
359+
currentPage = data.page;
360+
totalCount = data.total_count;
361+
totalPages = data.total_pages;
362+
updatePaginationUI(currentPage, totalPages, totalCount);
363+
} catch (error) {
364+
console.error('Error fetching messages:', error);
365+
}
372366
}
373367

374368
function updatePaginationUI(page, totalPages, totalItems) {
@@ -389,6 +383,8 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
389383
if (!ul) return;
390384
ul.innerHTML = '';
391385

386+
if (totalPages <= 1) return;
387+
392388
const prevLi = document.createElement('li');
393389
prevLi.className = `page-item ${page === 1 ? 'disabled' : ''}`;
394390
prevLi.innerHTML = `
@@ -399,7 +395,7 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
399395
if (page > 1) {
400396
prevLi.querySelector('a').addEventListener('click', (e) => {
401397
e.preventDefault();
402-
showPage(page - 1, filteredProducts);
398+
fetchMessages(page - 1);
403399
});
404400
}
405401
ul.appendChild(prevLi);
@@ -441,7 +437,7 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
441437
if (page < totalPages) {
442438
nextLi.querySelector('a').addEventListener('click', (e) => {
443439
e.preventDefault();
444-
showPage(page + 1, filteredProducts);
440+
fetchMessages(page + 1);
445441
});
446442
}
447443
ul.appendChild(nextLi);
@@ -456,7 +452,7 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
456452
if (pageNum !== currentPage) {
457453
li.querySelector('a').addEventListener('click', (e) => {
458454
e.preventDefault();
459-
showPage(pageNum, filteredProducts);
455+
fetchMessages(pageNum);
460456
});
461457
}
462458
return li;
@@ -490,7 +486,7 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
490486
button.classList.add('active');
491487
selectedDate = button.dataset.date;
492488
selectedDateLabel = button.dataset.label;
493-
filterProducts();
489+
fetchMessages(1);
494490
});
495491
});
496492

@@ -503,7 +499,7 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
503499
option.classList.add('active');
504500
currentSort = option.dataset.sort;
505501
document.getElementById('sortDropdown').textContent = option.textContent;
506-
filterProducts();
502+
fetchMessages(1);
507503
});
508504
});
509505

@@ -518,21 +514,21 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
518514
const year = date.getFullYear();
519515
selectedDate = `${day}/${month}/${year}`;
520516
selectedDateLabel = '';
521-
517+
522518
document.querySelectorAll('.date-button').forEach(btn => {
523519
btn.classList.remove('active');
524520
});
525-
526-
filterProducts();
527-
521+
522+
fetchMessages(1);
523+
528524
const modal = bootstrap.Modal.getInstance(document.getElementById('customDateModal'));
529525
modal.hide();
530526
});
531527
}
532528

533529
searchInput.addEventListener('input', debounce((e) => {
534530
searchTerm = e.target.value.toLowerCase();
535-
filterProducts();
531+
fetchMessages(1);
536532
}, 150));
537533

538534
const crazyModeToggle = document.getElementById('crazyModeToggle');
@@ -552,29 +548,28 @@ <h5 class="modal-title" id="customDateModalLabel">Select Custom Date</h5>
552548
}
553549
}
554550

555-
filterProducts();
551+
updatePaginationUI(currentPage, totalPages, totalCount);
556552

557553
function resetAll() {
558554
showMainView();
559555

560556
searchInput.value = '';
561557
searchTerm = '';
562-
558+
563559
selectedDate = null;
564560
selectedDateLabel = '';
565561
document.querySelectorAll('.date-button').forEach(btn => {
566562
btn.classList.remove('active');
567563
});
568-
564+
569565
currentSort = 'date';
570566
document.querySelectorAll('.sort-option').forEach(opt => {
571567
opt.classList.remove('active');
572568
});
573569
document.querySelector('.sort-option[data-sort="date"]').classList.add('active');
574570
document.getElementById('sortDropdown').textContent = 'Sort by Date';
575-
576-
filteredProducts = [...allProducts];
577-
filterProducts();
571+
572+
fetchMessages(1);
578573

579574
history.pushState({}, '', '/');
580575
}

0 commit comments

Comments
 (0)