feat: implement user profiles and social walls (Tasks #171, #173, #174)

This commit is contained in:
2026-01-13 23:24:19 +01:00
parent 477f447b67
commit ed62ac0641
8 changed files with 372 additions and 20 deletions

View File

@@ -11,6 +11,8 @@ con.exec(`
CREATE TABLE IF NOT EXISTS users (
wallet_address VARCHAR PRIMARY KEY,
username VARCHAR UNIQUE,
bio VARCHAR DEFAULT '',
banner_color VARCHAR DEFAULT '#6366f1',
last_seen TIMESTAMP
);
@@ -31,8 +33,17 @@ con.exec(`
PRIMARY KEY (message_id, wallet_address, emoji),
FOREIGN KEY (wallet_address) REFERENCES users(wallet_address)
);
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY,
wallet_address VARCHAR,
content VARCHAR,
timestamp TIMESTAMP,
FOREIGN KEY (wallet_address) REFERENCES users(wallet_address)
);
CREATE SEQUENCE IF NOT EXISTS seq_msg_id START 1;
CREATE SEQUENCE IF NOT EXISTS seq_post_id START 1;
-- Migration: Add tx_id to messages if it doesn't exist (for existing DBs)
PRAGMA table_info('messages');
@@ -46,7 +57,20 @@ con.exec(`
if (!hasTxId) {
con.run("ALTER TABLE messages ADD COLUMN tx_id VARCHAR", (err) => {
if (err) console.error("Error adding tx_id column:", err);
else console.log("Added tx_id column to messages table");
});
}
});
// Migration: Add bio and banner_color to users
con.all("PRAGMA table_info('users')", (err, rows) => {
if (err) return;
const hasBio = rows.some(r => r.name === 'bio');
if (!hasBio) {
con.run("ALTER TABLE users ADD COLUMN bio VARCHAR DEFAULT ''", (err) => {
if (err) console.error("Error adding bio column:", err);
});
con.run("ALTER TABLE users ADD COLUMN banner_color VARCHAR DEFAULT '#6366f1'", (err) => {
if (err) console.error("Error adding banner_color column:", err);
});
}
});

View File

@@ -51,11 +51,13 @@ io.on('connection', (socket) => {
if (rows.length > 0) {
// User exists, update last seen
const existingUsername = rows[0].username;
con.prepare(`UPDATE users SET last_seen = ? WHERE wallet_address = ?`, (err, uStmt) => {
if (err) return console.error("Prepare error:", err);
uStmt.run(now, walletAddress, (err) => {
uStmt.finalize();
if (err) console.error("Update error:", err);
socket.emit('usernameUpdated', { username: existingUsername });
broadcastUserList();
});
});
@@ -77,6 +79,7 @@ io.on('connection', (socket) => {
iStmt.run(walletAddress, finalUsername, now, (err) => {
iStmt.finalize();
if (err) console.error("Insert error:", err);
socket.emit('usernameUpdated', { username: finalUsername });
broadcastUserList();
});
});
@@ -165,6 +168,61 @@ io.on('connection', (socket) => {
});
});
socket.on('getProfile', (walletAddress) => {
console.log(`Fetching profile for ${walletAddress}`);
con.prepare(`SELECT wallet_address, username, bio, banner_color, last_seen FROM users WHERE wallet_address = ?`, (err, stmt) => {
if (err) return socket.emit('error', { message: 'Database error' });
stmt.all(walletAddress, (err, rows) => {
stmt.finalize();
if (err || rows.length === 0) return socket.emit('error', { message: 'User not found' });
const user = rows[0];
// Fetch posts
con.prepare(`SELECT * FROM posts WHERE wallet_address = ? ORDER BY timestamp DESC LIMIT 50`, (err, pStmt) => {
if (err) return socket.emit('profileData', { ...user, posts: [] });
pStmt.all(walletAddress, (err, posts) => {
pStmt.finalize();
socket.emit('profileData', { ...user, posts: posts || [] });
});
});
});
});
});
socket.on('updateProfile', ({ walletAddress, bio, bannerColor }) => {
console.log(`Updating profile for ${walletAddress}`);
con.prepare(`UPDATE users SET bio = ?, banner_color = ? WHERE wallet_address = ?`, (err, stmt) => {
if (err) return socket.emit('error', { message: 'Database error' });
stmt.run(bio, bannerColor, walletAddress, (err) => {
stmt.finalize();
if (err) return socket.emit('error', { message: 'Failed to update profile' });
socket.emit('profileUpdated', { bio, bannerColor });
broadcastUserList();
});
});
});
socket.on('createPost', ({ walletAddress, content }) => {
if (!content || content.trim() === '') return;
console.log(`New post from ${walletAddress}: ${content}`);
const timestamp = new Date().toISOString();
con.prepare(`INSERT INTO posts (id, wallet_address, content, timestamp) VALUES (nextval('seq_post_id'), ?, ?, ?)`, (err, stmt) => {
if (err) return socket.emit('error', { message: 'Database error' });
stmt.run(walletAddress, content, timestamp, (err) => {
stmt.finalize();
if (err) return socket.emit('error', { message: 'Failed to create post' });
// Fetch all posts to broadcast update or just emit the new one
// For simplicity, we'll just tell the user it was created
socket.emit('postCreated', { content, timestamp });
// If we want a live feed, we could broadcast to a "profile room"
// For now, the user can just refresh or we emit to them
});
});
});
socket.on('toggleReaction', ({ messageId, walletAddress, emoji }) => {
console.log(`Toggling reaction: ${emoji} on message ${messageId} by ${walletAddress}`);
con.prepare(`SELECT * FROM reactions WHERE message_id = ? AND wallet_address = ? AND emoji = ?`, (err, stmt) => {