const { Client } = require("pg"); const crypto = require("crypto"); require("dotenv").config(); const client = new Client({ user: process.env.PG_USER, password: process.env.PG_PASSWORD, database: process.env.PG_DATABASE, host: process.env.PG_HOST, port: 5432, }); client .connect() .then(() => { createTables() .then(() => console.log("Tables created successfully")) .catch((e) => console.error("Failed to create tables ", e)); console.log( `Successfully connected to database: ${process.env.PG_DATABASE}`, ); }) .catch((err) => console.error( `Failed to connect to database: ${process.env.PG_DATABASE}, ${err}`, ), ); // Creating database tables async function createTables() { try { await client.query(` CREATE TABLE IF NOT EXISTS accounts ( username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, user_id VARCHAR(128) PRIMARY KEY, created_at TIMESTAMPTZ DEFAULT NOW() ); `); } catch (e) { console.error("Failed to create accounts table ", e); } try { await client.query(` CREATE TABLE IF NOT EXISTS messages ( sender VARCHAR(128) NOT NULL, recipient VARCHAR(128) NOT NULL, message TEXT NOT NULL, timestamp TIMESTAMPTZ DEFAULT NOW(), message_id SERIAL PRIMARY KEY, attachment_url TEXT, UNIQUE (sender, recipient, message_id) ); CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages (sender, recipient, message_id ASC); `); } catch (e) { console.error("Failed to create messages table ", e); } try { await client.query(` CREATE TABLE IF NOT EXISTS contacts ( username VARCHAR(20) NOT NULL, usernameContact VARCHAR(20) NOT NULL, read BOOLEAN NOT NULL, lastActive VARCHAR(100) NOT NULL, CONSTRAINT unique_username_contact UNIQUE (username, usernameContact) ); `); } catch (e) { console.error("Failed to create messages table ", e); } } async function insertUser(username, password) { const user_id = crypto.randomUUID(); const query = ` INSERT INTO accounts (username, password, user_id) VALUES ($1, $2, $3); `; try { await client.query(query, [username, password, user_id]); } catch (e) { console.error("Failed to insert user ", e); } } async function getUserId(username) { const query = ` SELECT user_id FROM accounts WHERE username = $1; `; try { const result = await client.query(query, [username]); console.log("GETUSERID: ", result.rows[0].user_id); return result.rows[0].user_id; } catch (e) { console.error("Failed to get user id", e); } } async function insertMessage(sender, recipient, message, attachmentUrl) { const query = ` INSERT INTO messages (sender, recipient, message, attachment_url) VALUES ($1, $2, $3, $4) RETURNING message, timestamp, message_id, attachment_url; `; try { const results = await client.query(query, [ sender, recipient, message, attachmentUrl, ]); return results.rows[0]; } catch (e) { console.error("Failed to insert message ", e); } } async function getMessages(username, recipient, limit = 50, cursor = 0) { console.log( `getMessages for Username: ${username}, recipient: ${recipient}, limit: ${limit}, cursor: ${cursor}`, ); let query; let params; if (cursor) { query = ` SELECT * FROM messages WHERE ((sender = $1 AND recipient = $2) OR (sender = $2 AND recipient = $1)) AND message_id < $3 ORDER BY message_id DESC LIMIT $4; `; params = [username, recipient, cursor, limit]; } else { query = ` SELECT * FROM messages WHERE (sender = $1 AND recipient = $2) OR (sender = $2 AND recipient = $1) ORDER BY message_id DESC LIMIT $3; `; params = [username, recipient, limit]; } try { const results = await client.query(query, params); let messages = results.rows; if (!cursor) { messages = messages.reverse(); } return messages; } catch (e) { console.error("Failed to get messages ", e); } } async function checkUserExist(username) { const query = ` SELECT 1 FROM accounts WHERE LOWER(username) = LOWER($1) LIMIT 1; `; try { const result = await client.query(query, [username]); return result.rows.length > 0; } catch (e) { console.error("Failed to check if user exist ", e); return false; } } async function getPassword(username) { console.log(`Get password for: ${username}`); const query = ` SELECT password FROM accounts WHERE LOWER(username) = LOWER($1); `; try { const result = await client.query(query, [username]); return result.rows[0].password; } catch (e) { console.error("Failed to get user password ", e); } } async function changePassword(username, newPassword) { const query = ` UPDATE accounts SET password = $1 WHERE username = $2; `; try { await client.query(query, [newPassword, username]); } catch (e) { console.error("Failed to change password ", e); } } async function insertContact(username, usernameContact, read) { const timestamp = getTime(); console.log( `insertContact username: ${username}, usernameContact: ${usernameContact}, read: ${read}`, ); const query = ` INSERT INTO contacts (username, usernameContact, lastActive, read) VALUES ($1, $2, $3, $4) ON CONFLICT ON CONSTRAINT unique_username_contact DO NOTHING; `; try { await client.query(query, [username, usernameContact, timestamp, read]); } catch (e) { console.error("Failed to insert contact ", e); } } async function getContacts(username) { const query = ` SELECT usernameContact, read FROM contacts WHERE username = $1 ORDER BY lastActive ASC; `; try { const result = await client.query(query, [username]); return result.rows; } catch (e) { console.error("Failed to get contacts ", e); } } async function deleteContact(username, usernamecontact) { const query = ` DELETE FROM contacts WHERE (LOWER(username) = LOWER($1) AND LOWER(usernamecontact) = LOWER($2)) RETURNING *; `; try { const result = await client.query(query, [username, usernamecontact]); if (result.rowCount === 0) { console.log("No matching contact found with:", { username, usernamecontact, }); } else { console.log("Successfully deleted contact"); } } catch (e) { console.error("Failed to remove contact ", e); } } async function updateContactStatus(username, usernamecontact, read) { const query = ` UPDATE contacts SET read = $1 WHERE username = $2 AND usernamecontact = $3 `; try { await client.query(query, [read, username, usernamecontact]); await updateContactLastActive(username, usernamecontact); console.log("Successfully updated contact status"); } catch (e) { console.error("Failed to update contact status ", e); } } async function updateContactLastActive(username, usernamecontact) { const timestamp = getTime(); const query = ` UPDATE contacts SET lastActive = $1 WHERE username = $2 AND usernamecontact = $3 `; try { await client.query(query, [timestamp, username, usernamecontact]); console.log("Successfully updated contact last active time"); } catch (e) { console.error("Failed to update contact last active time"); } } function getTime() { const now = new Date(); const year = now.getFullYear(); const month = ("0" + (now.getMonth() + 1)).slice(-2); const day = ("0" + now.getDate()).slice(-2); const hour = ("0" + now.getHours()).slice(-2); const minute = ("0" + now.getMinutes()).slice(-2); const second = ("0" + now.getSeconds()).slice(-2); return `${year}-${month}-${day} ${hour}:${minute}:${second}`; } module.exports = { client, insertUser, insertMessage, checkUserExist, changePassword, getPassword, insertContact, deleteContact, getMessages, getUserId, getContacts, updateContactStatus, updateContactLastActive, };