chartboard/app/Controllers/Share/LinkController.php
2026-03-30 09:51:33 +05:30

185 lines
6.2 KiB
PHP

<?php
namespace App\Controllers\Share;
use App\Controllers\BaseController;
use App\Libraries\AuditLogger;
use App\Models\ChartModel;
use App\Models\DashboardModel;
use App\Models\SharedLinkModel;
/**
* Authenticated: create / revoke share links.
*/
class LinkController extends BaseController
{
public function generate()
{
$workspaceId = (int) $this->session->get('active_workspace_id');
$userId = (int) $this->session->get('user_id');
$rules = [
'type' => 'required|in_list[dashboard,chart]',
'resource_id' => 'required|integer',
'password' => 'permit_empty|max_length[200]',
'expires_at' => 'permit_empty|max_length[40]',
];
if (! $this->validate($rules)) {
if ($this->request->isAJAX()) {
return $this->response->setJSON(['success' => false, 'errors' => $this->validator->getErrors()])->setStatusCode(422);
}
return redirect()->back()->with('errors', $this->validator->getErrors());
}
$type = (string) $this->request->getPost('type');
$resourceId = (int) $this->request->getPost('resource_id');
if ($type === 'dashboard') {
$d = (new DashboardModel())->findForWorkspace($resourceId, $workspaceId);
if (! $d) {
return $this->ajaxOrRedirect(false, 'Dashboard not found.');
}
} else {
$c = (new ChartModel())->where('workspace_id', $workspaceId)->find($resourceId);
if (! $c) {
return $this->ajaxOrRedirect(false, 'Chart not found.');
}
}
$pwd = trim((string) $this->request->getPost('password'));
$hash = $pwd !== '' ? password_hash($pwd, PASSWORD_DEFAULT) : null;
$expRaw = trim((string) $this->request->getPost('expires_at'));
$expiresAt = null;
if ($expRaw !== '') {
$ts = strtotime($expRaw);
if ($ts === false) {
return $this->ajaxOrRedirect(false, 'Invalid expiry date.');
}
$expiresAt = date('Y-m-d H:i:s', $ts);
}
$model = new SharedLinkModel();
$token = SharedLinkModel::generateToken();
$id = (int) $model->insert([
'token' => $token,
'type' => $type,
'resource_id' => $resourceId,
'workspace_id' => $workspaceId,
'password_hash' => $hash,
'expires_at' => $expiresAt,
'view_count' => 0,
'is_active' => 1,
'created_by' => $userId,
], true);
$publicUrl = rtrim(base_url(), '/') . '/share/' . $token;
AuditLogger::log(
'share.created',
'shared_link',
$id,
null,
[
'type' => $type,
'resource_id' => $resourceId,
'has_password' => $hash !== null,
'expires_at' => $expiresAt,
],
$workspaceId,
$userId
);
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'success' => true,
'id' => $id,
'token' => $token,
'url' => $publicUrl,
'view_count' => 0,
'csrf' => ['name' => csrf_token(), 'hash' => csrf_hash()],
]);
}
return redirect()->back()->with('success', 'Share link created: ' . $publicUrl);
}
public function revoke(int $id)
{
$workspaceId = (int) $this->session->get('active_workspace_id');
$model = new SharedLinkModel();
$row = $model->findOwned($id, $workspaceId);
if (! $row) {
return $this->ajaxOrRedirect(false, 'Link not found.');
}
AuditLogger::log(
'share.revoked',
'shared_link',
$id,
['is_active' => 1],
['is_active' => 0],
$workspaceId,
(int) $this->session->get('user_id')
);
$model->update($id, ['is_active' => 0]);
if ($this->request->isAJAX()) {
return $this->response->setJSON([
'success' => true,
'csrf' => ['name' => csrf_token(), 'hash' => csrf_hash()],
]);
}
return redirect()->back()->with('success', 'Share link revoked.');
}
/**
* Existing links for a resource (AJAX).
*/
public function listResource()
{
$workspaceId = (int) $this->session->get('active_workspace_id');
$type = (string) $this->request->getGet('type');
$resourceId = (int) $this->request->getGet('resource_id');
if (! in_array($type, ['dashboard', 'chart'], true) || $resourceId <= 0) {
return $this->response->setJSON(['success' => false, 'message' => 'Invalid parameters.'])->setStatusCode(422);
}
if ($type === 'dashboard') {
if (! (new DashboardModel())->findForWorkspace($resourceId, $workspaceId)) {
return $this->response->setJSON(['success' => false])->setStatusCode(404);
}
} elseif (! (new ChartModel())->where('workspace_id', $workspaceId)->find($resourceId)) {
return $this->response->setJSON(['success' => false])->setStatusCode(404);
}
$rows = (new SharedLinkModel())->forResource($type, $resourceId, $workspaceId);
$out = [];
foreach ($rows as $r) {
$out[] = [
'id' => (int) $r['id'],
'url' => rtrim(base_url(), '/') . '/share/' . $r['token'],
'view_count' => (int) ($r['view_count'] ?? 0),
'expires_at' => $r['expires_at'],
'has_password' => ! empty($r['password_hash']),
];
}
return $this->response->setJSON(['success' => true, 'links' => $out]);
}
private function ajaxOrRedirect(bool $ok, string $message)
{
if ($this->request->isAJAX()) {
return $this->response->setJSON(['success' => $ok, 'message' => $message])->setStatusCode($ok ? 200 : 404);
}
return redirect()->back()->with($ok ? 'success' : 'error', $message);
}
}