Commit 6dfa5fed authored by Administrator's avatar Administrator

Update 1 files via Son of Anton

parent e92d64bc
<?php
declare(strict_types=1);
namespace Modules\Onboarding\Controllers;
use Engine\Core\Container;
use Engine\Core\Request;
use Engine\Core\Response;
use Engine\Database\Connection;
use Engine\Auth\PermissionEngine;
use Engine\Audit\AuditLogger;
use Engine\Notifications\NotificationManager;
use Engine\Template\TemplateEngine;
final class InviteController
{
private Connection $db;
private PermissionEngine $perms;
private AuditLogger $audit;
private NotificationManager $notif;
private TemplateEngine $templates;
public function __construct()
{
$c = Container::getInstance();
$this->db = $c->resolve(Connection::class);
$this->perms = $c->resolve(PermissionEngine::class);
$this->audit = $c->resolve(AuditLogger::class);
$this->notif = $c->resolve(NotificationManager::class);
$this->templates = $c->resolve(TemplateEngine::class);
}
public function index(Request $request): Response
{
$user = $request->user();
$this->perms->denyUnlessAllowed($user, 'invites.manage');
$status = $request->query('status');
$sql = "SELECT i.*, u.full_name_en as created_by_name,
uu.full_name_en as used_by_name
FROM invites i
JOIN users u ON u.id = i.created_by_id
LEFT JOIN users uu ON uu.id = i.used_by_user_id";
$params = [];
if ($status) {
$sql .= " WHERE i.status = ?";
$params[] = $status;
}
$sql .= " ORDER BY i.created_at DESC LIMIT 200";
$invites = $this->db->fetchAll($sql, $params);
$stats = [
'active' => (int)$this->db->fetchColumn("SELECT COUNT(*) FROM invites WHERE status = 'active'"),
'used' => (int)$this->db->fetchColumn("SELECT COUNT(*) FROM invites WHERE status = 'used'"),
'expired' => (int)$this->db->fetchColumn("SELECT COUNT(*) FROM invites WHERE status = 'expired'"),
'revoked' => (int)$this->db->fetchColumn("SELECT COUNT(*) FROM invites WHERE status = 'revoked'"),
];
$projectLeaders = $this->db->fetchAll(
"SELECT id, full_name_en FROM users WHERE role IN ('project_leader','admin','super_admin') AND is_active = 1 ORDER BY full_name_en"
);
$data = [
'user' => $user,
'invites' => $invites,
'stats' => $stats,
'project_leaders' => $projectLeaders,
];
if ($request->wantsJson()) return Response::json($data);
return Response::html($this->templates->render('onboarding/invites', $data));
}
public function create(Request $request): Response
{
$user = $request->user();
$this->perms->denyUnlessAllowed($user, 'invites.create');
$contractorType = $request->input('contractor_type');
if (!in_array($contractorType, ['full_timer', 'intern'])) {
return Response::json(['error' => 'Invalid contractor type.'], 422);
}
$expirationDays = max(1, min(30, (int)($request->input('expiration_days', 7))));
$expiresAt = date('Y-m-d H:i:s', strtotime("+{$expirationDays} days"));
$code = strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));
$token = bin2hex(random_bytes(64));
$attempts = 0;
while ($this->db->fetchOne("SELECT id FROM invites WHERE code = ?", [$code]) && $attempts < 10) {
$code = strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));
$attempts++;
}
$id = $this->db->insert('invites', [
'code' => $code,
'token' => $token,
'contractor_type' => $contractorType,
'assigned_pl_id' => $request->input('assigned_pl_id') ? (int)$request->input('assigned_pl_id') : null,
'custom_welcome_note' => $request->input('custom_welcome_note') ?: null,
'status' => 'active',
'expires_at' => $expiresAt,
'created_by_id' => $user['id'],
]);
$appUrl = rtrim(Container::getInstance()->resolve(\Engine\Core\Config::class)->get('app.url', ''), '/');
$inviteLink = $appUrl . '/register?token=' . $token;
$this->audit->log($user, 'INVITE_CREATED', 'invite', $id, 'onboarding', '/invites',
null, ['code' => $code, 'type' => $contractorType, 'expires_at' => $expiresAt],
$request->ip(), $request->userAgent());
return Response::json([
'success' => true,
'id' => $id,
'code' => $code,
'invite_link' => $inviteLink,
'expires_at' => $expiresAt,
]);
}
public function revoke(Request $request, string $inviteId): Response
{
$user = $request->user();
$this->perms->denyUnlessAllowed($user, 'invites.manage');
$invite = $this->db->fetchOne("SELECT * FROM invites WHERE id = ?", [(int)$inviteId]);
if (!$invite) return Response::json(['error' => 'Invite not found.'], 404);
if ($invite['status'] !== 'active') return Response::json(['error' => 'Only active invites can be revoked.'], 422);
$this->db->update('invites', ['status' => 'revoked'], 'id = ?', [(int)$inviteId]);
$this->audit->log($user, 'INVITE_REVOKED', 'invite', (int)$inviteId, 'onboarding', '/invites',
['status' => 'active'], ['status' => 'revoked'], $request->ip(), $request->userAgent());
return Response::json(['success' => true]);
}
public function delete(Request $request, string $inviteId): Response
{
$user = $request->user();
$this->perms->denyUnlessAllowed($user, 'invites.delete');
$invite = $this->db->fetchOne("SELECT * FROM invites WHERE id = ?", [(int)$inviteId]);
if (!$invite) return Response::json(['error' => 'Invite not found.'], 404);
$this->db->delete('invites', 'id = ?', [(int)$inviteId]);
$this->audit->log($user, 'INVITE_DELETED', 'invite', (int)$inviteId, 'onboarding', '/invites',
$invite, null, $request->ip(), $request->userAgent());
return Response::json(['success' => true]);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment