<commit_before><?php
/*
 * Fusio
 * A web-application to create dynamically RESTful APIs
 *
 * Copyright (C) 2015-2018 <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Fusio\Impl\Service;

use Fusio\Engine\Model;
use Fusio\Impl\Authorization\UserContext;
use Fusio\Impl\Event\Rate\CreatedEvent;
use Fusio\Impl\Event\Rate\DeletedEvent;
use Fusio\Impl\Event\Rate\UpdatedEvent;
use Fusio\Impl\Event\RateEvents;
use Fusio\Impl\Table;
use PSX\Http\Exception as StatusCode;
use PSX\Http\ResponseInterface;
use PSX\Sql\Condition;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Rate
 *
 * @author  <NAME> <<EMAIL>>
 * @license http://www.gnu.org/licenses/agpl-3.0
 * @link    http://fusio-project.org
 */
class Rate
{
    /**
     * @var \Fusio\Impl\Table\Rate
     */
    protected $rateTable;

    /**
     * @var \Fusio\Impl\Table\Rate\Allocation
     */
    protected $rateAllocationTable;

    /**
     * @var \Fusio\Impl\Table\Log
     */
    protected $logTable;

    /**
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @param \Fusio\Impl\Table\Rate $rateTable
     * @param \Fusio\Impl\Table\Rate\Allocation $rateAllocationTable
     * @param \Fusio\Impl\Table\Log $logTable
     * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
     */
    public function __construct(Table\Rate $rateTable, Table\Rate\Allocation $rateAllocationTable, Table\Log $logTable, EventDispatcherInterface $eventDispatcher)
    {
        $this->rateTable           = $rateTable;
        $this->rateAllocationTable = $rateAllocationTable;
        $this->logTable            = $logTable;
        $this->eventDispatcher     = $eventDispatcher;
    }

    public function create($priority, $name, $rateLimit, \DateInterval $timespan, array $allocations = null, UserContext $context)
    {
        // check whether rate exists
        $condition  = new Condition();
        $condition->notEquals('status', Table\Rate::STATUS_DELETED);
        $condition->equals('name', $name);

        $app = $this->rateTable->getOneBy($condition);

        if (!empty($app)) {
            throw new StatusCode\BadRequestException('Rate already exists');
        }

        try {
            $this->rateTable->beginTransaction();

            // create rate
            $record = [
                'status'     => Table\Rate::STATUS_ACTIVE,
                'priority'   => $priority,
                'name'       => $name,
                'rate_limit' => $rateLimit,
                'timespan'   => $timespan,
            ];

            $this->rateTable->create($record);

            // get last insert id
            $rateId = $this->rateTable->getLastInsertId();

            $this->handleAllocation($rateId, $allocations);

            $this->rateTable->commit();
        } catch (\Throwable $e) {
            $this->rateTable->rollBack();

            throw $e;
        }

        $this->eventDispatcher->dispatch(RateEvents::CREATE, new CreatedEvent($rateId, $record, $allocations, $context));
    }

    public function update($rateId, $priority, $name, $rateLimit, \DateInterval $timespan, array $allocations = null, UserContext $context)
    {
        $rate = $this->rateTable->get($rateId);

        if (empty($rate)) {
            throw new StatusCode\NotFoundException('Could not find rate');
        }

        if ($rate['status'] == Table\Rate::STATUS_DELETED) {
            throw new StatusCode\GoneException('Rate was deleted');
        }

        try {
            $this->rateTable->beginTransaction();

            // update rate
            $record = [
                'id'         => $rate['id'],
                'priority'   => $priority,
                'name'       => $name,
                'rate_limit' => $rateLimit,
                'timespan'   => $timespan,
            ];

            $this->rateTable->update($record);

            $this->handleAllocation($rate['id'], $allocations);

            $this->rateTable->commit();
        } catch (\Throwable $e) {
            $this->rateTable->rollBack();

            throw $e;
        }

        $this->eventDispatcher->dispatch(RateEvents::UPDATE, new UpdatedEvent($rateId, $record, $allocations, $rate, $context));
    }

    public function delete($rateId, UserContext $context)
    {
        $rate = $this->rateTable->get($rateId);

        if (empty($rate)) {
            throw new StatusCode\NotFoundException('Could not find rate');
        }

        $record = [
            'id'     => $rate['id'],
            'status' => Table\Rate::STATUS_DELETED,
        ];

        $this->rateTable->update($record);

        $this->eventDispatcher->dispatch(RateEvents::DELETE, new DeletedEvent($rateId, $rate, $context));
    }
    
