# API - Models

This document describes the model interfaces to be used in the app, usefull for create a new source type or to access the data.

When a method is created, updated or deleted, this document must be updated to reflect the changes.

## Model API Contracts

### AccountModel

```php
class AccountModel {
  public static function count(): int;
  public static function getByUID(int $uid): array|false;
  public static function getByUsername(string $username): array|false;
  public static function getWithLogin(string $username, string $password): array|false;
  public static function getGMAccounts(): array;
  public static function isAccountValid(string $account): bool;
  public static function updatePassword(string $username, string $newPassword): bool;
  public static function updateEmail(string $username, string $newEmail): bool;
  public static function updateLanguage(string $username, string $newLanguage): bool;
  public static function createAccount(string $username, string $password, string $email, string $language): bool;
}
```

### NotificationModel

```php
class NotificationModel {
  public static function getByUID(int $uid): array;
  public static function isEnabled(int $uid, string $type): bool;
  public static function setEnabled(int $uid, string $type, bool $enabled): bool;
}
```

### EntitiesModel

```php
class EntitiesModel {
  public static function count(): int;
  public static function onlinePlayers(): ?int;
  public static function onlinePlayersList(): array|null;
  public static function isEntityOnline(int $entityId): ?bool;
  public static function getByUID(int $uid): array|false;
  public static function getByName(string $name): array|false;
  public static function getByGuildID(int $guildID): array;
  public static function getEquippedItems(int $entityID): ?array;
}
```

For file-backed stream sources, website entity and guild lookups read from the synchronized `cms_rank_entities` and `cms_rank_guilds` projections. Online state is read from `cms_rank_entities.Online`.
Online list rows include the CMS entity fields and the `Online` field.

### GuildsModel

```php
class GuildsModel {
  public static function count(): int;
  public static function getByID(int $id): array|false;
  public static function getByName(string $name): array|false;
}
```

### StoreModel

```php
class StoreModel {
  public static $ownerField = 'Username';  // Override to 'UID' for stream

  public static function getAllProducts(): array;
  public static function getProduct(int $productId): array|false;
  public static function getSubProducts(string $productList): array;
  public static function alreadyPaid(string $txnId): bool;
  public static function addPurchase(array $accountModel, int $type, mixed $value, int $quantity, string $txnId): bool;
  public static function getPurchasesUnclaimed(array $accountModel): array;
  public static function getPurchasesUnclaimedByTrxID(array $accountModel, string $txnId): array;
  public static function getPurchasesFromAccount(array $accountModel): array;
  public static function getTransaction(string $txnId): array|false;
  public static function logPayment(array $accountModel, array $paymentData, array $itemInfo): bool;
}
```

### OrderModel

```php
class OrderModel {
  public static function create(array $accountModel, array $cart): int|false;
  public static function getForAccount(int $orderId, array $accountModel): array|false;
  public static function getById(int $orderId): array|false;
  public static function getAdminOrders(int $startOrder, int $limitOrders, ?int $orderId = null, ?string $owner = null): array;
  public static function getAdminOrder(int $orderId): array|false;
  public static function getAdminHistory(int $orderId, array $accountModel): array|false;
  public static function getItems(int $orderId): array;
  public static function getHistory(array $accountModel): array;
  public static function isPayable(array $order): bool;
  public static function isPaid(array $order): bool;
  public static function markPaid(int $orderId, string $paymentMethod, string $transactionId): bool;
  public static function markPaidByAdmin(int $orderId, string $paymentMethod, string $transactionId, float $totalAfterDiscount, float $discountApplied): bool;
  public static function fulfill(array $accountModel, array $order): void;
}
```

### OrderTempTxnModel

```php
class OrderTempTxnModel {
  public static function getByOrderAndPaymentMethod(int $orderId, string $paymentMethod): array|false;
  public static function getByTransactionId(string $paymentMethod, string $transactionId): array|false;
  public static function create(int $orderId, string $paymentMethod, string $transactionId): bool;
  public static function deleteByTransactionId(string $paymentMethod, string $transactionId): bool;
}
```

### OrderCryptoDataModel

```php
class OrderCryptoDataModel {
  public static function save(int $orderId, string $paymentMethod, string $referenceId, array $cryptoData): bool;
}
```

### VoteModel

```php
class VoteModel {
  public static function getLastRecentAccountVote(string $account): ?array;
  public static function getLastRecentIpVote(string $ip): ?array;
  public static function executeVote(string $account, string $ip): bool;
}
```

### TokenModel

```php
class TokenModel {
  public static function getByIdType(int $id, string $type): array|false;
  public static function getTokenByIDAndType(int $id, string $type, string $token): array|false;
  public static function createToken(int $id, string $type): string;
  public static function deleteByIdType(int $id, string $type): bool;
  public static function hasTokenForIdType(int $id, string $type, int $duration_minutes): bool;
  public static function isTokenValid(int $id, string $type, string $token, int $duration_minutes): bool;
  public static function isExpired(string $creation_date, int $duration_minutes): bool;
}
```

