crm-summit/app/Controllers/Api/FigmaApi.php
mahdahar 6776d539ae feat(figma): add version author id and robust incremental sync pagination
Improve Figma synchronization to persist version author identity and handle API pagination links reliably during incremental sync jobs.

Changes included:
- Added  support end-to-end:
  - New migration  adds column and index.
  -  now allows .
  -  maps user id from Figma version payload ( / ).
  -  snapshot query now selects .
  -  displays Figma User ID column and updates table colspan states.
- Hardened Figma pagination flow in :
  - Follow absolute  URLs when returned by API.
  - Track seen pagination URLs to prevent loops.
  - Support additional page-size query key and URL-safe encoding.
  - Updated request builder to work with absolute endpoints and existing query strings.
- Consolidated schema intent:
  - Moved Volume in drive C: has no label
Volume Serial Number is 2B45-1F84/ columns into initial Figma table migration.
  - Removed superseded follow-up migrations that previously added/dropped these fields.
- Updated project docs and dependencies:
  - Replaced starter README with CRM Summit specific project README.
  - Refreshed  (framework and dependency version bumps).
- Added database artifact: .

Impact:
- Incremental sync should now process paginated version/history feeds more reliably.
- Snapshot API and dashboard expose author-level metadata for auditing and filtering.
- Fresh installs get cleaner Figma schema history from baseline migration.
2026-04-28 05:39:02 +07:00

190 lines
5.0 KiB
PHP

<?php
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Libraries\FigmaSyncService;
use CodeIgniter\API\ResponseTrait;
class FigmaApi extends BaseController
{
use ResponseTrait;
private function ensureLoggedIn()
{
if (!session()->get('userid')) {
return $this->respond([
'status' => 'error',
'message' => 'Unauthorized',
], 401);
}
return null;
}
private function ensureAdmin()
{
$level = (int) session()->get('level');
if (!in_array($level, [0, 1, 2], true)) {
return $this->respond([
'status' => 'error',
'message' => 'Forbidden. Admin only.',
], 403);
}
return null;
}
public function summary()
{
if ($response = $this->ensureLoggedIn()) {
return $response;
}
$db = \Config\Database::connect();
$file = $db->table('figma_files')->orderBy('id', 'DESC')->get()->getRowArray();
$versionsCount = $db->table('figma_file_versions')->countAllResults();
$commentsCount = $db->table('figma_comments')->countAllResults();
$latestVersion = $db->table('figma_file_versions')->orderBy('created_at_figma', 'DESC')->get()->getRowArray();
$latestComment = $db->table('figma_comments')->selectMax('created_at_figma', 'latest')->get()->getRowArray();
return $this->respond([
'status' => 'success',
'message' => 'Summary fetched',
'data' => [
'file' => $file,
'versions' => $versionsCount,
'comments' => $commentsCount,
'latest_version_at' => $latestVersion['created_at_figma'] ?? null,
'latest_version_label' => $latestVersion['label'] ?? $latestVersion['version'] ?? null,
'latest_version_description' => $latestVersion['description'] ?? null,
'latest_comment_at' => $latestComment['latest'] ?? null,
],
], 200);
}
private function getPaginationParams(): array
{
$page = (int) ($this->request->getGet('page') ?? 1);
if ($page <= 0) {
$page = 1;
}
$perPage = (int) ($this->request->getGet('per_page') ?? $this->request->getGet('limit') ?? 25);
if ($perPage <= 0 || $perPage > 100) {
$perPage = 25;
}
return [$page, $perPage];
}
public function snapshots()
{
if ($response = $this->ensureLoggedIn()) {
return $response;
}
$startDate = $this->request->getGet('start_date');
$endDate = $this->request->getGet('end_date');
[$page, $perPage] = $this->getPaginationParams();
$offset = ($page - 1) * $perPage;
$db = \Config\Database::connect();
$baseBuilder = $db->table('figma_file_versions v')
->join('figma_files f', 'f.id = v.file_id', 'inner');
if (!empty($startDate)) {
$baseBuilder->where('v.created_at_figma >=', $startDate . ' 00:00:00');
}
if (!empty($endDate)) {
$baseBuilder->where('v.created_at_figma <=', $endDate . ' 23:59:59');
}
$total = (int) (clone $baseBuilder)->countAllResults();
$rows = $baseBuilder
->select('v.id, v.figma_version_id, v.version, v.label, v.description, v.name, v.editor_type, v.figma_user_id, v.last_modified_figma, v.created_at_figma, f.file_key, f.last_synced_at')
->orderBy('v.created_at_figma', 'DESC')
->limit($perPage, $offset)
->get()
->getResultArray();
return $this->respond([
'status' => 'success',
'message' => 'Snapshots fetched',
'data' => $rows,
'meta' => [
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => $perPage > 0 ? (int) ceil($total / $perPage) : 0,
],
], 200);
}
public function comments()
{
if ($response = $this->ensureLoggedIn()) {
return $response;
}
$startDate = $this->request->getGet('start_date');
$endDate = $this->request->getGet('end_date');
[$page, $perPage] = $this->getPaginationParams();
$offset = ($page - 1) * $perPage;
$db = \Config\Database::connect();
$baseBuilder = $db->table('figma_comments c')
->join('figma_files f', 'f.id = c.file_id', 'inner');
if (!empty($startDate)) {
$baseBuilder->where('c.created_at_figma >=', $startDate . ' 00:00:00');
}
if (!empty($endDate)) {
$baseBuilder->where('c.created_at_figma <=', $endDate . ' 23:59:59');
}
$total = (int) (clone $baseBuilder)->countAllResults();
$rows = $baseBuilder
->select('c.id, c.figma_comment_id, c.user_name, c.message, c.is_resolved, c.resolved_at, c.created_at_figma, c.client_meta_json, f.file_key')
->orderBy('c.created_at_figma', 'DESC')
->limit($perPage, $offset)
->get()
->getResultArray();
return $this->respond([
'status' => 'success',
'message' => 'Comments fetched',
'data' => $rows,
'meta' => [
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => $perPage > 0 ? (int) ceil($total / $perPage) : 0,
],
], 200);
}
public function sync()
{
if ($response = $this->ensureLoggedIn()) {
return $response;
}
if ($response = $this->ensureAdmin()) {
return $response;
}
$service = new FigmaSyncService();
$result = $service->syncIncremental(1);
$statusCode = $result['success'] ? 200 : 500;
return $this->respond([
'status' => $result['success'] ? 'success' : 'error',
'message' => $result['message'],
'data' => $result['stats'] ?? [],
], $statusCode);
}
}