- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
250 lines
7.3 KiB
PHP
250 lines
7.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Waf\Feedback;
|
|
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\Database\ConnectionInterface;
|
|
use App\Framework\Database\ValueObjects\SqlQuery;
|
|
use App\Framework\Waf\DetectionCategory;
|
|
use App\Framework\Waf\DetectionSeverity;
|
|
use PDO;
|
|
|
|
/**
|
|
* Database implementation of the feedback repository
|
|
*/
|
|
final readonly class DatabaseFeedbackRepository implements FeedbackRepositoryInterface
|
|
{
|
|
/**
|
|
* @param PDO $pdo Database connection
|
|
*/
|
|
public function __construct(
|
|
private readonly ConnectionInterface $connection
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function saveFeedback(DetectionFeedback $feedback): void
|
|
{
|
|
$sql = '
|
|
INSERT INTO waf_feedback (
|
|
detection_id, feedback_type, user_id, comment,
|
|
timestamp, category, severity, context
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
';
|
|
|
|
$this->connection->execute(SqlQuery::create($sql, [
|
|
$feedback->detectionId,
|
|
$feedback->feedbackType->value,
|
|
$feedback->userId,
|
|
$feedback->comment,
|
|
$feedback->timestamp->toSqlString(),
|
|
$feedback->category->value,
|
|
$feedback->severity->value,
|
|
json_encode($feedback->context),
|
|
]));
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFeedbackForDetection(string $detectionId): array
|
|
{
|
|
$sql = '
|
|
SELECT
|
|
detection_id, feedback_type, user_id, comment,
|
|
timestamp, category, severity, context
|
|
FROM waf_feedback
|
|
WHERE detection_id = ?
|
|
ORDER BY timestamp DESC
|
|
';
|
|
|
|
$result = $this->connection->query(SqlQuery::create($sql, [$detectionId]));
|
|
|
|
return $this->hydrateMultipleResults($result);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFeedbackByCategory(DetectionCategory $category, ?Timestamp $since = null): array
|
|
{
|
|
$sql = '
|
|
SELECT
|
|
detection_id, feedback_type, user_id, comment,
|
|
timestamp, category, severity, context
|
|
FROM waf_feedback
|
|
WHERE category = ?
|
|
';
|
|
|
|
$params = [$category->value];
|
|
|
|
if ($since !== null) {
|
|
$sql .= ' AND timestamp >= ?';
|
|
$params[] = $since->toSqlString();
|
|
}
|
|
|
|
$sql .= ' ORDER BY timestamp DESC';
|
|
|
|
$result = $this->connection->query(SqlQuery::create($sql, $params));
|
|
|
|
return $this->hydrateMultipleResults($result);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFeedbackByFeedbackType(FeedbackType $feedbackType, ?Timestamp $since = null): array
|
|
{
|
|
$sql = '
|
|
SELECT
|
|
detection_id, feedback_type, user_id, comment,
|
|
timestamp, category, severity, context
|
|
FROM waf_feedback
|
|
WHERE feedback_type = ?
|
|
';
|
|
|
|
$params = [$feedbackType->value];
|
|
|
|
if ($since !== null) {
|
|
$sql .= ' AND timestamp >= ?';
|
|
$params[] = $since->toSqlString();
|
|
}
|
|
|
|
$sql .= ' ORDER BY timestamp DESC';
|
|
|
|
$result = $this->connection->query(SqlQuery::create($sql, $params));
|
|
|
|
return $this->hydrateMultipleResults($result);
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getFeedbackStats(): array
|
|
{
|
|
// Get total count
|
|
$totalResult = $this->connection->query(SqlQuery::create('SELECT COUNT(*) FROM waf_feedback'));
|
|
$totalCount = (int)$totalResult->fetchColumn();
|
|
|
|
// Get counts by feedback type
|
|
$typeResult = $this->connection->query(SqlQuery::create('
|
|
SELECT feedback_type, COUNT(*) as count
|
|
FROM waf_feedback
|
|
GROUP BY feedback_type
|
|
'));
|
|
$typeStats = [];
|
|
foreach ($typeResult->fetchAll() as $row) {
|
|
$typeStats[$row['feedback_type']] = (int)$row['count'];
|
|
}
|
|
|
|
// Get counts by category
|
|
$categoryResult = $this->connection->query(SqlQuery::create('
|
|
SELECT category, COUNT(*) as count
|
|
FROM waf_feedback
|
|
GROUP BY category
|
|
'));
|
|
$categoryStats = [];
|
|
foreach ($categoryResult->fetchAll() as $row) {
|
|
$categoryStats[$row['category']] = (int)$row['count'];
|
|
}
|
|
|
|
// Get counts by severity
|
|
$severityResult = $this->connection->query(SqlQuery::create('
|
|
SELECT severity, COUNT(*) as count
|
|
FROM waf_feedback
|
|
GROUP BY severity
|
|
'));
|
|
$severityStats = [];
|
|
foreach ($severityResult->fetchAll() as $row) {
|
|
$severityStats[$row['severity']] = (int)$row['count'];
|
|
}
|
|
|
|
// Get trend data (last 30 days)
|
|
$trendResult = $this->connection->query(SqlQuery::create('
|
|
SELECT
|
|
DATE(timestamp) as date,
|
|
feedback_type,
|
|
COUNT(*) as count
|
|
FROM waf_feedback
|
|
WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
|
|
GROUP BY DATE(timestamp), feedback_type
|
|
ORDER BY date
|
|
'));
|
|
$trendData = [];
|
|
foreach ($trendResult->fetchAll() as $row) {
|
|
if (! isset($trendData[$row['date']])) {
|
|
$trendData[$row['date']] = [];
|
|
}
|
|
$trendData[$row['date']][$row['feedback_type']] = (int)$row['count'];
|
|
}
|
|
|
|
return [
|
|
'total_count' => $totalCount,
|
|
'by_feedback_type' => $typeStats,
|
|
'by_category' => $categoryStats,
|
|
'by_severity' => $severityStats,
|
|
'trend_data' => $trendData,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* {@inheritdoc}
|
|
*/
|
|
public function getRecentFeedback(int $limit = 10): array
|
|
{
|
|
$sql = '
|
|
SELECT
|
|
detection_id, feedback_type, user_id, comment,
|
|
timestamp, category, severity, context
|
|
FROM waf_feedback
|
|
ORDER BY timestamp DESC
|
|
LIMIT ?
|
|
';
|
|
|
|
$result = $this->connection->query(SqlQuery::create($sql, [$limit]));
|
|
|
|
return $this->hydrateMultipleResults($result);
|
|
}
|
|
|
|
/**
|
|
* Hydrates multiple DetectionFeedback objects from a PDO statement
|
|
*
|
|
* @param \PDOStatement $stmt The executed PDO statement
|
|
* @return DetectionFeedback[] Array of DetectionFeedback objects
|
|
*/
|
|
private function hydrateMultipleResults($result): array
|
|
{
|
|
$results = [];
|
|
|
|
foreach ($result->fetchAll() as $row) {
|
|
$results[] = $this->hydrateFromRow($row);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Hydrates a DetectionFeedback object from a database row
|
|
*
|
|
* @param array $row Database row
|
|
* @return DetectionFeedback
|
|
*/
|
|
private function hydrateFromRow(array $row): DetectionFeedback
|
|
{
|
|
return new DetectionFeedback(
|
|
detectionId: $row['detection_id'],
|
|
feedbackType: FeedbackType::from($row['feedback_type']),
|
|
userId: $row['user_id'],
|
|
comment: $row['comment'],
|
|
timestamp: Timestamp::fromString($row['timestamp']),
|
|
category: DetectionCategory::from($row['category']),
|
|
severity: DetectionSeverity::from($row['severity']),
|
|
context: json_decode($row['context'] ?? '{}', true) ?: []
|
|
);
|
|
}
|
|
}
|