Add ability to create a feed from the UI. Still TODO: make it not look like utter shite.
This commit is contained in:
parent
cee4f9944e
commit
0e06d5587f
@ -24,7 +24,8 @@
|
||||
"ext-simplexml": "*",
|
||||
"ext-curl": "*",
|
||||
"ext-dom": "*",
|
||||
"php": ">=8.3"
|
||||
"php": ">=8.3",
|
||||
"doctrine/migrations": "^3.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.0"
|
||||
|
1422
composer.lock
generated
1422
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,11 @@ declare(strict_types=1);
|
||||
namespace Lewisdale\App\Controllers;
|
||||
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use Lewisdale\App\Models\Data\Feed;
|
||||
use Lewisdale\App\Models\Data\FeedFilter;
|
||||
use Lewisdale\App\Models\Data\FilterTarget;
|
||||
use Lewisdale\App\Models\Data\FilterType;
|
||||
use Lewisdale\App\Models\Repositories\FeedFilterRepository;
|
||||
use Lewisdale\App\Models\Repositories\FeedRepository;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@ -16,7 +21,8 @@ class FeedController
|
||||
public function __construct(
|
||||
private readonly Twig $view,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly FeedRepository $feedRepository
|
||||
private readonly FeedRepository $feedRepository,
|
||||
private readonly FeedFilterRepository $feedFilterRepository,
|
||||
) {}
|
||||
|
||||
public function get(ServerRequestInterface $request, ResponseInterface $response)
|
||||
@ -50,9 +56,47 @@ class FeedController
|
||||
public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$this->logger->info("FeedController::create() called");
|
||||
|
||||
$feed = $this->feedRepository->find($request->getAttribute('id'));
|
||||
|
||||
return $this->view->render($response, 'feed/edit.twig.html', ['FilterTypes' => FilterType::cases(), 'FilterTargets' => FilterTarget::cases(), 'feed' => $feed ]);
|
||||
}
|
||||
|
||||
public function save(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$this->logger->info("FeedController::save() called");
|
||||
|
||||
$body = $request->getParsedBody();
|
||||
|
||||
return $this->view->render($response, 'create.twig.html');
|
||||
$id = $request->getAttribute('id');
|
||||
|
||||
$feed = $id ? $this->feedRepository->find($request->getAttribute('id')) : new Feed();
|
||||
|
||||
$feed->title = $body['title'];
|
||||
$feed->url = $body['url'];
|
||||
|
||||
$this->feedRepository->save($feed);
|
||||
|
||||
foreach ($body['feedFilters'] as $filter) {
|
||||
$this->logger->info("Creating filter", ['filter' => $filter, 'post' => $_POST]);
|
||||
|
||||
$f = $filter['id'] ? $this->feedFilterRepository->find($filter['id']) : new FeedFilter(
|
||||
FilterTarget::from($filter['target']),
|
||||
FilterType::from($filter['type']),
|
||||
$filter['value'],
|
||||
$feed
|
||||
);
|
||||
|
||||
if ($f !== null && $filter['id']) {
|
||||
$f->filter = FilterType::from($filter['type']);
|
||||
$f->target = FilterTarget::from($filter['target']);
|
||||
$f->value = $filter['value'];
|
||||
}
|
||||
|
||||
$this->feedFilterRepository->save($f);
|
||||
}
|
||||
|
||||
return $response->withStatus(302)->withHeader('Location', "/feed");
|
||||
}
|
||||
|
||||
public function delete(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
@ -64,6 +108,6 @@ class FeedController
|
||||
}
|
||||
|
||||
$this->feedRepository->delete($feed);
|
||||
return $response->withStatus(201)->withHeader('Location', '/feed');
|
||||
return $response->withStatus(302)->withHeader('Location', '/feed');
|
||||
}
|
||||
}
|
@ -48,6 +48,10 @@ class Feed
|
||||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: true)]
|
||||
public \DateTimeInterface|null $lastFetched;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist', 'remove'], inversedBy: 'feeds')]
|
||||
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id')]
|
||||
public User|null $user;
|
||||
|
||||
public function fetch(): void
|
||||
{
|
||||
if (Robots::allowed($this->url)) {
|
||||
|
@ -3,6 +3,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Lewisdale\App\Models\Data;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Lewisdale\App\Models\Traits\AutoUpdate;
|
||||
use Lewisdale\App\Models\View;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@ -26,6 +27,12 @@ class User
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $salt;
|
||||
|
||||
/**
|
||||
* @var Collection<Feed>
|
||||
*/
|
||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: Feed::class, cascade: ['persist', 'remove'])]
|
||||
public Collection $feeds;
|
||||
|
||||
function __construct(
|
||||
string $password,
|
||||
string $email,
|
||||
|
@ -15,4 +15,10 @@ class FeedFilterRepository extends EntityRepository
|
||||
{
|
||||
parent::__construct($em, $em->getClassMetadata(FeedFilter::class));
|
||||
}
|
||||
|
||||
public function save(FeedFilter $filter): void
|
||||
{
|
||||
$this->getEntityManager()->persist($filter);
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ class LoginMiddleware implements MiddlewareInterface {
|
||||
private readonly UserRepository $users,
|
||||
) {}
|
||||
|
||||
private function redirectToLogin() {
|
||||
private function redirectToLogin(): ResponseInterface {
|
||||
$response = new Response(302);
|
||||
return $response->withHeader('Location', '/account/login');
|
||||
}
|
||||
|
17
src/app.php
17
src/app.php
@ -3,6 +3,8 @@
|
||||
use Lewisdale\App\Controllers\FeedController;
|
||||
use Lewisdale\App\Controllers\HomeController;
|
||||
use Lewisdale\App\Controllers\LoginController;
|
||||
use Lewisdale\App\Session\LoginMiddleware;
|
||||
use Slim\Routing\RouteCollectorProxy;
|
||||
use Slim\Views\TwigMiddleware;
|
||||
|
||||
ini_set('user_agent', 'Baleen/1.0 (https://baleen.lewisdale.dev)');
|
||||
@ -24,13 +26,20 @@ $app->add('csrf');
|
||||
|
||||
$app->get("/", [HomeController::class, 'get']);
|
||||
|
||||
$app->group('/feed', function (\Slim\Routing\RouteCollectorProxy $group) use ($app) {
|
||||
$group->get('[/]', [FeedController::class, 'get'])->add(\Lewisdale\App\Session\LoginMiddleware::class);
|
||||
$app->group('/feed', function (RouteCollectorProxy $group) use ($app) {
|
||||
$group->get('[/]', [FeedController::class, 'get'])->add(LoginMiddleware::class);
|
||||
$group->post('[/]', [FeedController::class, 'save'])->add(LoginMiddleware::class);
|
||||
|
||||
$group->get('/new', [FeedController::class, 'create'])->add(LoginMiddleware::class);
|
||||
|
||||
$group->get('/{id}', [FeedController::class, 'get_feed']);
|
||||
$group->get('/{id}/delete', [FeedController::class, 'delete'])->add(\Lewisdale\App\Session\LoginMiddleware::class);
|
||||
$group->post('/{id}', [FeedController::class, 'save'])->add(LoginMiddleware::class);
|
||||
|
||||
$group->get('/{id}/delete', [FeedController::class, 'delete'])->add(LoginMiddleware::class);
|
||||
$group->get('/{id}/edit', [FeedController::class, 'create'])->add(LoginMiddleware::class);
|
||||
});
|
||||
|
||||
$app->group('/account', function (\Slim\Routing\RouteCollectorProxy $group) use ($app, $container) {
|
||||
$app->group('/account', function (RouteCollectorProxy $group) use ($app, $container) {
|
||||
$group->get('/login', [LoginController::class, 'index']);
|
||||
$group->post('/login', [LoginController::class, 'login']);
|
||||
});
|
||||
|
23
views/_includes/filterBlock.twig.html
Normal file
23
views/_includes/filterBlock.twig.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% macro filterBlock(ctx, idx, type, target, value, id) %}
|
||||
<fieldset>
|
||||
<legend>Filter</legend>
|
||||
<input type="hidden" name="feedFilters[{{ idx }}][id]" value="{{ id }}"/>
|
||||
|
||||
<label for="target">Filter target:</label>
|
||||
<select name="feedFilters[{{ idx }}][target]" id="target">
|
||||
{% for filterTarget in ctx.FilterTargets %}
|
||||
<option value="{{ filterTarget.value }}" {% if filterTarget.value is same as(target) %}selected{% endif %}>{{ filterTarget.name | capitalize }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="type">Filter type: </label>
|
||||
<select name="feedFilters[{{ idx }}][type]" id="type">
|
||||
{% for filterType in ctx.FilterTypes %}
|
||||
<option value="{{ filterType.value }}" {% if filterType is same as(type) %}selected{% endif %}>{{ filterType.name | capitalize }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<label for="value">Filter value:</label>
|
||||
<input type="text" name="feedFilters[{{ idx }}][value]" id="value" value="{{ value }}" />
|
||||
</fieldset>
|
||||
{% endmacro %}
|
37
views/feed/edit.twig.html
Normal file
37
views/feed/edit.twig.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% from "_includes/filterBlock.twig.html" import filterBlock %}
|
||||
|
||||
<h1>Create feed</h1>
|
||||
|
||||
<form method="POST" action="/feed/{{ feed.id }}">
|
||||
{{ csrf() | raw }}
|
||||
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
|
||||
<label for="url">
|
||||
Feed URL
|
||||
</label>
|
||||
<input type="text" inputmode="url" name="url" value="{{ feed.url }}" id="url"/>
|
||||
|
||||
<label for="title">
|
||||
Title
|
||||
</label>
|
||||
<input type="text" inputmode="title" name="title" value="{{ feed.title }}" id="title"/>
|
||||
|
||||
<fieldset>
|
||||
<legend>Filters</legend>
|
||||
|
||||
{% for filter in feed.feedFilters %}
|
||||
{{ filterBlock(_context, loop.index0, filter.filter, filter.target, filter.value, filter.id) }}
|
||||
{% endfor %}
|
||||
|
||||
{{ filterBlock(_context, (feed.feedFilters | length)) }}
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">Save feed</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
</script>
|
@ -9,7 +9,11 @@
|
||||
{% for feed in feeds %}
|
||||
<tr>
|
||||
<td>{{ feed.title }}</td>
|
||||
<td><a href="/feed/{{ feed.id }}">Link to feed</a><a href="/feed/{{ feed.id }}/delete">Delete</a></td>
|
||||
<td>
|
||||
<a href="/feed/{{ feed.id }}">Link to feed</a>
|
||||
<a href="/feed/{{ feed.id }}/edit">Edit feed</a>
|
||||
<a href="/feed/{{ feed.id }}/delete">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
Loading…
Reference in New Issue
Block a user