Browse Source

Downsizing: remove credits

imwald
Nuša Pukšič 9 months ago
parent
commit
313a6fa052
  1. 1
      config/packages/cache.yaml
  2. 12
      docs/features.md
  3. 24
      src/Controller/Administration/CreditTransactionController.php
  4. 100
      src/Credits/Entity/CreditTransaction.php
  5. 68
      src/Credits/Service/CreditsManager.php
  6. 90
      src/Credits/Util/RedisCreditStore.php
  7. 40
      src/Twig/Components/GetCreditsComponent.php
  8. 47
      src/Twig/Components/SearchComponent.php
  9. 12
      templates/components/SearchComponent.html.twig
  10. 3
      translations/messages.en.yaml

1
config/packages/cache.yaml

@ -15,4 +15,3 @@ framework: @@ -15,4 +15,3 @@ framework:
pools:
#my.dedicated.cache: null
subscriptions.cache: null
credits.cache: null

12
docs/features.md

@ -10,6 +10,18 @@ @@ -10,6 +10,18 @@
- Article indexing commands
- Controllers using Elasticsearch queries
## Credit System (REMOVED)
- **Status**: Completely removed as part of scaling down
- **Previous implementation**: Credit-based search system with Redis storage
- **Components removed**:
- Entire `src/Credits/` directory (CreditsManager, RedisCreditStore, CreditTransaction entity)
- GetCreditsComponent (Twig component for adding credits)
- CreditTransactionController (admin interface)
- Credit accounting in SearchComponent
- Credit balance display in search interface
- Credits cache configuration
- Credit translation keys
## Core Features to Preserve
- Article management (CRUD operations)
- Article display and listing

24
src/Controller/Administration/CreditTransactionController.php

@ -1,24 +0,0 @@ @@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Administration;
use App\Credits\Entity\CreditTransaction;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class CreditTransactionController extends AbstractController
{
#[Route('/admin/transactions', name: 'admin_credit_transactions')]
public function index(EntityManagerInterface $em): Response
{
$transactions = $em->getRepository(CreditTransaction::class)->findBy([], ['createdAt' => 'DESC']);
return $this->render('admin/transactions.html.twig', [
'transactions' => $transactions,
]);
}
}

100
src/Credits/Entity/CreditTransaction.php

@ -1,100 +0,0 @@ @@ -1,100 +0,0 @@
<?php
namespace App\Credits\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class CreditTransaction
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(length: 64)]
private string $npub;
#[ORM\Column(type: 'integer')]
private int $amount;
#[ORM\Column(length: 16)]
private string $type; // 'credit' or 'debit'
#[ORM\Column(type: 'datetime')]
private \DateTime $createdAt;
#[ORM\Column(nullable: true)]
private ?string $reason = null;
public function __construct(string $npub, int $amount, string $type, ?string $reason = null)
{
$this->npub = $npub;
$this->amount = $amount;
$this->type = $type;
$this->createdAt = new \DateTime();
$this->reason = $reason;
}
public function getId(): int
{
return $this->id;
}
public function setId(int $id): void
{
$this->id = $id;
}
public function getNpub(): string
{
return $this->npub;
}
public function setNpub(string $npub): void
{
$this->npub = $npub;
}
public function getAmount(): int
{
return $this->amount;
}
public function setAmount(int $amount): void
{
$this->amount = $amount;
}
public function getType(): string
{
return $this->type;
}
public function setType(string $type): void
{
$this->type = $type;
}
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
public function setCreatedAt(\DateTime $createdAt): void
{
$this->createdAt = $createdAt;
}
public function getReason(): ?string
{
return $this->reason;
}
public function setReason(?string $reason): void
{
$this->reason = $reason;
}
}

68
src/Credits/Service/CreditsManager.php