### RankingModel (extends CmsRankingModel)

```php
class RankingModel extends CmsRankingModel {
  public static function getTopPlayer(int $limit): array;
  public static function getTopPlayerLevel(int $limit): array;
  public static function getTopPlayerMoney(int $limit): array;
  public static function getTopPlayerCPs(int $limit): array;
  public static function getTopNobilityDonation(int $limit): array;
  public static function getTopKOBoard(int $limit): array;
  public static function getTopPlayerPK(int $limit): array;
  public static function getTopVirtue(int $limit): array;
  public static function getTopClass(int $class, int $limit): array;
  public static function getTopGuildDonation(int $limit): array;
  public static function getTopUnionGoldBricks(int $limit): array;
  public static function getTopArena(int $limit): array;
}
```

### CmsRankingModel CLI Import API

These methods are used by `src/core/cli/ranking.php` and are not intended for
web requests. They synchronize file-backed ranking snapshots without truncating
the live tables.

```php
class CmsRankingModel {
  public static function acquireRankingLock(): bool;
  public static function releaseRankingLock(): void;

  public static function startEntitiesSync(): void;
  public static function upsertEntities(array $entities): bool;
  public static function removeMissingEntities(): int;

  public static function startGuildsSync(): void;
  public static function upsertGuilds(array $guilds): bool;
  public static function removeMissingGuilds(): int;

  public static function startArenaSync(): void;
  public static function upsertArena(array $arenaList): bool;
  public static function removeMissingArena(): int;

  public static function cleanupRankingSync(): void;
}
```

Each `upsert*()` call accepts a batch of normalized source rows. Primary keys
are used for `INSERT ... ON DUPLICATE KEY UPDATE`; unchanged values are not
physically rewritten by MySQL. The matching `start*Sync()` creates a temporary
seen-key table, and `removeMissing*()` removes rows absent from that table.
Call these methods inside one CMS transaction and clean up the temporary tables
after completion. The ranking tables must use InnoDB.

### ContentModel

Content is stored in the CMS database as soft-deletable category/content nodes and one translation row per language.

```php
class ContentModel {
  public static function create(string $type, ?int $parentId, ?string $listStyle, int $authorUid): int|false;
  public static function saveTranslation(int $nodeId, string $language, string $title, string $slug, string $body, bool $isDraft): bool;
  public static function generateSlug(string $title, string $language, ?int $nodeId = null): string;
  public static function update(int $nodeId, ?int $parentId, ?string $listStyle, string $childrenSort = 'order_asc', ?string $publishAt = null, int $sticky = 0, string $previewStyle = 'description', string $image = ContentModel::IMAGE_UNCHANGED): bool;
  public static function get(int $nodeId, ?string $language = null, bool $includeDeleted = false): array|false;
  public static function getForLanguage(int $nodeId, string $language, bool $includeDeleted = false): array|false;
  public static function getTranslationSlugs(int $nodeId): array;
  public static function getTree(bool $includeDeleted = false): array;
  public static function getDeleted(): array;
  public static function saveTree(array $items): bool;
  public static function setTranslationEnabled(int $nodeId, string $language, bool $enabled): bool;
  public static function delete(int $nodeId): string|false;
  public static function recover(int $nodeId): bool;
  public static function findPublishedBySlug(string $slug, string $language): array|false;
  public static function getPublishedChildren(?int $parentId, string $language): array;
  public static function getAncestorIds(int $nodeId): array;
}
```

Visibility is stored as `is_draft` on each translation and exposed as `isDraft`. Node deletion protection is stored as `protected` and exposed as a boolean by tree results. Protected nodes cannot be deleted; deleting a category is also rejected when it contains an active protected descendant. Category deletion uses a deletion batch so recovering a category also recovers the descendants deleted by that operation.

Category bodies may be empty. Slugs are globally unique across all languages. A conflicting generated slug uses `{slug}-{language}`, then `{slug}-{language}-{id}`, then `{slug}-{language}-{id}-{counter}`.

`publish_at` and `sticky` belong to the content node and are shared by all translations. `sticky` accepts `0` (No), `1` (Only first page), or `2` (Always) for both categories and content. Translation draft state remains language-specific.

Node images are also shared by all translations. Pass a filename to `update()` to replace it or `null` to remove it; omit the argument to leave the image unchanged.

Category child sorting values accepted by `update()` are:

- `order_asc`: manual `sort_order`, ascending.
- `order_desc`: manual `sort_order`, descending.
- `publish_asc`: `publish_at`, ascending.
- `publish_desc`: `publish_at`, descending.

Category preview styles accepted by `update()` are `description`, `children`, and `dropdown`. They control how a category is rendered when it appears as a child of another category.
