diff --git a/composer.json b/composer.json index 4e323b6909..8dda910d25 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "cboden/ratchet": "^0.4.1", "clue/block-react": "^1.4", "clue/reactphp-sqlite": "^1.0", + "react/mysql": "^0.5", "clue/redis-react": "^2.3", "doctrine/dbal": "^2.9", "evenement/evenement": "^2.0|^3.0", diff --git a/config/websockets.php b/config/websockets.php index 7ea9f99b6f..2ab224f70f 100644 --- a/config/websockets.php +++ b/config/websockets.php @@ -50,10 +50,27 @@ | The SQLite database to use when using the SQLite application manager. | */ + 'sqlite' => [ 'database' => storage_path('laravel-websockets.sqlite'), ], + /* + |-------------------------------------------------------------------------- + | MySql application manager + |-------------------------------------------------------------------------- + | + | The MySql database to use when using the MySql application manager. + | + */ + + 'mysql' => [ + 'host' => 'localhost', + 'port' => 3306, + 'database' => 'default', + 'username' => 'root', + 'password' => 'root', + ] ], /* diff --git a/database/migrations/mysql/0000_00_00_000000_create_apps_table.sql b/database/migrations/mysql/0000_00_00_000000_create_apps_table.sql new file mode 100644 index 0000000000..c318aadcf2 --- /dev/null +++ b/database/migrations/mysql/0000_00_00_000000_create_apps_table.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS apps ( + `id` VARCHAR(255) NOT NULL, + `key` VARCHAR(255) NOT NULL, + `secret` VARCHAR(255) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `host` VARCHAR(255), + `path` VARCHAR(255), + `enable_client_messages` BOOLEAN DEFAULT 0, + `enable_statistics` BOOLEAN DEFAULT 1, + `capacity` INT, + `allowed_origins` VARCHAR(255) +) diff --git a/src/Apps/MysqlAppManager.php b/src/Apps/MysqlAppManager.php new file mode 100644 index 0000000000..482256760c --- /dev/null +++ b/src/Apps/MysqlAppManager.php @@ -0,0 +1,166 @@ +database = $database; + } + + /** + * Get all apps. + * + * @return PromiseInterface + */ + public function all(): PromiseInterface + { + $deferred = new Deferred(); + + $this->database->query('SELECT * FROM `apps`') + ->then(function (QueryResult $result) use ($deferred) { + $deferred->resolve($result->resultRows); + }, function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } + + /** + * Get app by id. + * + * @param string|int $appId + * @return PromiseInterface + */ + public function findById($appId): PromiseInterface + { + $deferred = new Deferred(); + + $this->database->query('SELECT * from `apps` WHERE `id` = ?', [$appId]) + ->then(function (QueryResult $result) use ($deferred) { + $deferred->resolve($this->convertIntoApp($result->resultRows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } + + /** + * Get app by app key. + * + * @param string $appKey + * @return PromiseInterface + */ + public function findByKey($appKey): PromiseInterface + { + $deferred = new Deferred(); + + $this->database->query('SELECT * from `apps` WHERE `key` = ?', [$appKey]) + ->then(function (QueryResult $result) use ($deferred) { + $deferred->resolve($this->convertIntoApp($result->resultRows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } + + /** + * Get app by secret. + * + * @param string $appSecret + * @return PromiseInterface + */ + public function findBySecret($appSecret): PromiseInterface + { + $deferred = new Deferred(); + + $this->database->query('SELECT * from `apps` WHERE `secret` = ?', [$appSecret]) + ->then(function (QueryResult $result) use ($deferred) { + $deferred->resolve($this->convertIntoApp($result->resultRows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } + + /** + * Map the app into an App instance. + * + * @param array|null $app + * @return \BeyondCode\LaravelWebSockets\Apps\App|null + */ + protected function convertIntoApp(?array $appAttributes): ?App + { + if (! $appAttributes) { + return null; + } + + $app = new App( + $appAttributes['id'], + $appAttributes['key'], + $appAttributes['secret'] + ); + + if (isset($appAttributes['name'])) { + $app->setName($appAttributes['name']); + } + + if (isset($appAttributes['host'])) { + $app->setHost($appAttributes['host']); + } + + if (isset($appAttributes['path'])) { + $app->setPath($appAttributes['path']); + } + + $app + ->enableClientMessages((bool) $appAttributes['enable_client_messages']) + ->enableStatistics((bool) $appAttributes['enable_statistics']) + ->setCapacity($appAttributes['capacity'] ?? null) + ->setAllowedOrigins(array_filter(explode(',', $appAttributes['allowed_origins']))); + + return $app; + } + + /** + * @inheritDoc + */ + public function createApp($appData): PromiseInterface + { + $deferred = new Deferred(); + + $this->database->query( + 'INSERT INTO `apps` (id, key, secret, name, enable_client_messages, enable_statistics, allowed_origins) VALUES (?, ?, ?, ?, ?, ?, ?)', + [$appData['id'], $appData['key'], $appData['secret'], $appData['name'], $appData['enable_client_messages'], $appData['enable_statistics'], $appData['allowed_origins']]) + ->then(function () use ($deferred) { + $deferred->resolve(); + }, function ($error) use ($deferred) { + $deferred->reject($error); + }); + + return $deferred->promise(); + } +} diff --git a/src/Apps/SQLiteAppManager.php b/src/Apps/SQLiteAppManager.php index bbfdf7a081..30a00c2ec1 100644 --- a/src/Apps/SQLiteAppManager.php +++ b/src/Apps/SQLiteAppManager.php @@ -38,7 +38,9 @@ public function all(): PromiseInterface $this->database->query('SELECT * FROM `apps`') ->then(function (Result $result) use ($deferred) { - return $deferred->resolve($result->rows); + $deferred->resolve($result->rows); + }, function ($error) use ($deferred) { + $deferred->reject($error); }); return $deferred->promise(); @@ -56,7 +58,9 @@ public function findById($appId): PromiseInterface $this->database->query('SELECT * from apps WHERE `id` = :id', ['id' => $appId]) ->then(function (Result $result) use ($deferred) { - return $deferred->resolve($this->convertIntoApp($result->rows[0])); + $deferred->resolve($this->convertIntoApp($result->rows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); }); return $deferred->promise(); @@ -74,7 +78,9 @@ public function findByKey($appKey): PromiseInterface $this->database->query('SELECT * from apps WHERE `key` = :key', ['key' => $appKey]) ->then(function (Result $result) use ($deferred) { - return $deferred->resolve($this->convertIntoApp($result->rows[0])); + $deferred->resolve($this->convertIntoApp($result->rows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); }); return $deferred->promise(); @@ -92,7 +98,9 @@ public function findBySecret($appSecret): PromiseInterface $this->database->query('SELECT * from apps WHERE `secret` = :secret', ['secret' => $appSecret]) ->then(function (Result $result) use ($deferred) { - return $deferred->resolve($this->convertIntoApp($result->rows[0])); + $deferred->resolve($this->convertIntoApp($result->rows[0])); + }, function ($error) use ($deferred) { + $deferred->reject($error); }); return $deferred->promise(); @@ -150,6 +158,8 @@ public function createApp($appData): PromiseInterface ', $appData) ->then(function (Result $result) use ($deferred) { $deferred->resolve(); + }, function ($error) use ($deferred) { + $deferred->reject($error); }); return $deferred->promise(); diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index c8288ff4bf..3f2090575e 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -21,6 +21,8 @@ use Illuminate\Support\ServiceProvider; use React\EventLoop\Factory; use React\EventLoop\LoopInterface; +use React\MySQL\ConnectionInterface; +use React\MySQL\Factory as MySQLFactory; use SplFileInfo; use Symfony\Component\Finder\Finder; @@ -50,6 +52,8 @@ public function boot() $this->registerSQLiteDatabase(); + $this->registerMySqlDatabase(); + $this->registerAsyncRedisQueueDriver(); $this->registerRouter(); @@ -116,6 +120,32 @@ protected function registerSQLiteDatabase() }); } + protected function registerMySqlDatabase() + { + $this->app->singleton(ConnectionInterface::class, function () { + $factory = new MySQLFactory($this->app->make(LoopInterface::class)); + + $auth = trim(config('websockets.managers.mysql.username').':'.config('websockets.managers.mysql.password'), ':'); + $connection = trim(config('websockets.managers.mysql.host').':'.config('websockets.managers.mysql.port'), ':'); + $database = config('websockets.managers.mysql.database'); + + $database = $factory->createLazyConnection(trim("{$auth}@{$connection}/{$database}", '@')); + + $migrations = (new Finder()) + ->files() + ->ignoreDotFiles(true) + ->in(__DIR__.'/../database/migrations/mysql') + ->name('*.sql'); + + /** @var SplFileInfo $migration */ + foreach ($migrations as $migration) { + $database->query($migration->getContents()); + } + + return $database; + }); + } + /** * Register the statistics-related contracts. *