@ -1,68 +0,0 @@ @@ -1,68 +0,0 @@
<?php
namespace App\Credits\Service;
use App\Credits\Entity\CreditTransaction;
use App\Credits\Util\RedisCreditStore;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Cache\InvalidArgumentException;
readonly class CreditsManager
{
public function __construct(
private RedisCreditStore $redisStore,
private EntityManagerInterface $em
) {}
/**
* @throws InvalidArgumentException
*/
public function getBalance(string $npub): int
{
return $this->redisStore->getBalance($npub);
}
/**
* @throws InvalidArgumentException
*/
public function resetBalance(string $npub): int
{
return $this->redisStore->resetBalance($npub);
}
/**
* @throws InvalidArgumentException
*/
public function addCredits(string $npub, int $amount, ?string $reason = null): void
{
$this->redisStore->addCredits($npub, $amount);
$tx = new CreditTransaction($npub, $amount, 'credit', $reason);
$this->em->persist($tx);
$this->em->flush();
}
/**
* @throws InvalidArgumentException
*/
public function canAfford(string $npub, int $cost): bool
{
return $this->getBalance($npub) >= $cost;
}
/**
* @throws InvalidArgumentException
*/
public function spendCredits(string $npub, int $cost, ?string $reason = null): void
{
if (!$this->canAfford($npub, $cost)) {
throw new \RuntimeException("Insufficient credits for $npub");
}
$this->redisStore->spendCredits($npub, $cost);
$tx = new CreditTransaction($npub, $cost, 'debit', $reason);
$this->em->persist($tx);
$this->em->flush();
}
}

90
src/Credits/Util/RedisCreditStore.php

@ -1,90 +0,0 @@ @@ -1,90 +0,0 @@
<?php
namespace App\Credits\Util;
use App\Credits\Entity\CreditTransaction;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Cache\InvalidArgumentException;
use Symfony\Contracts\Cache\CacheInterface;
readonly class RedisCreditStore
{
public function __construct(
private CacheInterface $creditsCache,
private EntityManagerInterface $em
) {}
private function key(string $npub): string
{
return 'credits_' . $npub;
}
/**
* @throws InvalidArgumentException
*/
public function resetBalance(string $npub): int
{
$this->creditsCache->delete($this->key($npub));
// Fetch all transactions for the given npub
$transactions = $this->em->getRepository(CreditTransaction::class)
->findBy(['npub' => $npub]);
// Initialize the balance
$balance = 0;
// Calculate the final balance based on the transactions
foreach ($transactions as $tx) {
if ($tx->getType() === 'credit') {
$balance += $tx->getAmount();
} elseif ($tx->getType() === 'debit') {
$balance -= $tx->getAmount();
}
}
// Write the calculated balance into the Redis cache
$item = $this->creditsCache->getItem($this->key($npub));
$item->set($balance);
$this->creditsCache->save($item);
return $balance;
}
/**
* @throws InvalidArgumentException
*/
public function getBalance(string $npub): int
{
// Use cache pool to fetch the credit balance
return $this->creditsCache->get($this->key($npub), function () use ($npub) {
return $this->resetBalance($npub);
});
}
/**
* @throws InvalidArgumentException
*/
public function addCredits(string $npub, int $amount): void
{
$currentBalance = $this->getBalance($npub);
$item = $this->creditsCache->getItem($this->key($npub));
$balance = $currentBalance + $amount;
$item->set($balance);
$this->creditsCache->save($item);
}
/**
* @throws InvalidArgumentException
*/
public function spendCredits(string $npub, int $amount): void
{
$currentBalance = $this->getBalance($npub);
$item = $this->creditsCache->getItem($this->key($npub));
if ($currentBalance < $amount) {
throw new \RuntimeException('Insufficient credits');
}
$balance = $currentBalance - $amount;
$item->set($balance);
$this->creditsCache->save($item);
}
}

40
src/Twig/Components/GetCreditsComponent.php

@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
<?php
namespace App\Twig\Components;
use App\Credits\Service\CreditsManager;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
final class GetCreditsComponent
{
use DefaultActionTrait;
use ComponentToolsTrait;
public function __construct(
private readonly CreditsManager $creditsManager,
private readonly TokenStorageInterface $tokenStorage)
{
}
/**
* @throws InvalidArgumentException
*/
#[LiveAction]
public function grantVoucher(): void
{
$npub = $this->tokenStorage->getToken()?->getUserIdentifier();
if ($npub) {
$this->creditsManager->addCredits($npub, 5, 'voucher');
}
// Dispatch event to notify parent
$this->emit('creditsAdded');
}
}

47
src/Twig/Components/SearchComponent.php

