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>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script src="script.js?v=2.0"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
219
script.js
219
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 = '<div style="text-align:center; padding:40px; color:#666;">No posts yet. Be the first!</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
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 ---
|
||||
function renderPost(post, prepend = false) {
|
||||
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