Skip to content

Commit

Permalink
#21 -- Make spotlighted worlds manageable via admin UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
BusterNeece committed Jul 22, 2024
1 parent e03e641 commit c58f330
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 46 deletions.
22 changes: 17 additions & 5 deletions backend/config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,23 @@
->setName('dashboard');

$group->group('/admin', function (RouteCollectorProxy $group) {
$group->map(
['GET', 'POST'],
'/add_world',
App\Controller\Dashboard\Admin\AddWorldAction::class
)->setName('dashboard:admin:add_world');
$group->group('/worlds', function (RouteCollectorProxy $group) {
$group->get(
'',
App\Controller\Dashboard\Admin\WorldsController::class . ':listAction'
)->setName('dashboard:admin:worlds');

$group->map(
['GET', 'POST'],
'/create',
App\Controller\Dashboard\Admin\WorldsController::class . ':createAction'
)->setName('dashboard:admin:worlds:create');

$group->get(
'/delete[/{id}]',
App\Controller\Dashboard\Admin\WorldsController::class . ':deleteAction'
)->setName('dashboard:admin:worlds:delete');
});

$group->get(
'/users',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@

namespace App\Controller\Dashboard\Admin;

use App\Exception\NotFoundException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Media;
use App\Service\VrcApi;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use GuzzleHttp\Client;
use Intervention\Image\ImageManager;
use League\Flysystem\UnableToDeleteFile;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface;

final readonly class AddWorldAction
final readonly class WorldsController
{
private Client $vrcApiClient;

Expand All @@ -24,7 +28,29 @@ public function __construct(
$this->vrcApiClient = $vrcApi->getHttpClient();
}

public function __invoke(
public function listAction(
ServerRequest $request,
Response $response,
array $params
): ResponseInterface {
$worlds = $this->db->fetchAllAssociative(
<<<'SQL'
SELECT w.*
FROM web_worlds AS w
ORDER BY id DESC
SQL
);

return $request->getView()->renderToResponse(
$response,
'dashboard/admin/worlds/list',
[
'worlds' => $worlds,
]
);
}

public function createAction(
ServerRequest $request,
Response $response,
array $params
Expand Down Expand Up @@ -72,12 +98,9 @@ public function __invoke(
]
);

$worldDbId = $this->db->lastInsertId();

$request->getFlash()->success('World successfully imported!');

return $response->withRedirect(
$request->getRouter()->urlFor('world', ['id' => $worldDbId])
$request->getRouter()->urlFor('dashboard:admin:worlds')
);
} catch (\Throwable $e) {
$error = $e->getMessage();
Expand All @@ -86,10 +109,48 @@ public function __invoke(

return $request->getView()->renderToResponse(
$response,
'dashboard/admin/add_world',
'dashboard/admin/worlds/create',
[
'error' => $error,
]
);
}

public function deleteAction(
ServerRequest $request,
Response $response,
array $params
): ResponseInterface {
$id = $params['id'];

$world = $this->db->fetchAssociative(
<<<'SQL'
SELECT id, image
FROM web_worlds
WHERE id = :id
SQL,
[
'id' => $id,
]
);

if ($world === false) {
throw NotFoundException::world($request);
}

$fs = Media::getFilesystem();
$fs->delete($world['image']);

$this->db->delete(
'web_worlds',
[
'id' => $world['id'],
]
);

$request->getFlash()->success('World removed.');
return $response->withRedirect(
$request->getRouter()->urlFor('dashboard:admin:worlds')
);
}
}
29 changes: 0 additions & 29 deletions backend/templates/dashboard/admin/add_world.twig

This file was deleted.

2 changes: 1 addition & 1 deletion backend/templates/dashboard/admin/users.twig
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<th scope="col" style="width:10%">Team?</th>
<th scope="col" style="width:10%">DJ?</th>
<th scope="col" style="width:10%">Banned?</th>
<th scope="col" style="width:15%">Action</th>
<th scope="col" style="width:15%">Actions</th>
</tr>
</thead>
<tbody>
Expand Down
37 changes: 37 additions & 0 deletions backend/templates/dashboard/admin/worlds/create.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends "layouts/dashboard.twig" %}

{% import "macros/breadcrumbs.twig" as breadcrumbs %}

{% block content %}
{{ breadcrumbs.body(
{
'dashboard': 'My Dashboard',
'dashboard:admin:worlds': 'Spotlighted Worlds'
},
'Add New Spotlighted World'
) }}

<h1>Spotlighted Worlds</h1>

<div class="card">
<h2 class="card-header">
Add New Spotlighted World
</h2>
<div class="card-body">
{% if error %}
<div class="alert alert-danger">
<b>Error:</b> {{ error }}
</div>
{% endif %}

<form action="" method="post">
<div class="form-label-group mb-2">
<label for="id">VRChat World ID:</label>
<input class="form-control form-control-secondary" type="text" id="id" name="id" required>
</div>

<button type="submit" class="btn btn-primary">Submit to Database</button>
</form>
</div>
</div>
{% endblock %}
97 changes: 97 additions & 0 deletions backend/templates/dashboard/admin/worlds/list.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{% extends "layouts/dashboard.twig" %}

{% import "macros/breadcrumbs.twig" as breadcrumbs %}

{% block head %}
{{ parent() }}

<script>
function doFilter(filter, selector) {
if (filter === '') {
$(selector).removeClass('d-none');
} else {
$(selector).each(function() {
const searchData = $.map(
$(this).find('.search'),
function(element) {
return $(element).text()
}
).join(' ');
if (!searchData.toUpperCase().includes(filter)) {
$(this).addClass('d-none');
} else {
$(this).removeClass('d-none');
}
});
}
}
function filterTable(event) {
doFilter(
event.target.value.toUpperCase(),
'#data_table tbody tr'
);
}
ready(() => {
document.querySelector('#search_input').addEventListener('load', filterTable, false);
document.querySelector('#search_input').addEventListener('keyup', filterTable, false);
});
</script>
{% endblock %}

{% block content %}
{{ breadcrumbs.body(
{
'dashboard': 'My Dashboard'
},
'Spotlighted Worlds'
) }}

<h1>Spotlighted Worlds</h1>

<div class="card">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="flex-fill">
<input id="search_input" class="form-control" value="" type="text" Placeholder="Search..." />
</div>
<div class="ms-3 flex-shrink-0">
<a href="{{ urlFor('dashboard:admin:worlds:create') }}" class="btn btn btn-success">
Add New Spotlighted World
</a>
</div>
</div>

<table class="table table-sm table-striped table-bordered align-middle mb-0" id="data_table">
<thead>
<tr>
<th scope="col" style="width:15%">Image</th>
<th scope="col" style="width:70%">World Name</th>
<th scope="col" style="width:15%">Actions</th>
</tr>
</thead>
<tbody>
{% for row in worlds %}
<tr>
<td>
<img src="{{ mediaUrl(row.image) }}" style="max-width: 128px;">
</td>
<th class="search" scope="row">{{ row.title }}</th>
<td>
<a href="{{ urlFor('world', {id: row.id}) }}" class="btn btn-secondary btn-sm">
View
</a>
<a href="{{ urlFor('dashboard:admin:worlds:delete', {id: row.id}) }}"
class="btn btn-danger btn-sm" data-confirm-danger="Remove world?">
<i class="bi-trash" aria-hidden="true"></i> Remove
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
4 changes: 2 additions & 2 deletions backend/templates/dashboard/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@
<i class="bi-people"></i> User Management
</a>
<a class="list-group-item list-group-item-action"
href="{{ urlFor('dashboard:admin:add_world') }}">
<i class="bi-globe2"></i> Add VRChat World
href="{{ urlFor('dashboard:admin:worlds') }}">
<i class="bi-globe2"></i> Spotlighted Worlds
</a>
</div>
</div>
Expand Down
3 changes: 1 addition & 2 deletions backend/templates/layouts/sections/dashboard_sidebar.twig
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@
Management</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ urlFor('dashboard:admin:add_world') }}">Add
VRChat World</a>
<a class="nav-link" href="{{ urlFor('dashboard:admin:worlds') }}">Spotlighted Worlds</a>
</li>
</ul>
</div>
Expand Down

0 comments on commit c58f330

Please sign in to comment.