| Server IP : 167.235.67.158 / Your IP : 216.73.216.95 Web Server : Apache System : Linux ubuntu-8gb-nbg1-1 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64 User : root ( 0) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/api.pda.the-word.com/common/ |
Upload File : |
const crypto = require('node:crypto');
const mysql = require('mysql2');
const pool = mysql.createPool({
host: process.env.MYSQL_HOST,
port: Number(process.env.MYSQL_PORT || 3306),
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DB,
charset: 'utf8mb4',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
function queryDatabase(sql, values = []) {
return new Promise((resolve, reject) => {
pool.query(sql, values, (error, results) => {
if (error) {
reject(error);
return;
}
resolve(results);
});
});
}
function getConnection() {
return new Promise((resolve, reject) => {
pool.getConnection((error, connection) => {
if (error) {
reject(error);
return;
}
resolve(connection);
});
});
}
async function withTransaction(work) {
const connection = await getConnection();
const tx = {
query(sql, values = []) {
return new Promise((resolve, reject) => {
connection.query(sql, values, (error, results) => {
if (error) {
reject(error);
return;
}
resolve(results);
});
});
},
async insertRow(table, data) {
const keys = Object.keys(data);
const values = Object.values(data);
const placeholders = keys.map(() => '?').join(', ');
const columns = keys.map((key) => `\`${key}\``).join(', ');
const sql = `INSERT INTO \`${table}\` (${columns}) VALUES (${placeholders})`;
const result = await this.query(sql, values);
return result.insertId;
},
async updateRows(table, data, whereClause, whereValues = []) {
const keys = Object.keys(data);
const values = Object.values(data);
const setClause = keys.map((key) => `\`${key}\` = ?`).join(', ');
const sql = `UPDATE \`${table}\` SET ${setClause} WHERE ${whereClause}`;
const result = await this.query(sql, [...values, ...whereValues]);
return result.affectedRows;
}
};
try {
await tx.query('START TRANSACTION');
const result = await work(tx);
await tx.query('COMMIT');
connection.release();
return result;
} catch (error) {
try {
await tx.query('ROLLBACK');
} finally {
connection.release();
}
throw error;
}
}
async function insertRow(table, data) {
const keys = Object.keys(data);
const values = Object.values(data);
const placeholders = keys.map(() => '?').join(', ');
const columns = keys.map((key) => `\`${key}\``).join(', ');
const sql = `INSERT INTO \`${table}\` (${columns}) VALUES (${placeholders})`;
const result = await queryDatabase(sql, values);
return result.insertId;
}
async function updateRows(table, data, whereClause, whereValues = []) {
const keys = Object.keys(data);
const values = Object.values(data);
const setClause = keys.map((key) => `\`${key}\` = ?`).join(', ');
const sql = `UPDATE \`${table}\` SET ${setClause} WHERE ${whereClause}`;
const result = await queryDatabase(sql, [...values, ...whereValues]);
return result.affectedRows;
}
async function deleteRows(table, whereClause, whereValues = []) {
const sql = `DELETE FROM \`${table}\` WHERE ${whereClause}`;
const result = await queryDatabase(sql, whereValues);
return result.affectedRows;
}
async function insertOrUpdateRow(table, data, whereClause, whereValues = []) {
const updatedRows = await updateRows(table, data, whereClause, whereValues);
if (updatedRows > 0) {
return updatedRows;
}
return insertRow(table, data);
}
let ensurePromise;
function isUuidLike(value) {
return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
}
async function columnExists(tableName, columnName) {
const rows = await queryDatabase(
`SELECT COUNT(*) AS count
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME = ?`,
[tableName, columnName]
);
return Number(rows[0]?.count || 0) > 0;
}
async function indexExists(tableName, indexName) {
const rows = await queryDatabase(
`SELECT COUNT(*) AS count
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND INDEX_NAME = ?`,
[tableName, indexName]
);
return Number(rows[0]?.count || 0) > 0;
}
async function ensureSyncUuid(tableName, indexName) {
if (!await columnExists(tableName, 'SyncUUID')) {
await queryDatabase(`ALTER TABLE \`${tableName}\` ADD COLUMN SyncUUID VARCHAR(36) NULL`);
}
await ensureEntitySyncUuids(tableName);
if (!await indexExists(tableName, indexName)) {
await queryDatabase(`CREATE UNIQUE INDEX \`${indexName}\` ON \`${tableName}\` (Instructor, SyncUUID)`);
}
}
async function ensureEntitySyncUuids(tableName, instructorId = null) {
const where = instructorId ? 'WHERE Instructor = ?' : '';
const values = instructorId ? [instructorId] : [];
const rows = await queryDatabase(
`SELECT ID, Instructor, SyncUUID FROM \`${tableName}\` ${where} ORDER BY Instructor ASC, ID ASC`,
values
);
const seen = new Set();
for (const row of rows) {
const instructor = Number(row.Instructor || 0);
const existingUuid = String(row.SyncUUID || '').trim().toLowerCase();
const existingKey = `${instructor}:${existingUuid}`;
if (isUuidLike(existingUuid) && !seen.has(existingKey)) {
seen.add(existingKey);
if (existingUuid !== row.SyncUUID) {
await queryDatabase(`UPDATE \`${tableName}\` SET SyncUUID = ? WHERE ID = ? AND Instructor = ?`, [existingUuid, row.ID, instructor]);
}
continue;
}
let replacementUuid;
do {
replacementUuid = crypto.randomUUID();
} while (seen.has(`${instructor}:${replacementUuid}`));
seen.add(`${instructor}:${replacementUuid}`);
await queryDatabase(`UPDATE \`${tableName}\` SET SyncUUID = ? WHERE ID = ? AND Instructor = ?`, [replacementUuid, row.ID, instructor]);
}
}
function ensureApiTables() {
if (ensurePromise) {
return ensurePromise;
}
const statements = [
`CREATE TABLE IF NOT EXISTS api_user_tokens (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
token_hash VARCHAR(64) NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
last_used_at DATETIME(3) NULL,
revoked_at DATETIME(3) NULL,
UNIQUE KEY uq_api_user_tokens_hash (token_hash),
KEY idx_api_user_tokens_user (user_id)
)`,
`CREATE TABLE IF NOT EXISTS api_refresh_tokens (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
token_hash VARCHAR(64) NOT NULL,
expires_at DATETIME(3) NOT NULL,
revoked_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
replaced_by_id BIGINT NULL,
UNIQUE KEY uq_api_refresh_tokens_hash (token_hash),
KEY idx_api_refresh_tokens_user (user_id)
)`,
`CREATE TABLE IF NOT EXISTS sync_operations (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
action_id VARCHAR(128) NOT NULL,
entity_type VARCHAR(64) NOT NULL,
entity_id BIGINT NULL,
status VARCHAR(32) NOT NULL,
request_payload LONGTEXT NOT NULL,
response_payload LONGTEXT NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uq_sync_operations_user_action (user_id, action_id)
)`,
`CREATE TABLE IF NOT EXISTS sync_entity_keys (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
device_id VARCHAR(128) NOT NULL,
entity_type VARCHAR(64) NOT NULL,
local_id VARCHAR(128) NOT NULL,
entity_id BIGINT NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uq_sync_entity_keys_lookup (user_id, device_id, entity_type, local_id),
KEY idx_sync_entity_keys_entity (entity_type, entity_id)
)`,
`CREATE TABLE IF NOT EXISTS sync_entity_state (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
entity_type VARCHAR(64) NOT NULL,
entity_id BIGINT NOT NULL,
last_client_edited_at_utc DATETIME(3) NOT NULL,
last_device_id VARCHAR(128) NOT NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
UNIQUE KEY uq_sync_entity_state_entity (user_id, entity_type, entity_id)
)`,
`CREATE TABLE IF NOT EXISTS sync_change_log (
cursor_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
entity_type VARCHAR(64) NOT NULL,
entity_id BIGINT NOT NULL,
operation VARCHAR(32) NOT NULL,
changed_at_utc DATETIME(3) NOT NULL,
KEY idx_sync_change_log_user_cursor (user_id, cursor_id),
KEY idx_sync_change_log_entity (entity_type, entity_id)
)`
];
ensurePromise = (async () => {
for (const statement of statements) {
await queryDatabase(statement);
}
await ensureSyncUuid('Students', 'uq_students_instructor_syncuuid');
await ensureSyncUuid('Lessons', 'uq_lessons_instructor_syncuuid');
})().catch((error) => {
ensurePromise = null;
throw error;
});
return ensurePromise;
}
module.exports = {
deleteRows,
ensureEntitySyncUuids,
ensureApiTables,
insertOrUpdateRow,
insertRow,
queryDatabase,
updateRows,
withTransaction
};