Skip to content

Commit

Permalink
UpdateQueryToSupportPostgres
Browse files Browse the repository at this point in the history
  • Loading branch information
docjyJ committed Oct 12, 2024
1 parent 855c094 commit 3fcc705
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 155 deletions.
27 changes: 16 additions & 11 deletions lib/Models/AccountEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

readonly class AccountEntity {
public const TABLE = 'stalwart_accounts';
public const COL_ID = 'uid';
public const COL_TYPE = 'type';
public const COL_DISPLAY = 'display_name';
public const COL_PASSWORD = 'password';
public const COL_QUOTA = 'quota';

public function __construct(
public string $uid,
Expand All @@ -21,31 +26,31 @@ public static function parse(ConfigEntity $conf, mixed $value): self {
if (!is_array($value)) {
throw new ValueError('value must be an array');
}
if (!is_string($value['uid'])) {
if (!is_string($value[self::COL_ID])) {
throw new ValueError('uid must be a string');
}
if ($value['cid'] !== $conf->cid) {
if ($conf->cid !== $value[ConfigEntity::COL_ID]) {
throw new ValueError('cid must be an integer');
}
if (!is_string($value['display_name'])) {
if (!is_string($value[self::COL_DISPLAY])) {
throw new ValueError('display_name must be a string');
}
if (!is_string($value['password'])) {
if (!is_string($value[self::COL_PASSWORD])) {
throw new ValueError('password must be a string');
}
if (!is_string($value['type'])) {
if (!is_string($value[self::COL_TYPE])) {
throw new ValueError('type must be a string');
}
if (!is_int($value['quota'])) {
if (!is_int($value[self::COL_QUOTA])) {
throw new ValueError('quota must be an integer');
}
return new self(
$value['uid'],
$value[self::COL_ID],
$conf,
$value['display_name'],
$value['password'],
AccountType::from($value['type']),
$value['quota']
$value[self::COL_DISPLAY],
$value[self::COL_PASSWORD],
AccountType::from($value[self::COL_TYPE]),
$value[self::COL_QUOTA],
);
}
}
25 changes: 15 additions & 10 deletions lib/Models/ConfigEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
/** @psalm-import-type StalwartServerConfig from ResponseDefinitions */
readonly class ConfigEntity implements JsonSerializable {
public const TABLE = 'stalwart_configs';
public const COL_ID = 'cid';
public const COL_ENDPOINT = 'endpoint';
public const COL_USERNAME = 'username';
public const COL_PASSWORD = 'password';
public const COL_HEALTH = 'health';
private const URL_PATTERN = '/^https?:\\/\\/([a-z0-9-]+\\.)*[a-z0-9-]+(:\\d{1,5})?\\/api$/';


Expand All @@ -29,27 +34,27 @@ public static function parse(mixed $value): ConfigEntity {
if (!is_array($value)) {
throw new ValueError('value must be an array');
}
if (!is_string($value['cid'])) {
if (!is_string($value[self::COL_ID])) {
throw new ValueError('cid must be a string');
}
if (!is_string($value['endpoint'])) {
if (!is_string($value[self::COL_ENDPOINT])) {
throw new ValueError('endpoint must be a string');
}
if (!is_string($value['username'])) {
if (!is_string($value[self::COL_USERNAME])) {
throw new ValueError('username must be a string');
}
if (!is_string($value['password'])) {
if (!is_string($value[self::COL_PASSWORD])) {
throw new ValueError('password must be a string');
}
if (!is_string($value['health'])) {
if (!is_string($value[self::COL_HEALTH])) {
throw new ValueError('health must be a string');
}
return new self(
$value['cid'],
$value['endpoint'],
$value['username'],
$value['password'],
ServerStatus::from($value['health'])
$value[self::COL_ID],
$value[self::COL_ENDPOINT],
$value[self::COL_USERNAME],
$value[self::COL_PASSWORD],
ServerStatus::from($value[self::COL_HEALTH]),
);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Models/EmailEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

readonly class EmailEntity {
public const TABLE = 'stalwart_emails';
public const COL_EMAIL = 'email';
public const COL_TYPE = 'type';

public function __construct(
public AccountEntity $account,
Expand Down
146 changes: 143 additions & 3 deletions lib/Services/ISqlService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,149 @@

namespace OCA\Stalwart\Services;

use Exception;
use OCA\Stalwart\Models\AccountEntity;
use OCA\Stalwart\Models\ConfigEntity;
use OCA\Stalwart\Models\EmailEntity;
use OCA\Stalwart\Models\EmailType;
use OCP\IConfig;

interface ISqlService {
public function __construct(IConfig $config);
public function getStalwartConfig(string $cid): string;
abstract readonly class ISqlService {
/**
* @throws Exception
*/
protected function queryName(string $cid): string {
if (preg_match('/\W/', $cid)) {
throw new Exception('The configuration ID is invalid, only word characters allowed to prevent SQL injection');
}
$table = $this->dbNcPrefix . AccountEntity::TABLE;
$colId = AccountEntity::COL_ID;
$colType = AccountEntity::COL_TYPE;
$colDisplay = AccountEntity::COL_DISPLAY;
$colPassword = AccountEntity::COL_PASSWORD;
$colQuota = AccountEntity::COL_QUOTA;
$colConfig = ConfigEntity::COL_ID;
$param = $this->dbParam;
// SELECT name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true
// SELECT name, type, secret, description, quota FROM accounts WHERE name = $1 AND active = true
return "SELECT $colId, $colType, $colDisplay, $colPassword, $colQuota FROM $table WHERE $colConfig = '$cid' AND $colId = $param";
}

// SELECT member_of FROM group_members WHERE name = ?
// SELECT member_of FROM group_members WHERE name = $1
protected function queryMembers(): string {
$table = $this->dbNcPrefix . AccountEntity::TABLE;
$colId = AccountEntity::COL_ID;
$colMember = AccountEntity::COL_ID;
$param = $this->dbParam;
return "SELECT $colMember FROM $table WHERE $colId = $param LIMIT 0";
}

protected function queryRecipients(): string {
$table = $this->dbNcPrefix . AccountEntity::TABLE;
$colId = AccountEntity::COL_ID;
$colEmail = EmailEntity::COL_EMAIL;
$param = $this->dbParam;
// SELECT name FROM emails WHERE address = ? ORDER BY name ASC
// SELECT name FROM emails WHERE address = $1 ORDER BY name ASC
return "SELECT $colId FROM $table WHERE $colEmail = $param ORDER BY $colId ASC";
}

protected function queryEmails(): string {
$table = $this->dbNcPrefix . EmailEntity::TABLE;
$colId = AccountEntity::COL_ID;
$colEmail = EmailEntity::COL_EMAIL;
$colType = EmailEntity::COL_TYPE;
$typeList = EmailType::List->value;
$param = $this->dbParam;
// SELECT address FROM emails WHERE name = ? AND type != 'list' ORDER BY type DESC, address ASC
// SELECT address FROM emails WHERE name = $1 AND type != 'list' ORDER BY type DESC, address ASC
return "SELECT $colEmail FROM $table WHERE $colId = $param AND $colType != '$typeList' ORDER BY $colType DESC, $colEmail ASC";
}

protected function queryVerify(): string {
$table = $this->dbNcPrefix . EmailEntity::TABLE;
$colEmail = EmailEntity::COL_EMAIL;
$colType = EmailEntity::COL_TYPE;
$typePrimary = EmailType::Primary->value;
$concat = $this->concatVerify;
// SELECT address FROM emails WHERE address LIKE CONCAT('%', ?, '%') AND type = 'primary' ORDER BY address LIMIT 5
// SELECT address FROM emails WHERE address LIKE '%' || $1 || '%' AND type = 'primary' ORDER BY address LIMIT 5
return "SELECT $colEmail FROM $table WHERE $colEmail LIKE $concat AND $colType = '$typePrimary' ORDER BY $colEmail LIMIT 5";
}

protected function queryExpand(): string {
$table = $this->dbNcPrefix . EmailEntity::TABLE;
$colId = AccountEntity::COL_ID;
$colEmail = EmailEntity::COL_EMAIL;
$colType = EmailEntity::COL_TYPE;
$typePrimary = EmailType::Primary->value;
$typeList = EmailType::List->value;
$param = $this->dbParam;
// SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50
// SELECT p.address FROM emails AS p JOIN emails AS l ON p.name = l.name WHERE p.type = 'primary' AND l.address = $1 AND l.type = 'list' ORDER BY p.address LIMIT 50
return "SELECT p.$colEmail FROM $table AS p JOIN $table AS l USING ($colId) WHERE p.$colType = '$typePrimary' AND l.$colType = '$typeList' AND l.$colEmail = $param ORDER BY p.$colEmail LIMIT 50";
}

protected function queryDomains(): string {
$table = $this->dbNcPrefix . EmailEntity::TABLE;
$colEmail = EmailEntity::COL_EMAIL;
$concat = $this->concatDomains;
// SELECT 1 FROM emails WHERE address LIKE CONCAT('%@', ?) LIMIT 1
// SELECT 1 FROM emails WHERE address LIKE '%@' || $1 LIMIT 1
return "SELECT 1 FROM $table WHERE $colEmail LIKE $concat LIMIT 1";
}

protected string $dbHost;
protected int $dbPort;
protected string $dbName;
protected string $dbUser;
protected string $dbPassword;
private string $dbNcPrefix;
private string $dbParam;
private string $concatVerify;
private string $concatDomains;

/**
* @throws Exception
*/
public function __construct(IConfig $config) {
$host = $config->getSystemValueString('dbhost');
if ($host === '') {
throw new Exception('The database host looks empty but it should be set');
}
$this->dbHost = $host;

$name = $config->getSystemValueString('dbname');
if ($name === '') {
throw new Exception('The database name looks empty but it should be set');
}
$this->dbName = $name;

$prefix = $config->getSystemValueString('dbtableprefix');
if (preg_match('/\W/', $prefix)) {
throw new Exception('The database table prefix is invalid, only word characters allowed to prevent SQL injection');
}
$this->dbNcPrefix = $prefix;

$port = $config->getSystemValueInt('dbport');
$type = $config->getSystemValueString('dbtype');
if ($type == 'mysql') {
$this->dbPort = $port === 0 ? 3306 : $port;
$this->dbParam = '?';
$this->concatVerify = "CONCAT('%', ?, '%')";
$this->concatDomains = "CONCAT('%@', ?)";
} elseif ($type == 'pgsql') {
$this->dbPort = $port === 0 ? 5432 : $port;
$this->dbParam = '$1';
$this->concatVerify = "'%' || $1 || '%'";
$this->concatDomains = "'%@' || $1";
} else {
throw new Exception('This app only supports MySQL and PostgreSQL');
}
$this->dbUser = $config->getSystemValueString('dbuser');
$this->dbPassword = $config->getSystemValueString('dbpassword');
}

abstract public function getStalwartConfig(string $cid): string;
}
Loading

0 comments on commit 3fcc705

Please sign in to comment.