Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Negative:

Learn more about the Nextcloud Ethical AI Rating [in our blog](https://nextcloud.com/blog/nextcloud-ethical-ai-rating/).
]]> </description>
<version>3.7.1</version>
<version>3.8.0</version>
<licence>agpl</licence>
<author>Julien Veyssier</author>
<namespace>OpenAi</namespace>
Expand Down
4 changes: 4 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@
['name' => 'openAiAPI#getModels', 'url' => '/models', 'verb' => 'GET'],
['name' => 'openAiAPI#getUserQuotaInfo', 'url' => '/quota-info', 'verb' => 'GET'],
['name' => 'openAiAPI#getAdminQuotaInfo', 'url' => '/admin-quota-info', 'verb' => 'GET'],

['name' => 'quotaRule#addRule', 'url' => '/quota/rule', 'verb' => 'POST'],
['name' => 'quotaRule#updateRule', 'url' => '/quota/rule', 'verb' => 'PUT'],
['name' => 'quotaRule#deleteRule', 'url' => '/quota/rule', 'verb' => 'DELETE'],
],
];
1 change: 1 addition & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class Application extends App implements IBootstrap {
];

public const MODELS_CACHE_KEY = 'models';
public const QUOTA_RULES_CACHE_PREFIX = 'quota_rules';
public const MODELS_CACHE_TTL = 60 * 30;

private IAppConfig $appConfig;
Expand Down
82 changes: 82 additions & 0 deletions lib/Controller/QuotaRuleController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<?php

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\OpenAi\Controller;

use Exception;
use OCA\OpenAi\Service\QuotaRuleService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;

class QuotaRuleController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private QuotaRuleService $quotaRuleService,
) {
parent::__construct($appName, $request);
}

