feat: integrate Bugtracker module, UpdateService enhancements & Token hierarchy
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace Deploymentcenter\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class TokenManager
|
||||
{
|
||||
private PDO $db;
|
||||
|
||||
public function __construct(PDO $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Master Token.
|
||||
*/
|
||||
public function createMasterToken(
|
||||
string $name,
|
||||
?string $projectSlug = null,
|
||||
?string $licenseKey = null,
|
||||
string $ownerType = 'custom',
|
||||
?string $ownerIdentity = null,
|
||||
array $scopes = ['*'],
|
||||
string $environment = 'all'
|
||||
): array {
|
||||
$tokenId = 'tok_m_' . bin2hex(random_bytes(8));
|
||||
$rawToken = 'dc_master_' . bin2hex(random_bytes(20));
|
||||
$tokenHash = hash('sha256', $rawToken);
|
||||
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO dc_tokens (
|
||||
token_id, parent_token_id, token_hash, raw_token, name,
|
||||
project_slug, license_key, owner_type, owner_identity,
|
||||
type, scopes, environment, created_at
|
||||
) VALUES (
|
||||
:id, NULL, :hash, :raw, :name,
|
||||
:proj, :lic, :type, :identity,
|
||||
"master", :scopes, :env, NOW()
|
||||
)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
':id' => $tokenId,
|
||||
':hash' => $tokenHash,
|
||||
':raw' => $rawToken,
|
||||
':name' => $name,
|
||||
':proj' => !empty($projectSlug) ? $projectSlug : null,
|
||||
':lic' => !empty($licenseKey) ? $licenseKey : null,
|
||||
':type' => in_array($ownerType, ['license', 'project', 'host', 'dev_agent', 'custom']) ? $ownerType : 'custom',
|
||||
':identity' => !empty($ownerIdentity) ? $ownerIdentity : null,
|
||||
':scopes' => json_encode(!empty($scopes) ? $scopes : ['*']),
|
||||
':env' => in_array($environment, ['production', 'development', 'all']) ? $environment : 'all',
|
||||
]);
|
||||
|
||||
return [
|
||||
'token_id' => $tokenId,
|
||||
'raw_token' => $rawToken,
|
||||
'name' => $name,
|
||||
'type' => 'master',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Provision a Sub-Token using a Master-Token.
|
||||
*/
|
||||
public function provisionSubToken(
|
||||
string $rawMasterToken,
|
||||
string $name,
|
||||
?string $instanceIdentity = null,
|
||||
array $requestedScopes = [],
|
||||
string $environment = 'all'
|
||||
): array {
|
||||
$masterHash = hash('sha256', $rawMasterToken);
|
||||
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT * FROM dc_tokens
|
||||
WHERE (token_hash = :hash OR raw_token = :raw)
|
||||
AND type = "master"
|
||||
AND revoked = 0
|
||||
');
|
||||
$stmt->execute([':hash' => $masterHash, ':raw' => $rawMasterToken]);
|
||||
$master = $stmt->fetch();
|
||||
|
||||
if (!$master) {
|
||||
throw new \InvalidArgumentException('Invalid or revoked Master Token.');
|
||||
}
|
||||
|
||||
$masterScopes = json_decode($master['scopes'], true) ?: ['*'];
|
||||
|
||||
// Determine effective scopes
|
||||
$effectiveScopes = [];
|
||||
if (in_array('*', $masterScopes)) {
|
||||
$effectiveScopes = !empty($requestedScopes) ? $requestedScopes : ['*'];
|
||||
} else {
|
||||
if (empty($requestedScopes)) {
|
||||
$effectiveScopes = $masterScopes;
|
||||
} else {
|
||||
$effectiveScopes = array_intersect($requestedScopes, $masterScopes);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($effectiveScopes)) {
|
||||
throw new \InvalidArgumentException('Requested scopes are not allowed by this Master Token.');
|
||||
}
|
||||
|
||||
// Determine effective environment
|
||||
$effectiveEnv = $environment;
|
||||
if ($master['environment'] !== 'all') {
|
||||
$effectiveEnv = $master['environment'];
|
||||
}
|
||||
|
||||
$subTokenId = 'tok_s_' . bin2hex(random_bytes(8));
|
||||
$rawSubToken = 'dc_sub_' . bin2hex(random_bytes(20));
|
||||
$subHash = hash('sha256', $rawSubToken);
|
||||
|
||||
$ins = $this->db->prepare('
|
||||
INSERT INTO dc_tokens (
|
||||
token_id, parent_token_id, token_hash, raw_token, name,
|
||||
project_slug, license_key, owner_type, owner_identity,
|
||||
type, scopes, environment, created_at
|
||||
) VALUES (
|
||||
:id, :parent_id, :hash, :raw, :name,
|
||||
:proj, :lic, :owner_type, :identity,
|
||||
"sub", :scopes, :env, NOW()
|
||||
)
|
||||
');
|
||||
|
||||
$ins->execute([
|
||||
':id' => $subTokenId,
|
||||
':parent_id' => $master['token_id'],
|
||||
':hash' => $subHash,
|
||||
':raw' => $rawSubToken,
|
||||
':name' => $name,
|
||||
':proj' => $master['project_slug'],
|
||||
':lic' => $master['license_key'],
|
||||
':owner_type'=> $master['owner_type'],
|
||||
':identity' => !empty($instanceIdentity) ? $instanceIdentity : $master['owner_identity'],
|
||||
':scopes' => json_encode(array_values($effectiveScopes)),
|
||||
':env' => $effectiveEnv,
|
||||
]);
|
||||
|
||||
return [
|
||||
'token_id' => $subTokenId,
|
||||
'raw_token' => $rawSubToken,
|
||||
'name' => $name,
|
||||
'scopes' => array_values($effectiveScopes),
|
||||
'environment'=> $effectiveEnv,
|
||||
'type' => 'sub',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate any Token (Master or Sub) and check cascading revocation of parent tokens.
|
||||
*/
|
||||
public function validateToken(string $rawToken, ?string $requiredScope = null, ?string $environment = null): ?array
|
||||
{
|
||||
$hash = hash('sha256', $rawToken);
|
||||
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT t.*, p.revoked as parent_revoked
|
||||
FROM dc_tokens t
|
||||
LEFT JOIN dc_tokens p ON t.parent_token_id = p.token_id
|
||||
WHERE (t.token_hash = :hash OR t.raw_token = :raw)
|
||||
AND t.revoked = 0
|
||||
');
|
||||
$stmt->execute([':hash' => $hash, ':raw' => $rawToken]);
|
||||
$token = $stmt->fetch();
|
||||
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cascading Revocation Check
|
||||
if ($token['type'] === 'sub' && !empty($token['parent_token_id']) && (int)$token['parent_revoked'] === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Scope Check
|
||||
if ($requiredScope !== null) {
|
||||
$scopes = json_decode($token['scopes'], true) ?: [];
|
||||
if (!in_array('*', $scopes) && !in_array($requiredScope, $scopes)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Environment Check
|
||||
if ($environment !== null && $token['environment'] !== 'all' && $token['environment'] !== $environment) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update Last Used Timestamp
|
||||
$upd = $this->db->prepare('UPDATE dc_tokens SET last_used_at = NOW() WHERE id = :id');
|
||||
$upd->execute([':id' => $token['id']]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke a Token (Master or Sub). If Master, cascade revokes all child Sub-Tokens via DB foreign key or query.
|
||||
*/
|
||||
public function revokeToken(string $tokenId): bool
|
||||
{
|
||||
$stmt = $this->db->prepare('UPDATE dc_tokens SET revoked = 1 WHERE token_id = :id OR parent_token_id = :id');
|
||||
return $stmt->execute([':id' => $tokenId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Master Tokens with child count.
|
||||
*/
|
||||
public function getAllMasterTokens(): array
|
||||
{
|
||||
$stmt = $this->db->query('
|
||||
SELECT m.*, COUNT(s.id) as sub_token_count
|
||||
FROM dc_tokens m
|
||||
LEFT JOIN dc_tokens s ON m.token_id = s.parent_token_id
|
||||
WHERE m.type = "master"
|
||||
GROUP BY m.id
|
||||
ORDER BY m.created_at DESC
|
||||
');
|
||||
return $stmt->fetchAll() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Tokens (Master & Sub).
|
||||
*/
|
||||
public function getAllTokens(): array
|
||||
{
|
||||
$stmt = $this->db->query('SELECT * FROM dc_tokens ORDER BY created_at DESC');
|
||||
return $stmt->fetchAll() ?: [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace Deploymentcenter\Modules\Bugtracker;
|
||||
|
||||
use PDO;
|
||||
|
||||
class BugRepo
|
||||
{
|
||||
private PDO $db;
|
||||
|
||||
public function __construct(PDO $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ingest a Bug or Feature Request. Automates error-hash deduplication for bugs.
|
||||
*/
|
||||
public function reportItem(array $data): array
|
||||
{
|
||||
$projectSlug = !empty($data['project_slug']) ? trim($data['project_slug']) : 'default';
|
||||
$type = (isset($data['type']) && $data['type'] === 'feature_request') ? 'feature_request' : 'bug';
|
||||
$title = !empty($data['title']) ? trim($data['title']) : ($type === 'bug' ? 'Unhandled Exception' : 'New Feature Request');
|
||||
$description = $data['description'] ?? null;
|
||||
$errorMessage = $data['error_message'] ?? null;
|
||||
$stackTrace = $data['stack_trace'] ?? null;
|
||||
$buildVersion = $data['build_version'] ?? 'v1.0.0';
|
||||
$environment = in_array($data['environment'] ?? '', ['production', 'development', 'staging', 'testing'])
|
||||
? $data['environment']
|
||||
: 'production';
|
||||
$severity = in_array($data['severity'] ?? '', ['low', 'medium', 'high', 'critical'])
|
||||
? $data['severity']
|
||||
: 'medium';
|
||||
$createdBy = !empty($data['created_by']) ? trim($data['created_by']) : 'agent';
|
||||
|
||||
// Find associated project_id from dc_projects
|
||||
$projStmt = $this->db->prepare('SELECT id FROM dc_projects WHERE slug = :slug');
|
||||
$projStmt->execute([':slug' => $projectSlug]);
|
||||
$projectId = $projStmt->fetchColumn() ?: null;
|
||||
|
||||
// Deduplication logic for Bugs
|
||||
$errorHash = null;
|
||||
if ($type === 'bug') {
|
||||
$hashInput = $projectSlug . '|' . ($errorMessage ?: $title) . '|' . substr($stackTrace ?: '', 0, 200) . '|' . $environment;
|
||||
$errorHash = substr(hash('sha256', $hashInput), 0, 24);
|
||||
|
||||
$existingStmt = $this->db->prepare('
|
||||
SELECT id, occurrence_count
|
||||
FROM bugtracker_items
|
||||
WHERE error_hash = :hash
|
||||
AND environment = :env
|
||||
AND status IN ("open", "in_progress", "planned")
|
||||
LIMIT 1
|
||||
');
|
||||
$existingStmt->execute([':hash' => $errorHash, ':env' => $environment]);
|
||||
$existing = $existingStmt->fetch();
|
||||
|
||||
if ($existing) {
|
||||
$newCount = (int)$existing['occurrence_count'] + 1;
|
||||
$upd = $this->db->prepare('
|
||||
UPDATE bugtracker_items
|
||||
SET occurrence_count = :count, last_seen_at = NOW()
|
||||
WHERE id = :id
|
||||
');
|
||||
$upd->execute([':count' => $newCount, ':id' => $existing['id']]);
|
||||
|
||||
return [
|
||||
'id' => (int)$existing['id'],
|
||||
'is_new' => false,
|
||||
'occurrence_count' => $newCount,
|
||||
'error_hash' => $errorHash,
|
||||
'type' => $type,
|
||||
'environment' => $environment,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$ins = $this->db->prepare('
|
||||
INSERT INTO bugtracker_items (
|
||||
project_id, project_slug, type, title, description,
|
||||
error_message, stack_trace, error_hash, build_version,
|
||||
environment, severity, status, occurrence_count,
|
||||
first_seen_at, last_seen_at, created_by, created_at
|
||||
) VALUES (
|
||||
:pid, :slug, :type, :title, :desc,
|
||||
:err, :trace, :hash, :build,
|
||||
:env, :sev, "open", 1,
|
||||
NOW(), NOW(), :created_by, NOW()
|
||||
)
|
||||
');
|
||||
|
||||
$ins->execute([
|
||||
':pid' => $projectId,
|
||||
':slug' => $projectSlug,
|
||||
':type' => $type,
|
||||
':title' => $title,
|
||||
':desc' => $description,
|
||||
':err' => $errorMessage,
|
||||
':trace' => $stackTrace,
|
||||
':hash' => $errorHash,
|
||||
':build' => $buildVersion,
|
||||
':env' => $environment,
|
||||
':sev' => $severity,
|
||||
':created_by' => $createdBy,
|
||||
]);
|
||||
|
||||
$newItemId = (int)$this->db->lastInsertId();
|
||||
|
||||
// Initial comment log
|
||||
$this->addComment(
|
||||
$newItemId,
|
||||
$createdBy,
|
||||
$type === 'bug' ? 'Bug in System erfasst.' : 'Feature-Request eingereicht.',
|
||||
'reported'
|
||||
);
|
||||
|
||||
return [
|
||||
'id' => $newItemId,
|
||||
'is_new' => true,
|
||||
'occurrence_count' => 1,
|
||||
'error_hash' => $errorHash,
|
||||
'type' => $type,
|
||||
'environment' => $environment,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filtered list of Bugs & Feature Requests.
|
||||
*/
|
||||
public function getItems(array $filters = []): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['project_slug']) && $filters['project_slug'] !== 'all') {
|
||||
$where[] = 'project_slug = :slug';
|
||||
$params[':slug'] = $filters['project_slug'];
|
||||
}
|
||||
|
||||
if (!empty($filters['environment']) && $filters['environment'] !== 'all') {
|
||||
$where[] = 'environment = :env';
|
||||
$params[':env'] = $filters['environment'];
|
||||
}
|
||||
|
||||
if (!empty($filters['type']) && $filters['type'] !== 'all') {
|
||||
$where[] = 'type = :type';
|
||||
$params[':type'] = $filters['type'];
|
||||
}
|
||||
|
||||
if (!empty($filters['status']) && $filters['status'] !== 'all') {
|
||||
$where[] = 'status = :status';
|
||||
$params[':status'] = $filters['status'];
|
||||
}
|
||||
|
||||
if (!empty($filters['severity']) && $filters['severity'] !== 'all') {
|
||||
$where[] = 'severity = :severity';
|
||||
$params[':severity'] = $filters['severity'];
|
||||
}
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$where[] = '(title LIKE :q OR description LIKE :q OR error_message LIKE :q)';
|
||||
$params[':q'] = '%' . trim($filters['search']) . '%';
|
||||
}
|
||||
|
||||
$sql = 'SELECT * FROM bugtracker_items';
|
||||
if (!empty($where)) {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $where);
|
||||
}
|
||||
$sql .= ' ORDER BY last_seen_at DESC, id DESC';
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
|
||||
return $stmt->fetchAll() ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single Item details including complete comment history.
|
||||
*/
|
||||
public function getItemDetails(int $id): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare('SELECT * FROM bugtracker_items WHERE id = :id');
|
||||
$stmt->execute([':id' => $id]);
|
||||
$item = $stmt->fetch();
|
||||
|
||||
if (!$item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$commStmt = $this->db->prepare('SELECT * FROM bugtracker_comments WHERE item_id = :id ORDER BY created_at ASC');
|
||||
$commStmt->execute([':id' => $id]);
|
||||
$item['comments'] = $commStmt->fetchAll() ?: [];
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a diagnostic comment or timeline entry to an Item.
|
||||
*/
|
||||
public function addComment(
|
||||
int $itemId,
|
||||
string $author,
|
||||
string $comment,
|
||||
?string $actionTaken = null,
|
||||
?array $meta = null
|
||||
): array {
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO bugtracker_comments (
|
||||
item_id, author, comment, action_taken, meta_json, created_at
|
||||
) VALUES (
|
||||
:item_id, :author, :comment, :action, :meta, NOW()
|
||||
)
|
||||
');
|
||||
$stmt->execute([
|
||||
':item_id' => $itemId,
|
||||
':author' => $author,
|
||||
':comment' => $comment,
|
||||
':action' => $actionTaken,
|
||||
':meta' => !empty($meta) ? json_encode($meta) : null,
|
||||
]);
|
||||
|
||||
return [
|
||||
'id' => (int)$this->db->lastInsertId(),
|
||||
'item_id' => $itemId,
|
||||
'author' => $author,
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Item status.
|
||||
*/
|
||||
public function updateStatus(int $itemId, string $status, ?string $notes = null, string $author = 'agent'): bool
|
||||
{
|
||||
$allowed = ['open', 'planned', 'in_progress', 'resolved', 'closed', 'rejected'];
|
||||
if (!in_array($status, $allowed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stmt = $this->db->prepare('UPDATE bugtracker_items SET status = :status WHERE id = :id');
|
||||
$result = $stmt->execute([':status' => $status, ':id' => $itemId]);
|
||||
|
||||
if ($result) {
|
||||
$msg = 'Status geändert auf "' . $status . '"' . ($notes ? ': ' . $notes : '');
|
||||
$this->addComment($itemId, $author, $msg, 'status_changed');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a Bug or complete a Feature Request with build details.
|
||||
*/
|
||||
public function resolveItem(
|
||||
int $itemId,
|
||||
string $resolvedInBuild,
|
||||
?string $resolutionNotes = null,
|
||||
string $author = 'agent'
|
||||
): bool {
|
||||
$stmt = $this->db->prepare('
|
||||
UPDATE bugtracker_items
|
||||
SET status = "resolved",
|
||||
resolved_in_build = :build,
|
||||
resolution_notes = :notes,
|
||||
resolved_at = NOW()
|
||||
WHERE id = :id
|
||||
');
|
||||
$result = $stmt->execute([
|
||||
':build' => $resolvedInBuild,
|
||||
':notes' => $resolutionNotes,
|
||||
':id' => $itemId,
|
||||
]);
|
||||
|
||||
if ($result) {
|
||||
$msg = 'Als gelöst/umgesetzt markiert in Build "' . $resolvedInBuild . '"' . ($resolutionNotes ? '. Note: ' . $resolutionNotes : '');
|
||||
$this->addComment($itemId, $author, $msg, 'marked_resolved');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get summary stats for the dashboard.
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
$stats = [
|
||||
'open_bugs_prod' => 0,
|
||||
'open_bugs_dev' => 0,
|
||||
'open_features' => 0,
|
||||
'resolved_total' => 0,
|
||||
'critical_bugs' => 0,
|
||||
];
|
||||
|
||||
$res = $this->db->query('
|
||||
SELECT
|
||||
SUM(CASE WHEN type = "bug" AND environment = "production" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as open_bugs_prod,
|
||||
SUM(CASE WHEN type = "bug" AND environment = "development" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as open_bugs_dev,
|
||||
SUM(CASE WHEN type = "feature_request" AND status IN ("open", "planned", "in_progress") THEN 1 ELSE 0 END) as open_features,
|
||||
SUM(CASE WHEN status = "resolved" THEN 1 ELSE 0 END) as resolved_total,
|
||||
SUM(CASE WHEN type = "bug" AND severity = "critical" AND status IN ("open", "in_progress") THEN 1 ELSE 0 END) as critical_bugs
|
||||
FROM bugtracker_items
|
||||
')->fetch();
|
||||
|
||||
if ($res) {
|
||||
$stats['open_bugs_prod'] = (int)($res['open_bugs_prod'] ?? 0);
|
||||
$stats['open_bugs_dev'] = (int)($res['open_bugs_dev'] ?? 0);
|
||||
$stats['open_features'] = (int)($res['open_features'] ?? 0);
|
||||
$stats['resolved_total'] = (int)($res['resolved_total'] ?? 0);
|
||||
$stats['critical_bugs'] = (int)($res['critical_bugs'] ?? 0);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,14 @@ class UpdateManager
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function checkUpdate(string $productSlug, string $currentVersion): ?array
|
||||
public function checkUpdate(string $productSlug, string $currentVersion, string $channel = 'prod'): ?array
|
||||
{
|
||||
$stmt = $this->db->prepare('
|
||||
SELECT * FROM updateservice_releases
|
||||
WHERE product_slug = :slug AND version > :ver
|
||||
WHERE product_slug = :slug AND channel = :channel AND version > :ver
|
||||
ORDER BY created_at DESC LIMIT 1
|
||||
');
|
||||
$stmt->execute([':slug' => $productSlug, ':ver' => $currentVersion]);
|
||||
$stmt->execute([':slug' => $productSlug, ':channel' => $channel, ':ver' => $currentVersion]);
|
||||
$latest = $stmt->fetch();
|
||||
|
||||
return $latest ?: null;
|
||||
@@ -29,38 +29,55 @@ class UpdateManager
|
||||
public function addRelease(
|
||||
string $productSlug,
|
||||
string $version,
|
||||
?string $releaseNotes,
|
||||
string $downloadUrl,
|
||||
?string $sha256Hash,
|
||||
string $channel = 'prod',
|
||||
?string $releaseNotes = null,
|
||||
string $downloadUrl = '',
|
||||
?string $sha256Hash = null,
|
||||
?string $gitCommit = null,
|
||||
int $sizeBytes = 0,
|
||||
?string $manifestJson = null,
|
||||
bool $isCritical = false
|
||||
): bool {
|
||||
$stmt = $this->db->prepare('
|
||||
INSERT INTO updateservice_releases (
|
||||
product_slug, version, release_notes, download_url, sha256_hash, is_critical
|
||||
product_slug, version, channel, release_notes, download_url, sha256_hash, git_commit, size_bytes, manifest_json, is_critical
|
||||
) VALUES (
|
||||
:slug, :version, :notes, :url, :hash, :critical
|
||||
:slug, :version, :channel, :notes, :url, :hash, :git, :size, :manifest, :critical
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
release_notes = VALUES(release_notes),
|
||||
download_url = VALUES(download_url),
|
||||
sha256_hash = VALUES(sha256_hash),
|
||||
git_commit = VALUES(git_commit),
|
||||
size_bytes = VALUES(size_bytes),
|
||||
manifest_json = VALUES(manifest_json),
|
||||
is_critical = VALUES(is_critical)
|
||||
');
|
||||
|
||||
return $stmt->execute([
|
||||
':slug' => $productSlug,
|
||||
':version' => $version,
|
||||
':channel' => $channel,
|
||||
':notes' => $releaseNotes,
|
||||
':url' => $downloadUrl,
|
||||
':hash' => $sha256Hash,
|
||||
':git' => $gitCommit,
|
||||
':size' => $sizeBytes,
|
||||
':manifest' => $manifestJson,
|
||||
':critical' => $isCritical ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getReleases(?string $productSlug = null): array
|
||||
public function getReleases(?string $productSlug = null, ?string $channel = null): array
|
||||
{
|
||||
if ($productSlug) {
|
||||
if ($productSlug && $channel) {
|
||||
$stmt = $this->db->prepare('SELECT * FROM updateservice_releases WHERE product_slug = :slug AND channel = :channel ORDER BY created_at DESC');
|
||||
$stmt->execute([':slug' => $productSlug, ':channel' => $channel]);
|
||||
} elseif ($productSlug) {
|
||||
$stmt = $this->db->prepare('SELECT * FROM updateservice_releases WHERE product_slug = :slug ORDER BY created_at DESC');
|
||||
$stmt->execute([':slug' => $productSlug]);
|
||||
} elseif ($channel) {
|
||||
$stmt = $this->db->prepare('SELECT * FROM updateservice_releases WHERE channel = :channel ORDER BY created_at DESC');
|
||||
$stmt->execute([':channel' => $channel]);
|
||||
} else {
|
||||
$stmt = $this->db->query('SELECT * FROM updateservice_releases ORDER BY created_at DESC');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user