Non interactive koel:init (#886)

* Use ADMIN_* variables if available to create the admin account

* Add APP_MEDIA_PATH for media directory

* Use the standard --no-interaction flag to koel:init

* Undo variable aligment and code formatting

* Prefer early return over else, add new line before return statements

* Some fixes
This commit is contained in:
Javier López 2019-01-01 05:53:20 -06:00 committed by Phan An
parent e554448a3e
commit 7ba295efad
8 changed files with 98 additions and 36 deletions

View file

@ -1,3 +1,5 @@
APP_NAME=Koel
# Database connection name, which corresponds to the database driver.
# Possible values are:
# mysql (MySQL/MariaDB - default)
@ -16,6 +18,17 @@ APP_KEY=
# Another random 32-char string. You can leave this empty if use php artisan koel:init.
JWT_SECRET=
# Credentials and other info to be used when Koel is installed in non-interactive mode
# (php artisan koel:init --no-interaction)
# By default (interactive mode), Koel will still prompt for these information during installation,
# but provide the values here as the defaults (except ADMIN_PASSWORD, for security reason).
ADMIN_NAME="Koel Admin"
ADMIN_EMAIL=admin@koel.com
ADMIN_PASSWORD=SoSecureMuchWow
# The ABSOLUTE path to your media. This value can always be changed later via the web interface.
MEDIA_PATH=
# By default, Koel ignores dot files and folders. This greatly improves performance if your media
# root have folders like .git or .cache. If by any chance your media files are under a dot folder,
# set the following setting to false.

View file

@ -49,6 +49,10 @@ class InitCommand extends Command
$this->comment('Remember, you can always install/upgrade manually following the guide here:');
$this->info('📙 '.config('koel.misc.docs_url').PHP_EOL);
if ($this->inNoInteractionMode()) {
$this->info('Running in no-interaction mode');
}
$this->maybeGenerateAppKey();
$this->maybeGenerateJwtSecret();
$this->maybeSetUpDatabase();
@ -121,24 +125,16 @@ class InitCommand extends Command
]);
}
private function inNoInteractionMode(): bool
{
return (bool) $this->option('no-interaction');
}
private function setUpAdminAccount(): void
{
$this->info("Let's create the admin account.");
$name = $this->ask('Your name');
$email = $this->ask('Your email address');
$passwordConfirmed = false;
$password = null;
while (!$passwordConfirmed) {
$password = $this->secret('Your desired password');
$confirmation = $this->secret('Again, just to make sure');
if ($confirmation !== $password) {
$this->error('That doesn\'t match. Let\'s try again.');
} else {
$passwordConfirmed = true;
}
}
[$name, $email, $password] = $this->gatherAdminAccountCredentials();
User::create([
'name' => $name,
@ -154,16 +150,22 @@ class InitCommand extends Command
return;
}
if ($this->inNoInteractionMode()) {
$this->setMediaPathFromEnvFile();
return;
}
$this->info('The absolute path to your media directory. If this is skipped (left blank) now, you can set it later via the web interface.');
while (true) {
$path = $this->ask('Media path', false);
$path = $this->ask('Media path', config('koel.media_path'));
if ($path === false) {
if (!$path) {
return;
}
if (is_dir($path) && is_readable($path)) {
if ($this->isValidMediaPath($path)) {
Setting::set('media_path', $path);
return;
@ -216,7 +218,7 @@ class InitCommand extends Command
$dbSetUp = true;
} catch (Exception $e) {
$this->error($e->getMessage());
$this->warn(PHP_EOL.'Koel cannot connect to the database. Let\'s set it up.');
$this->warn(PHP_EOL."Koel cannot connect to the database. Let's set it up.");
$this->setUpDatabase();
}
}
@ -236,4 +238,50 @@ class InitCommand extends Command
$this->info('Compiling front-end stuff');
system('yarn install');
}
/** @return array<string> */
private function gatherAdminAccountCredentials(): array
{
if ($this->inNoInteractionMode()) {
return [config('koel.admin.name'), config('koel.admin.email'), config('koel.admin.password')];
}
$name = $this->ask('Your name', config('koel.admin.name'));
$email = $this->ask('Your email address', config('koel.admin.email'));
$passwordConfirmed = false;
$password = null;
while (!$passwordConfirmed) {
$password = $this->secret('Your desired password');
$confirmation = $this->secret('Again, just to make sure');
if ($confirmation !== $password) {
$this->error("That doesn't match. Let's try again.");
} else {
$passwordConfirmed = true;
}
}
return [$name, $email, $password];
}
private function isValidMediaPath(string $path): bool
{
return is_dir($path) && is_readable($path);
}
private function setMediaPathFromEnvFile(): void
{
with(config('koel.media_path'), function (?string $path): void {
if (!$path) {
return;
}
if ($this->isValidMediaPath($path)) {
Setting::set('media_path', $path);
} else {
$this->warn(sprintf('The path %s does not exist or not readable. Skipping.', $path));
}
});
}
}

View file

@ -2,6 +2,7 @@
namespace App\Repositories;
use Exception;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@ -10,14 +11,21 @@ abstract class AbstractRepository implements RepositoryInterface
{
/** @var Model */
protected $model;
/** @var Guard */
protected $auth;
abstract public function getModelClass(): string;
public function __construct(Guard $auth)
public function __construct()
{
$this->model = app($this->getModelClass());
$this->auth = $auth;
// This instantiation may fail during a console command if e.g. APP_KEY is empty,
// rendering the whole installation failing.
try {
$this->auth = app(Guard::class);
} catch (Exception $e) {}
}
public function getOneById($id): ?Model

View file

@ -10,9 +10,9 @@ class SongRepository extends AbstractRepository
{
private $helperService;
public function __construct(Guard $auth, HelperService $helperService)
public function __construct(HelperService $helperService)
{
parent::__construct($auth);
parent::__construct();
$this->helperService = $helperService;
}

View file

@ -5,13 +5,13 @@ namespace App\Services;
use App\Models\Album;
use App\Models\Artist;
use Exception;
use Illuminate\Log\Logger;
use Psr\Log\LoggerInterface;
class MediaMetadataService
{
private $logger;
public function __construct(Logger $logger)
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}

View file

@ -14,7 +14,7 @@ use App\Repositories\ArtistRepository;
use App\Repositories\SettingRepository;
use App\Repositories\SongRepository;
use Exception;
use Illuminate\Log\Logger;
use Psr\Log\LoggerInterface;
use SplFileInfo;
use Symfony\Component\Finder\Finder;
@ -58,7 +58,7 @@ class MediaSyncService
HelperService $helperService,
FileSynchronizer $fileSynchronizer,
Finder $finder,
Logger $logger
LoggerInterface $logger
) {
$this->mediaMetadataService = $mediaMetadataService;
$this->songRepository = $songRepository;

View file

@ -17,6 +17,8 @@ return [
'password' => env('ADMIN_PASSWORD'),
],
'media_path' => env('MEDIA_PATH'),
/*
|--------------------------------------------------------------------------
| Sync Options

View file

@ -5,9 +5,6 @@ namespace Tests\Integration\Repositories;
use App\Models\Song;
use App\Repositories\SongRepository;
use App\Services\HelperService;
use Illuminate\Contracts\Auth\Guard;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase;
class SongRepositoryTest extends TestCase
@ -17,11 +14,6 @@ class SongRepositoryTest extends TestCase
*/
private $helperService;
/**
* @var Guard|MockInterface
*/
private $auth;
/**
* @var SongRepository
*/
@ -30,9 +22,8 @@ class SongRepositoryTest extends TestCase
public function setUp()
{
parent::setUp();
$this->auth = Mockery::mock(Guard::class);
$this->helperService = new HelperService();
$this->songRepository = new SongRepository($this->auth, $this->helperService);
$this->songRepository = new SongRepository($this->helperService);
}
public function testGetOneByPath(): void