*/ abstract protected function searchableColumns(): array; abstract protected function sortableColumns(): array; protected function defaultSortColumn(): string { return 'created_at'; } protected function defaultSortDirection(): string { return 'desc'; } /** @param array $filters column => value (eq) */ public function paginate( ?string $search = null, ?string $sortColumn = null, ?string $sortDirection = null, int $page = 1, int $perPage = 25, array $filters = [], ): LengthAwarePaginator { $query = [ 'select' => '*', 'limit' => max(1, min($perPage, 100)), 'offset' => ($page - 1) * $perPage, 'order' => sprintf( '%s.%s.nullslast', in_array($sortColumn, $this->sortableColumns(), true) ? $sortColumn : $this->defaultSortColumn(), strtolower($sortDirection ?? '') === 'asc' ? 'asc' : $this->defaultSortDirection(), ), ]; foreach ($filters as $column => $value) { if ($value !== null && $value !== '') { $query[$column] = 'eq.'.$value; } } if (filled($search) && $this->searchableColumns() !== []) { $terms = str_replace(['(', ')', ','], ' ', $search); $clauses = array_map( fn (string $column): string => sprintf('%s.ilike.*%s*', $column, trim($terms)), $this->searchableColumns(), ); $query['or'] = '('.implode(',', $clauses).')'; } [$client, $asUser] = $this->scoped(); $response = $client->send('GET', '/api/database/records/'.$this->table(), query: $query, asUser: $asUser, headers: ['Prefer' => 'count=exact']); $rows = $response->json() ?? []; $total = $this->parseTotal($response->header('Content-Range')) ?? count($rows); return new LengthAwarePaginator( items: $rows, total: $total, perPage: $perPage, currentPage: $page, ); } public function find(string $id): ?array { [$client, $asUser] = $this->scoped(); return $client->getRecord($this->table(), $id, asUser: $asUser); } /** @param array $filters */ public function findBy(array $filters): ?array { [$client, $asUser] = $this->scoped(); $params = ['limit' => 1]; foreach ($filters as $column => $value) { $params[$column] = 'eq.'.$value; } return $client->listRecords($this->table(), $params, asUser: $asUser)[0] ?? null; } public function create(array $data): void { [$client, $asUser] = $this->scoped(); $client->insertRecord($this->table(), [$data], asUser: $asUser); } public function update(string $id, array $data): void { [$client, $asUser] = $this->scoped(); $client->updateRecords($this->table(), ['id' => $id], $data, asUser: $asUser); } public function delete(string $id): void { [$client, $asUser] = $this->scoped(); $client->deleteRecords($this->table(), ['id' => $id], asUser: $asUser); } /** * Pakai JWT user saat ada sesi (RLS aktif); jatuh ke admin key untuk konteks tanpa sesi. * * @return array{0: InsForgeClient, 1: bool} */ private function scoped(): array { $token = session(InsForgeAuthService::SESSION_ACCESS_TOKEN); return [ $token ? $this->client->withToken($token) : $this->client, filled($token), ]; } private function parseTotal(?string $contentRange): ?int { if (! filled($contentRange)) { return null; } $total = explode('/', $contentRange)[1] ?? ''; return ctype_digit($total) ? (int) $total : null; } }