    /**
     * @param string $ip
     * @param integer $routeId
     * @param \Fusio\Engine\Model\AppInterface $app
     * @param \PSX\Http\ResponseInterface $response
     * @return boolean
     */
    public function assertLimit($ip, $routeId, Model\AppInterface $app, ResponseInterface $response)
    {
        $rate = $this->rateAllocationTable->getRateForRequest($routeId, $app);

        if (empty($rate)) {
            return false;
        }

        $count     = $this->getRequestCount($ip, $rate['timespan'], $app);
        $rateLimit = (int) $rate['rate_limit'];

        $response->setHeader('X-RateLimit-Limit', $rateLimit);
        $response->setHeader('X-RateLimit-Remaining', $rateLimit - $count);<commit_msg>make response optional<commit_after><?php
/*
 * Fusio
 * A web-application to create dynamically RESTful APIs
 *
 * Copyright (C) 2015-2018 <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace Fusio\Impl\Service;

use Fusio\Engine\Model;
use Fusio\Impl\Authorization\UserContext;
use Fusio\Impl\Event\Rate\CreatedEvent;
use Fusio\Impl\Event\Rate\DeletedEvent;
use Fusio\Impl\Event\Rate\UpdatedEvent;
use Fusio\Impl\Event\RateEvents;
use Fusio\Impl\Table;
use PSX\Http\Exception as StatusCode;
use PSX\Http\ResponseInterface;
use PSX\Sql\Condition;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * Rate
 *
 * @author  <NAME> <<EMAIL>>
 * @license http://www.gnu.org/licenses/agpl-3.0
 * @link    http://fusio-project.org
 */
class Rate
{
    /**
     * @var \Fusio\Impl\Table\Rate
     */
    protected $rateTable;

    /**
     * @var \Fusio\Impl\Table\Rate\Allocation
     */
    protected $rateAllocationTable;

    /**
     * @var \Fusio\Impl\Table\Log
     */
    protected $logTable;

    /**
     * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * @param \Fusio\Impl\Table\Rate $rateTable
     * @param \Fusio\Impl\Table\Rate\Allocation $rateAllocationTable
     * @param \Fusio\Impl\Table\Log $logTable
     * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
     */
    public function __construct(Table\Rate $rateTable, Table\Rate\Allocation $rateAllocationTable, Table\Log $logTable, EventDispatcherInterface $eventDispatcher)
    {
        $this->rateTable           = $rateTable;
        $this->rateAllocationTable = $rateAllocationTable;
        $this->logTable            = $logTable;
        $this->eventDispatcher     = $eventDispatcher;
    }

    public function create($priority, $name, $rateLimit, \DateInterval $timespan, array $allocations = null, UserContext $context)
    {
        // check whether rate exists
        $condition  = new Condition();
        $condition->notEquals('status', Table\Rate::STATUS_DELETED);
        $condition->equals('name', $name);

        $app = $this->rateTable->getOneBy($condition);

        if (!empty($app)) {
            throw new StatusCode\BadRequestException('Rate already exists');
        }

        try {
            $this->rateTable->beginTransaction();

            // create rate
            $record = [
                'status'     => Table\Rate::STATUS_ACTIVE,
                'priority'   => $priority,
                'name'       => $name,
                'rate_limit' => $rateLimit,
                'timespan'   => $timespan,
            ];

            $this->rateTable->create($record);

            // get last insert id
            $rateId = $this->rateTable->getLastInsertId();

            $this->handleAllocation($rateId, $allocations);

            $this->rateTable->commit();
        } catch (\Throwable $e) {
            $this->rateTable->rollBack();

            throw $e;
        }

        $this->eventDispatcher->dispatch(RateEvents::CREATE, new CreatedEvent($rateId, $record, $allocations, $context));
    }

    public function update($rateId, $priority, $name, $rateLimit, \DateInterval $timespan, array $allocations = null, UserContext $context)
    {
        $rate = $this->rateTable->get($rateId);

        if (empty($rate)) {
            throw new StatusCode\NotFoundException('Could not find rate');
        }

        if ($rate['status'] == Table\Rate::STATUS_DELETED) {
            throw new StatusCode\GoneException('Rate was deleted');
        }

        try {
            $this->rateTable->beginTransaction();

            // update rate
            $record = [
                'id'         => $rate['id'],
                'priority'   => $priority,
                'name'       => $name,
                'rate_limit' => $rateLimit,
                'timespan'   => $timespan,
            ];

            $this->rateTable->update($record);

            $this->handleAllocation($rate['id'], $allocations);

            $this->rateTable->commit();
        } catch (\Throwable $e) {
            $this->rateTable->rollBack();

            throw $e;
        }

        $this->eventDispatcher->dispatch(RateEvents::UPDATE, new UpdatedEvent($rateId, $record, $allocations, $rate, $context));
    }

    public function delete($rateId, UserContext $context)
    {
        $rate = $this->rateTable->get($rateId);

        if (empty($rate)) {
            throw new StatusCode\NotFoundException('Could not find rate');
        }

        $record = [
            'id'     => $rate['id'],
            'status' => Table\Rate::STATUS_DELETED,
        ];

        $this->rateTable->update($record);

        $this->eventDispatcher->dispatch(RateEvents::DELETE, new DeletedEvent($rateId, $rate, $context));
    }
    
    /**
     * @param string $ip
     * @param integer $routeId
     * @param \Fusio\Engine\Model\AppInterface $app
     * @param \PSX\Http\ResponseInterface|null $response
     * @return boolean
     */
    public function assertLimit($ip, $routeId, Model\AppInterface $app, ResponseInterface $response = null)
    {
        $rate = $this->rateAllocationTable->getRateForRequest($routeId, $app);

        if (empty($rate)) {
            return false;
        }

        $count     = $this->getRequestCount($ip, $rate['timespan'], $app);
        $rateLimit = (int) $rate['rate_limit'];

        if ($response !== null) {
            $response->setHeader('X-RateLimit-Limit', $rateLimit);
            $response->setHeader('X-RateLimit-Remaining', $rateLimit - $count);
        }
{'fraction_non_alphanumeric': 0.13453637315717393, 'fraction_numerical': 0.0017723354547651655, 'mean_word_length': 2.3779591836734695, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 131, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6846855', 'n_tokens_mistral': 3619, 'n_tokens_neox': 3490, 'n_words': 793}