absenat
Version:
dedicated messaging service core
558 lines (534 loc) • 23 kB
JavaScript
/**
* @author mr-exception
* @description This file contains all the database functions. Outside this file, no part of the source communicates directly with the database. Currently the database used is sqlite3. Because this database can be source as a companion file and does not require complicated user setup and configuration.
*/
const sqlite3 = require('sqlite3').verbose();
let mineDB = null;
let bridgeDB = null;
let connectionsDb = null;
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* initialization
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description initilize clients main database
* @param {*} profile
* @param {*} fresh
*/
const getMyDatabase = (profile = 'default', fresh = false) => {
if (fresh && fs.existsSync(`./profiles/${profile}.mine.sqlite`)) {
fs.unlinkSync(`./profiles/${profile}.mine.sqlite`);
}
if (!fs.existsSync('./profiles')) {
fs.mkdirSync('./profiles');
}
const mineDatabase = new sqlite3.Database(`./profiles/${profile}.mine.sqlite`);
mineDatabase.serialize(() => {
const queries = fs.readFileSync(`./db_templates/mine.sql`).toString();
mineDatabase.exec(queries);
});
mineDB = mineDatabase;
return mineDB;
}
/**
* @author mr-exception
* @description initilize bridge main database
* @param {*} profile
* @param {*} fresh
*/
const getBridgeDatabase = (profile = 'default', fresh = false) => {
if (fresh && fs.existsSync(`./profiles/${profile}.bridge.sqlite`)) {
fs.unlinkSync(`./profiles/${profile}.bridge.sqlite`);
}
if (!fs.existsSync('./profiles')) {
fs.mkdirSync('./profiles');
}
const bridgeDatabase = new sqlite3.Database(`./profiles/${profile}.bridge.sqlite`);
bridgeDatabase.serialize(() => {
const queries = fs.readFileSync(`./db_templates/bridge.sql`).toString();
bridgeDatabase.exec(queries);
});
bridgeDB = bridgeDatabase;
return bridgeDB;
}
/**
* @author mr-exception
* @description initilize connections database
* @param {*} profile
* @param {*} fresh
*/
const getConnectionsDatabase = (profile = 'default', fresh = false) => {
if (fresh && fs.existsSync(`./profiles/${profile}.connections.sqlite`)) {
fs.unlinkSync(`./profiles/${profile}.connections.sqlite`);
}
if (!fs.existsSync('./profiles')) {
fs.mkdirSync('./profiles');
}
const connectionsDatabase = new sqlite3.Database(`./profiles/${profile}.connections.sqlite`);
connectionsDatabase.serialize(() => {
const queries = fs.readFileSync(`./db_templates/connections.sql`).toString();
connectionsDatabase.exec(queries);
});
connectionsDb = connectionsDatabase;
return connectionsDatabase;
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* contacts and connections
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
const addBridge = (title, description, public_key, ip, port) => {
connectionsDb.all(`SELECT * FROM bridges WHERE public_key = ?`, [public_key], (err, rows) => {
if (rows.length === 0) {
const stmt = connectionsDb.prepare('INSERT INTO bridges (title, description, ip, port, public_key, last_time_connected) VALUES (?, ?, ?, ?, ?, ?)');
stmt.run(title, description, ip, port, public_key, Date.now());
stmt.finalize();
} else {
const stmt = connectionsDb.prepare('UPDATE bridges set title = ?, description = ?, ip = ?, port = ?, last_time_connected = ? WHERE public_key = ?');
stmt.run(title, description, ip, port, Date.now(), public_key);
stmt.finalize();
}
});
}
/**
* @author mr-exception
* @description adds new connection to database
* @param {string} nickname
* @param {scoket object} socket
* @param {string} public_key
*/
const addConnection = (nickname, socket, public_key, is_bridge = false) => {
connectionsDb.all(`SELECT * FROM contacts WHERE public_key = ?`, [public_key], (err, rows) => {
if (rows.length === 0) {
const stmt = connectionsDb.prepare('INSERT INTO contacts (nickname, public_key, last_time_connected, socket_id, is_bridge) VALUES (?, ?, ?, ?, ?)');
stmt.run(nickname, public_key, Date.now(), socket.id, is_bridge);
stmt.finalize();
} else {
const stmt = connectionsDb.prepare('UPDATE contacts set nickname = ? ,last_time_connected = ? ,socket_id = ?, is_bridge = ? WHERE public_key = ?');
stmt.run(nickname, Date.now(), socket.id, is_bridge, public_key);
stmt.finalize();
}
});
}
/**
* @author mr-exception
* @description adds new contact into database
* @param {*} nickname
* @param {*} public_key
*/
const addIndirectConnection = (nickname, public_key) => {
connectionsDb.all(`SELECT * FROM contacts WHERE public_key = ?`, [public_key], (err, rows) => {
if (rows.length === 0) {
const stmt = connectionsDb.prepare('INSERT INTO contacts (nickname, public_key, last_time_connected, socket_id, is_bridge) VALUES (?, ?, ?, "NuLL", false)');
stmt.run(nickname, public_key, Date.now());
stmt.finalize();
} else {
const stmt = connectionsDb.prepare('UPDATE contacts set nickname = ? ,last_time_connected = ? ,socket_id = "NuLL", is_bridge = ? WHERE public_key = ?');
stmt.run(nickname, Date.now(), is_bridge, public_key);
stmt.finalize();
}
});
}
/**
* @author mr-exception
* @description gets a contact from database by its id
* @param {integer} contact_id
* @param {function} found
* @param {function} not_found
*/
const getContact = (contact_id, found, not_found) => {
connectionsDb.all(`SELECT * FROM contacts WHERE id = ?`, [contact_id], (err, rows) => {
if (rows.length > 0) {
found(rows[0]);
} else {
not_found();
}
});
}
/**
* @author mr-exception
* @description gets a contact from database by its public key
* @param {string} public_key
* @param {function} found
* @param {function} not_found
*/
const getContactByPublicKey = (public_key, found, not_found) => {
connectionsDb.all(`SELECT * FROM contacts WHERE public_key = ?`, [public_key], (err, rows) => {
if (rows.length > 0) {
found(rows[0]);
} else {
not_found();
}
});
}
/**
* @author mr-exception
* @description updates heartbeat time for a normal node
* @param {string} public_key
*/
const connectionHeartBeat = (public_key) => {
const stmt = connectionsDb.prepare('UPDATE contacts set last_time_connected = ? WHERE public_key = ?');
stmt.run(Date.now(), public_key);
stmt.finalize();
}
/**
* @author mr-exception
* @description updates heartbeat time for a bridge
* @param {string} public_key
*/
const bridgeHeartBeat = (public_key) => {
const stmt = connectionsDb.prepare('UPDATE bridges set last_time_connected = ? WHERE public_key = ?');
stmt.run(Date.now(), public_key);
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* text messages
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description saves the text message that has been received and is for me.
* @param {string} uuid
* @param {public key} dst_public_key
* @param {*} content
*/
const addRecieveingPrivateTextMessage = (uuid, dst_public_key, content) => {
getContactByPublicKey(dst_public_key, contact => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, contact.id, 0, 1, 1, content, 'NuLL', 'NuLL', Date.now());
stmt.finalize();
}, () => {
console.log(chalk.red('Err: contact not found.'));
})
}
/**
* @author mr-exception
* @description saves the text message that has been sent and is from me.
* @param {string} uuid
* @param {contact object} contact
* @param {string} content
*/
const addSendingPrivateTextMessage = (uuid, contact, content) => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, 0, contact.id, 1, 1, content, 'NuLL', 'NuLL', Date.now());
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* audio messages
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description saves the audio message that has been received and is for me.
* @param {string} uuid
* @param {string} dst_public_key
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addRecieveingPrivateAudioMessage = (uuid, dst_public_key, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
getContactByPublicKey(dst_public_key, contact => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, contact.id, 0, 1, 2, caption, filename, path, Date.now());
stmt.finalize();
}, () => {
console.log(chalk.red('Err: contact not found.'));
})
}
/**
* @author mr-exception
* @description saves the audio message that has been sent and is from me.
* @param {string} uuid
* @param {contact object} contact
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addSendingPrivateAudioMessage = (uuid, contact, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, 0, contact.id, 1, 2, caption, filename, path, Date.now());
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* image messages
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description saves the image message that has been received and is for me.
* @param {string} uuid
* @param {string} dst_public_key
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addRecieveingPrivateImageMessage = (uuid, dst_public_key, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
getContactByPublicKey(dst_public_key, contact => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, contact.id, 0, 1, 3, caption, filename, path, Date.now());
stmt.finalize();
}, () => {
console.log(chalk.red('Err: contact not found.'));
})
}
/**
* @author mr-exception
* @description saves the image message that has been sent and is from me.
* @param {string} uuid
* @param {contact object} contact
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addSendingPrivateImageMessage = (uuid, contact, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, 0, contact.id, 1, 3, caption, filename, path, Date.now());
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* movie messages
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description saves the movie message that has been received and is for me.
* @param {string} uuid
* @param {string} dst_public_key
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addRecieveingPrivateMovieMessage = (uuid, dst_public_key, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
getContactByPublicKey(dst_public_key, contact => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, contact.id, 0, 1, 4, caption, filename, path, Date.now());
stmt.finalize();
}, () => {
console.log(chalk.red('Err: contact not found.'));
})
}
/**
* @author mr-exception
* @description saves the movie message that has been sent and is from me.
* @param {string} uuid
* @param {contact object} contact
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addSendingPrivateMovieMessage = (uuid, contact, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, 0, contact.id, 1, 4, caption, filename, path, Date.now());
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* file messages
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
/**
* @author mr-exception
* @description saves the file message that has been received and is for me.
* @param {string} uuid
* @param {string} dst_public_key
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addRecieveingPrivateFileMessage = (uuid, dst_public_key, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
getContactByPublicKey(dst_public_key, contact => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, contact.id, 0, 1, 5, caption, filename, path, Date.now());
stmt.finalize();
}, () => {
console.log(chalk.red('Err: contact not found.'));
})
}
/**
* @author mr-exception
* @description saves the file message that has been sent and is from me.
* @param {string} uuid
* @param {contact object} contact
* @param {string} caption
* @param {string} path
* @param {string} filename
*/
const addSendingPrivateFileMessage = (uuid, contact, caption = 'NuLL', path = 'NuLL', filename = 'NuLL') => {
const stmt = mineDB.prepare('INSERT INTO messages (uuid, contact_id, channel_id, channel_type, type, content, filename, path, timestamp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
stmt.run(uuid, 0, contact.id, 1, 5, caption, filename, path, Date.now());
stmt.finalize();
}
/**
* ===========================================================================================================================================
* ===========================================================================================================================================
*
* history and storage
*
* ===========================================================================================================================================
* ===========================================================================================================================================
*/
const saveBridgeMessage = (uuid, src_public_key, dst_public_key, content_path) => {
const stmt = bridgeDB.prepare('INSERT INTO messages (uuid, src_public_key, dst_public_key, content_path, received_at, delivered_at, delivered) VALUES (?, ?, ?, ?, ?, 0, 0)');
stmt.run(uuid, src_public_key, dst_public_key, content_path, Date.now(), )
stmt.finalize();
}
const getUndeliveredMessages = (public_key, on_success) => {
bridgeDB.all(`SELECT * FROM messages WHERE delivered = true AND dst_public_key = ?`, [public_key], (err, rows) => {
on_success(rows);
});
}
/**
* @author mr-exception
* @description get messages list of a conversation
* @param {integer} channel_id
* @param {integer} channel_type
* @param {integer} limit
* @param {integer} offset
* @param {function} found
* @param {function} not_found
*/
const getConversation = (channel_id, channel_type, limit, offset, found, not_found) => {
mineDB.all(`SELECT * FROM messages WHERE ((channel_id = 0 AND contact_id = ?) OR (channel_id = ? AND contact_id = 0)) AND channel_type = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?`,
[channel_id, channel_id, channel_type, limit, offset], (err, rows) => {
if (rows.length === 0) {
not_found();
} else {
found(rows);
}
}
);
}
const getBridges = (on_success, on_not_found) => {
connectionsDb.all('SELECT * FROM bridges WHERE 1', [], (err, rows) => {
if (rows.length === 0) {
on_not_found();
} else {
on_success(rows);
}
});
}
const getBridge = (id, on_found, on_not_found) => {
connectionsDb.all('SELECT * FROM bridges WHERE id = ?', [id], (err, rows) => {
if (rows.length === 0) {
on_not_found();
} else {
on_found(rows[0]);
}
})
}
/**
* @author mr-exception
* @description add first name update record
* @param {string} first_name
*/
const updateFirstName = (first_name) => {
const stmt = mineDB.prepare(`INSERT INTO profile_versions (field_name, field_value, action, action_value, timestamp) VALUES ('first_name', ?, 'NULL', 'NULL', ?)`);
stmt.run(first_name, Date.now());
stmt.finalize();
}
/**
* @author mr-exception
* @description add last name update record
* @param {string} last_name
*/
const updateLastName = (last_name) => {
const stmt = mineDB.prepare(`INSERT INTO profile_versions (field_name, field_value, action, action_value, timestamp) VALUES ('last_name', ?, 'NULL', 'NULL', ?)`);
stmt.run(last_name, Date.now());
stmt.finalize();
}
/**
* @author mr-exception
* @description add bio update record
* @param {string} bio
*/
const updateBio = (bio) => {
const stmt = mineDB.prepare(`INSERT INTO profile_versions (field_name, field_value, action, action_value, timestamp) VALUES ('bio', ?, 'NULL', 'NULL', ?)`);
stmt.run(bio, Date.now());
stmt.finalize();
}
/**
* @author mr-exception
* @description add email update record
* @param {string} email
*/
const updateEmail = (email) => {
const stmt = mineDB.prepare(`INSERT INTO profile_versions (field_name, field_value, action, action_value, timestamp) VALUES ('email', ?, 'NULL', 'NULL', ?)`);
stmt.run(email, Date.now());
stmt.finalize();
}
/**
* @author mr-exception
* @description add phone update record
* @param {string} phone
*/
const updatePhone = (phone) => {
const stmt = mineDB.prepare(`INSERT INTO profile_versions (field_name, field_value, action, action_value, timestamp) VALUES ('phone', ?, 'NULL', 'NULL', ?)`);
stmt.run(phone, Date.now());
stmt.finalize();
}
module.exports = {
getBridgeDatabase,
getConnectionsDatabase,
getMyDatabase,
addBridge,
addConnection,
addIndirectConnection,
getContact,
getBridges,
getBridge,
getContactByPublicKey,
connectionHeartBeat,
bridgeHeartBeat,
addSendingPrivateTextMessage,
addRecieveingPrivateTextMessage,
addSendingPrivateImageMessage,
addRecieveingPrivateImageMessage,
addSendingPrivateMovieMessage,
addRecieveingPrivateMovieMessage,
addSendingPrivateAudioMessage,
addRecieveingPrivateAudioMessage,
addSendingPrivateFileMessage,
addRecieveingPrivateFileMessage,
saveBridgeMessage,
getUndeliveredMessages,
getConversation,
updateBio,
updateEmail,
updateFirstName,
updateLastName,
updatePhone,
}