diff --git a/database.json b/database.json new file mode 100644 index 0000000..d51722d --- /dev/null +++ b/database.json @@ -0,0 +1,13 @@ +{ + "users": [ + { + "email": "test@test", + "password": "test", + "joinedAt": "2026-01-30T09:47:19.314Z", + "username": "Test", + "bio": "test", + "avatar": "https://i.pravatar.cc/150?img=12" + } + ], + "posts": [] +} \ No newline at end of file diff --git a/index.html b/index.html index 4829328..83db187 100644 --- a/index.html +++ b/index.html @@ -321,7 +321,7 @@
- + \ No newline at end of file diff --git a/script.js b/script.js index a99cdb7..047e322 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,30 @@ document.addEventListener('DOMContentLoaded', () => { + // --- API CONFIG --- + const API = { + async getUsers() { + const res = await fetch('/api/users'); + if (!res.ok) throw new Error(`Server Error: ${res.status}`); + return await res.json(); + }, + async addUser(user) { + await fetch('/api/users/add', { method: 'POST', body: JSON.stringify(user) }); + }, + async getPosts() { + const res = await fetch('/api/posts'); + if (!res.ok) throw new Error(`Server Error: ${res.status}`); + return await res.json(); + }, + async addPost(post) { + await fetch('/api/posts/add', { method: 'POST', body: JSON.stringify(post) }); + }, + async updatePost(post) { + await fetch('/api/posts/update', { method: 'POST', body: JSON.stringify(post) }); + }, + async deletePost(id) { + await fetch('/api/posts/delete', { method: 'POST', body: JSON.stringify({ id }) }); + } + }; + // --- INDEXED DB HELPERS (Multi-Media Support) --- const DB_NAME = 'SocialAppDB'; const DB_VERSION = 1; @@ -106,24 +132,44 @@ document.addEventListener('DOMContentLoaded', () => { // Lightbox Elements const lightboxView = document.getElementById('lightbox-view'); const lightboxClose = document.getElementById('lightbox-close'); - const lightboxCarousel = document.querySelector('.lightbox-carousel'); + const lightboxCarousel = lightboxView?.querySelector('.lightbox-carousel'); + // State let isLoginMode = true; let pendingUser = null; let generatedOTP = null; let newAvatarBase64 = null; // --- CHECK SESSION & LOGOUT --- - function checkSession() { + async function checkSession() { const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); if (currentUser.email) { - if (loginView) { - loginView.classList.add('hidden'); - loginView.style.display = 'none'; - } - if (feedView) { - feedView.classList.remove('hidden'); - initFeed(); + try { + // Verify user actually exists in DB (fixes "Ghost User" after DB wipe) + const users = await API.getUsers(); + const validUser = users.find(u => u.email === currentUser.email); + + if (!validUser) { + console.log('Session invalid: User not found on server.'); + localStorage.removeItem('currentUser'); + if (loginView && !loginView.classList.contains('hidden')) return; // Already on login + location.reload(); + return; + } + + if (loginView) { + loginView.classList.add('hidden'); + loginView.style.display = 'none'; + } + if (feedView) { + feedView.classList.remove('hidden'); + initFeed(); + } + } catch (err) { + console.warn('Session check failed:', err); + alert('Connection to Server Failed.\n\nReason: ' + err.message + '\n\nLogging out for security.'); + localStorage.removeItem('currentUser'); + location.reload(); } } } @@ -140,7 +186,6 @@ document.addEventListener('DOMContentLoaded', () => { } // --- ONBOARDING LOGIC --- - // Avatar Upload Handler if (avatarTrigger && avatarInput) { avatarTrigger.addEventListener('click', () => avatarInput.click()); avatarInput.addEventListener('change', (e) => { @@ -193,11 +238,9 @@ document.addEventListener('DOMContentLoaded', () => { avatar: newAvatarBase64 || 'https://i.pravatar.cc/150?img=12' }; - // Save to DB - const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]'); - existingUsers.push(finalUser); - localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers)); - localStorage.setItem('currentUser', JSON.stringify(finalUser)); // Log them in + // --- ASYNC SAVE --- + await API.addUser(finalUser); + localStorage.setItem('currentUser', JSON.stringify(finalUser)); // Session persistence // Transition onboardingView.classList.add('hidden'); @@ -206,7 +249,7 @@ document.addEventListener('DOMContentLoaded', () => { initFeed(); } catch (err) { console.error('Setup Error:', err); - alert('Something went wrong during setup: ' + err.message); + alert('Connection Error: ' + err.message); } }); } @@ -334,42 +377,49 @@ document.addEventListener('DOMContentLoaded', () => { } if (loginForm) { - loginForm.addEventListener('submit', (e) => { + loginForm.addEventListener('submit', async (e) => { e.preventDefault(); - const email = emailInput.value.trim(); + const email = emailInput.value.trim().toLowerCase(); const password = passwordInput.value.trim(); const confirmPassword = confirmPasswordInput.value.trim(); const timestamp = new Date().toISOString(); - const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]'); - if (isLoginMode) { - const user = existingUsers.find(u => u.email === email && u.password === password); - if (user) { - localStorage.setItem('currentUser', JSON.stringify(user)); - showFeedback(true, 'Success!'); - setTimeout(navigateToFeed, 1000); + if (!email || !password) { showFeedback(false, 'Missing fields'); return; } + + try { + const users = await API.getUsers(); + + if (isLoginMode) { + const user = users.find(u => u.email === email && u.password === password); + if (user) { + localStorage.setItem('currentUser', JSON.stringify(user)); + showFeedback(true, 'Success!'); + setTimeout(navigateToFeed, 1000); + } else { + showFeedback(false, 'Incorrect details'); + } } else { - showFeedback(false, 'Incorrect details'); - } - } else { - if (existingUsers.some(u => u.email === email)) { - showFeedback(false, 'User exists'); - return; - } - if (password !== confirmPassword) { - showFeedback(false, 'Passwords do not match'); - return; - } + if (users.find(u => u.email === email)) { + showFeedback(false, 'User exists'); + return; + } + if (password !== confirmPassword) { + showFeedback(false, 'Passwords mismatch'); + return; + } - // Start Verification Flow - pendingUser = { email, password, joinedAt: timestamp }; - generatedOTP = Math.floor(100000 + Math.random() * 900000).toString(); + // Start Verification Flow + pendingUser = { email, password, joinedAt: timestamp }; + generatedOTP = Math.floor(100000 + Math.random() * 900000).toString(); + alert(`[SERVER SYNC] Verification code: ${generatedOTP}`); - alert(`[MOCK EMAIL] Your verification code is: ${generatedOTP}`); - - loginView.classList.add('hidden'); - verificationView.classList.remove('hidden'); - verificationView.classList.add('fade-in'); + loginView.classList.add('hidden'); + verificationView.classList.remove('hidden'); + verificationView.classList.add('fade-in'); + } + } catch (err) { + console.error(err); + showFeedback(false, 'Server Error'); } }); } @@ -379,76 +429,31 @@ document.addEventListener('DOMContentLoaded', () => { function savePostState(state) { localStorage.setItem('socialAppPostState', JSON.stringify(state)); } function isPostDeleted(id) { const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]'); return deletedPosts.includes(id); } + // --- ASYNC LOAD FEED --- // --- ASYNC LOAD FEED --- async function initFeed() { - const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]'); - const postsToRender = [...savedUserPosts].reverse(); + const container = document.querySelector('.posts-container'); + if (!container) return; + container.innerHTML = ''; - for (const post of postsToRender) { - if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) { - let mediaItems = post.media; - if (!mediaItems && post.imageSrc) { - mediaItems = [{ type: 'image', src: post.imageSrc }]; - } - if (!mediaItems || (mediaItems.length > 0 && mediaItems[0].src && mediaItems[0].src.length < 100)) { - const dbMedia = await getMediaFromDB(post.id); - if (dbMedia) mediaItems = dbMedia; - } - - if (mediaItems && mediaItems.length > 0) { - const postWithMedia = { ...post, media: mediaItems }; - renderPost(postWithMedia, true); - } + try { + const posts = await API.getPosts(); + if (posts.length === 0) { + container.innerHTML = '
No posts yet. Be the first!
'; + return; } + + for (const post of posts) { + renderPost(post, false); + } + } catch (err) { + console.error('Feed Error:', err); + container.innerHTML = '
Connection Failed
'; } - - // Restore State - const state = getPostState(); - document.querySelectorAll('.post-card').forEach(card => { - const id = card.getAttribute('data-post-id'); - if (id && isPostDeleted(id)) { card.remove(); return; } - if (id && state[id]) { - const data = state[id]; - const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); - const userEmail = (currentUser.email || '').toLowerCase(); // Fix missing email check - - const likeIcon = card.querySelector('.heart-icon'); - const likesText = card.querySelector('.likes'); - if (likeIcon && likesText) { - // Check if current user is in `likedBy` array - let likedBy = data.likedBy || []; - // Migration: if old `liked` boolean exists, legacy support (or reset) - if (data.liked && likedBy.length === 0) { likedBy = []; } - - if (likedBy.includes(userEmail)) { - likeIcon.classList.add('liked'); - likeIcon.style.fill = '#EF4444'; - likeIcon.style.stroke = '#EF4444'; - } - likesText.innerText = (data.likesCount || likedBy.length) + ' likes'; - } - const commentsContainer = card.querySelector('.added-comments'); - if (commentsContainer) { - commentsContainer.innerHTML = ''; - if (data.comments) { - data.comments.forEach(comment => { - const el = document.createElement('div'); - el.classList.add('comment-item'); - // Handle both legacy strings and new objects - const author = (typeof comment === 'object') ? comment.username : 'someone'; - const text = (typeof comment === 'object') ? comment.text : comment; - // Highlight own comments - const displayUser = ((typeof comment === 'object') && comment.email === userEmail) ? 'you' : author; - - el.innerHTML = `${displayUser} ${text}`; - commentsContainer.appendChild(el); - }); - } - } - } - }); } + + // --- RENDER POST --- function renderPost(post, prepend = false) { if (document.querySelector(`[data-post-id="${post.id}"]`)) return; diff --git a/server.py b/server.py new file mode 100644 index 0000000..5affd3f --- /dev/null +++ b/server.py @@ -0,0 +1,108 @@ +import http.server +import socketserver +import json +import os +import sys +from urllib.parse import urlparse, parse_qs + +PORT = 8000 +DB_FILE = 'database.json' + +def load_db(): + if not os.path.exists(DB_FILE): + return {"users": [], "posts": []} + try: + with open(DB_FILE, 'r') as f: + return json.load(f) + except: + return {"users": [], "posts": []} + +def save_db(data): + with open(DB_FILE, 'w') as f: + json.dump(data, f, indent=2) + +class CustomHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + parsed = urlparse(self.path) + if parsed.path == '/api/users': + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') + self.end_headers() + db = load_db() + self.wfile.write(json.dumps(db.get("users", [])).encode()) + return + elif parsed.path == '/api/posts': + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') + self.end_headers() + db = load_db() + self.wfile.write(json.dumps(db.get("posts", [])).encode()) + return + + # Default behavior (serve files) + super().do_GET() + + def do_POST(self): + parsed = urlparse(self.path) + length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(length).decode('utf-8') + try: + payload = json.loads(body) + except: + payload = None + + if parsed.path == '/api/users/add': + db = load_db() + db["users"].append(payload) + save_db(db) + self.success(payload) + return + + elif parsed.path == '/api/posts/add': + db = load_db() + # New posts go to top + db["posts"].insert(0, payload) + save_db(db) + self.success(payload) + return + + elif parsed.path == '/api/posts/update': + # Expects full post object, finds by ID and replaces + db = load_db() + posts = db.get("posts", []) + for i, p in enumerate(posts): + if p["id"] == payload["id"]: + posts[i] = payload + break + db["posts"] = posts + save_db(db) + self.success(payload) + return + + elif parsed.path == '/api/posts/delete': + # Expects { "id": "..." } + db = load_db() + posts = db.get("posts", []) + db["posts"] = [p for p in posts if p["id"] != payload["id"]] + save_db(db) + self.success({"deleted": True}) + return + + self.send_error(404, "API Endpoint not found") + + def success(self, data): + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(data).encode()) + +# Support binding to all interfaces +Handler = CustomHandler +with socketserver.TCPServer(("0.0.0.0", PORT), Handler) as httpd: + print(f"Serving at http://0.0.0.0:{PORT}") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nStopping server...")