/**
* POST /rule Creates a new empty rule returning the value of the rule
* @return DataResponse
*/
public function addRule(): DataResponse {
try {
$result = $this->quotaRuleService->addRule();
return new DataResponse($result);
} catch (Exception $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
}

/**
* PUT /rule
* @param int $id
* @param array $rule expects: type, amount, priority, pool, entities[]
* @return DataResponse
*/
public function updateRule(int $id, array $rule): DataResponse {
if (!isset($rule['type']) || !is_int($rule['type'])) {
return new DataResponse(['error' => 'Missing or invalid type'], Http::STATUS_BAD_REQUEST);
}
if (!isset($rule['amount']) || !is_int($rule['amount'])) {
return new DataResponse(['error' => 'Missing or invalid amount'], Http::STATUS_BAD_REQUEST);
}
if (!isset($rule['priority']) || !is_int($rule['priority'])) {
return new DataResponse(['error' => 'Missing or invalid priority'], Http::STATUS_BAD_REQUEST);
}
if (!isset($rule['pool']) || !is_bool($rule['pool'])) {
return new DataResponse(['error' => 'Missing or invalid pool value'], Http::STATUS_BAD_REQUEST);
}
if (!isset($rule['entities']) || !is_array($rule['entities'])) {
return new DataResponse(['error' => 'Missing or invalid entities'], Http::STATUS_BAD_REQUEST);
}
try {
$result = $this->quotaRuleService->updateRule($id, $rule);
return new DataResponse($result);
} catch (Exception $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
}

/**
* DELETE /rule
* @param int $id
* @return DataResponse
*/
public function deleteRule(int $id): DataResponse {
try {
$this->quotaRuleService->deleteRule($id);
return new DataResponse('');
} catch (Exception $e) {
return new DataResponse(['error' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
}
}
}
54 changes: 54 additions & 0 deletions lib/Db/QuotaRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\OpenAi\Db;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
use ReturnTypeWillChange;

/**
* @method int getType()
* @method void setType(int $type)
* @method int getAmount()
* @method void setAmount(int $amount)
* @method int getPriority()
* @method void setPriority(int $priority)
* @method int getPool()
* @method void setPool(int $pool)
*/
class QuotaRule extends Entity implements JsonSerializable {
/** @var int */
protected $type;
/** @var int */
protected $amount;
/** @var int */
protected $priority;
/** @var int */
protected $pool;

public function __construct() {
$this->addType('type', Types::INTEGER);
$this->addType('amount', Types::INTEGER);
$this->addType('priority', Types::INTEGER);
$this->addType('pool', Types::INTEGER);
}

#[ReturnTypeWillChange]
public function jsonSerialize() {
return [
'id' => $this->getId(),
'type' => $this->getType(),
'amount' => $this->getAmount(),
'priority' => $this->getPriority(),
'pool' => $this->getPool()
];
}
}
131 changes: 131 additions & 0 deletions lib/Db/QuotaRuleMapper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\OpenAi\Db;

use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\Exception;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;

/**
* @extends QBMapper<QuotaRule>
*/
class QuotaRuleMapper extends QBMapper {
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, 'openai_quota_rule', QuotaRule::class);
}

/**
* @return array
* @throws Exception
*/
public function getRules(): array {
$qb = $this->db->getQueryBuilder();

$qb->select('*')
->from($this->getTableName());

return $this->findEntities($qb);
}

/**
* @param int $quotaType
* @param string $userId
* @param array $groups
* @return QuotaRule
* @throws DoesNotExistException
* @throws Exception
* @throws MultipleObjectsReturnedException
*/
public function getRule(int $quotaType, string $userId, array $groups): QuotaRule {
$qb = $this->db->getQueryBuilder();

$qb->select('r.*')
->from($this->getTableName(), 'r')
->leftJoin('r', 'openai_quota_user', 'u', 'r.id = u.rule_id')
->where(
$qb->expr()->eq('r.type', $qb->createNamedParameter($quotaType, IQueryBuilder::PARAM_INT))
)->andWhere(
$qb->expr()->orX(
$qb->expr()->andX(
$qb->expr()->eq('u.entity_type', $qb->createNamedParameter(EntityType::USER->value, IQueryBuilder::PARAM_INT)),
$qb->expr()->eq('u.entity_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
),
$qb->expr()->andX(
$qb->expr()->eq('u.entity_type', $qb->createNamedParameter(EntityType::GROUP->value, IQueryBuilder::PARAM_INT)),
$qb->expr()->in('u.entity_id', $qb->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY))
),

)
)->orderBy('r.priority', 'ASC')
->setMaxResults(1);
/** @var QuotaRule $entity */
$entity = $this->findEntity($qb);
return $entity;
}
/**
* @param int $quotaType
* @param int $amount
* @param int $priority
* @param int $pool
* @return int
* @throws Exception
*/
public function addRule(int $quotaType, int $amount, int $priority, int $pool): int {
$qb = $this->db->getQueryBuilder();

$qb->insert($this->getTableName())
->values(
[
'type' => $qb->createNamedParameter($quotaType, IQueryBuilder::PARAM_INT),
'amount' => $qb->createNamedParameter($amount, IQueryBuilder::PARAM_INT),
'priority' => $qb->createNamedParameter($priority, IQueryBuilder::PARAM_INT),
'pool' => $qb->createNamedParameter($pool, IQueryBuilder::PARAM_INT)
]
);
$qb->executeStatement();
return $qb->getLastInsertId();
}
/**
* @param int $id
* @param int $quotaType
* @param int $amount
* @param int $priority
* @param int $pool
* @return void
* @throws Exception
*/
public function updateRule(int $id, int $quotaType, int $amount, int $priority, int $pool): void {
$qb = $this->db->getQueryBuilder();

$qb->update($this->getTableName())
->set('type', $qb->createNamedParameter($quotaType, IQueryBuilder::PARAM_INT))
->set('amount', $qb->createNamedParameter($amount, IQueryBuilder::PARAM_INT))
->set('priority', $qb->createNamedParameter($priority, IQueryBuilder::PARAM_INT))
->set('pool', $qb->createNamedParameter($pool, IQueryBuilder::PARAM_INT))
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
);
$qb->executeStatement();
}
/**
* @param int $id
* @throws Exception
*/
public function deleteRule(int $id): void {
$qb = $this->db->getQueryBuilder();

$qb->delete($this->getTableName())
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$qb->executeStatement();
}
}
31 changes: 20 additions & 11 deletions lib/Db/QuotaUsage.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

namespace OCA\OpenAi\Db;

use JsonSerializable;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
use ReturnTypeWillChange;

/**
* @method string getUserId()
Expand All @@ -20,8 +23,10 @@
* @method void setUnits(int $units)
* @method int getTimestamp()
* @method void setTimestamp(int $timestamp)
* @method int getPool()
* @method void setPool(int $pool)
*/
class QuotaUsage extends Entity implements \JsonSerializable {
class QuotaUsage extends Entity implements JsonSerializable {
/** @var string */
protected $userId;
/** @var int */
Expand All @@ -30,22 +35,26 @@ class QuotaUsage extends Entity implements \JsonSerializable {
protected $units;
/** @var int */
protected $timestamp;
/** @var int */
protected $pool;

public function __construct() {
$this->addType('user_id', 'string');
$this->addType('type', 'integer');
$this->addType('units', 'integer');
$this->addType('timestamp', 'integer');
$this->addType('user_id', Types::STRING);
$this->addType('type', Types::INTEGER);
$this->addType('units', Types::INTEGER);
$this->addType('timestamp', Types::INTEGER);
$this->addType('pool', Types::INTEGER);
}

#[\ReturnTypeWillChange]
#[ReturnTypeWillChange]
public function jsonSerialize() {
return [
'id' => $this->id,
'user_id' => $this->userId,
'type' => $this->type,
'units' => $this->units,
'timestamp' => $this->timestamp,
'id' => $this->getId(),
'user_id' => $this->getUserId(),
'type' => $this->getType(),
'units' => $this->getUnits(),
'timestamp' => $this->getTimestamp(),
'pool' => $this->getPool()
];
}
}
Loading
Loading