db = new Database(); } public function create(array $assignedProjects = [], ?string $note = null, int $maxUses = 1, ?string $expiresAt = null): string { $token = bin2hex(random_bytes(24)); // 48-char secure hex token $projectsJson = json_encode(array_values(array_filter($assignedProjects))); $sql = "INSERT INTO home_invitations (token, assigned_projects, max_uses, uses_count, expires_at, note) VALUES (:token, :assigned_projects, :max_uses, 0, :expires_at, :note)"; $this->db->query($sql); $this->db->bind(':token', $token); $this->db->bind(':assigned_projects', $projectsJson); $this->db->bind(':max_uses', $maxUses); $this->db->bind(':expires_at', $expiresAt); $this->db->bind(':note', $note); $this->db->execute(); return $token; } public function getByToken(string $token): ?array { $sql = "SELECT * FROM home_invitations WHERE LOWER(token) = LOWER(:token)"; $this->db->query($sql); $this->db->bind(':token', $token); $invitation = $this->db->single(); if (!$invitation) { return null; } // Check uses count if ($invitation['max_uses'] > 0 && $invitation['uses_count'] >= $invitation['max_uses']) { return null; } // Check expiration if (!empty($invitation['expires_at'])) { $now = new DateTime(); $expires = new DateTime($invitation['expires_at']); if ($now > $expires) { return null; } } $invitation['assigned_projects'] = json_decode($invitation['assigned_projects'] ?? '[]', true) ?? []; return $invitation; } public function recordUse(string $token): bool { $sql = "UPDATE home_invitations SET uses_count = uses_count + 1 WHERE LOWER(token) = LOWER(:token)"; $this->db->query($sql); $this->db->bind(':token', $token); return $this->db->execute(); } public function getAll(): array { $sql = "SELECT * FROM home_invitations ORDER BY id DESC"; $this->db->query($sql); $invitations = $this->db->resultSet(); foreach ($invitations as &$inv) { $inv['assigned_projects'] = json_decode($inv['assigned_projects'] ?? '[]', true) ?? []; $isExpired = false; if (!empty($inv['expires_at'])) { $isExpired = (new DateTime()) > (new DateTime($inv['expires_at'])); } $isDepleted = ($inv['max_uses'] > 0 && $inv['uses_count'] >= $inv['max_uses']); $inv['is_valid'] = (!$isExpired && !$isDepleted); } return $invitations; } public function delete(int $id): bool { $sql = "DELETE FROM home_invitations WHERE id = :id"; $this->db->query($sql); $this->db->bind(':id', $id); return $this->db->execute(); } }