implemented user lists
This commit is contained in:
parent
3218253076
commit
c3e81ce6ea
16 changed files with 365 additions and 74 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
import {getCurrentlySelectedList} from "./userlists";
|
||||||
|
import {getTableAPI} from "./table";
|
||||||
|
|
||||||
async function checkImportFile() {
|
async function checkImportFile() {
|
||||||
const fileInput = document.querySelector<HTMLInputElement>('#import');
|
const fileInput = document.querySelector<HTMLInputElement>('#import');
|
||||||
|
|
||||||
|
@ -108,6 +111,9 @@ async function doImport() {
|
||||||
formData.set(`columns[${columnIndex}]`, attribute);
|
formData.set(`columns[${columnIndex}]`, attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listId = getCurrentlySelectedList() ?? 0;
|
||||||
|
formData.set('listid', listId.toString());
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/web/keys/import/perform`,
|
`/api/web/keys/import/perform`,
|
||||||
{
|
{
|
||||||
|
@ -127,6 +133,8 @@ async function doImport() {
|
||||||
|
|
||||||
container.classList.add('d-none');
|
container.classList.add('d-none');
|
||||||
fileInput.value = '';
|
fileInput.value = '';
|
||||||
|
|
||||||
|
getTableAPI().ajax.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Tab } from 'bootstrap';
|
||||||
|
|
||||||
import {init as initImport} from "./import";
|
import {init as initImport} from "./import";
|
||||||
import {init as initTable} from "./table";
|
import {init as initTable} from "./table";
|
||||||
|
import {init as initUserLists} from "./userlists";
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const triggerTabList = document.querySelectorAll('#key-tab button')
|
const triggerTabList = document.querySelectorAll('#key-tab button')
|
||||||
|
@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
initImport();
|
initImport();
|
||||||
initTable();
|
initTable();
|
||||||
|
initUserLists();
|
||||||
})
|
})
|
|
@ -1,12 +1,19 @@
|
||||||
import DataTable from "datatables.net-bs5";
|
import DataTable, {Api} from "datatables.net-bs5";
|
||||||
import 'datatables.net-bs5/css/dataTables.bootstrap5.css';
|
import 'datatables.net-bs5/css/dataTables.bootstrap5.css';
|
||||||
import {Key} from "../types/entities";
|
import {Key} from "../types/entities";
|
||||||
import {getIconForKeyState, getKeyStateExplanation} from "../types/keyState";
|
import {getIconForKeyState, getKeyStateExplanation} from "../types/keyState";
|
||||||
|
|
||||||
import {Dropdown} from "bootstrap";
|
import {Dropdown, Tooltip} from "bootstrap";
|
||||||
|
import {getCurrentlySelectedList} from "./userlists";
|
||||||
|
|
||||||
const TABLE_AJAX_URL = '/api/dt/keys/provider';
|
const TABLE_AJAX_URL = '/api/dt/keys/provider';
|
||||||
|
|
||||||
|
let tableAPI: Api;
|
||||||
|
|
||||||
|
export function getTableAPI(): Api {
|
||||||
|
return tableAPI;
|
||||||
|
}
|
||||||
|
|
||||||
function getKeyDisplay(parent: HTMLElement, keys: Key[]) {
|
function getKeyDisplay(parent: HTMLElement, keys: Key[]) {
|
||||||
const header = document.createElement("h1");
|
const header = document.createElement("h1");
|
||||||
header.innerText = 'Keys';
|
header.innerText = 'Keys';
|
||||||
|
@ -16,19 +23,19 @@ function getKeyDisplay(parent: HTMLElement, keys: Key[]) {
|
||||||
table.classList.add('table', 'table-striped', 'w-100');
|
table.classList.add('table', 'table-striped', 'w-100');
|
||||||
const body = table.createTBody();
|
const body = table.createTBody();
|
||||||
|
|
||||||
for (const {fromWhere, keyState, key, store, store_link} of keys) {
|
for (const {fromWhere, state, key, store, store_link} of keys) {
|
||||||
const row = body.insertRow();
|
const row = body.insertRow();
|
||||||
row.classList.add('cursor-pointer');
|
row.classList.add('cursor-pointer');
|
||||||
|
|
||||||
const stateCell = row.insertCell();
|
const stateCell = row.insertCell();
|
||||||
stateCell.classList.add('w-auto', 'align-content-center');
|
stateCell.classList.add('w-auto', 'align-content-center');
|
||||||
stateCell.innerHTML = `<a data-bs-toggle="tooltip" data-bs-title="${getKeyStateExplanation(keyState)}"><i class="fa-solid ${ getIconForKeyState(keyState) }"></i></a>`
|
stateCell.innerHTML = `<a data-bs-toggle="tooltip" data-bs-title="${getKeyStateExplanation(state)}"><i class="fa-solid ${ getIconForKeyState(state) }"></i></a>`
|
||||||
|
|
||||||
const anchor = stateCell.querySelector('a');
|
const anchor = stateCell.querySelector('a');
|
||||||
if (!anchor) {
|
if (!anchor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
new Dropdown(anchor);
|
new Tooltip(anchor);
|
||||||
|
|
||||||
row.insertCell().textContent = key;
|
row.insertCell().textContent = key;
|
||||||
row.insertCell().textContent = store === 'external' ? store_link : store;
|
row.insertCell().textContent = store === 'external' ? store_link : store;
|
||||||
|
@ -49,9 +56,13 @@ export function init() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = new DataTable(keyTable, {
|
const table = tableAPI = new DataTable(keyTable, {
|
||||||
ajax: {
|
ajax: {
|
||||||
url: TABLE_AJAX_URL
|
url: TABLE_AJAX_URL,
|
||||||
|
data: function (d) {
|
||||||
|
// @ts-ignore
|
||||||
|
d.listid = getCurrentlySelectedList();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
processing: true,
|
processing: true,
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -72,7 +83,6 @@ export function init() {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
ordering: false,
|
ordering: false,
|
||||||
serverSide: true,
|
|
||||||
order: [ [1, 'asc'] ],
|
order: [ [1, 'asc'] ],
|
||||||
createdRow(row: Node, data: any) {
|
createdRow(row: Node, data: any) {
|
||||||
const tableRow = <HTMLTableRowElement>row;
|
const tableRow = <HTMLTableRowElement>row;
|
||||||
|
|
71
src/js/pages/keys/userlists.ts
Normal file
71
src/js/pages/keys/userlists.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import {Modal} from "bootstrap";
|
||||||
|
import {getTableAPI} from "./table";
|
||||||
|
|
||||||
|
export function getCurrentlySelectedList(): number|null {
|
||||||
|
const listSelect = document.querySelector<HTMLSelectElement>('#list-select');
|
||||||
|
if (!listSelect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(listSelect.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
const modal = document.querySelector('#create-list-modal');
|
||||||
|
if (!modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalObj = new Modal(modal);
|
||||||
|
|
||||||
|
modal.addEventListener('show.bs.modal', (e) => {
|
||||||
|
const input = modal.querySelector<HTMLInputElement>('#createListName');
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.value = '';
|
||||||
|
})
|
||||||
|
modal.querySelector('.js--create-list')?.addEventListener('click', async (e) => {
|
||||||
|
const input = modal.querySelector<HTMLInputElement>('#createListName');
|
||||||
|
if (!input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newName = input.value;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', newName);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/web/keys/list/create`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
const listSelect = document.querySelector<HTMLSelectElement>('#list-select');
|
||||||
|
if (listSelect) {
|
||||||
|
listSelect.addEventListener('change', (e) => {
|
||||||
|
if (listSelect.value === '_create') {
|
||||||
|
modalObj.show()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableAPI().ajax.reload();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const newButton = document.querySelector('.js--create-list-button');
|
||||||
|
if (newButton) {
|
||||||
|
newButton.addEventListener('click', () => modalObj.show())
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ export type Key = {
|
||||||
game?: Game,
|
game?: Game,
|
||||||
key: string,
|
key: string,
|
||||||
store: Store,
|
store: Store,
|
||||||
keyState: KeyState,
|
state: KeyState,
|
||||||
store_link: string|null,
|
store_link: string|null,
|
||||||
fromWhere: string|null,
|
fromWhere: string|null,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'games')]
|
#[ORM\Table(name: 'games')]
|
||||||
final class Game
|
class Game
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||||
|
|
|
@ -5,10 +5,12 @@ namespace GamesShop\Entities\Games;
|
||||||
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use GamesShop\Entities\Account\User;
|
use GamesShop\Entities\Account\User;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'keys')]
|
#[ORM\Table(name: 'keys')]
|
||||||
final class Key
|
final class Key implements JsonSerializable
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||||
|
@ -17,7 +19,7 @@ final class Key
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
private Game $game;
|
private Game $game;
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
private User $contributedUser;
|
private GamesList $list;
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private string $key;
|
private string $key;
|
||||||
#[ORM\Column(type: 'string', enumType: Store::class)]
|
#[ORM\Column(type: 'string', enumType: Store::class)]
|
||||||
|
@ -29,10 +31,10 @@ final class Key
|
||||||
#[ORM\Column(type: 'integer', enumType: KeyState::class)]
|
#[ORM\Column(type: 'integer', enumType: KeyState::class)]
|
||||||
private KeyState $state;
|
private KeyState $state;
|
||||||
|
|
||||||
public function __construct(Game $game, User $contributedUser, string $key, Store $store, ?string $storeLink, ?string $fromWhere)
|
public function __construct(Game $game, GamesList $list, string $key, Store $store, ?string $storeLink, ?string $fromWhere)
|
||||||
{
|
{
|
||||||
$this->game = $game;
|
$this->game = $game;
|
||||||
$this->contributedUser = $contributedUser;
|
$this->list = $list;
|
||||||
$this->key = $key;
|
$this->key = $key;
|
||||||
$this->store = $store;
|
$this->store = $store;
|
||||||
$this->storeLink = $storeLink;
|
$this->storeLink = $storeLink;
|
||||||
|
@ -79,4 +81,16 @@ final class Key
|
||||||
{
|
{
|
||||||
return $this->state;
|
return $this->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'key' => $this->key,
|
||||||
|
'store' => $this->store->value,
|
||||||
|
'store_link' => $this->storeLink,
|
||||||
|
'from_where' => $this->fromWhere,
|
||||||
|
'state' => $this->state->value,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
62
src/php/Entities/GamesList.php
Normal file
62
src/php/Entities/GamesList.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GamesShop\Entities;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'games_lists')]
|
||||||
|
final class GamesList
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
private int|null $id;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
private User $owner;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private string|null $name;
|
||||||
|
|
||||||
|
#[ORM\JoinTable(name: 'games_list_claimer')]
|
||||||
|
#[ORM\JoinColumn(name: 'id', referencedColumnName: 'id')]
|
||||||
|
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||||
|
private Collection $claimer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $owner
|
||||||
|
* @param string|null $name
|
||||||
|
*/
|
||||||
|
public function __construct(User $owner, ?string $name)
|
||||||
|
{
|
||||||
|
$this->owner = $owner;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOwner(): User
|
||||||
|
{
|
||||||
|
return $this->owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): ?string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClaimer(): array
|
||||||
|
{
|
||||||
|
return $this->claimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use GamesShop\Entities\Games\Game;
|
||||||
use GamesShop\Entities\Games\Key;
|
use GamesShop\Entities\Games\Key;
|
||||||
use GamesShop\Entities\Games\KeyAttribute;
|
use GamesShop\Entities\Games\KeyAttribute;
|
||||||
use GamesShop\Entities\Games\Store;
|
use GamesShop\Entities\Games\Store;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
|
||||||
final class GameImporter
|
final class GameImporter
|
||||||
|
@ -75,7 +76,7 @@ final class GameImporter
|
||||||
/**
|
/**
|
||||||
* @param string[] $columnDefinitions
|
* @param string[] $columnDefinitions
|
||||||
*/
|
*/
|
||||||
public function import(string $path, array $columnDefinitions, User $contributedUser): array {
|
public function import(string $path, array $columnDefinitions, GamesList $list): array {
|
||||||
$spreadsheet = IOFactory::load($path);
|
$spreadsheet = IOFactory::load($path);
|
||||||
|
|
||||||
$worksheet = $spreadsheet->getSheet(0);
|
$worksheet = $spreadsheet->getSheet(0);
|
||||||
|
@ -129,7 +130,7 @@ final class GameImporter
|
||||||
|
|
||||||
$key = new Key(
|
$key = new Key(
|
||||||
$game,
|
$game,
|
||||||
$contributedUser,
|
$list,
|
||||||
$values['key'],
|
$values['key'],
|
||||||
$values['store'],
|
$values['store'],
|
||||||
$values['store_link'],
|
$values['store_link'],
|
||||||
|
|
|
@ -9,9 +9,11 @@ use Doctrine\ORM\EntityManager;
|
||||||
use GamesShop\Entities\Account\User;
|
use GamesShop\Entities\Account\User;
|
||||||
use GamesShop\Entities\Games\Game;
|
use GamesShop\Entities\Games\Game;
|
||||||
use GamesShop\Entities\Games\Key;
|
use GamesShop\Entities\Games\Key;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
use GamesShop\Login\LoginHandler;
|
use GamesShop\Login\LoginHandler;
|
||||||
use GamesShop\Login\UserPermission;
|
use GamesShop\Login\UserPermission;
|
||||||
use Laminas\Diactoros\Response\JsonResponse;
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use League\Route\Http\Exception\BadRequestException;
|
||||||
use League\Route\Http\Exception\ForbiddenException;
|
use League\Route\Http\Exception\ForbiddenException;
|
||||||
use League\Route\Http\Exception\UnauthorizedException;
|
use League\Route\Http\Exception\UnauthorizedException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
@ -37,62 +39,40 @@ final class ProviderKeysEndpoint
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$body = $request->getQueryParams();
|
||||||
|
if (!array_key_exists('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
$params = $request->getQueryParams();
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy([ 'owner' => $user, 'id' => $body['listid'] ]);
|
||||||
$draw = $params['draw'];
|
if (!$list instanceof GamesList) {
|
||||||
$start = $params['start'];
|
throw new BadRequestException();
|
||||||
$length = $params['length'];
|
}
|
||||||
|
|
||||||
$searchValue = $params['search']['value'];
|
$keys = $this->entityManager->getRepository(Key::class)->findBy(['list' => $list]);
|
||||||
|
$gameToKeyArray = [];
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$game = $key->getGame();
|
||||||
|
$id = $game->getId();
|
||||||
|
|
||||||
$repo = $this->entityManager->getRepository(Game::class);
|
if (!array_key_exists($id, $gameToKeyArray)) {
|
||||||
$total = $repo->count();
|
$gameToKeyArray[$id] = [ $game, [] ];
|
||||||
|
}
|
||||||
|
|
||||||
$criteria = Criteria::create();
|
$gameToKeyArray[$id][1][] = $key;
|
||||||
$criteria->where(Criteria::expr()->contains('name', $searchValue));
|
}
|
||||||
$criteria->setFirstResult((int)$start);
|
|
||||||
$criteria->setMaxResults((int)$length);
|
|
||||||
$criteria->orderBy([ 'name' => Order::Ascending ]);
|
|
||||||
|
|
||||||
$values = $repo->matching($criteria);
|
$result = [];
|
||||||
$filteredCount = $values->count();
|
foreach ($gameToKeyArray as [$game, $keys]) {
|
||||||
|
$result[] = [
|
||||||
|
'gamePicture' => '',
|
||||||
|
'name' => $game->getName(),
|
||||||
|
'keysAmount' => count($keys),
|
||||||
|
'igdbState' => 'not implermented',
|
||||||
|
'keys' => $keys,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$entityManager = $this->entityManager;
|
return new JsonResponse([ 'data' => $result ]);
|
||||||
|
|
||||||
return new JsonResponse([
|
|
||||||
'draw' => $draw,
|
|
||||||
'recordsTotal' => $total,
|
|
||||||
'recordsFiltered' => $filteredCount,
|
|
||||||
'data' =>
|
|
||||||
array_filter($values->map(function (Game $game) use ($entityManager, $user) {
|
|
||||||
$criteria = Criteria::create();
|
|
||||||
$criteria->where(Criteria::expr()->eq('game', $game));
|
|
||||||
$criteria->andWhere(Criteria::expr()->eq('contributedUser', $user));
|
|
||||||
$criteria->orderBy(['state' => Order::Ascending]);
|
|
||||||
|
|
||||||
$keys = $entityManager->getRepository(Key::class)->matching($criteria);
|
|
||||||
|
|
||||||
$keysAmount = $keys->count();
|
|
||||||
if ($keysAmount < 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'gamePicture' => '',
|
|
||||||
'name' => $game->getName(),
|
|
||||||
'keysAmount' => $keysAmount,
|
|
||||||
'igdbState' => 'yet to be implermented',
|
|
||||||
'keys' => $keys->map(function (Key $key) use ($entityManager) {
|
|
||||||
return [
|
|
||||||
'keyState' => $key->getState()->value,
|
|
||||||
'key' => $key->getKey(),
|
|
||||||
'store' => $key->getStore()->value,
|
|
||||||
'store_link' => $key->getStoreLink(),
|
|
||||||
'from' => $key->getFromWhere(),
|
|
||||||
];
|
|
||||||
})->toArray()
|
|
||||||
];
|
|
||||||
})->toArray())
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
49
src/php/Routing/Api/Web/CreateKeyListRoute.php
Normal file
49
src/php/Routing/Api/Web/CreateKeyListRoute.php
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use Laminas\Diactoros\Response;
|
||||||
|
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 CreateKeyListRoute
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LoginHandler $loginHandler,
|
||||||
|
private readonly EntityManager $entityManager
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
if (!array_key_exists('name', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $body['name'];
|
||||||
|
$list = new GamesList($user, $name);
|
||||||
|
$this->entityManager->persist($list);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace GamesShop\Routing\Api\Web;
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
use GamesShop\Importer\GameImporter;
|
use GamesShop\Importer\GameImporter;
|
||||||
use GamesShop\Login\LoginHandler;
|
use GamesShop\Login\LoginHandler;
|
||||||
use GamesShop\Login\UserPermission;
|
use GamesShop\Login\UserPermission;
|
||||||
|
@ -19,6 +21,7 @@ final class ImportKeysRoute
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly LoginHandler $loginHandler,
|
private readonly LoginHandler $loginHandler,
|
||||||
private readonly GameImporter $importer,
|
private readonly GameImporter $importer,
|
||||||
|
private readonly EntityManager $entityManager,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
public function __invoke(ServerRequestInterface $request): ResponseInterface
|
||||||
|
@ -32,6 +35,16 @@ final class ImportKeysRoute
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
if (!array_key_exists('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy([ 'owner' => $user, 'id' => $body['listid'] ]);
|
||||||
|
if (!$list instanceof GamesList) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var UploadedFile $file
|
* @var UploadedFile $file
|
||||||
*/
|
*/
|
||||||
|
@ -45,7 +58,7 @@ final class ImportKeysRoute
|
||||||
|
|
||||||
$columnDefs = $request->getParsedBody()['columns'];
|
$columnDefs = $request->getParsedBody()['columns'];
|
||||||
|
|
||||||
[$total, $imported] = $this->importer->import($fileName, $columnDefs, $user);
|
[$total, $imported] = $this->importer->import($fileName, $columnDefs, $list);
|
||||||
unlink($fileName);
|
unlink($fileName);
|
||||||
|
|
||||||
return new JsonResponse([ 'success' => true, 'total' => $total, 'imported' => $imported ]);
|
return new JsonResponse([ 'success' => true, 'total' => $total, 'imported' => $imported ]);
|
||||||
|
|
|
@ -12,5 +12,7 @@ final class WebAPIRoutes
|
||||||
|
|
||||||
$group->post('/keys/import/prepare', ImportKeysPrepareRoute::class);
|
$group->post('/keys/import/prepare', ImportKeysPrepareRoute::class);
|
||||||
$group->post('/keys/import/perform', ImportKeysRoute::class);
|
$group->post('/keys/import/perform', ImportKeysRoute::class);
|
||||||
|
|
||||||
|
$group->post('/keys/list/create', CreateKeyListRoute::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ final class ErrorRoute
|
||||||
public function renderErrorPage(int $errorCode): ResponseInterface {
|
public function renderErrorPage(int $errorCode): ResponseInterface {
|
||||||
$pageContent = ContainerHandler::get(TemplateEngine::class)->renderErrorPage($errorCode);
|
$pageContent = ContainerHandler::get(TemplateEngine::class)->renderErrorPage($errorCode);
|
||||||
|
|
||||||
$response = new Response;
|
$response = new Response(status: $errorCode);
|
||||||
$response->getBody()->write($pageContent);
|
$response->getBody()->write($pageContent);
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace GamesShop\Routing;
|
namespace GamesShop\Routing;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
use GamesShop\Login\LoginHandler;
|
use GamesShop\Login\LoginHandler;
|
||||||
use GamesShop\Login\UserPermission;
|
use GamesShop\Login\UserPermission;
|
||||||
use GamesShop\Routing\Responses\TemplateResponse;
|
use GamesShop\Routing\Responses\TemplateResponse;
|
||||||
|
@ -14,7 +16,8 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||||
final class KeysRoute
|
final class KeysRoute
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly LoginHandler $loginHandler
|
private readonly LoginHandler $loginHandler,
|
||||||
|
private readonly EntityManager $entityManager
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -30,8 +33,9 @@ final class KeysRoute
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$entityManager = $this->entityManager->getRepository(GamesList::class);
|
||||||
return new TemplateResponse('key-manager');
|
$lists = $entityManager->findBy([ 'owner' => $user ]);
|
||||||
|
return new TemplateResponse('key-manager', [ 'usersLists' => $lists ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function applyRoutes(\League\Route\Router $router): void
|
public static function applyRoutes(\League\Route\Router $router): void
|
||||||
|
|
|
@ -2,15 +2,44 @@
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use GamesShop\Entities\Games\KeyAttribute;
|
use GamesShop\Entities\Games\KeyAttribute;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
use League\Plates\Template\Template;
|
use League\Plates\Template\Template;
|
||||||
|
|
||||||
assert($this instanceof Template);
|
assert($this instanceof Template);
|
||||||
|
|
||||||
|
/** @var GamesList[] $usersLists */
|
||||||
|
|
||||||
$this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
$this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
?>
|
?>
|
||||||
<meta name="key-attributes" content="<?= htmlspecialchars(json_encode(KeyAttribute::casesAsAssociative())) ?>" />
|
<meta name="key-attributes" content="<?= htmlspecialchars(json_encode(KeyAttribute::casesAsAssociative())) ?>" />
|
||||||
|
|
||||||
<h1>My Keys</h1>
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<h1>My Keys</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 align-self-center">
|
||||||
|
<?php if (!empty($usersLists)): ?>
|
||||||
|
<select name="lists" id="list-select" class="form-select w-100">
|
||||||
|
<?php foreach ($usersLists as $list): ?>
|
||||||
|
<option value="<?= $list->getId() ?>"><?= $list->getName() ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<option value="_create">+ Create New</option>
|
||||||
|
</select>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php if (empty($usersLists)): ?>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="fs-4 mb-4">You don't have a key list. Create one here.</p>
|
||||||
|
|
||||||
|
<button class="btn btn-primary btn-lg js--create-list-button">
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
|
||||||
<ul id="key-tab" class="nav nav-tabs">
|
<ul id="key-tab" class="nav nav-tabs">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#keys-tab-pane" role="tab">
|
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#keys-tab-pane" role="tab">
|
||||||
|
@ -22,6 +51,11 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
Import
|
Import
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#share-tab-pane" role="tab">
|
||||||
|
Share
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane fade show active" id="keys-tab-pane" role="tabpanel">
|
<div class="tab-pane fade show active" id="keys-tab-pane" role="tabpanel">
|
||||||
|
@ -65,4 +99,45 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="tab-pane fade" id="share-tab-pane" role="tabpanel">
|
||||||
|
<h2>
|
||||||
|
Share your list
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<label for="share-user-search">Search for a user...</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control" id="share-user-search" placeholder="">
|
||||||
|
<button class="btn btn-primary js--search-shared-user">Search</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<?php $this->start('modal') ?>
|
||||||
|
|
||||||
|
<div class="modal" id="create-list-modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title h3">
|
||||||
|
Create list
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-floating">
|
||||||
|
<input class="form-control" type="text" id="createListName" placeholder="">
|
||||||
|
<label for="createListName">Name</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary js--create-list">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $this->end() ?>
|
||||||
|
|
Loading…
Reference in a new issue