x
This commit is contained in:
parent
ecf43500a1
commit
95a66ea6b0
3 changed files with 190 additions and 2457 deletions
2316
cloudflare worker.js
2316
cloudflare worker.js
File diff suppressed because it is too large
Load diff
147
index.html
147
index.html
|
|
@ -596,7 +596,7 @@ async function loadUserData() {
|
|||
}, 3000);
|
||||
}
|
||||
|
||||
// Improved fetch implementation
|
||||
// Improved safeFetch implementation with Cloudflare error handling
|
||||
async function safeFetch(url, options = {}) {
|
||||
try {
|
||||
// Ensure URL is a string
|
||||
|
|
@ -607,7 +607,6 @@ async function safeFetch(url, options = {}) {
|
|||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// Add any additional default headers
|
||||
},
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin'
|
||||
|
|
@ -642,14 +641,26 @@ async function safeFetch(url, options = {}) {
|
|||
|
||||
// Check for successful response
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
console.error('Fetch Error Response:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorBody
|
||||
});
|
||||
const errorText = await response.text();
|
||||
|
||||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorBody}`);
|
||||
// Check if this is a Cloudflare error
|
||||
if (errorText.includes('Cloudflare') || errorText.includes('cf-error')) {
|
||||
console.error('Cloudflare Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorText
|
||||
});
|
||||
|
||||
// Return a special error object for Cloudflare errors
|
||||
return {
|
||||
error: true,
|
||||
cloudflareError: true,
|
||||
status: response.status,
|
||||
message: 'The server is temporarily unavailable. Please try again later.'
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
|
|
@ -671,7 +682,7 @@ async function safeFetch(url, options = {}) {
|
|||
|
||||
|
||||
|
||||
// Fetch proposals from API
|
||||
// Updated fetchProposals with fallback mechanism
|
||||
async function fetchProposals() {
|
||||
try {
|
||||
// Show loading indicator
|
||||
|
|
@ -680,14 +691,38 @@ async function safeFetch(url, options = {}) {
|
|||
const sortSelect = document.getElementById('sort-select');
|
||||
const sortBy = sortSelect ? sortSelect.value : 'newest';
|
||||
|
||||
const data = await safeFetch(
|
||||
`${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`
|
||||
);
|
||||
// Log the full request URL and parameters
|
||||
const requestUrl = `${API_BASE_URL}/proposals?sortBy=${sortBy}&userId=${currentUser.id}`;
|
||||
console.log('Fetching proposals from:', requestUrl);
|
||||
console.log('Current user:', currentUser);
|
||||
|
||||
const response = await fetch(requestUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin'
|
||||
});
|
||||
|
||||
// Log the response details
|
||||
console.log('Response status:', response.status);
|
||||
console.log('Response headers:', Object.fromEntries(response.headers.entries()));
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Error response:', errorText);
|
||||
throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Proposals data:', data);
|
||||
|
||||
renderProposals(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching proposals:', error);
|
||||
showErrorMessage('Oops! Failed to load petitions. Try refreshing the page.');
|
||||
showErrorMessage('Failed to load petitions. Please check the console for details.', true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2566,6 +2601,90 @@ function submitComment(proposalId, commentText) {
|
|||
});
|
||||
}
|
||||
|
||||
// Add these new functions for comment management
|
||||
const commentRequestQueue = new Set();
|
||||
let commentRequestTimeout = null;
|
||||
const MAX_BATCH_SIZE = 10;
|
||||
const COMMENT_REQUEST_DELAY = 1000; // 1 second delay between batches
|
||||
|
||||
async function batchLoadComments() {
|
||||
if (commentRequestQueue.size === 0) return;
|
||||
|
||||
const batch = Array.from(commentRequestQueue).slice(0, MAX_BATCH_SIZE);
|
||||
commentRequestQueue.clear();
|
||||
|
||||
try {
|
||||
const responses = await Promise.all(
|
||||
batch.map(proposalId =>
|
||||
fetch(`${API_BASE_URL}/comments?proposalId=${proposalId}&userId=${currentUser.id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
mode: 'cors',
|
||||
credentials: 'same-origin'
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.json().then(data => ({ proposalId, data }));
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
responses.forEach(({ proposalId, data }) => {
|
||||
const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`);
|
||||
if (commentsContainer) {
|
||||
renderComments(commentsContainer.querySelector('.comments-content'), data);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading comments batch:', error);
|
||||
// Retry failed requests
|
||||
batch.forEach(proposalId => commentRequestQueue.add(proposalId));
|
||||
}
|
||||
|
||||
// Schedule next batch if there are more requests
|
||||
if (commentRequestQueue.size > 0) {
|
||||
commentRequestTimeout = setTimeout(batchLoadComments, COMMENT_REQUEST_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
function queueCommentLoad(proposalId) {
|
||||
commentRequestQueue.add(proposalId);
|
||||
|
||||
if (!commentRequestTimeout) {
|
||||
commentRequestTimeout = setTimeout(batchLoadComments, COMMENT_REQUEST_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the loadComments function to use the queue
|
||||
function loadComments(proposalId) {
|
||||
const commentsContainer = document.querySelector(`.comments-container[data-proposal-id="${proposalId}"]`);
|
||||
const commentsContent = commentsContainer?.querySelector('.comments-content');
|
||||
|
||||
if (!commentsContent) return;
|
||||
|
||||
// Show loading state
|
||||
commentsContent.innerHTML = `<div class="comments-loading">Loading comments...</div>`;
|
||||
|
||||
// Queue the comment load
|
||||
queueCommentLoad(proposalId);
|
||||
}
|
||||
|
||||
// Update the loadModalComments function to use the queue
|
||||
async function loadModalComments(proposalId) {
|
||||
const commentsContent = document.getElementById(`modal-comments-content-${proposalId}`);
|
||||
if (!commentsContent) return;
|
||||
|
||||
// Show loading state
|
||||
commentsContent.innerHTML = `<div class="comments-loading">Loading comments...</div>`;
|
||||
|
||||
// Queue the comment load
|
||||
queueCommentLoad(proposalId);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="music-prompt" id="music-prompt">hit me</div>
|
||||
|
|
|
|||
184
worker.js
184
worker.js
|
|
@ -3,6 +3,7 @@ const corsHeaders = {
|
|||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400'
|
||||
};
|
||||
|
||||
// Handle OPTIONS request for CORS preflight
|
||||
|
|
@ -1109,137 +1110,31 @@ async function healthCheck(env) {
|
|||
async function getProposals(request, env) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const sortBy = url.searchParams.get('sortBy') || 'newest';
|
||||
const page = parseInt(url.searchParams.get('page')) || 1;
|
||||
const limit = Math.min(
|
||||
parseInt(url.searchParams.get('limit')) || DEFAULT_PAGE_SIZE,
|
||||
MAX_PAGE_SIZE
|
||||
);
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// Log request details
|
||||
console.log(`Getting proposals with sort: ${sortBy}`);
|
||||
// Add to your SQL query
|
||||
query += ` LIMIT ? OFFSET ?`;
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
p.id, p.text, p.timestamp, p.trending, p.meme_url,
|
||||
u.name as author_name, u.id as author_id,
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'upvote') as upvotes,
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'downvote') as downvotes,
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'upvote' AND is_petition = 1) as petition_signatures,
|
||||
(SELECT COUNT(DISTINCT pd.user_id) FROM petition_details pd WHERE pd.proposal_id = p.id AND pd.verified = 1) as verified_petitioners
|
||||
FROM proposals p
|
||||
JOIN users u ON p.author_id = u.id
|
||||
`;
|
||||
// Add sorting
|
||||
if (sortBy === 'newest') {
|
||||
query += ' ORDER BY p.timestamp DESC';
|
||||
} else if (sortBy === 'popular') {
|
||||
query += `
|
||||
ORDER BY (
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'upvote') -
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'downvote')
|
||||
) DESC
|
||||
`;
|
||||
} else if (sortBy === 'controversial') {
|
||||
query += `
|
||||
ORDER BY (
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'upvote') +
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'downvote')
|
||||
) DESC
|
||||
`;
|
||||
} else if (sortBy === 'petitions') {
|
||||
// New sorting option for most petitioned
|
||||
query += `
|
||||
ORDER BY (
|
||||
(SELECT COUNT(*) FROM votes WHERE proposal_id = p.id AND vote_type = 'upvote' AND is_petition = 1)
|
||||
) DESC
|
||||
`;
|
||||
}
|
||||
// Add to your bind parameters
|
||||
const proposals = await env.DB.prepare(query).bind(limit, offset).all();
|
||||
|
||||
console.log(`Executing query: ${query}`);
|
||||
|
||||
// Get user ID for checking existing votes
|
||||
const userId = url.searchParams.get('userId');
|
||||
let proposals;
|
||||
|
||||
try {
|
||||
proposals = await env.DB.prepare(query).all();
|
||||
console.log(`Retrieved ${proposals.results.length} proposals`);
|
||||
} catch (dbError) {
|
||||
return createResponse({
|
||||
error: 'Failed to retrieve proposals',
|
||||
details: logError(dbError, { action: 'get_proposals', query })
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// If we have a userId, get their votes for these proposals
|
||||
if (userId && proposals.results.length > 0) {
|
||||
const proposalIds = proposals.results.map(p => p.id);
|
||||
|
||||
try {
|
||||
// Use parameter binding with placeholders
|
||||
let placeholderStr = '';
|
||||
for (let i = 0; i < proposalIds.length; i++) {
|
||||
placeholderStr += (i === 0 ? '?' : ',?');
|
||||
}
|
||||
|
||||
const votesQuery = `
|
||||
SELECT proposal_id, vote_type, is_petition
|
||||
FROM votes
|
||||
WHERE user_id = ? AND proposal_id IN (${placeholderStr})
|
||||
`;
|
||||
|
||||
console.log(`Executing votes query for user ${userId} with ${proposalIds.length} proposals`);
|
||||
|
||||
const bindParams = [userId, ...proposalIds];
|
||||
const votes = await env.DB.prepare(votesQuery).bind(...bindParams).all();
|
||||
|
||||
console.log(`Retrieved ${votes.results.length} votes for user ${userId}`);
|
||||
|
||||
// Create a map of proposal_id to vote info
|
||||
const voteMap = {};
|
||||
for (const vote of votes.results) {
|
||||
voteMap[vote.proposal_id] = {
|
||||
voteType: vote.vote_type,
|
||||
isPetition: vote.is_petition === 1
|
||||
};
|
||||
}
|
||||
|
||||
// Also get petition details status
|
||||
const petitionQuery = `
|
||||
SELECT proposal_id, verified
|
||||
FROM petition_details
|
||||
WHERE user_id = ? AND proposal_id IN (${placeholderStr})
|
||||
`;
|
||||
|
||||
const petitionDetails = await env.DB.prepare(petitionQuery).bind(...bindParams).all();
|
||||
const petitionMap = {};
|
||||
|
||||
for (const petition of petitionDetails.results) {
|
||||
petitionMap[petition.proposal_id] = {
|
||||
verified: petition.verified === 1
|
||||
};
|
||||
}
|
||||
|
||||
// Add user's vote and petition status to each proposal
|
||||
for (const proposal of proposals.results) {
|
||||
const voteInfo = voteMap[proposal.id];
|
||||
proposal.userVote = voteInfo ? voteInfo.voteType : null;
|
||||
proposal.userPetitionSigned = voteInfo ? voteInfo.isPetition : false;
|
||||
proposal.userPetitionVerified = petitionMap[proposal.id] ? petitionMap[proposal.id].verified : false;
|
||||
}
|
||||
} catch (voteError) {
|
||||
// Just log the error but continue - we'll return proposals without vote data
|
||||
logError(voteError, {
|
||||
action: 'get_user_votes',
|
||||
userId,
|
||||
proposalCount: proposalIds.length
|
||||
});
|
||||
console.log('Warning: Failed to retrieve user votes, continuing without vote data');
|
||||
// Add pagination info to response
|
||||
return createResponse({
|
||||
data: proposals.results,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: proposals.results.length,
|
||||
hasMore: proposals.results.length === limit
|
||||
}
|
||||
}
|
||||
|
||||
return createResponse(proposals.results);
|
||||
});
|
||||
} catch (error) {
|
||||
return createResponse({
|
||||
error: 'Failed to process proposals request',
|
||||
details: logError(error, { action: 'get_proposals_outer' })
|
||||
}, 500);
|
||||
// ... error handling ...
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2375,4 +2270,39 @@ async function serveCustomizedHtml(proposalId, request, env) {
|
|||
// Fall back to regular page if something goes wrong
|
||||
return await fetch(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCacheControl(path) {
|
||||
const cacheRules = {
|
||||
'/api/health': 'public, max-age=3600',
|
||||
'/api/proposals': 'public, max-age=300, stale-while-revalidate=60',
|
||||
'/api/comments': 'public, max-age=60, stale-while-revalidate=30',
|
||||
default: 'no-cache'
|
||||
};
|
||||
|
||||
return cacheRules[path] || cacheRules.default;
|
||||
}
|
||||
|
||||
// In your fetch handler
|
||||
if (request.method === "GET") {
|
||||
const cacheControl = getCacheControl(path);
|
||||
response.headers.set('Cache-Control', cacheControl);
|
||||
}
|
||||
|
||||
function validateRequest(request, requiredParams = []) {
|
||||
const url = new URL(request.url);
|
||||
const missingParams = requiredParams.filter(param => !url.searchParams.has(param));
|
||||
|
||||
if (missingParams.length > 0) {
|
||||
return createResponse({
|
||||
error: 'Missing required parameters',
|
||||
missing: missingParams
|
||||
}, 400);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// In your endpoint handlers
|
||||
const validationError = validateRequest(request, ['userId']);
|
||||
if (validationError) return validationError;
|
||||
Loading…
Add table
Reference in a new issue