implermented key import
This commit is contained in:
parent
ace0de4063
commit
74e1b25fcf
20 changed files with 1035 additions and 12 deletions
|
@ -6,6 +6,7 @@ namespace GamesShop;
|
|||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMSetup;
|
||||
use GamesShop\Environment\EnvironmentHandler;
|
||||
|
||||
|
@ -27,6 +28,7 @@ final class DoctrineManager
|
|||
$entityManager = new EntityManager($connection, $config);
|
||||
|
||||
$container->addShared(EntityManager::class, $entityManager);
|
||||
$container->addShared(EntityManagerInterface::class, $entityManager);
|
||||
$container->addShared(Connection::class, $connection);
|
||||
}
|
||||
|
||||
|
|
26
src/php/Entities/Games/Game.php
Normal file
26
src/php/Entities/Games/Game.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Entities\Games;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'games')]
|
||||
final class Game
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||
#[ORM\GeneratedValue]
|
||||
private int|null $id;
|
||||
#[ORM\Column]
|
||||
private string $name;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
39
src/php/Entities/Games/Key.php
Normal file
39
src/php/Entities/Games/Key.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Entities\Games;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use GamesShop\Entities\Account\User;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'keys')]
|
||||
final class Key
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||
#[ORM\GeneratedValue]
|
||||
private int|null $id;
|
||||
#[ORM\ManyToOne]
|
||||
private Game $game;
|
||||
#[ORM\ManyToOne]
|
||||
private User $contributedUser;
|
||||
#[ORM\Column]
|
||||
private string $key;
|
||||
#[ORM\Column(type: 'string', enumType: Store::class)]
|
||||
private Store $store;
|
||||
#[ORM\Column(nullable: true)]
|
||||
private string|null $storeLink;
|
||||
#[ORM\Column]
|
||||
private string|null $fromWhere;
|
||||
|
||||
public function __construct(Game $game, User $contributedUser, string $key, Store $store, ?string $storeLink, ?string $fromWhere)
|
||||
{
|
||||
$this->game = $game;
|
||||
$this->contributedUser = $contributedUser;
|
||||
$this->key = $key;
|
||||
$this->store = $store;
|
||||
$this->storeLink = $storeLink;
|
||||
$this->fromWhere = $fromWhere;
|
||||
}
|
||||
}
|
22
src/php/Entities/Games/KeyAttribute.php
Normal file
22
src/php/Entities/Games/KeyAttribute.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Entities\Games;
|
||||
|
||||
enum KeyAttribute: string
|
||||
{
|
||||
case NONE = 'none';
|
||||
case GAME_NAME = "game_name";
|
||||
case KEY = "key";
|
||||
case STORE = 'store';
|
||||
case FROM = 'from';
|
||||
|
||||
public static function casesAsAssociative(): array {
|
||||
$result = [];
|
||||
foreach (self::cases() as $case) {
|
||||
$result[$case->name] = $case->value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
15
src/php/Entities/Games/Store.php
Normal file
15
src/php/Entities/Games/Store.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Entities\Games;
|
||||
|
||||
enum Store: string
|
||||
{
|
||||
case STEAM = 'steam';
|
||||
case GOG = 'gog';
|
||||
case EPICGAMES = 'epicgames';
|
||||
case ORIGIN = 'origin';
|
||||
case UPLAY = 'uplay';
|
||||
case BATTLENET = 'battlenet';
|
||||
case EXTERNAL = 'external';
|
||||
}
|
165
src/php/Importer/GameImporter.php
Normal file
165
src/php/Importer/GameImporter.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Importer;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Exception;
|
||||
use GamesShop\Entities\Account\User;
|
||||
use GamesShop\Entities\Games\Game;
|
||||
use GamesShop\Entities\Games\Key;
|
||||
use GamesShop\Entities\Games\KeyAttribute;
|
||||
use GamesShop\Entities\Games\Store;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
final class GameImporter
|
||||
{
|
||||
private const HEADER_ROW_INDEX = 1;
|
||||
|
||||
private const STORE_ADDITIONAL_CASES = [
|
||||
'epic' => Store::EPICGAMES,
|
||||
'ea' => Store::ORIGIN,
|
||||
'eaplay' => Store::ORIGIN,
|
||||
'ubisoft' => Store::UPLAY,
|
||||
'activision' => Store::BATTLENET
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return ImportColumnInterpretation[]
|
||||
*/
|
||||
public function interpret(string $path): array {
|
||||
$spreadsheet = IOFactory::load($path);
|
||||
|
||||
$worksheet = $spreadsheet->getSheet(0);
|
||||
$result = [];
|
||||
foreach ($worksheet->getColumnIterator() as $column) {
|
||||
$columnIndex = $column->getColumnIndex();
|
||||
|
||||
$value = $worksheet->getCell(sprintf('%s%d', $columnIndex, self::HEADER_ROW_INDEX))->getValueString();
|
||||
|
||||
if (empty(trim($value))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$guessedAttribute = $this->guessAttribute($value);
|
||||
$result[] = new ImportColumnInterpretation(
|
||||
$columnIndex,
|
||||
$value,
|
||||
$guessedAttribute
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function guessAttribute(string $value): KeyAttribute|null {
|
||||
$value = trim($value);
|
||||
$value = strtolower($value);
|
||||
$value = str_replace(' ', '_', $value);
|
||||
|
||||
$attribute = match($value) {
|
||||
'key' => KeyAttribute::KEY,
|
||||
'name', 'game_name', 'game' => KeyAttribute::GAME_NAME,
|
||||
'from' => KeyAttribute::FROM,
|
||||
'store', 'for' => KeyAttribute::STORE,
|
||||
default => null
|
||||
};
|
||||
|
||||
return $attribute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $columnDefinitions
|
||||
*/
|
||||
public function import(string $path, array $columnDefinitions, User $contributedUser): array {
|
||||
$spreadsheet = IOFactory::load($path);
|
||||
|
||||
$worksheet = $spreadsheet->getSheet(0);
|
||||
|
||||
$totalRows = 0;
|
||||
$addedAmount = 0;
|
||||
|
||||
foreach ($worksheet->getRowIterator(self::HEADER_ROW_INDEX + 1) as $row) {
|
||||
$totalRows++;
|
||||
$values = [
|
||||
'name' => null,
|
||||
'key' => null,
|
||||
'from' => null,
|
||||
'store' => null,
|
||||
'store_link' => null
|
||||
];
|
||||
|
||||
foreach ($columnDefinitions as $columnIndex => $attribute) {
|
||||
$value = $worksheet->getCell(sprintf('%s%d', $columnIndex, $row->getRowIndex()))->getValueString();
|
||||
|
||||
switch(KeyAttribute::from($attribute)) {
|
||||
case KeyAttribute::NONE:
|
||||
break;
|
||||
case KeyAttribute::GAME_NAME:
|
||||
$values['name'] = $value;
|
||||
break;
|
||||
case KeyAttribute::KEY:
|
||||
$values['key'] = $value;
|
||||
break;
|
||||
case KeyAttribute::FROM:
|
||||
$values['from'] = $value;
|
||||
break;
|
||||
case KeyAttribute::STORE:
|
||||
$store = $this->interpretStore($value);
|
||||
$values['store'] = $store;
|
||||
if ($store === Store::EXTERNAL) {
|
||||
$values['store_link'] = $value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($values['key'] === null || $values['name'] === null || $values['store'] === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$game = $this->entityManager->getRepository(Game::class)->findOneBy([ 'name' => $values['name'] ]);
|
||||
if ($game === null) {
|
||||
$game = new Game($values['name']);
|
||||
}
|
||||
|
||||
$key = new Key(
|
||||
$game,
|
||||
$contributedUser,
|
||||
$values['key'],
|
||||
$values['store'],
|
||||
$values['store_link'],
|
||||
$values['from'],
|
||||
);
|
||||
|
||||
$this->entityManager->persist($game);
|
||||
$this->entityManager->persist($key);
|
||||
$addedAmount++;
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return [$totalRows, $addedAmount];
|
||||
}
|
||||
|
||||
private function interpretStore(string $storeString): Store {
|
||||
$storeString = trim($storeString);
|
||||
$storeString = strtolower($storeString);
|
||||
$storeString = str_replace(' ', '', $storeString);
|
||||
|
||||
$triedConversion = Store::tryFrom($storeString);
|
||||
if ($triedConversion !== null) {
|
||||
return $triedConversion;
|
||||
}
|
||||
|
||||
if (array_key_exists($storeString, self::STORE_ADDITIONAL_CASES)) {
|
||||
return self::STORE_ADDITIONAL_CASES[$storeString];
|
||||
}
|
||||
|
||||
return Store::EXTERNAL;
|
||||
}
|
||||
}
|
26
src/php/Importer/ImportColumnInterpretation.php
Normal file
26
src/php/Importer/ImportColumnInterpretation.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Importer;
|
||||
|
||||
use GamesShop\Entities\Games\KeyAttribute;
|
||||
use JsonSerializable;
|
||||
|
||||
final readonly class ImportColumnInterpretation implements JsonSerializable
|
||||
{
|
||||
public function __construct(
|
||||
public string $index,
|
||||
public string $displayName,
|
||||
public KeyAttribute|null $guessedAttribute
|
||||
)
|
||||
{ }
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'index' => $this->index,
|
||||
'displayName' => $this->displayName,
|
||||
'guessedAttribute' => $this->guessedAttribute ?? null
|
||||
];
|
||||
}
|
||||
}
|
50
src/php/Routing/Api/Web/ImportKeysPrepareRoute.php
Normal file
50
src/php/Routing/Api/Web/ImportKeysPrepareRoute.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\Web;
|
||||
|
||||
use GamesShop\Importer\GameImporter;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Laminas\Diactoros\UploadedFile;
|
||||
use League\Route\Http\Exception\BadRequestException;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class ImportKeysPrepareRoute
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoginHandler $loginHandler,
|
||||
private readonly GameImporter $importer,
|
||||
) { }
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if (!$user->getPermission()->hasLevel(UserPermission::PROVIDER)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var UploadedFile $file
|
||||
*/
|
||||
$file = $request->getUploadedFiles()['file'] ?? null;
|
||||
if (!$file === null) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
$fileName = tempnam(sys_get_temp_dir(), 'ImportKeys');
|
||||
$file->moveTo($fileName);
|
||||
$results = $this->importer->interpret($fileName);
|
||||
unlink($fileName);
|
||||
|
||||
return new JsonResponse($results);
|
||||
}
|
||||
}
|
53
src/php/Routing/Api/Web/ImportKeysRoute.php
Normal file
53
src/php/Routing/Api/Web/ImportKeysRoute.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing\Api\Web;
|
||||
|
||||
use GamesShop\Importer\GameImporter;
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Laminas\Diactoros\UploadedFile;
|
||||
use League\Route\Http\Exception\BadRequestException;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class ImportKeysRoute
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoginHandler $loginHandler,
|
||||
private readonly GameImporter $importer,
|
||||
) { }
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if (!$user->getPermission()->hasLevel(UserPermission::PROVIDER)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var UploadedFile $file
|
||||
*/
|
||||
$file = $request->getUploadedFiles()['file'] ?? null;
|
||||
if (!$file === null) {
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
$fileName = tempnam(sys_get_temp_dir(), 'ImportKeys');
|
||||
$file->moveTo($fileName);
|
||||
|
||||
$columnDefs = $request->getParsedBody()['columns'];
|
||||
|
||||
[$total, $imported] = $this->importer->import($fileName, $columnDefs, $user);
|
||||
unlink($fileName);
|
||||
|
||||
return new JsonResponse([ 'success' => true, 'total' => $total, 'imported' => $imported ]);
|
||||
}
|
||||
}
|
|
@ -9,5 +9,8 @@ final class WebAPIRoutes
|
|||
{
|
||||
public static function applyRoutes(RouteGroup $group): void {
|
||||
$group->post('/users/{id:number}', UserModifyRoute::class);
|
||||
|
||||
$group->post('/keys/import/prepare', ImportKeysPrepareRoute::class);
|
||||
$group->post('/keys/import/perform', ImportKeysRoute::class);
|
||||
}
|
||||
}
|
41
src/php/Routing/KeysRoute.php
Normal file
41
src/php/Routing/KeysRoute.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace GamesShop\Routing;
|
||||
|
||||
use GamesShop\Login\LoginHandler;
|
||||
use GamesShop\Login\UserPermission;
|
||||
use GamesShop\Routing\Responses\TemplateResponse;
|
||||
use League\Route\Http\Exception\ForbiddenException;
|
||||
use League\Route\Http\Exception\UnauthorizedException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
final class KeysRoute
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoginHandler $loginHandler
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!$this->loginHandler->isLoggedIn()) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
$user = $this->loginHandler->getCurrentUser();
|
||||
if (!$user->getPermission()->hasLevel(UserPermission::PROVIDER)) {
|
||||
throw new ForbiddenException();
|
||||
}
|
||||
|
||||
|
||||
return new TemplateResponse('key-manager');
|
||||
}
|
||||
|
||||
public static function applyRoutes(\League\Route\Router $router): void
|
||||
{
|
||||
$router->get('/keys', KeysRoute::class);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,8 @@ final class Router
|
|||
IndexRoute::applyRoutes($router);
|
||||
LoginRoutes::addRoutes($router);
|
||||
SetupRoute::applyRoutes($router);
|
||||
|
||||
KeysRoute::applyRoutes($router);
|
||||
AdminAccountConfigRoute::applyRoutes($router);
|
||||
|
||||
APIRoutes::applyRoutes($router);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue