Compare commits
21 commits
Author | SHA1 | Date | |
---|---|---|---|
0c48e7df70 | |||
52d68dcf51 | |||
8388c2ccc6 | |||
bfeb2a6e2a | |||
6c01948902 | |||
da67fbc7b0 | |||
bd47dda496 | |||
0b3eae20a6 | |||
ff05142acc | |||
ab4ebe93a3 | |||
5e77ba32c1 | |||
07315422d3 | |||
296dd9730d | |||
4648507cdd | |||
e5b60e410d | |||
08142c5796 | |||
6c9c51d66d | |||
8a24f5b1cc | |||
9e12deca4a | |||
22d87b773d | |||
aec0aa19d1 |
9 changed files with 288 additions and 18 deletions
67
README.md
Normal file
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# Games Shop
|
||||||
|
This is the repository for the free games shop hosted on [shop.iedsoftworks.com](https://shop.iedsoftworks.com).
|
||||||
|
|
||||||
|
## Developers and local testers
|
||||||
|
To start a local variant of the site, you **need** Docker (with docker compose).
|
||||||
|
|
||||||
|
### 1 Cloning the repo
|
||||||
|
Start with cloning the repository
|
||||||
|
|
||||||
|
`git clone https://git.php.fail/neintonine/gamesshop.git`
|
||||||
|
|
||||||
|
and entering the folder for it
|
||||||
|
|
||||||
|
`cd gamesshop`
|
||||||
|
|
||||||
|
### 2 Installing dependencies
|
||||||
|
Then you need to install/build the NPM and composer portion.
|
||||||
|
There are two ways of doing it.
|
||||||
|
|
||||||
|
#### 2.1 Local NPM & Composer
|
||||||
|
If you have NodeJS and PHP8.3 with composer installed, you can simply run:
|
||||||
|
|
||||||
|
`npm install && npm run build && composer install`
|
||||||
|
|
||||||
|
#### 2.2 Docker
|
||||||
|
If you don't have those installed, and are unwilling to install them, you can use docker to install and build the required packages.
|
||||||
|
|
||||||
|
JS: `docker run --rm --volume "$PWD":/app -w /app node npm install && npm run build`
|
||||||
|
|
||||||
|
PHP: `docker run --rm --volume "$PWD":/app -w /app composer composer install --ignore-platform-req=ext-gd`
|
||||||
|
|
||||||
|
### 3 Creating the environment file
|
||||||
|
To run the site, you need a valid environment file.
|
||||||
|
#### 3.1 Copy the example
|
||||||
|
Under the folder 'config', you can find a `.env.example`-file, which you can use to create your enviroment file.
|
||||||
|
Start of by coping the file, and rename it to `.env`
|
||||||
|
|
||||||
|
`cp config/.env.example config/.env`
|
||||||
|
|
||||||
|
The example already includes settings for the database and logs.
|
||||||
|
|
||||||
|
#### 3.2 Adding your Discord-App
|
||||||
|
One thing, that the example doesn't contain is the discord app. Thats something you need to add.
|
||||||
|
Simply create an app in the [Discord Developer Portal](https://discord.com/developers/applications) and add the required values in the environment file
|
||||||
|
|
||||||
|
### 4 Creating the Database
|
||||||
|
Now after ensuring you have the dependencies and a valid environment file, we can create the database.
|
||||||
|
|
||||||
|
Again, if you have PHP installed you can just do: `php ./src/php/bin/doctrine.php orm:schema-tool:create`
|
||||||
|
|
||||||
|
If you don't want to do this, just let docker to do it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -v .:/app --rm -it php:8.3-cli php /app/src/php/bin/doctrine.php orm:schema-tool:create
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5 Running the containers
|
||||||
|
Now finally, after creating the database, just go into the deploy folder (`cd deploy`) and start the compose file.
|
||||||
|
|
||||||
|
You may see, that there are two file (`docker-compose.yaml` and `docker-compose-dev.yml`). As you may expect the second is for local development so probably the one you should use.
|
||||||
|
The first does effectively the same thing, but for the production environment.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f ./docker-compose-dev.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
Congratulations! The website should now boot up and after docker is done downloading, you should be able to use http://localhost:8080 to open the page.
|
|
@ -7,6 +7,8 @@ import {init as initImport} from "./import";
|
||||||
import {init as initTable} from "./table";
|
import {init as initTable} from "./table";
|
||||||
import {init as initUserLists} from "./userlists";
|
import {init as initUserLists} from "./userlists";
|
||||||
import {initShare} from "./share";
|
import {initShare} from "./share";
|
||||||
|
import {init as initEditModal} from "./modals/edit";
|
||||||
|
import {init as initDeleteModal} from "./modals/delete";
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const triggerTabList = document.querySelectorAll('#key-tab button')
|
const triggerTabList = document.querySelectorAll('#key-tab button')
|
||||||
|
@ -23,4 +25,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
initTable();
|
initTable();
|
||||||
initUserLists();
|
initUserLists();
|
||||||
initShare();
|
initShare();
|
||||||
|
|
||||||
|
initEditModal();
|
||||||
|
initDeleteModal();
|
||||||
})
|
})
|
42
src/js/pages/keys/modals/delete.ts
Normal file
42
src/js/pages/keys/modals/delete.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import {getCurrentlySelectedList} from "../userlists";
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
const modalElem = document.querySelector<HTMLDivElement>('#delete-list-modal');
|
||||||
|
|
||||||
|
if (!modalElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalElem.addEventListener('show.bs.modal', () => {
|
||||||
|
const listSelect = document.querySelector<HTMLSelectElement>('#list-select');
|
||||||
|
const text = listSelect?.selectedOptions[0].text ?? '';
|
||||||
|
|
||||||
|
const listnameElem = modalElem.querySelector<HTMLSpanElement>('.list-name');
|
||||||
|
if (!listnameElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listnameElem.textContent = text;
|
||||||
|
})
|
||||||
|
|
||||||
|
modalElem.querySelector<HTMLButtonElement>('.js--yes')
|
||||||
|
?.addEventListener('click', async () => {
|
||||||
|
const id = getCurrentlySelectedList();
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id?.toString() ?? '-1');
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/web/keys/list/delete`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(response.statusText);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
}
|
18
src/js/pages/keys/modals/edit.ts
Normal file
18
src/js/pages/keys/modals/edit.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export function init() {
|
||||||
|
const modalElem = document.querySelector<HTMLDivElement>('#edit-list-modal');
|
||||||
|
|
||||||
|
if (!modalElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalElem.addEventListener('show.bs.modal', () => {
|
||||||
|
const listSelect = document.querySelector<HTMLSelectElement>('#list-select');
|
||||||
|
const text = listSelect?.selectedOptions[0].text ?? '';
|
||||||
|
|
||||||
|
const listnameElem = modalElem.querySelector<HTMLSpanElement>('.list-name');
|
||||||
|
if (!listnameElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listnameElem.textContent = text;
|
||||||
|
})
|
||||||
|
}
|
70
src/php/Routing/Api/Web/DeleteKeyList.php
Normal file
70
src/php/Routing/Api/Web/DeleteKeyList.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace GamesShop\Routing\Api\Web;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use GamesShop\Entities\Games\Key;
|
||||||
|
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 DeleteKeyList
|
||||||
|
{
|
||||||
|
|
||||||
|
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('id', $body)) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $body['id'];
|
||||||
|
$list = $this->entityManager->getRepository(GamesList::class)->findOneBy(
|
||||||
|
[
|
||||||
|
'id' => $id,
|
||||||
|
'owner' => $user,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($list === null) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->remove($list);
|
||||||
|
|
||||||
|
$keys = $this->entityManager->getRepository(Key::class)->findBy(
|
||||||
|
[
|
||||||
|
'list' => $list
|
||||||
|
]
|
||||||
|
);
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$this->entityManager->remove($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ 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->post('/keys/list/delete',DeleteKeyList::class);
|
||||||
|
|
||||||
$group->get('/share/search', SearchForUsers::class);
|
$group->get('/share/search', SearchForUsers::class);
|
||||||
$group->post('/share/add', AddUserToList::class);
|
$group->post('/share/add', AddUserToList::class);
|
||||||
|
|
|
@ -45,5 +45,16 @@ $resource = $resources->getResource($resourceEntry);
|
||||||
<span class="h1">PROTOTYPE / POC</span>
|
<span class="h1">PROTOTYPE / POC</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="position-fixed bottom-0 start-0 m-3 fs-6">
|
||||||
|
<a href="https://git.php.fail/neintonine/gamesshop/issues/new" class="nav-link text-muted mb-3">
|
||||||
|
<i class="fa-solid fa-bug fa-xl me-3"></i>
|
||||||
|
You found a bug? Please report it.
|
||||||
|
</a>
|
||||||
|
<a href="https://git.php.fail/neintonine/gamesshop" class="nav-link text-muted">
|
||||||
|
<i class="fa-brands fa-git-alt me-3 fa-xl"></i>
|
||||||
|
Interested to help? Checkout the git repository.
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -29,25 +29,24 @@ $currentPermission = $activeUser === null ? UserPermission::NONE : $activeUser->
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|
||||||
<li class="nav-link">
|
<li class="nav-link">
|
||||||
<a href="<?= $header->link ?>" class="nav-link"><?= $header->title ?></a>
|
<a href="<?= $header->link ?>" class="nav-link"><?= $header->title ?></a>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="d-flex mode-switch me-4">
|
||||||
|
<button title="Use dark mode" id="dark" class="btn btn-sm btn-default text-secondary">
|
||||||
|
<i class="fa-regular fa-moon"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Use light mode" id="light" class="btn btn-sm btn-default text-secondary">
|
||||||
|
<i class="fa-regular fa-sun"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Use system preferred mode" id="system" class="btn btn-sm btn-default text-secondary">
|
||||||
|
<i class="fa-solid fa-display"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<?= $this->insert('layout/accountDisplay'); ?>
|
<?= $this->insert('layout/accountDisplay'); ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex mode-switch me-3">
|
|
||||||
<button title="Use dark mode" id="dark" class="btn btn-sm btn-default text-secondary">
|
|
||||||
<i class="fa-regular fa-moon"></i>
|
|
||||||
</button>
|
|
||||||
<button title="Use light mode" id="light" class="btn btn-sm btn-default text-secondary">
|
|
||||||
<i class="fa-regular fa-sun"></i>
|
|
||||||
</button>
|
|
||||||
<button title="Use system preferred mode" id="system" class="btn btn-sm btn-default text-secondary">
|
|
||||||
<i class="fa-solid fa-display"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
|
@ -19,12 +19,17 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 align-self-center">
|
<div class="col-sm-6 align-self-center">
|
||||||
<?php if (!empty($usersLists)): ?>
|
<?php if (!empty($usersLists)): ?>
|
||||||
<select name="lists" id="list-select" class="form-select w-100">
|
<div class="input-group w-100">
|
||||||
<?php foreach ($usersLists as $list): ?>
|
<select name="lists" id="list-select" class="form-select">
|
||||||
<option value="<?= $list->getId() ?>"><?= $list->getName() ?></option>
|
<?php foreach ($usersLists as $list): ?>
|
||||||
<?php endforeach; ?>
|
<option value="<?= $list->getId() ?>"><?= $list->getName() ?></option>
|
||||||
<option value="_create">+ Create New</option>
|
<?php endforeach; ?>
|
||||||
</select>
|
<option value="_create">+ Create New</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" data-bs-target="#edit-list-modal" data-bs-toggle="modal">
|
||||||
|
<i class="fa-solid fa-gear"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,4 +156,56 @@ $this->layout('layout/main', [ 'resourceEntry' => 'keys' ]);
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="edit-list-modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title h3">
|
||||||
|
Edit <span class="list-name"></span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h2 class="h4 mt-2">
|
||||||
|
Actions
|
||||||
|
</h2>
|
||||||
|
<button data-bs-toggle="modal" data-bs-target="#delete-list-modal" class="w-100 btn btn-danger">Delete List</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button data-bs-dismiss="modal" class="flex-grow-1 btn btn-primary">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal" id="delete-list-modal">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title h3">
|
||||||
|
Delete <span class="list-name"></span>?
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
Are you <b>sure</b> you want to <b class="text-danger">delete</b> this list?
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Deleting this will remove all the keys you added from the database.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button data-bs-dismiss="modal" class="flex-grow-1 btn btn-outline-primary">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button data-bs-dismiss="modal" class="js--yes flex-grow-1 btn btn-danger">
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<?php $this->end() ?>
|
<?php $this->end() ?>
|
||||||
|
|
Loading…
Reference in a new issue