PHPackages                             kennofizet/release-support-backend - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. kennofizet/release-support-backend

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

kennofizet/release-support-backend
==================================

Release support backend package for issue reporting, draw annotations, and app version update workflow.

024↑150%PHP

Since May 21Pushed 2w agoCompare

[ Source](https://github.com/kennofizet/release-support-backend)[ Packagist](https://packagist.org/packages/kennofizet/release-support-backend)[ RSS](/packages/kennofizet-release-support-backend/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

Release Support Backend
=======================

[](#release-support-backend)

Laravel package for **in-app issue reporting**, runtime log capture payloads, **draw annotations**, and **version/update notices** for dev workflows. Uses **packages-core** for API auth and current user context. This package is **not zone-scoped** — support data is global per app install (dev access is controlled by config user ids).

---

Requirements
------------

[](#requirements)

- PHP 8.2+, Laravel 12.x
- **kennofizet/packages-core-backend** (token middleware, `currentUserId`, shared `BaseModel`)

---

Install
-------

[](#install)

```
composer require kennofizet/release-support-backend
php artisan vendor:publish --tag=release-support-config
php artisan vendor:publish --tag=release-support-migrations
php artisan migrate
```

**Optional .env:**

```
RELEASE_SUPPORT_API_PREFIX=release-support
RELEASE_SUPPORT_REPORTS_TABLE=release_support_reports
RELEASE_SUPPORT_REPORT_COMMENTS_TABLE=release_support_report_comments
RELEASE_SUPPORT_VERSION_UPDATES_TABLE=release_support_version_updates

# Open reporter UI once on first app load (frontend reads bootstrap)
RELEASE_SUPPORT_FORCE_SHOW_REPORTER=false

# Comma-separated user ids allowed to use /dev/* APIs and dev UI
RELEASE_SUPPORT_DEV_USER_IDS=1

# Max client log lines the frontend should keep (also returned in bootstrap)
RELEASE_SUPPORT_CAPTURE_MAX_LOGS=200
# disk = save PNG/JPG files on storage; DB keeps paths only (recommended)
# json = legacy: store base64 data URLs in DB (large rows)
RELEASE_SUPPORT_DRAWINGS_STORAGE=disk
RELEASE_SUPPORT_DRAWINGS_DISK=local
RELEASE_SUPPORT_DRAWINGS_PATH=release-support/drawings
RELEASE_SUPPORT_MAX_DRAWING_BYTES=5242880
RELEASE_SUPPORT_REPORT_SUBMIT_RATE_LIMIT=10
RELEASE_SUPPORT_DEDUPE_ENABLED=true
RELEASE_SUPPORT_DEDUPE_WINDOW_MINUTES=5
RELEASE_SUPPORT_QUEUE_LISTENERS=false
RELEASE_SUPPORT_WEBHOOK_URL=
```

User table mapping comes from **packages-core** (`table_user`, `user_col_name`) when the host app enriches responses — this package stores `user_id` only.

---

Config
------

[](#config)

**config/release-support.php**

KeyDescription`api_prefix`URL segment under packages-core API prefix`reports_table` / `report_comments_table` / `version_updates_table`Table names`force_show_reporter`Passed to frontend via bootstrap`dev_user_ids`Users with dev API + UI access`capture_max_logs`Client log buffer size hint`drawings_storage``disk` (default): files on storage, paths in DB. `json`: legacy base64 in DB`drawings_disk`Laravel disk name (default `local`, private). Use `public` only if you accept unauthenticated `/storage` URLs`report_tags`Allowed category ids: `bug`, `feature`, `question`, `improvement`, `other` — **required** in published config`drawings_path`Folder under disk root (default `release-support/drawings`)`max_drawing_bytes`Max decoded image size per drawing (default 5MB)`report_event_class`Event dispatched after report create (default `IssueReportSubmitted`)`report_status_changed_event_class`Event when dev changes report status (default `ReportStatusChanged`)`report_comment_added_event_class`Event when dev adds a comment (default `ReportCommentAdded`)`version_released_event_class`Event when a version release merges completed reports (default `VersionReleased`)`after_submitted_listeners`Classes implementing `AfterIssueReportSubmittedListener``after_status_changed_listeners`Callable listeners: `handle($subject, array $context)``after_comment_added_listeners`Callable listeners: `handle($subject, array $context)``after_version_released_listeners`Callable listeners: `handle($subject, array $context)`---

Drawings / screenshots (storage)
--------------------------------

[](#drawings--screenshots-storage)

The frontend still sends **base64 data URLs** on submit. With `drawings_storage=disk` (default), the backend:

1. Decodes each `data:image/...;base64,...` payload
2. Writes a file under `storage/app/public/release-support/drawings/{reportId}/`
3. Saves only the **relative path** in the `drawings` JSON column

API responses return full URLs (`/storage/...` for the `public` disk, or an authenticated API URL for private disks).

**Host app setup (public disk):**

```
php artisan storage:link
```

```
RELEASE_SUPPORT_DRAWINGS_STORAGE=disk
RELEASE_SUPPORT_DRAWINGS_DISK=local
```

For a private disk (`local`), the API returns relative paths like `drawings/{reportId}/{file}.png`. The frontend loads them with `GET …/drawings/{reportId}/{filename}` and **`X-Knf-Token`** (blob URL for ``). Do not rely on Laravel `APP_URL` in `img src`.

---

Events &amp; listeners
----------------------

[](#events--listeners)

After a report is saved, the package:

1. Dispatches `IssueReportSubmitted` (or class from `report_event_class`).
2. Runs each class in `after_submitted_listeners` that implements:

```
use Kennofizet\ReleaseSupport\Contracts\AfterIssueReportSubmittedListener;
use Kennofizet\ReleaseSupport\Models\ReleaseSupportReport;

class NotifyTeamOnIssueReport implements AfterIssueReportSubmittedListener
{
    public function handle(ReleaseSupportReport $report): void
    {
        // Slack, email, internal ticket, etc.
    }
}
```

Register in config:

```
'after_submitted_listeners' => [
    \App\Listeners\NotifyTeamOnIssueReport::class,
],
```

### Status, comment, and version events

[](#status-comment-and-version-events)

When a dev updates status or adds a comment, the package dispatches the configured event class, then runs `after_status_changed_listeners` / `after_comment_added_listeners`. Each listener receives the event instance and a context array (report id, old/new status, comment text, actor user id, etc.).

When a dev creates a **version release** (merge), the package dispatches `VersionReleased` (or `version_released_event_class`) and runs `after_version_released_listeners`.

Override event classes via `.env`:

```
RELEASE_SUPPORT_STATUS_CHANGED_EVENT_CLASS=\App\Events\MyReportStatusChanged
RELEASE_SUPPORT_COMMENT_ADDED_EVENT_CLASS=\App\Events\MyReportCommentAdded
RELEASE_SUPPORT_VERSION_RELEASED_EVENT_CLASS=\App\Events\MyVersionReleased
```

Example generic listener:

```
class LogReleaseSupportActivity
{
    public function handle(object $subject, array $context): void
    {
        // $subject is the dispatched event; $context has ids and payloads
    }
}
```

```
'after_version_released_listeners' => [
    \App\Listeners\LogReleaseSupportActivity::class,
],
```

---

Traits for host User model
--------------------------

[](#traits-for-host-user-model)

### `HasReleaseSupportReports` — reporter's own reports

[](#hasreleasesupportreports--reporters-own-reports)

```
use Kennofizet\ReleaseSupport\Traits\HasReleaseSupportReports;

class User extends Authenticatable
{
    use HasReleaseSupportReports;
}

$reports = $user->getReleaseSupportReports('open', 20);
```

### `ManagesReleaseSupportAsDev` — dev workflow from app code

[](#managesreleasesupportasdev--dev-workflow-from-app-code)

Use on staff/dev `User` models whose IDs are in `RELEASE_SUPPORT_DEV_USER_IDS`:

```
use Kennofizet\ReleaseSupport\Traits\HasReleaseSupportReports;
use Kennofizet\ReleaseSupport\Traits\ManagesReleaseSupportAsDev;

class User extends Authenticatable
{
    use HasReleaseSupportReports;
    use ManagesReleaseSupportAsDev;
}

// Status (open, in_progress, resolved, closed, cancelled)
$dev->releaseSupportUpdateReportStatus($reportId, 'resolved');

// Dev comment on any report
$dev->releaseSupportCommentOnReport($reportId, 'Fixed in build 42.');

// Preview next version + waiting queue (same as GET dev/release-preview)
$preview = $dev->releaseSupportReleasePreview();

// Merge ALL waiting resolved reports — auto semver, default title & release notes
$release = $dev->releaseSupportMergeAllWaitingReports();

// Optional overrides
$release = $dev->releaseSupportMergeAllWaitingReports([
    'title' => 'Hotfix '.$preview['next_version'],
    'content' => $preview['suggested_content'],
    'is_force' => true,
]);

// Merge only selected report IDs (empty title/content → package defaults)
$release = $dev->releaseSupportMergeReports([12, 15, 18]);

if ($dev->isReleaseSupportDev()) { /* ... */ }
```

Service equivalent (e.g. Artisan command without a User instance):

```
app(ReleaseSupportService::class)->createVersionReleaseMergeAllWaiting($actorUserId);
```

---

Report statuses
---------------

[](#report-statuses)

StatusMeaning`open`New report`in_progress`Being handled`resolved`Fixed / answered`closed`Closed without shipping in a release (not merged)`cancelled`Refused / cancelled (not merged)**Merge eligible**: `resolved` only, and not yet linked to a version (`version_update_id` is null).
**Waiting merge**: resolved reports in that queue.
**Open / in progress** reports do **not** block creating a release; pick which **resolved** reports to merge.
**Closed** and **cancelled** are not merge-eligible.

---

Version releases (merge workflow)
---------------------------------

[](#version-releases-merge-workflow)

Releases work like merging completed “pull requests” into the next semver:

1. Auto version: `0.0.1` → `0.0.2` → … → `0.0.99` → `0.1.0` (see `SemverHelper::nextReleaseVersion()`).
2. **Create release** when at least one **resolved** report is waiting merge (open reports are allowed).
3. On create, pass `report_ids` — only selected resolved reports are merged; release notes default to their titles.
4. `GET dev/release-preview` returns `can_create`, `blockers`, `next_version`, `waiting_reports`, and suggested title/content.
5. `POST dev/version-updates` creates the release (body: `title`, `content`, `is_active`, `is_force` — **no** manual `version`).
6. `GET dev/version-updates/{id}` returns merged reports (PR-style list) for the detail UI.

Run migrations after upgrade (adds `version_update_id`, `merged_at`, and index `rs_reports_waiting_merge_idx` on reports).

`RELEASE_SUPPORT_WAITING_MERGE_PREVIEW_LIMIT` (default `100`) caps rows in `dev/release-preview` waiting list; merge on create still includes **all** waiting reports.

---

Security
--------

[](#security)

All routes use middleware: `knf.core.token`, **`knf.core.validator`** (sanitizes input, `per_page` / `perPage` max **50**).

ControlBehaviorReport accessOwner or dev only; others get **404** (no ID enumeration)Drawings uploadData-URL images only; magic-byte check; no client file pathsDrawings downloadAuth + filename must belong to reportPayloadLogs/context/meta sanitized; sensitive keys redactedWebhookHTTPS only; blocks localhost / private IPsPagination`knf.core.validator` + `ListReportsRequest` (max 50)Metrics`days` max 365 (`DevMetricsRequest`)Use a **private** `drawings_disk` (`local`) in production. If you publish `config/release-support.php`, include `report_tags` from the package default.

**Note:** Browser `` cannot send `X-Knf-Token`. Private-disk screenshots are served only via authenticated API URLs — they will not load in `` unless you add **signed URL** support or use the `public` disk (less secure). Plan UI accordingly (e.g. fetch blob with axios + `Authorization` / token header, then `URL.createObjectURL`).

---

API
---

[](#api)

Base path: `{packages-core.api_prefix}/{release-support.api_prefix}/`
Example: `api/knf/release-support/`

Requires header **`X-Knf-Token`** (packages-core). No zone header required.

MethodEndpointAccessDescriptionGET`bootstrap`Authenticated`force_show_reporter`, `capture_max_logs`, `is_dev_user`, `latest_update`POST`reports`AuthenticatedSubmit issue (see body below)GET`reports/my?status=&page=&per_page=`AuthenticatedOwn reports (paginated, `per_page` ≤ 50)GET`version-updates?page=&per_page=`Authenticated**Read-only** active release notes (users)GET`version-updates/{id}`Authenticated**Read-only** release detail (users)GET`reports/{reportId}`Owner or devDetail with commentsGET`dev/reports?status=&page=&per_page=`Dev userAll reports (paginated)POST`dev/reports/{reportId}/status`Dev userBody: `{ "status": "in_progress" }`POST`dev/reports/{reportId}/comments`Dev userBody: `{ "comment": "..." }`GET`dev/release-preview`Dev userNext version, blockers, waiting queue, suggested notesGET`dev/version-updates?page=&per_page=`Dev userPaginated releases (`merges_count` per row)GET`dev/version-updates/{id}`Dev userRelease detail with `merges` (merged reports)POST`dev/version-updates`Dev user**Merge &amp; publish** release (auto version + merge all waiting)PUT`dev/version-updates/{id}`Dev userEdit title, content, flags (version is read-only)GET`dev/metrics?days=30`Dev userReports/day, median hours to resolved, open count`GET /bootstrap` accepts optional `?app_version=` and returns `version_outdated`, `version_compare`, `drawings_storage`.

`POST /reports` is rate-limited separately (`report_submit_rate_limit`). Duplicate submits within `dedupe_window_minutes` are rejected.

### POST `reports` body

[](#post-reports-body)

```
{
  "title": "Button submit fails",
  "description": "Happens after login",
  "app_version": "1.4.0",
  "captured_logs": [],
  "captured_context": {},
  "drawings": ["data:image/png;base64,..."],
  "meta": {}
}
```

`title` is required. Arrays default to `[]` if omitted.

---

Service (direct use in host app)
--------------------------------

[](#service-direct-use-in-host-app)

```
use Kennofizet\ReleaseSupport\Services\ReleaseSupportService;

$service = app(ReleaseSupportService::class);

if ($service->isDevUser()) {
    // ...
}

$payload = $service->getBootstrapPayload();
```

---

Summary
-------

[](#summary)

StepActionInstall`composer require kennofizet/release-support-backend`Config`php artisan vendor:publish --tag=release-support-config`Migrations`php artisan vendor:publish --tag=release-support-migrations` then `php artisan migrate`Dev usersSet `RELEASE_SUPPORT_DEV_USER_IDS`Hooks`after_submitted_listeners`, `after_status_changed_listeners`, `after_comment_added_listeners`, `after_version_released_listeners`Model (optional)`HasReleaseSupportReports` (reporter), `ManagesReleaseSupportAsDev` (staff) on UserPair with **@kennofizet/release-support-frontend** for the reporter UI and background capture.

###  Health Score

23

—

LowBetter than 26% of packages

Maintenance63

Regular maintenance activity

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/37218440?v=4)[kennofizet](/maintainers/kennofizet)[@kennofizet](https://github.com/kennofizet)

---

Top Contributors

[![phuongdpreg](https://avatars.githubusercontent.com/u/183365267?v=4)](https://github.com/phuongdpreg "phuongdpreg (4 commits)")

### Embed Badge

![Health badge](/badges/kennofizet-release-support-backend/health.svg)

```
[![Health](https://phpackages.com/badges/kennofizet-release-support-backend/health.svg)](https://phpackages.com/packages/kennofizet-release-support-backend)
```

###  Alternatives

[zvizvi/user-fields

This is my package user-fields

1311.7k](/packages/zvizvi-user-fields)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
