nawasara / job-vacancy
Job vacancy module for the Nawasara superapp framework — listing and detail, synced from an external service management platform. English port of nawasara/loker.
Requires
- php: ^8.2
- illuminate/database: ^10.0|^12.0
- illuminate/support: ^10.0|^12.0
- livewire/livewire: ^3.0
- nawasara/sync: *
- nawasara/ui: *
- nawasara/vault: *
- spatie/laravel-permission: ^6.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-20 12:16:05 UTC
README
Job vacancies module for the Nawasara superapp framework — a read-only mirror of job vacancy data from the upstream service management platform into a local DB snapshot.
This is the English port of nawasara/loker: the local table, API, UI and error strings are all
English (job vacancies, job_title, company_name, job_description, ...) while the upstream
contract stays the default loker / lokers endpoints for compatibility with the external service.
Uses UI components from nawasara-ui, connection credentials handled by nawasara-vault, and
data served via nawasara-api. The architecture pattern follows nawasara-zoom / nawasara-cloudflare.
Features
- Periodic sync (default every 15 minutes) snapshots job vacancies from the upstream platform into the local DB
- List UI (search + filter) with detail opened in a modal (
x-nawasara-ui::modal) - Standalone detail page (
/nawasara-job-vacancy/job-vacancies/{slug}) - Renders
requirements(EditorJS) via thex-nawasara-job-vacancy::editorjs-renderercomponent — XSS-safe (all texte()) - Nawasara read-only API with scope
job.vacancy.read - Permission
job.vacancy.view+ rolejob-vacancy - Idempotent migration creating the fresh English table
nawasara_job_vacancies
Setup
Install:
php artisan job-vacancy:install
This command (idempotent):
- (Optional) publishes
config/nawasara-job-vacancy.php— default no; without publishing, the config is still read viamergeConfigFromand can be overridden through.env - Runs the migration
- Seeds role
job-vacancy+ permissionjob.vacancy.view - Injects the Vault group
job-vacancyintoconfig/nawasara-vault.php(when the Vault config is already published)
Register the package with Tailwind
php artisan job-vacancy:install cannot do this one, and it is the step most
likely to be skipped. Add the package to resources/css/app.css in the host
application:
@source "../../vendor/nawasara/job-vacancy";
Without it every Tailwind class in this package's Blade files is dropped from the build. Nothing errors; the pages render with no styling at all, and the classes that do survive are only the ones another package happens to use as well, which makes the breakage look random rather than total.
Measured on 20 September 2026: adding that line grew the compiled CSS by 1,380 bytes. To check it yourself, build and look for a class this package actually needs:
npm run build grep -c "bg-emerald-600" public/build/assets/app-*.css
Add it manually when the Vault config is not published:
'groups' => [ 'job-vacancy' => [ 'label' => 'Job Vacancies', 'icon' => 'lucide-briefcase', 'test' => \Nawasara\JobVacancy\Services\JobVacancyClient::class.'@testConnection', 'fields' => [ 'base_url' => ['label' => 'Base URL', 'type' => 'text', 'placeholder' => 'https://jobs.example.com'], 'api_token' => ['label' => 'API Token', 'type' => 'password'], ], ], ],
Configuration
NAWASARA_JOB_VACANCY_VAULT_GROUP — Vault group name (default job-vacancy).
NAWASARA_JOB_VACANCY_SYNC_EVERY_MINUTES — sync interval in minutes (default 15; 0 = disabled).
The http, job_vacancy, sync and vault config keys are documented in config/nawasara-job-vacancy.php.
Usage
Vault
Fill in the credentials in the Vault → group job-vacancy menu:
| Field | Value |
|---|---|
| Base URL | Upstream host root, e.g. https://jobs.example.com (the path /api/v1/loker/lokers is appended automatically by JobVacancyClient) |
| API Token | Upstream loker contract token |
Connection check is available via the Test button in Vault (JobVacancyClient@testConnection).
Sync
Sync uses the queue job SyncJobVacanciesJob (extends Nawasara\Sync\Jobs\AbstractSyncJob —
tracked in nawasara_sync_jobs, retry 3× with backoff, queue routing falls back to the default
queue when no dedicated queue key is configured).
Dispatch (default):
php artisan job-vacancy:sync
Inline execution (first run / debug, skip the queue):
php artisan job-vacancy:sync --sync
Scheduled: the dispatcher is registered */15 * * * * (interval via
NAWASARA_JOB_VACANCY_SYNC_EVERY_MINUTES; 0 = disabled) using
$schedule->call(fn () => SyncJobVacanciesJob::dispatch(triggerSource: 'scheduled')) — following the
reference_schedule_call_workaround pattern (not $schedule->command(...)).
Data is served from the local DB snapshot, not from the upstream per request — the API and UI keep working when the upstream is unreachable (as long as a snapshot exists).
UI
/nawasara-job-vacancy/job-vacancies— list + search + filter, detail via modal (routenawasara-job-vacancy.job-vacancy.index)/nawasara-job-vacancy/job-vacancies/{slug}— detail page (routenawasara-job-vacancy.job-vacancy.show)
Access is restricted by permission job.vacancy.view (role job-vacancy).
Permissions
job.vacancy.view
Nawasara API (for other applications)
Requires nawasara/api. When not installed, routes are not mounted.
Served from the local snapshot, not from the upstream directly.
Scope
| Scope | Access |
|---|---|
job.vacancy.read |
List + detail of job vacancies (slug, title, description, company, location, salary, type, category, requirements, expiry) |
Endpoint
| Method | Path | Query |
|---|---|---|
| GET | /api/v1/job-vacancy/job-vacancies |
q, location, type, category, per_page |
| GET | /api/v1/job-vacancy/job-vacancies/{slug} |
The list uses a compact payload (JobVacancyListResource) optimized for Android/iOS apps —
each item only carries meta fields (slug, job title, company, location, type, category, salary,
expired flag, expiry date). Description, requirements and the apply link are fetched per item through
the detail endpoint.
The q search covers job title, company name, and job description. Only active and
not-yet-expired vacancies are listed; expired ones do not appear in the list and resolve to 404
on the detail endpoint. Pagination is stabilized with an id tie-breaker so ordering stays stable
while data changes.
Security & rate limiting:
job_descriptionis HTML that is already sanitized (tag-whitelist;<script>/<style>/ event handlers/javascript:/data:stripped; formatting<b>/<a>/<em>preserved). Clients may render this field as safe rich text — do not double-render values from any other source.- Endpoints are rate limited to a default of
120requests/minute per token + IP (config('nawasara-job-vacancy.job_vacancy.api.rate_limit_per_minute'), envNAWASARA_JOB_VACANCY_RATE_LIMIT_PER_MINUTE); exceeding →429.
curl -H "Authorization: Bearer nws_xxx" \ "https://job-vacancy.example.org/api/v1/job-vacancy/job-vacancies?q=operator&per_page=10"
Never returned
id(internal upstream UUID),is_active,is_expired,deleted_at, and the internal sync fields (source_id,extern_*,synced_at) stay private.
Database
| Table | Purpose |
|---|---|
nawasara_job_vacancies |
Job vacancy snapshot (filled by the periodic sync from the upstream platform) |
Model: Nawasara\JobVacancy\Models\JobVacancy. The public id is the slug, not the internal
upstream UUID.
Cross-package Integration
| Package | Role |
|---|---|
| nawasara/vault | Stores the sync credentials (group job-vacancy: base_url + api_token) |
| nawasara/api | Mounts routes/api.php (scope job.vacancy.read) |
| nawasara/ui | Page/table/modal/filter components and the editorjs-renderer |
Troubleshooting
Vault group missing or empty
Make sure Vault has a job-vacancy group with base_url + api_token filled in:
php artisan vault:show
Empty UI/API data
Run the sync (inline to see the result immediately):
php artisan job-vacancy:sync --sync
New vacancies appear late
Bounded staleness until the next scheduled sync — lower
NAWASARA_JOB_VACANCY_SYNC_EVERY_MINUTES if needed.
Author
Ricky R — mini dev
License
MIT