Compare commits

...

8 Commits

Author SHA1 Message Date
lexx 454cece1d8 auth fiux 2026-05-18 20:47:14 +03:00
lexx c49acd74de imporve import export 2026-05-18 19:56:11 +03:00
lexx 965b7e660b command rename 2026-05-18 19:09:55 +03:00
lexx 8c7f65abf5 improve fileservice 2026-05-18 19:07:47 +03:00
lexx 507d643aee sourece phpopyion 2026-05-18 19:04:57 +03:00
lexx f1a0d6a2b1 image filter generator 2026-05-18 18:38:43 +03:00
lexx f74c850e01 import fixed 2026-05-18 18:30:56 +03:00
lexx 9d6d39fe62 fixed import 2026-05-18 18:07:38 +03:00
32 changed files with 1187 additions and 140 deletions
-1
View File
@@ -9,7 +9,6 @@
"ext-imagick": "*", "ext-imagick": "*",
"ext-pdo": "*", "ext-pdo": "*",
"php": "^8.4", "php": "^8.4",
"phpoption/phpoption": "^1.9",
"spatie/image-optimizer": "^1.8", "spatie/image-optimizer": "^1.8",
"staudenmeir/laravel-cte": "^1.0", "staudenmeir/laravel-cte": "^1.0",
"intervention/image": "^4.0" "intervention/image": "^4.0"
Generated
+7 -7
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "b2f189b5c64498c6190267db27e55494", "content-hash": "a1bf12f1e2b86bc0da8547f2f6944a86",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -797,16 +797,16 @@
}, },
{ {
"name": "intervention/image", "name": "intervention/image",
"version": "4.0.4", "version": "4.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Intervention/image.git", "url": "https://github.com/Intervention/image.git",
"reference": "f58d379b1f13c036b2ef5c3c26eb4b0c88b647ed" "reference": "fb795553f76afbe55c80d32b6bfe2090a6b1a0af"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Intervention/image/zipball/f58d379b1f13c036b2ef5c3c26eb4b0c88b647ed", "url": "https://api.github.com/repos/Intervention/image/zipball/fb795553f76afbe55c80d32b6bfe2090a6b1a0af",
"reference": "f58d379b1f13c036b2ef5c3c26eb4b0c88b647ed", "reference": "fb795553f76afbe55c80d32b6bfe2090a6b1a0af",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -853,7 +853,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/Intervention/image/issues", "issues": "https://github.com/Intervention/image/issues",
"source": "https://github.com/Intervention/image/tree/4.0.4" "source": "https://github.com/Intervention/image/tree/4.1.0"
}, },
"funding": [ "funding": [
{ {
@@ -869,7 +869,7 @@
"type": "ko_fi" "type": "ko_fi"
} }
], ],
"time": "2026-05-03T04:47:13+00:00" "time": "2026-05-15T06:52:36+00:00"
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
+2
View File
@@ -50,4 +50,6 @@ interface AuthService
public function registerAdmin(string $name, string $email): User; public function registerAdmin(string $name, string $email): User;
public function validateRoles(array $roles): array; public function validateRoles(array $roles): array;
public function isExternal(): bool;
public function redirectHome(): \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse;
} }
+9
View File
@@ -220,4 +220,13 @@ readonly class AuthServiceLucent implements AuthService
->values() ->values()
->toArray(); ->toArray();
} }
public function isExternal(): bool
{
return false;
}
public function redirectHome(): \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
{
return redirect("/home");
}
} }
+9
View File
@@ -102,4 +102,13 @@ readonly class AuthServiceLunar implements AuthService
->values() ->values()
->toArray(); ->toArray();
} }
public function isExternal(): bool
{
return true;
}
public function redirectHome(): \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
{
return redirect("/lunar");
}
} }
+1 -1
View File
@@ -3,7 +3,7 @@
namespace Lucent\Account; namespace Lucent\Account;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use PhpOption\Option; use Lucent\Option\Option;
interface UserRepo interface UserRepo
{ {
+1 -1
View File
@@ -5,7 +5,7 @@ namespace Lucent\Account;
use Carbon\Carbon; use Carbon\Carbon;
use Lucent\Database\Database; use Lucent\Database\Database;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use PhpOption\Option; use Lucent\Option\Option;
class UserRepoLucent implements UserRepo class UserRepoLucent implements UserRepo
{ {
+1 -1
View File
@@ -5,7 +5,7 @@ namespace Lucent\Account;
use Carbon\Carbon; use Carbon\Carbon;
use Lucent\Database\Database; use Lucent\Database\Database;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use PhpOption\Option; use Lucent\Option\Option;
class UserRepoLunar implements UserRepo class UserRepoLunar implements UserRepo
{ {
+1 -1
View File
@@ -8,7 +8,7 @@ use Lucent\File\FileService;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Data\Schema; use Lucent\Data\Schema;
use Lucent\Schema\SchemaService; use Lucent\Schema\SchemaService;
use PhpOption\Option; use Lucent\Option\Option;
final class ChannelService final class ChannelService
{ {
@@ -8,7 +8,7 @@ use Lucent\Data\Schema;
use Lucent\Schema\SchemaService; use Lucent\Schema\SchemaService;
use Lucent\File\FileService; use Lucent\File\FileService;
class CompileSchemas extends Command class CompileSchemasCommand extends Command
{ {
protected $signature = "lucent:schemas"; protected $signature = "lucent:schemas";
@@ -4,12 +4,14 @@ namespace Lucent\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Lucent\File\FileService; use Lucent\File\FileService;
use Lucent\ResultType\Error;
use Lucent\ResultType\Result;
use Lucent\ResultType\Success;
use ZipArchive; use ZipArchive;
class Export extends Command class ExportCommand extends Command
{ {
protected $signature = "lucent:export"; protected $signature = "lucent:export";
protected $prefix = "lucent_";
protected $description = "Export data and files"; protected $description = "Export data and files";
@@ -32,8 +34,30 @@ class Export extends Command
$stamp = now()->format("Y_m_d_His"); $stamp = now()->format("Y_m_d_His");
$sqlFile = $exportDir . "/dump_{$stamp}.sql"; $sqlFile = $exportDir . "/dump_{$stamp}.sql";
$zipFile = $exportDir . "/export_{$stamp}.zip"; $zipFile = $exportDir . "/export_{$stamp}.zip";
$filesDir = $fileService->loadPublicDisk()->path("lucent/files");
// Dump database $result = $this->dumpDatabase($db, $tables, $sqlFile)->flatMap(
fn($sql) => $this->buildZip($sql, $filesDir, $zipFile),
);
if (file_exists($sqlFile)) {
unlink($sqlFile);
}
if ($result->error()->isDefined()) {
$this->error($result->error()->get());
return;
}
$this->info("Exported to {$zipFile}");
}
/** @return Result<string, string> */
private function dumpDatabase(
array $db,
array $tables,
string $sqlFile,
): Result {
$tableArgs = collect($tables)->map(fn($t) => "-t {$t}")->join(" "); $tableArgs = collect($tables)->map(fn($t) => "-t {$t}")->join(" ");
$command = sprintf( $command = sprintf(
"PGPASSWORD=%s pg_dump -h %s -p %s -U %s -d %s %s --no-owner --no-acl > %s", "PGPASSWORD=%s pg_dump -h %s -p %s -U %s -d %s %s --no-owner --no-acl > %s",
@@ -49,27 +73,29 @@ class Export extends Command
exec($command, result_code: $code); exec($command, result_code: $code);
if ($code !== 0) { if ($code !== 0) {
$this->error("pg_dump failed"); return Error::create("pg_dump failed.");
return;
} }
$this->info("Database dumped."); $this->info("Database dumped.");
// Zip SQL + files return Success::create($sqlFile);
$publicDisk = $fileService->loadPublicDisk(); }
$filesDir = $publicDisk->path("lucent/files");
/** @return Result<string, string> */
private function buildZip(
string $sqlFile,
string $filesDir,
string $zipFile,
): Result {
$zip = new ZipArchive(); $zip = new ZipArchive();
if ( if (
$zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== $zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !==
true true
) { ) {
$this->error("Could not create zip archive."); return Error::create("Could not create zip archive.");
return;
} }
$zip->addFile($sqlFile, "dump_{$stamp}.sql"); $zip->addFile($sqlFile, basename($sqlFile));
if (is_dir($filesDir)) { if (is_dir($filesDir)) {
$iterator = new \RecursiveIteratorIterator( $iterator = new \RecursiveIteratorIterator(
@@ -91,9 +117,6 @@ class Export extends Command
$zip->close(); $zip->close();
// Clean up originals return Success::create($zipFile);
unlink($sqlFile);
$this->info("Exported to {$zipFile}");
} }
} }
@@ -6,7 +6,7 @@ use Illuminate\Console\Command;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Schema\CollectionSchema; use Lucent\Schema\CollectionSchema;
class GenerateCollectionSchema extends Command class GenerateCollectionSchemaCommand extends Command
{ {
protected $signature = 'lucent:generate:collection {name}'; protected $signature = 'lucent:generate:collection {name}';
@@ -3,8 +3,9 @@
namespace Lucent\Commands; namespace Lucent\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Str;
class GenerateImageFilter extends Command class GenerateImageFilterCommand extends Command
{ {
protected $signature = "lucent:generate:image_filter {name}"; protected $signature = "lucent:generate:image_filter {name}";
@@ -20,7 +21,10 @@ class GenerateImageFilter extends Command
mkdir($dir, 0755, true); mkdir($dir, 0755, true);
} }
$filePath = "{$dir}/{$name}.php"; $className = Str::of($name)->camel()->ucfirst() . "ImageFilter";
$pathName = Str::of($name)->snake()->lower();
$filePath = "{$dir}/{$className}.php";
if (file_exists($filePath)) { if (file_exists($filePath)) {
$this->error("Filter {$name} already exists at {$filePath}"); $this->error("Filter {$name} already exists at {$filePath}");
@@ -35,7 +39,7 @@ class GenerateImageFilter extends Command
use Lucent\ImageFilterInterface; use Lucent\ImageFilterInterface;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
class {$name} implements ImageFilterInterface class {$className} implements ImageFilterInterface
{ {
public function apply(ImageInterface \$image): ImageInterface public function apply(ImageInterface \$image): ImageInterface
{ {
@@ -47,7 +51,7 @@ class GenerateImageFilter extends Command
} }
public function getPath(): string { public function getPath(): string {
return "{$name}"; return "{$pathName}";
} }
} }
PHP; PHP;
@@ -4,9 +4,12 @@ namespace Lucent\Commands;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Lucent\File\FileService; use Lucent\File\FileService;
use Lucent\ResultType\Error;
use Lucent\ResultType\Result;
use Lucent\ResultType\Success;
use ZipArchive; use ZipArchive;
class Import extends Command class ImportCommand extends Command
{ {
protected $signature = "lucent:import"; protected $signature = "lucent:import";
@@ -44,26 +47,44 @@ class Import extends Command
return; return;
} }
// Extract to temp directory
$tempDir = storage_path("exports/.import_tmp_" . uniqid()); $tempDir = storage_path("exports/.import_tmp_" . uniqid());
$result = $this->extractZip($zipFile, $tempDir)
->flatMap(fn($dir) => $this->restoreDatabase($dir))
->flatMap(fn($dir) => $this->restoreFiles($dir, $fileService));
$this->cleanup($tempDir);
if ($result->error()->isDefined()) {
$this->error($result->error()->get());
return;
}
$this->info("Import complete.");
}
/** @return Result<string, string> */
private function extractZip(string $zipFile, string $tempDir): Result
{
mkdir($tempDir, 0755, true); mkdir($tempDir, 0755, true);
$zip = new ZipArchive(); $zip = new ZipArchive();
if ($zip->open($zipFile) !== true) { if ($zip->open($zipFile) !== true) {
$this->error("Could not open zip archive."); return Error::create("Could not open zip archive.");
$this->cleanup($tempDir);
return;
} }
$zip->extractTo($tempDir); $zip->extractTo($tempDir);
$zip->close(); $zip->close();
// Restore database return Success::create($tempDir);
}
/** @return Result<string, string> */
private function restoreDatabase(string $tempDir): Result
{
$sqlFiles = glob($tempDir . "/*.sql"); $sqlFiles = glob($tempDir . "/*.sql");
if (empty($sqlFiles)) { if (empty($sqlFiles)) {
$this->error("No SQL dump found inside the archive."); return Error::create("No SQL dump found inside the archive.");
$this->cleanup($tempDir);
return;
} }
$db = config("database.connections.pgsql"); $db = config("database.connections.pgsql");
@@ -74,7 +95,6 @@ class Import extends Command
"lucent_edges", "lucent_edges",
]; ];
// Truncate existing tables before restore
$truncate = collect($tables) $truncate = collect($tables)
->map(fn($t) => "TRUNCATE TABLE {$t} CASCADE;") ->map(fn($t) => "TRUNCATE TABLE {$t} CASCADE;")
->join(" "); ->join(" ");
@@ -91,12 +111,6 @@ class Import extends Command
exec($truncateCmd, result_code: $truncateCode); exec($truncateCmd, result_code: $truncateCode);
if ($truncateCode !== 0) {
$this->error("Failed to truncate existing tables.");
$this->cleanup($tempDir);
return;
}
$restoreCmd = sprintf( $restoreCmd = sprintf(
"PGPASSWORD=%s psql -h %s -p %s -U %s -d %s -f %s", "PGPASSWORD=%s psql -h %s -p %s -U %s -d %s -f %s",
$db["password"], $db["password"],
@@ -108,53 +122,53 @@ class Import extends Command
); );
exec($restoreCmd, result_code: $restoreCode); exec($restoreCmd, result_code: $restoreCode);
if ($restoreCode !== 0) { if ($restoreCode !== 0) {
$this->error("Database restore failed."); return Error::create("Database restore failed.");
$this->cleanup($tempDir);
return;
} }
$this->info("Database restored."); $this->info("Database restored.");
// Replace files return Success::create($tempDir);
}
/** @return Result<null, string> */
private function restoreFiles(
string $tempDir,
FileService $fileService,
): Result {
$srcFilesDir = $tempDir . "/files"; $srcFilesDir = $tempDir . "/files";
if (is_dir($srcFilesDir)) { if (!is_dir($srcFilesDir)) {
$publicDisk = $fileService->loadPublicDisk();
$destFilesDir = $publicDisk->path("lucent/files");
// Remove existing files directory or create it if missing
if (is_dir($destFilesDir)) {
exec("rm -rf " . escapeshellarg($destFilesDir));
} else {
mkdir($destFilesDir, 0755, true);
}
exec(
sprintf(
"cp -R %s/* %s",
escapeshellarg($srcFilesDir),
escapeshellarg($destFilesDir),
),
result_code: $copyCode,
);
if ($copyCode !== 0) {
$this->error("Failed to restore files.");
$this->cleanup($tempDir);
return;
}
$this->info("Files restored.");
} else {
$this->warn( $this->warn(
"No files directory found in archive — skipping file restore.", "No files directory found in archive — skipping file restore.",
); );
return Success::create(null);
} }
$this->cleanup($tempDir); $publicDisk = $fileService->loadPublicDisk();
$this->info("Import complete."); $destFilesDir = $publicDisk->path("lucent/files");
if (is_dir($destFilesDir)) {
exec("rm -rf " . escapeshellarg($destFilesDir));
}
mkdir($destFilesDir, 0755, true);
exec(
sprintf(
"cp -R %s/* %s",
escapeshellarg($srcFilesDir),
escapeshellarg($destFilesDir),
),
result_code: $copyCode,
);
if ($copyCode !== 0) {
return Error::create("Failed to restore files.");
}
$this->info("Files restored.");
return Success::create(null);
} }
private function cleanup(string $dir): void private function cleanup(string $dir): void
@@ -5,7 +5,7 @@ namespace Lucent\Commands;
use DirectoryIterator; use DirectoryIterator;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class LiveLink extends Command class LiveLinkCommand extends Command
{ {
protected $signature = 'lucent:livelink'; protected $signature = 'lucent:livelink';
@@ -8,7 +8,7 @@ use Lucent\File\FileRepo;
use Lucent\File\FileService; use Lucent\File\FileService;
use Lucent\Query\Query; use Lucent\Query\Query;
class RebuildThumbnails extends Command class RebuildThumbnailsCommand extends Command
{ {
protected $signature = "lucent:rebuild:thumbnails"; protected $signature = "lucent:rebuild:thumbnails";
@@ -27,8 +27,8 @@ class RebuildThumbnails extends Command
$files = FileRepo::query()->get(); $files = FileRepo::query()->get();
$disk = $this->fileService->loadPublicDisk(); $disk = $this->fileService->loadPublicDisk();
foreach ($files as $file) { foreach ($files as $file) {
$this->fileService->createTemplates($disk, $file->path);
try { try {
$this->fileService->createTemplates($disk, $file->path);
} catch (Exception $e) { } catch (Exception $e) {
$this->error( $this->error(
"File " . $file->filename . " could not be rebuilt \n", "File " . $file->filename . " could not be rebuilt \n",
@@ -6,7 +6,7 @@ use Illuminate\Console\Command;
use Lucent\Edge\EdgeService; use Lucent\Edge\EdgeService;
use Lucent\Query\Query; use Lucent\Query\Query;
class RemoveOrphanEdges extends Command class RemoveOrphanEdgesCommand extends Command
{ {
protected $signature = 'lucent:removeOrphanEdges'; protected $signature = 'lucent:removeOrphanEdges';
@@ -11,7 +11,7 @@ use Lucent\Setup\Step\LucentConfigStep;
use Lucent\Setup\Step\StorageLinkSetupStep; use Lucent\Setup\Step\StorageLinkSetupStep;
use Lucent\Setup\Step\StorageSetupStep; use Lucent\Setup\Step\StorageSetupStep;
class Setup extends Command class SetupCommand extends Command
{ {
protected $signature = "lucent:setup"; protected $signature = "lucent:setup";
@@ -7,7 +7,7 @@ use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Lucent\Database\Database; use Lucent\Database\Database;
class SetupDatabase extends Command class SetupDatabaseCommand extends Command
{ {
protected $signature = "lucent:setup-db"; protected $signature = "lucent:setup-db";
protected $prefix = "lucent_"; protected $prefix = "lucent_";
+53 -25
View File
@@ -4,6 +4,7 @@ namespace Lucent\File;
use Illuminate\Contracts\Filesystem\Filesystem; use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Log\Logger;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Intervention\Image\Format; use Intervention\Image\Format;
@@ -12,6 +13,9 @@ use Lucent\Channel\ChannelService;
use Lucent\Id\Id; use Lucent\Id\Id;
use Lucent\LucentException; use Lucent\LucentException;
use Lucent\Data\File as DataFile; use Lucent\Data\File as DataFile;
use Lucent\ResultType\Error;
use Lucent\ResultType\Result;
use Lucent\ResultType\Success;
use Spatie\ImageOptimizer\OptimizerChainFactory; use Spatie\ImageOptimizer\OptimizerChainFactory;
class FileService class FileService
@@ -19,6 +23,7 @@ class FileService
public function __construct( public function __construct(
public ChannelService $channelService, public ChannelService $channelService,
public ImageManager $imageManager, public ImageManager $imageManager,
public Logger $logger,
) {} ) {}
public function createFromUrl( public function createFromUrl(
@@ -68,7 +73,10 @@ class FileService
} }
if ($this->isImage($mimetype)) { if ($this->isImage($mimetype)) {
$this->createTemplates($disk, $path); $result = $this->createTemplates($disk, $path);
if ($result->error()->isDefined()) {
throw new LucentException($result->error()->get());
}
} }
[$width, $height] = $this->isImage($mimetype) [$width, $height] = $this->isImage($mimetype)
@@ -124,45 +132,65 @@ class FileService
return Storage::disk(config("lucent.private_disk")); return Storage::disk(config("lucent.private_disk"));
} }
public function createTemplates(Filesystem $disk, string $path): void /** @return Result<null, string> */
public function createTemplates(Filesystem $disk, string $path): Result
{ {
$originalImage = $this->imageManager->decode( $filePath = "lucent/" . $path;
$this->loadPublicDisk()->get("lucent/" . $path),
);
foreach ($this->channelService->channel->imageFilters as $filterClass) {
$imageClone = clone $originalImage;
$filterClassInstance = new $filterClass();
$image = $imageClone->modify($filterClassInstance); if (!$this->loadPublicDisk()->exists($filePath)) {
return Error::create("File not found: {$filePath}");
}
$originalImage = $this->imageManager->decode(
$this->loadPublicDisk()->get($filePath),
);
foreach ($this->channelService->channel->imageFilters as $filterClass) {
$filterClassInstance = new $filterClass();
$templateUri = $templateUri =
"lucent/templates/" . "lucent/templates/" .
$filterClassInstance->getPath() . $filterClassInstance->getPath() .
"/" . "/" .
substr($path, 0, strrpos($path, ".")) . substr($path, 0, strrpos($path, ".")) .
".webp"; ".webp";
$disk->put(
$templateUri,
$image->encodeUsingFormat( $ok = $disk->put(
$templateUri,
(clone $originalImage)
->modify($filterClassInstance)
->encodeUsingFormat(
Format::WEBP,
progressive: true,
quality: 80,
),
);
if (!$ok) {
return Error::create(
"Failed to write template: {$templateUri}",
);
}
}
$thumbUri =
"lucent/thumbs/" . substr($path, 0, strrpos($path, ".")) . ".webp";
$ok = $disk->put(
$thumbUri,
$originalImage
->cover(300, 300)
->encodeUsingFormat(
Format::WEBP, Format::WEBP,
progressive: true, progressive: true,
quality: 80, quality: 80,
), ),
); );
if (!$ok) {
return Error::create("Failed to write thumbnail: {$thumbUri}");
} }
$thumbDir = return Success::create(null);
"lucent/thumbs/" . substr($path, 0, strrpos($path, ".")) . ".webp";
$image = $originalImage->cover(300, 300);
$disk->put(
$thumbDir,
$image->encodeUsingFormat(
Format::WEBP,
progressive: true,
quality: 80,
),
);
} }
/** /**
+26
View File
@@ -28,6 +28,10 @@ class AuthController
public function register(Request $request): View|RedirectResponse public function register(Request $request): View|RedirectResponse
{ {
if ($this->authService->isExternal()) {
return $this->authService->redirectHome();
}
if ($this->accountService->countUsers() > 0) { if ($this->accountService->countUsers() > 0) {
return redirect( return redirect(
$this->channelService->channel->lucentUrl . "/login", $this->channelService->channel->lucentUrl . "/login",
@@ -43,6 +47,10 @@ class AuthController
public function postRegister(Request $request): Response public function postRegister(Request $request): Response
{ {
if ($this->authService->isExternal()) {
abort(400);
}
if ($this->accountService->countUsers() > 0) { if ($this->accountService->countUsers() > 0) {
abort(400); abort(400);
} }
@@ -61,6 +69,9 @@ class AuthController
public function login() public function login()
{ {
if ($this->authService->isExternal()) {
return $this->authService->redirectHome();
}
if ($this->accountService->countUsers() == 0) { if ($this->accountService->countUsers() == 0) {
return redirect( return redirect(
$this->channelService->channel->lucentUrl . "/register", $this->channelService->channel->lucentUrl . "/register",
@@ -76,6 +87,9 @@ class AuthController
public function postLogin(Request $request) public function postLogin(Request $request)
{ {
if ($this->authService->isExternal()) {
abort(400);
}
$this->authService->sendLoginEmail($request->input("email")); $this->authService->sendLoginEmail($request->input("email"));
return []; return [];
} }
@@ -87,6 +101,10 @@ class AuthController
// "token" => $request->input("token"), // "token" => $request->input("token"),
// ]); // ]);
if ($this->authService->isExternal()) {
abort(400);
}
return $this->svelte->render( return $this->svelte->render(
layout: "account", layout: "account",
view: "verify", view: "verify",
@@ -100,6 +118,10 @@ class AuthController
public function postVerify(Request $request) public function postVerify(Request $request)
{ {
if ($this->authService->isExternal()) {
abort(400);
}
try { try {
$this->authService->login( $this->authService->login(
$request->input("email"), $request->input("email"),
@@ -113,6 +135,10 @@ class AuthController
public function logout(): RedirectResponse public function logout(): RedirectResponse
{ {
if ($this->authService->isExternal()) {
abort(400);
}
$this->session->flush(); $this->session->flush();
return redirect($this->channelService->channel->lucentUrl . "/login"); return redirect($this->channelService->channel->lucentUrl . "/login");
} }
+20 -20
View File
@@ -14,16 +14,16 @@ use Lucent\Account\UserRepo;
use Lucent\Account\UserRepoLucent; use Lucent\Account\UserRepoLucent;
use Lucent\Account\UserRepoLunar; use Lucent\Account\UserRepoLunar;
use Lucent\Channel\ChannelService; use Lucent\Channel\ChannelService;
use Lucent\Commands\CompileSchemas; use Lucent\Commands\CompileSchemasCommand;
use Lucent\Commands\GenerateCollectionSchema; use Lucent\Commands\ExportCommand;
use Lucent\Commands\GenerateImageFilter; use Lucent\Commands\GenerateCollectionSchemaCommand;
use Lucent\Commands\LiveLink; use Lucent\Commands\GenerateImageFilterCommand;
use Lucent\Commands\RebuildThumbnails; use Lucent\Commands\ImportCommand;
use Lucent\Commands\RemoveOrphanEdges; use Lucent\Commands\LiveLinkCommand;
use Lucent\Commands\SetupDatabase; use Lucent\Commands\RebuildThumbnailsCommand;
use Lucent\Commands\Export; use Lucent\Commands\RemoveOrphanEdgesCommand;
use Lucent\Commands\Import; use Lucent\Commands\SetupCommand;
use Lucent\Commands\Setup; use Lucent\Commands\SetupDatabaseCommand;
use Lucent\Data\ChannelAuth; use Lucent\Data\ChannelAuth;
use Lucent\File\FileService; use Lucent\File\FileService;
use Lucent\Query\DatabaseGraph\DatabaseGraph; use Lucent\Query\DatabaseGraph\DatabaseGraph;
@@ -95,16 +95,16 @@ class LucentServiceProvider extends ServiceProvider
if ($this->app->runningInConsole()) { if ($this->app->runningInConsole()) {
$this->commands([ $this->commands([
CompileSchemas::class, CompileSchemasCommand::class,
RebuildThumbnails::class, RebuildThumbnailsCommand::class,
LiveLink::class, LiveLinkCommand::class,
RemoveOrphanEdges::class, RemoveOrphanEdgesCommand::class,
SetupDatabase::class, SetupDatabaseCommand::class,
GenerateCollectionSchema::class, GenerateCollectionSchemaCommand::class,
GenerateImageFilter::class, GenerateImageFilterCommand::class,
Export::class, ExportCommand::class,
Import::class, ImportCommand::class,
Setup::class, SetupCommand::class,
]); ]);
} }
+155
View File
@@ -0,0 +1,155 @@
<?php
namespace Lucent\Option;
use Traversable;
/**
* @template T
*
* @extends Option<T>
*/
final class LazyOption extends Option
{
/** @var callable(mixed...):(Option<T>) */
private $callback;
/** @var array<int, mixed> */
private $arguments;
/** @var Option<T>|null */
private $option;
/**
* @template S
* @param callable(mixed...):(Option<S>) $callback
* @param array<int, mixed> $arguments
*
* @return LazyOption<S>
*/
public static function create($callback, array $arguments = []): self
{
return new self($callback, $arguments);
}
/**
* @param callable(mixed...):(Option<T>) $callback
* @param array<int, mixed> $arguments
*/
public function __construct($callback, array $arguments = [])
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Invalid callback given');
}
$this->callback = $callback;
$this->arguments = $arguments;
}
public function isDefined(): bool
{
return $this->option()->isDefined();
}
public function isEmpty(): bool
{
return $this->option()->isEmpty();
}
public function get()
{
return $this->option()->get();
}
public function getOrElse($default)
{
return $this->option()->getOrElse($default);
}
public function getOrCall($callable)
{
return $this->option()->getOrCall($callable);
}
public function getOrThrow(\Exception $ex)
{
return $this->option()->getOrThrow($ex);
}
public function orElse(Option $else)
{
return $this->option()->orElse($else);
}
public function ifDefined($callable)
{
$this->option()->forAll($callable);
}
public function forAll($callable)
{
return $this->option()->forAll($callable);
}
public function map($callable)
{
return $this->option()->map($callable);
}
public function flatMap($callable)
{
return $this->option()->flatMap($callable);
}
public function filter($callable)
{
return $this->option()->filter($callable);
}
public function filterNot($callable)
{
return $this->option()->filterNot($callable);
}
public function select($value)
{
return $this->option()->select($value);
}
public function reject($value)
{
return $this->option()->reject($value);
}
/** @return Traversable<T> */
public function getIterator(): Traversable
{
return $this->option()->getIterator();
}
public function foldLeft($initialValue, $callable)
{
return $this->option()->foldLeft($initialValue, $callable);
}
public function foldRight($initialValue, $callable)
{
return $this->option()->foldRight($initialValue, $callable);
}
/** @return Option<T> */
private function option(): Option
{
if (null === $this->option) {
/** @var mixed */
$option = call_user_func_array($this->callback, $this->arguments);
if ($option instanceof Option) {
$this->option = $option;
} else {
throw new \RuntimeException(sprintf('Expected instance of %s', Option::class));
}
}
return $this->option;
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
namespace Lucent\Option;
use EmptyIterator;
/**
* @extends Option<mixed>
*/
final class None extends Option
{
/** @var None|null */
private static $instance;
/** @return None */
public static function create(): self
{
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function get()
{
throw new \RuntimeException('None has no value.');
}
public function getOrCall($callable)
{
return $callable();
}
public function getOrElse($default)
{
return $default;
}
public function getOrThrow(\Exception $ex)
{
throw $ex;
}
public function isEmpty(): bool
{
return true;
}
public function isDefined(): bool
{
return false;
}
public function orElse(Option $else)
{
return $else;
}
public function ifDefined($callable)
{
// no-op
}
public function forAll($callable)
{
return $this;
}
public function map($callable)
{
return $this;
}
public function flatMap($callable)
{
return $this;
}
public function filter($callable)
{
return $this;
}
public function filterNot($callable)
{
return $this;
}
public function select($value)
{
return $this;
}
public function reject($value)
{
return $this;
}
public function getIterator(): EmptyIterator
{
return new EmptyIterator();
}
public function foldLeft($initialValue, $callable)
{
return $initialValue;
}
public function foldRight($initialValue, $callable)
{
return $initialValue;
}
private function __construct()
{
}
}
+230
View File
@@ -0,0 +1,230 @@
<?php
namespace Lucent\Option;
use ArrayAccess;
use IteratorAggregate;
/**
* @template T
*
* @implements IteratorAggregate<T>
*/
abstract class Option implements IteratorAggregate
{
/**
* @template S
*
* @param S $value
* @param S $noneValue
*
* @return Option<S>
*/
public static function fromValue($value, $noneValue = null)
{
if ($value === $noneValue) {
return None::create();
}
return new Some($value);
}
/**
* @template S
*
* @param array<string|int,S>|ArrayAccess<string|int,S>|null $array
* @param string|int|null $key
*
* @return Option<S>
*/
public static function fromArraysValue($array, $key)
{
if ($key === null || !(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) {
return None::create();
}
return new Some($array[$key]);
}
/**
* @template S
*
* @param callable $callback
* @param array $arguments
* @param S $noneValue
*
* @return LazyOption<S>
*/
public static function fromReturn($callback, array $arguments = [], $noneValue = null)
{
return new LazyOption(static function () use ($callback, $arguments, $noneValue) {
/** @var mixed */
$return = call_user_func_array($callback, $arguments);
if ($return === $noneValue) {
return None::create();
}
return new Some($return);
});
}
/**
* @template S
*
* @param Option<S>|callable|S $value
* @param S $noneValue
*
* @return Option<S>|LazyOption<S>
*/
public static function ensure($value, $noneValue = null)
{
if ($value instanceof self) {
return $value;
} elseif (is_callable($value)) {
return new LazyOption(static function () use ($value, $noneValue) {
/** @var mixed */
$return = $value();
if ($return instanceof self) {
return $return;
} else {
return self::fromValue($return, $noneValue);
}
});
} else {
return self::fromValue($value, $noneValue);
}
}
/**
* @template S
*
* @param callable $callback
* @param mixed $noneValue
*
* @return callable
*/
public static function lift($callback, $noneValue = null)
{
return static function () use ($callback, $noneValue) {
/** @var array<int, mixed> */
$args = func_get_args();
$reduced_args = array_reduce(
$args,
/** @param bool $status */
static function ($status, self $o) {
return $o->isEmpty() ? true : $status;
},
false
);
if ($reduced_args) {
return None::create();
}
$args = array_map(
static function (self $o) {
return $o->get();
},
$args
);
return self::ensure(call_user_func_array($callback, $args), $noneValue);
};
}
/** @return T */
abstract public function get();
/**
* @template S
* @param S $default
* @return T|S
*/
abstract public function getOrElse($default);
/**
* @template S
* @param callable():S $callable
* @return T|S
*/
abstract public function getOrCall($callable);
/** @return T */
abstract public function getOrThrow(\Exception $ex);
abstract public function isEmpty(): bool;
abstract public function isDefined(): bool;
/**
* @param Option<T> $else
* @return Option<T>
*/
abstract public function orElse(self $else);
/** @deprecated Use forAll() instead. */
abstract public function ifDefined($callable);
/**
* @param callable(T):mixed $callable
* @return Option<T>
*/
abstract public function forAll($callable);
/**
* @template S
* @param callable(T):S $callable
* @return Option<S>
*/
abstract public function map($callable);
/**
* @template S
* @param callable(T):Option<S> $callable
* @return Option<S>
*/
abstract public function flatMap($callable);
/**
* @param callable(T):bool $callable
* @return Option<T>
*/
abstract public function filter($callable);
/**
* @param callable(T):bool $callable
* @return Option<T>
*/
abstract public function filterNot($callable);
/**
* @param T $value
* @return Option<T>
*/
abstract public function select($value);
/**
* @param T $value
* @return Option<T>
*/
abstract public function reject($value);
/**
* @template S
* @param S $initialValue
* @param callable(S, T):S $callable
* @return S
*/
abstract public function foldLeft($initialValue, $callable);
/**
* @template S
* @param S $initialValue
* @param callable(T, S):S $callable
* @return S
*/
abstract public function foldRight($initialValue, $callable);
}
+147
View File
@@ -0,0 +1,147 @@
<?php
namespace Lucent\Option;
use ArrayIterator;
/**
* @template T
*
* @extends Option<T>
*/
final class Some extends Option
{
/** @var T */
private $value;
/** @param T $value */
public function __construct($value)
{
$this->value = $value;
}
/**
* @template U
* @param U $value
* @return Some<U>
*/
public static function create($value): self
{
return new self($value);
}
public function isDefined(): bool
{
return true;
}
public function isEmpty(): bool
{
return false;
}
public function get()
{
return $this->value;
}
public function getOrElse($default)
{
return $this->value;
}
public function getOrCall($callable)
{
return $this->value;
}
public function getOrThrow(\Exception $ex)
{
return $this->value;
}
public function orElse(Option $else)
{
return $this;
}
public function ifDefined($callable)
{
$this->forAll($callable);
}
public function forAll($callable)
{
$callable($this->value);
return $this;
}
public function map($callable)
{
return new self($callable($this->value));
}
public function flatMap($callable)
{
/** @var mixed */
$rs = $callable($this->value);
if (!$rs instanceof Option) {
throw new \RuntimeException('Callables passed to flatMap() must return an Option. Maybe you should use map() instead?');
}
return $rs;
}
public function filter($callable)
{
if (true === $callable($this->value)) {
return $this;
}
return None::create();
}
public function filterNot($callable)
{
if (false === $callable($this->value)) {
return $this;
}
return None::create();
}
public function select($value)
{
if ($this->value === $value) {
return $this;
}
return None::create();
}
public function reject($value)
{
if ($this->value === $value) {
return None::create();
}
return $this;
}
/** @return ArrayIterator<int, T> */
public function getIterator(): ArrayIterator
{
return new ArrayIterator([$this->value]);
}
public function foldLeft($initialValue, $callable)
{
return $callable($initialValue, $this->value);
}
public function foldRight($initialValue, $callable)
{
return $callable($this->value, $initialValue);
}
}
+112
View File
@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Lucent\ResultType;
use Lucent\Option\None;
use Lucent\Option\Some;
/**
* @template T
* @template E
*
* @extends \Lucent\ResultType\Result<T,E>
*/
final class Error extends Result
{
/**
* @var E
*/
private $value;
/**
* Internal constructor for an error value.
*
* @param E $value
*
* @return void
*/
private function __construct($value)
{
$this->value = $value;
}
/**
* Create a new error value.
*
* @template F
*
* @param F $value
*
* @return \Lucent\ResultType\Result<T,F>
*/
public static function create($value): Error
{
return new self($value);
}
/**
* Get the success option value.
*
* @return \Lucent\Option\Option<T>
*/
public function success()
{
return None::create();
}
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \Lucent\ResultType\Result<S,E>
*/
public function map(callable $f): Result
{
return self::create($this->value);
}
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\Lucent\ResultType\Result<S,F> $f
*
* @return \Lucent\ResultType\Result<S,F>
*/
public function flatMap(callable $f): Result
{
/** @var \Lucent\ResultType\Result<S,F> */
return self::create($this->value);
}
/**
* Get the error option value.
*
* @return \Lucent\Option\Option<E>
*/
public function error(): Some
{
return Some::create($this->value);
}
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \Lucent\ResultType\Result<T,F>
*/
public function mapError(callable $f): Result
{
return self::create($f($this->value));
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Lucent\ResultType;
/**
* @template T
* @template E
*/
abstract class Result
{
/**
* Get the success option value.
*
* @return \Lucent\Option\Option<T>
*/
abstract public function success();
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \Lucent\ResultType\Result<S,E>
*/
abstract public function map(callable $f);
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\Lucent\ResultType\Result<S,F> $f
*
* @return \Lucent\ResultType\Result<S,F>
*/
abstract public function flatMap(callable $f);
/**
* Get the error option value.
*
* @return \Lucent\Option\Option<E>
*/
abstract public function error();
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \Lucent\ResultType\Result<T,F>
*/
abstract public function mapError(callable $f);
}
+111
View File
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Lucent\ResultType;
use Lucent\Option\None;
use Lucent\Option\Some;
/**
* @template T
* @template E
*
* @extends \Lucent\ResultType\Result<T,E>
*/
final class Success extends Result
{
/**
* @var T
*/
private $value;
/**
* Internal constructor for a success value.
*
* @param T $value
*
* @return void
*/
private function __construct($value)
{
$this->value = $value;
}
/**
* Create a new error value.
*
* @template S
*
* @param S $value
*
* @return \Lucent\ResultType\Result<S,E>
*/
public static function create($value): Success
{
return new self($value);
}
/**
* Get the success option value.
*
* @return \Lucent\Option\Option<T>
*/
public function success(): Some
{
return Some::create($this->value);
}
/**
* Map over the success value.
*
* @template S
*
* @param callable(T):S $f
*
* @return \Lucent\ResultType\Result<S,E>
*/
public function map(callable $f): Result
{
return self::create($f($this->value));
}
/**
* Flat map over the success value.
*
* @template S
* @template F
*
* @param callable(T):\Lucent\ResultType\Result<S,F> $f
*
* @return \Lucent\ResultType\Result<S,F>
*/
public function flatMap(callable $f)
{
return $f($this->value);
}
/**
* Get the error option value.
*
* @return \Lucent\Option\Option<E>
*/
public function error()
{
return None::create();
}
/**
* Map over the error value.
*
* @template F
*
* @param callable(E):F $f
*
* @return \Lucent\ResultType\Result<T,F>
*/
public function mapError(callable $f): Result
{
return self::create($this->value);
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ use Lucent\Database\Database;
use Lucent\Edge\Edge; use Lucent\Edge\Edge;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Record\RecordData; use Lucent\Record\RecordData;
use PhpOption\Option; use Lucent\Option\Option;
use stdClass; use stdClass;
class RevisionRepo class RevisionRepo
+1 -1
View File
@@ -6,7 +6,7 @@ use Lucent\Channel\ChannelService;
use Lucent\Edge\Edge; use Lucent\Edge\Edge;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Record\Record; use Lucent\Record\Record;
use PhpOption\Option; use Lucent\Option\Option;
readonly class RevisionService readonly class RevisionService
{ {
+2 -2
View File
@@ -1,7 +1,7 @@
<?php <?php
use PhpOption\None; use Lucent\Option\None;
use PhpOption\Some; use Lucent\Option\Some;
if (!function_exists("some")) { if (!function_exists("some")) {
/** /**