Merge pull request 'Release 0.1' (#1) from develop into master
Reviewed-on: #1
This commit is contained in:
commit
068c25d2cf
27 changed files with 1036 additions and 45 deletions
9
deploy/create_doctrine.sh
Normal file
9
deploy/create_doctrine.sh
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
docker run \
|
||||||
|
--volume .:/app \
|
||||||
|
--rm \
|
||||||
|
--interactive \
|
||||||
|
--tty \
|
||||||
|
php:8.3-cli \
|
||||||
|
php /app/src/php/bin/doctrine.php orm:schema-tool:update --force
|
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -12,6 +12,7 @@
|
||||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap5-autocomplete": "^1.1.33",
|
||||||
"datatables.net-bs5": "^2.0.8"
|
"datatables.net-bs5": "^2.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -699,6 +700,12 @@
|
||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap5-autocomplete": {
|
||||||
|
"version": "1.1.33",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap5-autocomplete/-/bootstrap5-autocomplete-1.1.33.tgz",
|
||||||
|
"integrity": "sha512-VgHSx2hCNEBThFzb57HziDA2BNuc0wT5+V9XqIbXsV6oKYXcyRE2ytFIJcHjTCEIYqTsNCFiCQILIXc3YANGPQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
"@fortawesome/fontawesome-free": "^6.5.2",
|
"@fortawesome/fontawesome-free": "^6.5.2",
|
||||||
"@types/bootstrap": "^5.2.10",
|
"@types/bootstrap": "^5.2.10",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"bootstrap5-autocomplete": "^1.1.33",
|
||||||
"datatables.net-bs5": "^2.0.8"
|
"datatables.net-bs5": "^2.0.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,2 +1,193 @@
|
||||||
import '../../css/common/index.scss';
|
import '../../css/common/index.scss';
|
||||||
import '../common/index';
|
import '../common/index';
|
||||||
|
|
||||||
|
import DataTable, {Api} from "datatables.net-bs5";
|
||||||
|
import 'datatables.net-bs5/css/dataTables.bootstrap5.css';
|
||||||
|
|
||||||
|
const TABLE_AJAX_URL = '/api/dt/keys/available';
|
||||||
|
const GAME_GET_URL = '/api/web/game';
|
||||||
|
const CLAIM_URL = '/api/web/key/claim';
|
||||||
|
function getDetailsHTML(gameData: Game): string
|
||||||
|
{
|
||||||
|
let keyString = '';
|
||||||
|
gameData.keys.forEach(key => {
|
||||||
|
keyString += `
|
||||||
|
<div class="key input-group mb-2" data-key-id="${ key.id }">
|
||||||
|
|
||||||
|
<span class="input-group-text flex-grow-1">
|
||||||
|
<i class="${ key.store.icon } me-2"></i>
|
||||||
|
${ key.store.name }
|
||||||
|
|
||||||
|
<span class="text-muted ms-2">
|
||||||
|
provided by ${ key.list.owner }
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<button class="btn btn-primary claim-btn">Claim</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="ratio border-1 border-light-subtle rounded-3 bg-body-secondary" style="--bs-aspect-ratio: 161.803%">
|
||||||
|
<div>Image</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<h2>
|
||||||
|
${ gameData.name }
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="key-list">
|
||||||
|
${ keyString }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="key-claim-display d-none">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text store-display">
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<span class="input-group-text flex-grow-1">
|
||||||
|
<span class="text-center key-display w-100">
|
||||||
|
#####-#####-#####
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<a class="btn btn-primary open-claim-page-btn" target="_blank" rel="noopener noreferrer">Open Claim Page</a>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<span class="col-6 text-center">
|
||||||
|
<span class="text-muted">provided by:</span> <br>
|
||||||
|
<span class="providedBy"></span>
|
||||||
|
</span>
|
||||||
|
<span class="col-6 text-center">
|
||||||
|
<span class="text-muted">key from: </span><br>
|
||||||
|
<span class="from"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderDetails(childRow: HTMLDivElement, gameid: number) {
|
||||||
|
const loadingContainer = document.createElement('div');
|
||||||
|
loadingContainer.classList.add('m-3', 'mx-auto');
|
||||||
|
loadingContainer.style.width = '2rem';
|
||||||
|
const spinner = document.createElement('div');
|
||||||
|
spinner.classList.add('spinner-border');
|
||||||
|
loadingContainer.appendChild(spinner);
|
||||||
|
childRow.appendChild(loadingContainer);
|
||||||
|
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
queryParams.set('gameid', gameid.toString());
|
||||||
|
const response = await fetch(
|
||||||
|
GAME_GET_URL + '?' + queryParams.toString(),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameData: Game = await response.json();
|
||||||
|
|
||||||
|
childRow.removeChild(loadingContainer);
|
||||||
|
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const gameDisplay: HTMLDivElement|null = parser.parseFromString(getDetailsHTML(gameData), 'text/html').body.firstChild;
|
||||||
|
|
||||||
|
if (!gameDisplay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameDisplay.querySelectorAll('.claim-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
const key = btn.closest('.key')?.dataset.keyId ?? 0;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.set('keyid', key);
|
||||||
|
|
||||||
|
const response = await fetch(CLAIM_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
gameDisplay.querySelector('.key-list')?.classList.add('d-none');
|
||||||
|
const claimDisplay: HTMLDivElement|null = gameDisplay.querySelector('.key-claim-display')
|
||||||
|
|
||||||
|
if (!claimDisplay) {
|
||||||
|
throw new Error("Can't find claim display...");
|
||||||
|
}
|
||||||
|
|
||||||
|
claimDisplay.classList.remove('d-none');
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
claimDisplay.querySelector('.store-display').textContent = data.store.name;
|
||||||
|
// @ts-expect-error
|
||||||
|
claimDisplay.querySelector('.key-display').textContent = data.key;
|
||||||
|
// @ts-expect-error
|
||||||
|
claimDisplay.querySelector('.providedBy').textContent = data.providedBy;
|
||||||
|
// @ts-expect-error
|
||||||
|
claimDisplay.querySelector('.from').textContent = data.from;
|
||||||
|
const claimBtn = claimDisplay.querySelector<HTMLAnchorElement>('.open-claim-page-btn');
|
||||||
|
if (!claimBtn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.store.claimLink === null) {
|
||||||
|
claimBtn.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
claimBtn.href = data.store.claimLink;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
childRow.appendChild(gameDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const keyTable = document.getElementById('keyTable');
|
||||||
|
if (!keyTable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const table = new DataTable(keyTable, {
|
||||||
|
ajax: {
|
||||||
|
url: TABLE_AJAX_URL,
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
data: 'name',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
createdRow(row: Node, data: any) {
|
||||||
|
const tableRow = <HTMLTableRowElement>row;
|
||||||
|
tableRow.classList.add('cursor-pointer');
|
||||||
|
|
||||||
|
tableRow.addEventListener('click', () => {
|
||||||
|
const rowAPI = table.row(row);
|
||||||
|
if (rowAPI.child.isShown()) {
|
||||||
|
rowAPI.child.hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childRow = document.createElement('tr');
|
||||||
|
const cell = childRow.insertCell();
|
||||||
|
cell.colSpan = row.childNodes.length;
|
||||||
|
const cellContainer = document.createElement('div');
|
||||||
|
cell.appendChild(cellContainer);
|
||||||
|
|
||||||
|
rowAPI.child(childRow).show();
|
||||||
|
renderDetails(cellContainer, data.id);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,24 +1,162 @@
|
||||||
import DataTable from "datatables.net-bs5";
|
import DataTable, {Api} from "datatables.net-bs5";
|
||||||
|
// @ts-expect-error
|
||||||
|
import Autocomplete from "bootstrap5-autocomplete";
|
||||||
|
import {getCurrentlySelectedList} from "./userlists";
|
||||||
|
|
||||||
|
const SEARCH_API_URL = '/api/web/share/search';
|
||||||
|
const ADD_API_URL = '/api/web/share/add';
|
||||||
|
const REMOVE_API_URL = '/api/web/share/remove';
|
||||||
|
const SET_PUBLIC_API_URL = '/api/web/share/setPublic';
|
||||||
|
|
||||||
|
const DT_API_URL = '/api/dt/list/users';
|
||||||
|
|
||||||
|
let dtApi: Api;
|
||||||
|
|
||||||
|
function actionColumnRender(
|
||||||
|
id: number, type: string
|
||||||
|
) {
|
||||||
|
if (type !== 'display') {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.classList.add('fa-solid', 'fa-xmark', 'text-danger', 'cursor-pointer', 'fa-xl');
|
||||||
|
icon.addEventListener('click', async () => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('requestedUser', id.toString() ?? '');
|
||||||
|
formData.append('listid', getCurrentlySelectedList()?.toString() ?? '');
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
REMOVE_API_URL,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
dtApi.ajax.reload();
|
||||||
|
})
|
||||||
|
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
export function initShare() {
|
export function initShare() {
|
||||||
|
const shareContainer = document.querySelector<HTMLDivElement>('.share-content');
|
||||||
|
if (!shareContainer) {
|
||||||
|
throw new Error("Missing share container");
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicSwitch = document.querySelector<HTMLInputElement>('#public-switch');
|
||||||
|
if (!publicSwitch) {
|
||||||
|
throw new Error("Missing public switch");
|
||||||
|
}
|
||||||
|
|
||||||
|
shareContainer.classList.toggle('d-none', publicSwitch.checked);
|
||||||
|
publicSwitch.addEventListener('click', async () => {
|
||||||
|
shareContainer.classList.toggle('d-none', publicSwitch.checked);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('listid', getCurrentlySelectedList()?.toString() ?? '');
|
||||||
|
formData.append('publicState', publicSwitch.checked ? "1" : "0");
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
SET_PUBLIC_API_URL,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const input = document.querySelector<HTMLInputElement>('#share-user-search');
|
||||||
|
if (!input) {
|
||||||
|
throw new Error("Missing search element");
|
||||||
|
}
|
||||||
|
let currentlySelected: number|null = null;
|
||||||
|
const autocomplete = new Autocomplete(input, {
|
||||||
|
server: SEARCH_API_URL,
|
||||||
|
liveServer: true,
|
||||||
|
fullWidth: true,
|
||||||
|
fixed: true,
|
||||||
|
|
||||||
|
onSelectItem(item: Object, instance: Autocomplete) {
|
||||||
|
// @ts-expect-error
|
||||||
|
currentlySelected = item.value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
document.querySelector<HTMLButtonElement>('.js--adds-user')?.addEventListener('click', async (e) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('requested', currentlySelected?.toString() ?? '');
|
||||||
|
formData.append('listid', getCurrentlySelectedList()?.toString() ?? '');
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
ADD_API_URL,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
dtApi.ajax.reload()
|
||||||
|
})
|
||||||
|
|
||||||
const table = document.querySelector<HTMLTableElement>('#shared-users-table');
|
const table = document.querySelector<HTMLTableElement>('#shared-users-table');
|
||||||
if (!table) {
|
if (!table) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new DataTable(table, {
|
dtApi = new DataTable(table, {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
|
title: 'Icon',
|
||||||
data: 'icon',
|
data: 'icon',
|
||||||
|
className: 'avatar',
|
||||||
searchable: false,
|
searchable: false,
|
||||||
orderable: false,
|
orderable: false,
|
||||||
|
width: 'auto',
|
||||||
|
render(data, type) {
|
||||||
|
if (type !== 'display') {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<img src="${ data }" alt="Profile Picture" class="avatar-icon h-100 position-relative me-2 ratio-1 rounded-circle" />`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
data: 'name',
|
data: 'name',
|
||||||
|
title: 'Name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
data: 'id',
|
||||||
|
render: actionColumnRender,
|
||||||
|
searchable: false,
|
||||||
|
orderable: false,
|
||||||
|
type: 'html'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
ajax: {
|
||||||
|
url: DT_API_URL,
|
||||||
|
data(data) {
|
||||||
|
// @ts-expect-error
|
||||||
|
data.listid = getCurrentlySelectedList()
|
||||||
|
}
|
||||||
|
},
|
||||||
order: [ [1, "desc"] ]
|
order: [ [1, "desc"] ]
|
||||||
});
|
});
|
||||||
}
|
}
|
21
src/js/pages/types/gameindex.d.ts
vendored
Normal file
21
src/js/pages/types/gameindex.d.ts
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
type GameList = {
|
||||||
|
owner: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
type Store = {
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key = {
|
||||||
|
id: number;
|
||||||
|
store: Store;
|
||||||
|
fromWhere: string;
|
||||||
|
list: GameList;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Game = {
|
||||||
|
name: string;
|
||||||
|
id: number;
|
||||||
|
keys: Key[];
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ use Symfony\Component\Uid\UuidV4;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: "users")]
|
#[ORM\Table(name: "users")]
|
||||||
final class User
|
class User
|
||||||
{
|
{
|
||||||
|
|
||||||
#[ORM\Id()]
|
#[ORM\Id()]
|
||||||
|
|
|
@ -31,6 +31,9 @@ final class Key implements JsonSerializable
|
||||||
#[ORM\Column(type: 'integer', enumType: KeyState::class)]
|
#[ORM\Column(type: 'integer', enumType: KeyState::class)]
|
||||||
private KeyState $state;
|
private KeyState $state;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
private User $claimedUser;
|
||||||
|
|
||||||
public function __construct(Game $game, GamesList $list, 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;
|
||||||
|
@ -52,11 +55,6 @@ final class Key implements JsonSerializable
|
||||||
return $this->game;
|
return $this->game;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getContributedUser(): User
|
|
||||||
{
|
|
||||||
return $this->contributedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getKey(): string
|
public function getKey(): string
|
||||||
{
|
{
|
||||||
return $this->key;
|
return $this->key;
|
||||||
|
@ -81,6 +79,19 @@ final class Key implements JsonSerializable
|
||||||
{
|
{
|
||||||
return $this->state;
|
return $this->state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getList(): GamesList
|
||||||
|
{
|
||||||
|
return $this->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setState(KeyState $state): void {
|
||||||
|
$this->state = $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setClaimedUser(User $claimedUser): void {
|
||||||
|
$this->claimedUser = $claimedUser;
|
||||||
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): mixed
|
public function jsonSerialize(): mixed
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,4 +12,33 @@ enum Store: string
|
||||||
case UPLAY = 'uplay';
|
case UPLAY = 'uplay';
|
||||||
case BATTLENET = 'battlenet';
|
case BATTLENET = 'battlenet';
|
||||||
case EXTERNAL = 'external';
|
case EXTERNAL = 'external';
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::STEAM => 'Steam',
|
||||||
|
self::GOG => "GOG",
|
||||||
|
self::EPICGAMES => "Epic Games Store",
|
||||||
|
self::ORIGIN => "EA Play / Origin",
|
||||||
|
self::UPLAY => "UPlay",
|
||||||
|
self::BATTLENET => "Battlenet",
|
||||||
|
self::EXTERNAL => "Other",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIcon(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::STEAM => 'fa-solid fa-steam',
|
||||||
|
default => '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClaimURL(Key $key): ?string {
|
||||||
|
return match ($this) {
|
||||||
|
self::STEAM => 'https://store.steampowered.com/account/registerkey?key=' . $key->getKey(),
|
||||||
|
self::EXTERNAL => $key->getStoreLink(),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,14 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace GamesShop\Entities;
|
namespace GamesShop\Entities;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use GamesShop\Entities\Account\User;
|
use GamesShop\Entities\Account\User;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'games_lists')]
|
#[ORM\Table(name: 'games_lists')]
|
||||||
final class GamesList
|
class GamesList
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
#[ORM\Column(type: 'integer', options: ['unsigned' => true])]
|
||||||
|
@ -26,6 +27,9 @@ final class GamesList
|
||||||
#[ORM\JoinColumn(name: 'id', referencedColumnName: 'id')]
|
#[ORM\JoinColumn(name: 'id', referencedColumnName: 'id')]
|
||||||
#[ORM\ManyToMany(targetEntity: User::class)]
|
#[ORM\ManyToMany(targetEntity: User::class)]
|
||||||
private Collection $claimer;
|
private Collection $claimer;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'integer', options: ['unsigned' => true, 'default' => 0])]
|
||||||
|
private bool $isPublic = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $owner
|
* @param User $owner
|
||||||
|
@ -35,6 +39,7 @@ final class GamesList
|
||||||
{
|
{
|
||||||
$this->owner = $owner;
|
$this->owner = $owner;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
$this->claimer = new ArrayCollection([$owner]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,10 +58,21 @@ final class GamesList
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getClaimer(): array
|
public function getClaimer(): Collection
|
||||||
{
|
{
|
||||||
return $this->claimer;
|
return $this->claimer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPublic(): bool {
|
||||||
|
return $this->isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsPublic(bool $isPublic): void {
|
||||||
|
$this->isPublic = $isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addClaimer(User $claimer): void
|
||||||
|
{
|
||||||
|
$this->claimer[] = $claimer;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -119,7 +119,7 @@ final class GameImporter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($values['key'] === null || $values['name'] === null || $values['store'] === null) {
|
if (empty($values['key']) || empty($values['name']) || empty($values['store'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
src/php/Routing/Api/DataTables/AvailableKeysEndpoint.php
Normal file
74
src/php/Routing/Api/DataTables/AvailableKeysEndpoint.php
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\DataTables;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\ContainerHandler;
|
||||||
|
use GamesShop\Entities\Games\Key;
|
||||||
|
use GamesShop\Entities\Games\KeyState;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use GamesShop\UserManager;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use League\Route\Http\Exception\ForbiddenException;
|
||||||
|
use League\Route\Http\Exception\UnauthorizedException;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class AvailableKeysEndpoint
|
||||||
|
{
|
||||||
|
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::VIEWER)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicableLists = ContainerHandler::get(UserManager::class)
|
||||||
|
->getApplicableGameLists($user);
|
||||||
|
|
||||||
|
$keyRepo = $this->entityManager->getRepository(Key::class);
|
||||||
|
$keys = $keyRepo->matching(Criteria::create()
|
||||||
|
->where(Criteria::expr()->in('list', $applicableLists->toArray()))
|
||||||
|
->andWhere(Criteria::expr()->eq('state', KeyState::AVAILABLE))
|
||||||
|
);
|
||||||
|
|
||||||
|
$games = new ArrayCollection();
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$game = $key->getGame();
|
||||||
|
|
||||||
|
if ($games->contains($game)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$games->add($game);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
[
|
||||||
|
'data' =>
|
||||||
|
$games
|
||||||
|
->map(fn ($game) => [
|
||||||
|
'name' => $game->getName(),
|
||||||
|
'id' => $game->getId(),
|
||||||
|
])
|
||||||
|
->toArray()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,5 +12,7 @@ final class DataTablesAPIRoutes
|
||||||
AccountsEndpoint::applyRoutes($group);
|
AccountsEndpoint::applyRoutes($group);
|
||||||
|
|
||||||
$group->get('/keys/provider', ProviderKeysEndpoint::class);
|
$group->get('/keys/provider', ProviderKeysEndpoint::class);
|
||||||
|
$group->get('/keys/available', AvailableKeysEndpoint::class);
|
||||||
|
$group->get('/list/users', SharedUsersEndpoint::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
63
src/php/Routing/Api/DataTables/SharedUsersEndpoint.php
Normal file
63
src/php/Routing/Api/DataTables/SharedUsersEndpoint.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\DataTables;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
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 SharedUsersEndpoint
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LoginHandler $loginHandler,
|
||||||
|
private readonly EntityManager $entityManager,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws UnauthorizedException
|
||||||
|
* @throws ForbiddenException
|
||||||
|
*/
|
||||||
|
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->getQueryParams();
|
||||||
|
if (!array_key_exists('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy([ 'owner' => $user, 'id' => $body['listid'] ]);
|
||||||
|
$claimer = $list->getClaimer();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
[ 'data' => $claimer
|
||||||
|
->filter(fn ($claimerUser) => $claimerUser !== $user)
|
||||||
|
->map(
|
||||||
|
function (User $user) {
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $user->getId(),
|
||||||
|
'name' => $user->getName(),
|
||||||
|
'icon' => $user->getProfilePictureUrl()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
)->toArray()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
61
src/php/Routing/Api/Web/AddUserToList.php
Normal file
61
src/php/Routing/Api/Web/AddUserToList.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class AddUserToList
|
||||||
|
{
|
||||||
|
|
||||||
|
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('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy([ 'owner' => $user, 'id' => $body['listid'] ]);
|
||||||
|
if (!$list instanceof GamesList) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$requestedUser = $this->entityManager->getRepository(User::class)
|
||||||
|
->find($body['requested']);
|
||||||
|
|
||||||
|
if ($list->getClaimer()->contains($requestedUser)) {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
$list->addClaimer($requestedUser);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
}
|
71
src/php/Routing/Api/Web/ClaimKey.php
Normal file
71
src/php/Routing/Api/Web/ClaimKey.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Games\Key;
|
||||||
|
use GamesShop\Entities\Games\KeyState;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class ClaimKey
|
||||||
|
{
|
||||||
|
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::VIEWER)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
if (!array_key_exists('keyid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = $this->entityManager->getRepository(Key::class)->find($body['keyid']);
|
||||||
|
if (!$key instanceof Key) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$key->getList()->isPublic() && !$key->getList()->getClaimer()->contains($user)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($key->getState() !== KeyState::AVAILABLE) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$key->setState(KeyState::CLAIMED);
|
||||||
|
$key->setClaimedUser($user);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'key' => $key->getKey(),
|
||||||
|
'providedBy' => $key->getList()->getOwner()->getName(),
|
||||||
|
'from' => $key->getFromWhere() ?? 'unknown',
|
||||||
|
'store' => [
|
||||||
|
'name' => $key->getStore()->getName(),
|
||||||
|
'icon' => $key->getStore()->getIcon(),
|
||||||
|
'claimLink' => $key->getStore()->getClaimURL($key),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
75
src/php/Routing/Api/Web/GetGameData.php
Normal file
75
src/php/Routing/Api/Web/GetGameData.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\ContainerHandler;
|
||||||
|
use GamesShop\Entities\Games\Game;
|
||||||
|
use GamesShop\Entities\Games\Key;
|
||||||
|
use GamesShop\Entities\Games\KeyState;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use GamesShop\UserManager;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use League\Route\Http\Exception\ForbiddenException;
|
||||||
|
use League\Route\Http\Exception\NotFoundException;
|
||||||
|
use League\Route\Http\Exception\UnauthorizedException;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class GetGameData
|
||||||
|
{
|
||||||
|
|
||||||
|
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::VIEWER)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$game = $this->entityManager->getRepository(Game::class)
|
||||||
|
->find($request->getQueryParams()['gameid']);
|
||||||
|
if (!$game) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicableLists = ContainerHandler::get(UserManager::class)
|
||||||
|
->getApplicableGameLists($user);
|
||||||
|
|
||||||
|
$keyRepo = $this->entityManager->getRepository(Key::class);
|
||||||
|
$keys = $keyRepo->matching(Criteria::create()
|
||||||
|
->where(Criteria::expr()->in('list', $applicableLists->toArray()))
|
||||||
|
->andWhere(Criteria::expr()->eq('game', $game))
|
||||||
|
->andWhere(Criteria::expr()->eq('state', KeyState::AVAILABLE))
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'name' => $game->getName(),
|
||||||
|
'id' => $game->getId(),
|
||||||
|
'keys' => $keys->map(fn (Key $key) => [
|
||||||
|
'id' => $key->getId(),
|
||||||
|
'store' => [
|
||||||
|
'name' => $key->getStore()->getName(),
|
||||||
|
'icon' => $key->getStore()->getIcon()
|
||||||
|
],
|
||||||
|
'fromWhere' => $key->getFromWhere(),
|
||||||
|
'list' => [
|
||||||
|
'owner' => $key->getList()->getOwner()->getName(),
|
||||||
|
'name' => $key->getList()->getName(),
|
||||||
|
],
|
||||||
|
])->toArray(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
60
src/php/Routing/Api/Web/RemoveUserFromList.php
Normal file
60
src/php/Routing/Api/Web/RemoveUserFromList.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class RemoveUserFromList
|
||||||
|
{
|
||||||
|
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('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy([ 'owner' => $user, 'id' => $body['listid'] ]);
|
||||||
|
if (!$list instanceof GamesList) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$requestedUser = $this->entityManager->getRepository(User::class)
|
||||||
|
->find($body['requestedUser']);
|
||||||
|
|
||||||
|
if ($requestedUser === $user) {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list->getClaimer()->removeElement($requestedUser);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
}
|
59
src/php/Routing/Api/Web/SearchForUsers.php
Normal file
59
src/php/Routing/Api/Web/SearchForUsers.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
use GamesShop\Login\LoginHandler;
|
||||||
|
use GamesShop\Login\UserPermission;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use League\Route\Http\Exception\ForbiddenException;
|
||||||
|
use League\Route\Http\Exception\UnauthorizedException;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
final class SearchForUsers
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LoginHandler $loginHandler,
|
||||||
|
private readonly EntityManager $entityManager,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ForbiddenException
|
||||||
|
* @throws UnauthorizedException
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
$searchQuery = $request->getQueryParams()['query'] ?? '';
|
||||||
|
|
||||||
|
$repo = $this->entityManager->getRepository(User::class);
|
||||||
|
|
||||||
|
$criteria = Criteria::create();
|
||||||
|
$criteria->where(Criteria::expr()->contains('name', $searchQuery));
|
||||||
|
$criteria->setMaxResults(10);
|
||||||
|
|
||||||
|
$values = $repo->matching($criteria);
|
||||||
|
return new JsonResponse(
|
||||||
|
$values
|
||||||
|
->filter(fn ($value) => $value !== $user)
|
||||||
|
->map(function (User $user) {
|
||||||
|
return [
|
||||||
|
'value' => $user->getId(),
|
||||||
|
'label' => $user->getName()
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
53
src/php/Routing/Api/Web/SetListPublic.php
Normal file
53
src/php/Routing/Api/Web/SetListPublic.php
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
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 Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class SetListPublic
|
||||||
|
{
|
||||||
|
|
||||||
|
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('listid', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy(['owner' => $user, 'id' => $body['listid']]);
|
||||||
|
if (!$list instanceof GamesList) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$list->setIsPublic($body['publicState'] === '1');
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,5 +14,13 @@ final class WebAPIRoutes
|
||||||
$group->post('/keys/import/perform', ImportKeysRoute::class);
|
$group->post('/keys/import/perform', ImportKeysRoute::class);
|
||||||
|
|
||||||
$group->post('/keys/list/create', CreateKeyListRoute::class);
|
$group->post('/keys/list/create', CreateKeyListRoute::class);
|
||||||
|
|
||||||
|
$group->get('/share/search', SearchForUsers::class);
|
||||||
|
$group->post('/share/add', AddUserToList::class);
|
||||||
|
$group->post('/share/remove', RemoveUserFromList::class);
|
||||||
|
$group->post('/share/setPublic', SetListPublic::class);
|
||||||
|
|
||||||
|
$group->get('/game', GetGameData::class);
|
||||||
|
$group->post('/key/claim', ClaimKey::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,21 +20,12 @@ use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
final class Router
|
final class Router
|
||||||
{
|
{
|
||||||
public function __construct(
|
|
||||||
private ResourceRoute $resourceRoute
|
|
||||||
)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function route(): ResponseInterface
|
public function route(): ResponseInterface
|
||||||
{
|
{
|
||||||
$request = ServerRequestFactory::fromGlobals(
|
$request = ServerRequestFactory::fromGlobals(
|
||||||
$_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
|
$_SERVER, $_GET, $_POST, $_COOKIE, $_FILES
|
||||||
);
|
);
|
||||||
if ($this->resourceRoute->isValid($request->getUri())) {
|
|
||||||
return $this->resourceRoute->getResponse($request->getUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
$router = new \League\Route\Router;
|
$router = new \League\Route\Router;
|
||||||
$strategy = (new ApplicationStrategy)->setContainer(ContainerHandler::getInstance());
|
$strategy = (new ApplicationStrategy)->setContainer(ContainerHandler::getInstance());
|
||||||
$router->setStrategy($strategy);
|
$router->setStrategy($strategy);
|
||||||
|
|
41
src/php/UserManager.php
Normal file
41
src/php/UserManager.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace GamesShop;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Account\User;
|
||||||
|
use GamesShop\Entities\GamesList;
|
||||||
|
|
||||||
|
class UserManager
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManager $entityManager
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApplicableGameLists(User $user): Collection {
|
||||||
|
|
||||||
|
$listRepo = $this->entityManager->getRepository(GamesList::class);
|
||||||
|
|
||||||
|
$allLists = $listRepo->findAll();
|
||||||
|
$applicableLists = new ArrayCollection();
|
||||||
|
foreach ($allLists as $list) {
|
||||||
|
if ($list->isPublic()) {
|
||||||
|
$applicableLists->add($list);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$list->getClaimer()->contains($user)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicableLists->add($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $applicableLists;
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,7 +41,7 @@ $resource = $resources->getResource($resourceEntry);
|
||||||
|
|
||||||
<?= $this->section('modal') ?>
|
<?= $this->section('modal') ?>
|
||||||
|
|
||||||
<div class="position-absolute bottom-0 start-50 opacity-25 text-center translate-middle-x">
|
<div class="position-fixed bottom-0 start-50 opacity-25 text-center translate-middle-x">
|
||||||
<span class="h1">PROTOTYPE / POC</span>
|
<span class="h1">PROTOTYPE / POC</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,11 @@ $this->layout('layout/main', [ 'resourceEntry' => 'index' ]);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
Hello
|
Games
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
|
<table id="keyTable" class="w-100 table table-striped">
|
||||||
|
<thead>
|
||||||
|
<th>Name</th>
|
||||||
|
</thead>
|
||||||
|
</table>
|
|
@ -87,8 +87,7 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Column</th>
|
<th>Column</th>
|
||||||
<th>Header</th>
|
<th>Header</th> <th>Attribute</th>
|
||||||
<th>Attribute</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
|
@ -96,30 +95,32 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
|
|
||||||
<button class="btn btn-primary js--do-import">
|
<button class="btn btn-primary js--do-import">
|
||||||
Import
|
Import
|
||||||
</button>
|
</button></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="share-tab-pane" role="tabpanel">
|
<div class="tab-pane fade" id="share-tab-pane" role="tabpanel">
|
||||||
<h2>
|
<h2>
|
||||||
Share your list
|
Share your list
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<label for="share-user-search">Search for a user...</label>
|
<div class="form-check form-switch">
|
||||||
<div class="input-group mb-3">
|
<input class="form-check-input" type="checkbox" role="switch" id="public-switch" <?= $list->isPublic() ? "checked" : "" ?>>
|
||||||
<input type="text" class="form-control" id="share-user-search" placeholder="">
|
<label class="form-check-label" for="public-switch">Is public</label>
|
||||||
<button class="btn btn-primary js--search-shared-user">Search</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Users shared to</h3>
|
<div class="share-content">
|
||||||
<table id="shared-users-table" class="table table-striped w-100">
|
<label for="share-user-search">Search for a user...</label>
|
||||||
<thead>
|
<div class="input-group mb-3">
|
||||||
<tr>
|
<input type="text" class="form-control" id="share-user-search" placeholder="">
|
||||||
<th width="2.4rem"></th>
|
<button class="btn btn-primary js--adds-user">+ Add</button>
|
||||||
<th>Name</th>
|
</div>
|
||||||
</tr>
|
|
||||||
</thead>
|
<h3>Users shared to</h3>
|
||||||
<tbody></tbody>
|
<table id="shared-users-table" class="table table-striped w-100">
|
||||||
</table>
|
<thead>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -89,6 +89,9 @@ module.exports = {
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts'],
|
extensions: ['.js', '.ts'],
|
||||||
|
fallback: {
|
||||||
|
'bootstrap5-autocomplete/types/autocomplete': 'bootstrap5-autocomplete'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
entry: {
|
entry: {
|
||||||
index: JS_FOLDER + "/pages/index",
|
index: JS_FOLDER + "/pages/index",
|
||||||
|
|
Loading…
Reference in a new issue