AccountsAndCrossPlatformLogin
This commit is contained in:
13
database.json
Normal file
13
database.json
Normal file
@@ -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": []
|
||||||
|
}
|
||||||
@@ -321,7 +321,7 @@
|
|||||||
<div class="media-counter" style="bottom: 40px; top: auto;"></div> <!-- Re-use styling -->
|
<div class="media-counter" style="bottom: 40px; top: auto;"></div> <!-- Re-use styling -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="script.js"></script>
|
<script src="script.js?v=2.0"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
219
script.js
219
script.js
@@ -1,4 +1,30 @@
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
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) ---
|
// --- INDEXED DB HELPERS (Multi-Media Support) ---
|
||||||
const DB_NAME = 'SocialAppDB';
|
const DB_NAME = 'SocialAppDB';
|
||||||
const DB_VERSION = 1;
|
const DB_VERSION = 1;
|
||||||
@@ -106,24 +132,44 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Lightbox Elements
|
// Lightbox Elements
|
||||||
const lightboxView = document.getElementById('lightbox-view');
|
const lightboxView = document.getElementById('lightbox-view');
|
||||||
const lightboxClose = document.getElementById('lightbox-close');
|
const lightboxClose = document.getElementById('lightbox-close');
|
||||||
const lightboxCarousel = document.querySelector('.lightbox-carousel');
|
const lightboxCarousel = lightboxView?.querySelector('.lightbox-carousel');
|
||||||
|
|
||||||
|
// State
|
||||||
let isLoginMode = true;
|
let isLoginMode = true;
|
||||||
let pendingUser = null;
|
let pendingUser = null;
|
||||||
let generatedOTP = null;
|
let generatedOTP = null;
|
||||||
let newAvatarBase64 = null;
|
let newAvatarBase64 = null;
|
||||||
|
|
||||||
// --- CHECK SESSION & LOGOUT ---
|
// --- CHECK SESSION & LOGOUT ---
|
||||||
function checkSession() {
|
async function checkSession() {
|
||||||
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
||||||
if (currentUser.email) {
|
if (currentUser.email) {
|
||||||
if (loginView) {
|
try {
|
||||||
loginView.classList.add('hidden');
|
// Verify user actually exists in DB (fixes "Ghost User" after DB wipe)
|
||||||
loginView.style.display = 'none';
|
const users = await API.getUsers();
|
||||||
}
|
const validUser = users.find(u => u.email === currentUser.email);
|
||||||
if (feedView) {
|
|
||||||
feedView.classList.remove('hidden');
|
if (!validUser) {
|
||||||
initFeed();
|
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 ---
|
// --- ONBOARDING LOGIC ---
|
||||||
// Avatar Upload Handler
|
|
||||||
if (avatarTrigger && avatarInput) {
|
if (avatarTrigger && avatarInput) {
|
||||||
avatarTrigger.addEventListener('click', () => avatarInput.click());
|
avatarTrigger.addEventListener('click', () => avatarInput.click());
|
||||||
avatarInput.addEventListener('change', (e) => {
|
avatarInput.addEventListener('change', (e) => {
|
||||||
@@ -193,11 +238,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
avatar: newAvatarBase64 || 'https://i.pravatar.cc/150?img=12'
|
avatar: newAvatarBase64 || 'https://i.pravatar.cc/150?img=12'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save to DB
|
// --- ASYNC SAVE ---
|
||||||
const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]');
|
await API.addUser(finalUser);
|
||||||
existingUsers.push(finalUser);
|
localStorage.setItem('currentUser', JSON.stringify(finalUser)); // Session persistence
|
||||||
localStorage.setItem('socialAppUsers', JSON.stringify(existingUsers));
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(finalUser)); // Log them in
|
|
||||||
|
|
||||||
// Transition
|
// Transition
|
||||||
onboardingView.classList.add('hidden');
|
onboardingView.classList.add('hidden');
|
||||||
@@ -206,7 +249,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
initFeed();
|
initFeed();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Setup Error:', 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) {
|
if (loginForm) {
|
||||||
loginForm.addEventListener('submit', (e) => {
|
loginForm.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const email = emailInput.value.trim();
|
const email = emailInput.value.trim().toLowerCase();
|
||||||
const password = passwordInput.value.trim();
|
const password = passwordInput.value.trim();
|
||||||
const confirmPassword = confirmPasswordInput.value.trim();
|
const confirmPassword = confirmPasswordInput.value.trim();
|
||||||
const timestamp = new Date().toISOString();
|
const timestamp = new Date().toISOString();
|
||||||
const existingUsers = JSON.parse(localStorage.getItem('socialAppUsers') || '[]');
|
|
||||||
|
|
||||||
if (isLoginMode) {
|
if (!email || !password) { showFeedback(false, 'Missing fields'); return; }
|
||||||
const user = existingUsers.find(u => u.email === email && u.password === password);
|
|
||||||
if (user) {
|
try {
|
||||||
localStorage.setItem('currentUser', JSON.stringify(user));
|
const users = await API.getUsers();
|
||||||
showFeedback(true, 'Success!');
|
|
||||||
setTimeout(navigateToFeed, 1000);
|
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 {
|
} else {
|
||||||
showFeedback(false, 'Incorrect details');
|
if (users.find(u => u.email === email)) {
|
||||||
}
|
showFeedback(false, 'User exists');
|
||||||
} else {
|
return;
|
||||||
if (existingUsers.some(u => u.email === email)) {
|
}
|
||||||
showFeedback(false, 'User exists');
|
if (password !== confirmPassword) {
|
||||||
return;
|
showFeedback(false, 'Passwords mismatch');
|
||||||
}
|
return;
|
||||||
if (password !== confirmPassword) {
|
}
|
||||||
showFeedback(false, 'Passwords do not match');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start Verification Flow
|
// Start Verification Flow
|
||||||
pendingUser = { email, password, joinedAt: timestamp };
|
pendingUser = { email, password, joinedAt: timestamp };
|
||||||
generatedOTP = Math.floor(100000 + Math.random() * 900000).toString();
|
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');
|
||||||
loginView.classList.add('hidden');
|
verificationView.classList.add('fade-in');
|
||||||
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 savePostState(state) { localStorage.setItem('socialAppPostState', JSON.stringify(state)); }
|
||||||
function isPostDeleted(id) { const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]'); return deletedPosts.includes(id); }
|
function isPostDeleted(id) { const deletedPosts = JSON.parse(localStorage.getItem('socialAppDeletedPosts') || '[]'); return deletedPosts.includes(id); }
|
||||||
|
|
||||||
|
// --- ASYNC LOAD FEED ---
|
||||||
// --- ASYNC LOAD FEED ---
|
// --- ASYNC LOAD FEED ---
|
||||||
async function initFeed() {
|
async function initFeed() {
|
||||||
const savedUserPosts = JSON.parse(localStorage.getItem('socialAppUserPosts') || '[]');
|
const container = document.querySelector('.posts-container');
|
||||||
const postsToRender = [...savedUserPosts].reverse();
|
if (!container) return;
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
for (const post of postsToRender) {
|
try {
|
||||||
if (!isPostDeleted(post.id) && !document.querySelector(`[data-post-id="${post.id}"]`)) {
|
const posts = await API.getPosts();
|
||||||
let mediaItems = post.media;
|
if (posts.length === 0) {
|
||||||
if (!mediaItems && post.imageSrc) {
|
container.innerHTML = '<div style="text-align:center; padding:40px; color:#666;">No posts yet. Be the first!</div>';
|
||||||
mediaItems = [{ type: 'image', src: post.imageSrc }];
|
return;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const post of posts) {
|
||||||
|
renderPost(post, false);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Feed Error:', err);
|
||||||
|
container.innerHTML = '<div style="text-align:center; color:red;">Connection Failed</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 = `<span class="comment-user">${displayUser}</span> ${text}`;
|
|
||||||
commentsContainer.appendChild(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// --- RENDER POST ---
|
// --- RENDER POST ---
|
||||||
function renderPost(post, prepend = false) {
|
function renderPost(post, prepend = false) {
|
||||||
if (document.querySelector(`[data-post-id="${post.id}"]`)) return;
|
if (document.querySelector(`[data-post-id="${post.id}"]`)) return;
|
||||||
|
|||||||
108
server.py
Normal file
108
server.py
Normal file
@@ -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...")
|
||||||
Reference in New Issue
Block a user