@ -2,15 +2,12 @@ @@ -2,15 +2,12 @@
namespace App\Twig\Components;
use App\Credits\Service\CreditsManager;
use App\Repository\ArticleRepository;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\Contracts\Cache\CacheInterface;
@ -26,9 +23,6 @@ final class SearchComponent @@ -26,9 +23,6 @@ final class SearchComponent
public bool $interactive = true;
public int $credits = 0;
public ?string $npub = null;
#[LiveProp]
public int $vol = 0;
@ -43,7 +37,6 @@ final class SearchComponent @@ -43,7 +37,6 @@ final class SearchComponent
public function __construct(
private readonly ArticleRepository $articleRepository,
private readonly CreditsManager $creditsManager,
private readonly TokenStorageInterface $tokenStorage,
private readonly LoggerInterface $logger,
private readonly CacheInterface $cache,
@ -54,19 +47,6 @@ final class SearchComponent @@ -54,19 +47,6 @@ final class SearchComponent
public function mount(): void
{
$token = $this->tokenStorage->getToken();
$this->npub = $token?->getUserIdentifier();
if ($this->npub !== null) {
try {
$this->credits = $this->creditsManager->getBalance($this->npub);
$this->logger->info($this->credits);
} catch (InvalidArgumentException $e) {
$this->logger->error($e);
$this->credits = $this->creditsManager->resetBalance($this->npub);
}
}
// Restore search results from session if available and no query provided
if (empty($this->query)) {
$session = $this->requestStack->getSession();
@ -78,16 +58,10 @@ final class SearchComponent @@ -78,16 +58,10 @@ final class SearchComponent
}
}
/**
* @throws InvalidArgumentException
*/
#[LiveAction]
public function search(): void
{
$token = $this->tokenStorage->getToken();
$this->npub = $token?->getUserIdentifier();
$this->logger->info("Query: {$this->query}, npub: {$this->npub}");
$this->logger->info("Query: {$this->query}");
if (empty($this->query)) {
$this->results = [];
@ -95,12 +69,6 @@ final class SearchComponent @@ -95,12 +69,6 @@ final class SearchComponent
return;
}
try {
$this->credits = $this->creditsManager->getBalance($this->npub);
} catch (InvalidArgumentException $e) {
$this->credits = $this->creditsManager->resetBalance($this->npub);
}
// Check if the same query exists in session
$session = $this->requestStack->getSession();
if ($session->has(self::SESSION_QUERY_KEY) &&
@ -110,15 +78,8 @@ final class SearchComponent @@ -110,15 +78,8 @@ final class SearchComponent
return;
}
if (!$this->creditsManager->canAfford($this->npub, 1)) {
$this->results = [];
return;
}
try {
$this->results = [];
$this->creditsManager->spendCredits($this->npub, 1, 'search');
$this->credits--;
// Use database-based search instead of Elasticsearch
$offset = ($this->page - 1) * $this->resultsPerPage;
@ -142,12 +103,6 @@ final class SearchComponent @@ -142,12 +103,6 @@ final class SearchComponent
}
}
#[LiveListener('creditsAdded')]
public function incrementCreditsCount(): void
{
$this->credits += 5;
}
/**
* Save search results to session
*/

12
templates/components/SearchComponent.html.twig

@ -10,11 +10,6 @@ @@ -10,11 +10,6 @@
/>
<button type="submit"><twig:ux:icon name="iconoir:search" class="icon" /></button>
</label>
<div style="text-align: right">
<small class="help-text">
<em>{{ 'credit.balance'|trans({'%count%': credits, 'count': credits}) }}</em>
</small>
</div>
</form>
<!-- Loading Indicator -->
@ -31,11 +26,4 @@ @@ -31,11 +26,4 @@
{% elseif this.query is not empty %}
<p><small>{{ 'text.noResults'|trans }}</small></p>
{% endif %}
{% block aside %}
{% if credits == 0 %}
<twig:GetCreditsComponent />
{% endif %}
{% endblock %}
</div>

3
translations/messages.en.yaml

@ -11,6 +11,3 @@ heading: @@ -11,6 +11,3 @@ heading:
editNzine: 'Edit your N-Zine'
search: 'Search'
index: 'Index'
credit:
balance: '{0} No credits|{1} 1 credit|]1,Inf] %count% credits'

Loading…
Cancel
Save