Skip to main content

clockify-sync-feature

Add Clockify synchronization work safely: new synced entities, sync jobs, checkpoints, rate-limited fetching, idempotent upserts and scheduling, following TDR sections 8–24.

Datos de origen

Repositorio
AHS12/evoriq
Última actividad en el origen
23 de septiembre de 2026 a las 20:33
Idioma detectado de SKILL.md
inglés
Estrellas
0
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
clockify-sync-feature
description
Add Clockify synchronization work safely: new synced entities, sync jobs, checkpoints, rate-limited fetching, idempotent upserts and scheduling, following TDR sections 8–24.
argument-hint
Entity to sync or sync behavior, for example: sync clockify time entries for a date range.
# Clockify Sync Feature ## When to use Use when adding or changing anything that pulls Clockify data, schedules a sync, handles a webhook, or reconciles historical data. Read `TDR.md` §8–24 and `AGENTS.md` §7.9–7.10 first. ## Non-negotiable rules 1. **All Clockify HTTP goes through `App\Services\Clockify\ClockifyClient`.** Never call `Http::` against Clockify from a controller, job or module service. 2. **Never bypass the rate limiter.** Manual, scheduled and webhook syncs share one budget (`ClockifyRateLimiter`). 3. **Pagination is independent of the plan.** Use `ClockifyClient::paginate()`; never assume one range = one request. 4. **Idempotency.** Every synced table has a unique `clockify_id` (+ `workspace_id`) and is written with **upsert** semantics. Re-runs must not duplicate rows. 5. **Resumable.** Long imports are checkpointed in `sync_runs` / `sync_jobs` (`page`, `range_start`, `range_end`, `status`) so a failure resumes, not restarts. 6. **Jobs are thin and idempotent.** Resolve a service and delegate; no logic. 7. **Credentials never reach the frontend** and are stored encrypted per connection. ## 1. Add a synced entity 1. **Migration**: internal `id` + `clockify_id` (unique, indexed) + `workspace_id` (indexed) + foreign/internal IDs + audit columns. Add a unique composite index where needed (e.g. `['workspace_id', 'clockify_id']`). 2. **Model** `app/Models/Clockify{Entity}.php`: casts for dates/booleans/decimals, typed relations, scopes. 3. **Repository**: an `upsertByClockifyId()` (or `upsertMany()`) using `updateOrCreate(['workspace_id' => …, 'clockify_id' => …], $attributes)`. All queries live here. 4. **Service** `app/Services/Sync/{Entity}SyncService.php`: orchestrates fetch → map → upsert inside `DB::transaction`; increments `records_created` / `records_updated` counters. ```php foreach ($this->client->forCredentials($key)->paginate( "/workspaces/{$workspaceId}/time-entries", ['start' => $from, 'end' => $to], $connectionKey, ) as $page => $entries) { DB::transaction(fn () => $this->entries->upsertMany($workspaceId, $entries)); $this->checkpoints->advance($job, $page, count($entries)); } ``` ## 2. Add a sync job - `app/Jobs/Sync/Sync{Entity}Job.php` — thin; resolves the service and delegates. - Make it **resumable**: read the checkpoint from `sync_jobs`, continue from `page`, and persist progress after each page. - Assign a queue by priority (TDR §17): `high` (manual/webhook), `default` (daily), `low` (reconciliation). Priority affects ordering only — never the rate limiter. - Dispatch from a service, not a controller. ## 3. Scheduling & webhooks - Register recurring work in `routes/console.php`: `Schedule::job(new SyncDailyJob)->dailyAt('01:00');` - Daily sync = today/yesterday/previous 7 days; weekly = previous 30–31 days (TDR §19 rolling reconciliation). - Webhook handlers must **acknowledge quickly** and queue processing; validate the webhook token before doing anything. - Isolate the experimental Entities Changes API behind an adapter so it can be swapped if Clockify changes it (TDR §20). ## 4. Testing Always fake the network — never hit the real API. ```php use Illuminate\Support\Facades\Http; Http::fake([ 'https://api.clockify.me/api/v1/workspaces/ws-1/time-entries*' => Http::sequence() ->push([['id' => 'e1', 'description' => 'A']], 200, ['Last-Page' => 'false']) ->push([['id' => 'e2', 'description' => 'B']], 200, ['Last-Page' => 'true']), ]); // run the job/service, then assert idempotency: $service->sync($connection, $from, $to); $service->sync($connection, $from, $to); // second run expect(ClockifyTimeEntry::where('workspace_id', 'ws-1')->count())->toBe(2); ``` Assert: correct endpoint/headers sent, upsert (no duplicates on re-run), counters updated, checkpoint advanced, and failure resumes from the last page. ## 5. Completion checks - [ ] All HTTP via `ClockifyClient`; rate limiter respected. - [ ] Unique `clockify_id`; upsert semantics proven idempotent by a test. - [ ] Job is thin, queued with the right priority, and resumable via checkpoints. - [ ] No credentials logged or sent to the frontend. - [ ] `Http::fake()` tests cover success, pagination, and re-run idempotency. - [ ] `composer check` passes.
Ver en GitHub