verbose(sprintf("Checking if username %s already exists", $username)); try { $statement = Database::getConnection()->prepare('SELECT COUNT(*) FROM `registered_peers` WHERE username=?'); $statement->bindParam(1, $username); $statement->execute(); $result = $statement->fetchColumn(); return $result > 0; } catch(PDOException $e) { throw new DatabaseOperationException('Failed to check if the username exists', $e); } } /** * Creates a new peer with the given username. * * @param PeerAddress $peerAddress The address of the peer to be created. * @param bool $enabled True if the peer should be enabled, false otherwise. * @return string The UUID of the newly created peer. * @throws DatabaseOperationException If the operation fails. */ public static function createPeer(PeerAddress $peerAddress, bool $enabled=false): string { Logger::getLogger()->verbose(sprintf("Registering peer %s", $peerAddress->getAddress())); $uuid = Uuid::v4()->toRfc4122(); $server = $peerAddress->getDomain(); if($server === Configuration::getInstanceConfiguration()->getDomain()) { $server = 'host'; } try { $statement = Database::getConnection()->prepare('INSERT INTO `registered_peers` (uuid, username, server, enabled) VALUES (?, ?, ?, ?)'); $statement->bindParam(1, $uuid); $username = $peerAddress->getUsername(); $statement->bindParam(2, $username); $statement->bindParam(3, $server); $statement->bindParam(4, $enabled, PDO::PARAM_BOOL); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to create the peer in the database', $e); } return $uuid; } /** * Deletes a peer from the database based on the given UUID or RegisteredPeerRecord. * WARNING: This operation is cascading and will delete all associated data. * * @param string|RegisteredPeerRecord $uuid The UUID or RegisteredPeerRecord instance representing the peer to be deleted. * @return void * @throws DatabaseOperationException If the operation fails. */ public static function deletePeer(string|RegisteredPeerRecord $uuid): void { if($uuid instanceof RegisteredPeerRecord) { $uuid = $uuid->getUuid(); } Logger::getLogger()->verbose(sprintf("Deleting peer %s", $uuid)); try { $statement = Database::getConnection()->prepare('DELETE FROM `registered_peers` WHERE uuid=?'); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to delete the peer from the database', $e); } } /** * Retrieves a registered peer record based on the given unique identifier or RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $uuid The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @return RegisteredPeerRecord Returns a RegisteredPeerRecord object containing the peer's information. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function getPeer(string|RegisteredPeerRecord $uuid): RegisteredPeerRecord { if($uuid instanceof RegisteredPeerRecord) { $uuid = $uuid->getUuid(); } Logger::getLogger()->verbose(sprintf("Retrieving peer %s from the database", $uuid)); try { $statement = Database::getConnection()->prepare('SELECT * FROM `registered_peers` WHERE uuid=?'); $statement->bindParam(1, $uuid); $statement->execute(); $result = $statement->fetch(PDO::FETCH_ASSOC); if($result === false) { throw new DatabaseOperationException(sprintf("The requested peer '%s' does not exist", $uuid)); } return new RegisteredPeerRecord($result); } catch(Exception $e) { throw new DatabaseOperationException('Failed to get the peer from the database', $e); } } /** * Retrieves a peer record by the given username. * * @param PeerAddress $address The address of the peer to be retrieved. * @return RegisteredPeerRecord|null The record of the peer associated with the given username. * @throws DatabaseOperationException If there is an error while querying the database. */ public static function getPeerByAddress(PeerAddress $address): ?RegisteredPeerRecord { Logger::getLogger()->verbose(sprintf("Retrieving peer %s from the database", $address->getAddress())); try { $statement = Database::getConnection()->prepare('SELECT * FROM `registered_peers` WHERE username=? AND server=?'); $username = $address->getUsername(); $statement->bindParam(1, $username); $server = $address->getDomain(); $statement->bindParam(2, $server); $statement->execute(); $result = $statement->fetch(PDO::FETCH_ASSOC); if($result === false) { return null; } return new RegisteredPeerRecord($result); } catch(Exception $e) { throw new DatabaseOperationException('Failed to get the peer from the database', $e); } } /** * Enables a peer identified by the given UUID or RegisteredPeerRecord. * * @param string|RegisteredPeerRecord $uuid The UUID or RegisteredPeerRecord instance representing the peer to be enabled. * @return void * @throws DatabaseOperationException If there is an error while updating the database. */ public static function enablePeer(string|RegisteredPeerRecord $uuid): void { if($uuid instanceof RegisteredPeerRecord) { $uuid = $uuid->getUuid(); } Logger::getLogger()->verbose(sprintf("Enabling peer %s", $uuid)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET enabled=1 WHERE uuid=?'); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to enable the peer in the database', $e); } } /** * Disables the peer identified by the given UUID or RegisteredPeerRecord. * * @param string|RegisteredPeerRecord $uuid The UUID or RegisteredPeerRecord instance representing the peer. * @return void * @throws DatabaseOperationException If there is an error while updating the peer's status in the database. */ public static function disablePeer(string|RegisteredPeerRecord $uuid): void { if($uuid instanceof RegisteredPeerRecord) { $uuid = $uuid->getUuid(); } Logger::getLogger()->verbose(sprintf("Disabling peer %s", $uuid)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET enabled=0 WHERE uuid=?'); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to disable the peer in the database', $e); } } /** * Adds a specific flag to the peer identified by the given UUID or RegisteredPeerRecord. * * @param string|RegisteredPeerRecord $uuid The UUID or RegisteredPeerRecord instance representing the peer. * @param PeerFlags|array $flags The flag or array of flags to be added to the peer. * @return void * @throws DatabaseOperationException If there is an error while updating the database. */ public static function addFlag(string|RegisteredPeerRecord $uuid, PeerFlags|array $flags): void { if($uuid instanceof RegisteredPeerRecord) { $uuid = $uuid->getUuid(); } Logger::getLogger()->verbose(sprintf("Adding flag(s) %s to peer %s", implode(',', $flags), $uuid)); $peer = self::getPeer($uuid); $existingFlags = $peer->getFlags(); $flags = is_array($flags) ? $flags : [$flags]; foreach($flags as $flag) { if(!in_array($flag, $existingFlags)) { $existingFlags[] = $flag; } } try { $implodedFlags = implode(',', array_map(fn($flag) => $flag->name, $existingFlags)); $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET flags=? WHERE uuid=?'); $statement->bindParam(1, $implodedFlags); $statement->bindParam(2, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to add the flag to the peer in the database', $e); } } /** * Removes a specific flag from the peer identified by the given UUID or RegisteredPeerRecord. * * @param string|RegisteredPeerRecord $peer * @param PeerFlags $flag The flag to be removed from the peer. * @return void * @throws DatabaseOperationException If there is an error while updating the database. */ public static function removeFlag(string|RegisteredPeerRecord $peer, PeerFlags $flag): void { if(is_string($peer)) { $peer = self::getPeer($peer); } Logger::getLogger()->verbose(sprintf("Removing flag %s from peer %s", $flag->value, $peer->getUuid())); if(!$peer->flagExists($flag)) { return; } $peer->removeFlag($flag); try { $implodedFlags = PeerFlags::toString($peer->getFlags()); $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET flags=? WHERE uuid=?'); $statement->bindParam(1, $implodedFlags); $statement->bindParam(2, $registeredPeer); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to remove the flag from the peer in the database', $e); } } /** * Updates the display name of a registered peer based on the given unique identifier or RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @param string $name The new display name to set to the user * @throws DatabaseOperationException Thrown if there was an error while trying to update the display name */ public static function updateDisplayName(string|RegisteredPeerRecord $peer, string $name): void { if(empty($name)) { throw new InvalidArgumentException('The display name cannot be empty'); } if(strlen($name) > 256) { throw new InvalidArgumentException('The display name cannot exceed 256 characters'); } if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot update the display name of an external peer'); } Logger::getLogger()->verbose(sprintf("Updating display name of peer %s to %s", $peer->getUuid(), $name)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET display_name=? WHERE uuid=?'); $statement->bindParam(1, $name); $uuid = $peer->getUuid(); $statement->bindParam(2, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to update the display name of the peer in the database', $e); } } /** * Deletes the display name of a registered peer identified by a unique identifier or RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @return void * @throws InvalidArgumentException If the peer is external and its display name cannot be deleted. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function deleteDisplayName(string|RegisteredPeerRecord $peer): void { if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot delete the display name of an external peer'); } Logger::getLogger()->verbose(sprintf("Deleting display name of peer %s", $peer->getUuid())); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET display_name=NULL WHERE uuid=?'); $uuid = $peer->getUuid(); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to delete the display name of the peer in the database', $e); } } /** * Updates the display picture of a registered peer in the database. * * @param string|RegisteredPeerRecord $peer The unique identifier of the peer or an instance of RegisteredPeerRecord. * @param string $displayPictureData The raw jpeg data of the display picture. * @return void * @throws DatabaseOperationException If there is an error during the database operation. */ public static function updateDisplayPicture(string|RegisteredPeerRecord $peer, string $displayPictureData): void { if(empty($uuid)) { throw new InvalidArgumentException('The display picture UUID cannot be empty'); } $uuid = Uuid::v4()->toRfc4122(); $displayPicturePath = Configuration::getStorageConfiguration()->getUserDisplayImagesPath() . DIRECTORY_SEPARATOR . $uuid . '.jpeg'; // Delete the file if it already exists if(file_exists($displayPicturePath)) { unlink($displayPicturePath); } // Write the file contents & set the permissions file_put_contents($displayPicturePath, $displayPictureData); chmod($displayPicturePath, 0644); if(is_string($peer)) { $peer = self::getPeer($peer); } // TODO: Handle for external peers, needs a way to resolve peers to their external counterparts if($peer->isExternal()) { throw new InvalidArgumentException('Cannot update the display picture of an external peer'); } Logger::getLogger()->verbose(sprintf("Updating display picture of peer %s to %s", $peer->getUuid(), $uuid)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET display_picture=? WHERE uuid=?'); $statement->bindParam(1, $uuid); $peerUuid = $peer->getUuid(); $statement->bindParam(2, $peerUuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to update the display picture of the peer in the database', $e); } } /** * Deletes the display picture of a registered peer based on the given unique identifier or RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @return void * @throws InvalidArgumentException If the peer is external and its display picture cannot be deleted. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function deleteDisplayPicture(string|RegisteredPeerRecord $peer): void { if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { // TODO: Implement this throw new InvalidArgumentException('Cannot delete the display picture of an external peer'); } Logger::getLogger()->verbose(sprintf("Deleting display picture of peer %s", $peer->getUuid())); // Delete the file if it exists $displayPicturePath = Configuration::getStorageConfiguration()->getUserDisplayImagesPath() . DIRECTORY_SEPARATOR . $peer->getDisplayPicture() . '.jpeg'; if(file_exists($displayPicturePath)) { unlink($displayPicturePath); } try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET display_picture=NULL WHERE uuid=?'); $uuid = $peer->getUuid(); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to delete the display picture of the peer in the database', $e); } } /** * Updates the email address of a registered peer. * * @param string|RegisteredPeerRecord $peer The unique identifier of the peer, or an instance of RegisteredPeerRecord. * @param string $emailAddress The new email address to be assigned to the peer. * @return void * @throws InvalidArgumentException If the email address is empty, exceeds 256 characters, is not a valid email format, or if the peer is external. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function updateEmailAddress(string|RegisteredPeerRecord $peer, string $emailAddress): void { if(empty($emailAddress)) { throw new InvalidArgumentException('The email address cannot be empty'); } if(strlen($emailAddress) > 256) { throw new InvalidArgumentException('The email address cannot exceed 256 characters'); } if(filter_var($emailAddress, FILTER_VALIDATE_EMAIL) === false) { throw new InvalidArgumentException('The email address is not valid'); } if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot update the email address of an external peer'); } Logger::getLogger()->verbose(sprintf("Updating email address of peer %s to %s", $peer->getUuid(), $emailAddress)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET email_address=? WHERE uuid=?'); $statement->bindParam(1, $emailAddress); $uuid = $peer->getUuid(); $statement->bindParam(2, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to update the email address of the peer in the database', $e); } } /** * Deletes the email address of a registered peer identified by either a unique identifier or a RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @return void * @throws InvalidArgumentException If the peer is external and its email address cannot be deleted. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function deleteEmailAddress(string|RegisteredPeerRecord $peer): void { if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot delete the email address of an external peer'); } Logger::getLogger()->verbose(sprintf("Deleting email address of peer %s", $peer->getUuid())); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET email_address=NULL WHERE uuid=?'); $uuid = $peer->getUuid(); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to delete the email address of the peer in the database', $e); } } /** * Updates the phone number of the specified registered peer. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @param string $phoneNumber The new phone number to be set for the peer. * @return void * @throws InvalidArgumentException If the phone number is empty, exceeds 16 characters, is invalid, or if the peer is external. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function updatePhoneNumber(string|RegisteredPeerRecord $peer, string $phoneNumber): void { if(empty($phoneNumber)) { throw new InvalidArgumentException('The phone number cannot be empty'); } if(strlen($phoneNumber) > 16) { throw new InvalidArgumentException('The phone number cannot exceed 16 characters'); } if(!Validator::validatePhoneNumber($phoneNumber)) { throw new InvalidArgumentException('The phone number is not valid'); } if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot update the phone number of an external peer'); } Logger::getLogger()->verbose(sprintf("Updating phone number of peer %s to %s", $peer->getUuid(), $phoneNumber)); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET phone_number=? WHERE uuid=?'); $statement->bindParam(1, $phoneNumber); $uuid = $peer->getUuid(); $statement->bindParam(2, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to update the phone number of the peer in the database', $e); } } /** * Deletes the phone number of a registered peer based on the given unique identifier or RegisteredPeerRecord object. * * @param string|RegisteredPeerRecord $peer The unique identifier of the registered peer, or an instance of RegisteredPeerRecord. * @return void This method does not return a value. * @throws InvalidArgumentException If the peer is external and its phone number cannot be deleted. * @throws DatabaseOperationException If there is an error during the database operation. */ public static function deletePhoneNumber(string|RegisteredPeerRecord $peer): void { if(is_string($peer)) { $peer = self::getPeer($peer); } if($peer->isExternal()) { throw new InvalidArgumentException('Cannot delete the phone number of an external peer'); } Logger::getLogger()->verbose(sprintf("Deleting phone number of peer %s", $peer->getUuid())); try { $statement = Database::getConnection()->prepare('UPDATE `registered_peers` SET phone_number=NULL WHERE uuid=?'); $uuid = $peer->getUuid(); $statement->bindParam(1, $uuid); $statement->execute(); } catch(PDOException $e) { throw new DatabaseOperationException('Failed to delete the phone number of the peer in the database', $e); } } }