Tickets eines entfernten Projekts aus dem Kanban entfernen (Issue #43)
Bislang hat RepoConfigController::delete() nur die Projektkonfiguration geloescht - die Tickets des Projekts blieben (nur ohne Projektfarbe, Issue #34) dauerhaft im Kanban sichtbar. Jetzt loescht der Endpunkt zusaetzlich alle Tickets, deren repo-Feld auf das entfernte Projekt zeigt.
This commit is contained in:
parent
e51b373f07
commit
076dca2aef
8 changed files with 80 additions and 11 deletions
2
API.md
2
API.md
|
|
@ -335,7 +335,7 @@ bisherigen Default-Projekte `fds2610/monitor` (gelb), `fds2610/Steuerkram` (grue
|
|||
| `GET /api/v1/repo-configs` | - | Liste aller konfigurierten Projekte, sortiert nach `sort_order` |
|
||||
| `POST /api/v1/repo-configs` | `X-Admin-Key` | Legt ein Projekt an (`owner`, `repo`, `color` als 6-stelliger Hex-Wert, mit oder ohne `#`) |
|
||||
| `PATCH /api/v1/repo-configs/{id}` | `X-Admin-Key` | Aendert `owner`/`repo`/`color` eines Projekts |
|
||||
| `DELETE /api/v1/repo-configs/{id}` | `X-Admin-Key` | Entfernt ein Projekt (der Sync liest es danach nicht mehr) |
|
||||
| `DELETE /api/v1/repo-configs/{id}` | `X-Admin-Key` | Entfernt ein Projekt (der Sync liest es danach nicht mehr) und loescht dessen Tickets aus dem Kanban (Issue #43) |
|
||||
|
||||
`owner`+`repo` muessen zusammen eindeutig sein (409 -> hier als `422` mit Fehlermeldung, da
|
||||
kein Konflikt-Statuscode im Fehlerformat vorgesehen ist). Der laufende `sync`-Dienst
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ final class Application
|
|||
$eventController = new EventController($events);
|
||||
$systemController = new SystemController($systems, new LegacyStatusFileReader(), $config->statusFilePath());
|
||||
$kpiController = new KpiController($kpis);
|
||||
$repoConfigController = new RepoConfigController($repoConfigs);
|
||||
$repoConfigController = new RepoConfigController($repoConfigs, $tickets);
|
||||
$webhookController = new WebhookController(new SyncTrigger($config->syncTriggerPath()), $hooks);
|
||||
$streamController = new StreamController(
|
||||
new ChangeFeed($tickets, $events, $systems, $kpis, $agents, $hooks),
|
||||
|
|
|
|||
|
|
@ -8,11 +8,14 @@ use InvalidArgumentException;
|
|||
use Monitor\Api\Http\Request;
|
||||
use Monitor\Api\Http\Response;
|
||||
use Monitor\Api\Repository\RepoConfigRepository;
|
||||
use Monitor\Api\Repository\TicketRepository;
|
||||
|
||||
final class RepoConfigController
|
||||
{
|
||||
public function __construct(private readonly RepoConfigRepository $repoConfigs)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RepoConfigRepository $repoConfigs,
|
||||
private readonly TicketRepository $tickets,
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
|
|
@ -43,10 +46,16 @@ final class RepoConfigController
|
|||
return Response::json(['repo' => $repo]);
|
||||
}
|
||||
|
||||
/** @param array<string, string> $params */
|
||||
/**
|
||||
* Issue #43: Nach dem Entfernen eines Projekts sollen auch dessen Tickets aus dem Kanban
|
||||
* verschwinden statt (wie zuvor, Issue #34) nur ihre Projektfarbe zu verlieren.
|
||||
*
|
||||
* @param array<string, string> $params
|
||||
*/
|
||||
public function delete(Request $request, array $params): Response
|
||||
{
|
||||
$this->repoConfigs->delete((int) $params['id']);
|
||||
$repo = $this->repoConfigs->delete((int) $params['id']);
|
||||
$this->tickets->deleteByRepo($repo['owner'] . '/' . $repo['repo']);
|
||||
|
||||
return Response::json(['deleted' => true]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,14 +84,22 @@ final class RepoConfigRepository
|
|||
return $this->find($id);
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
/**
|
||||
* @return array<string, mixed> - der geloeschte Eintrag, damit der Aufrufer (Issue #43:
|
||||
* RepoConfigController::delete() raeumt anschliessend die Tickets des Projekts auf) dessen
|
||||
* owner/repo kennt, ohne ihn selbst erneut nachzuschlagen.
|
||||
*/
|
||||
public function delete(int $id): array
|
||||
{
|
||||
if ($this->find($id) === null) {
|
||||
$repo = $this->find($id);
|
||||
if ($repo === null) {
|
||||
throw new NotFoundException('Projekt nicht gefunden: ' . $id);
|
||||
}
|
||||
|
||||
$stmt = $this->db->pdo()->prepare('DELETE FROM repo_configs WHERE id = :id');
|
||||
$stmt->execute(['id' => $id]);
|
||||
|
||||
return $repo;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -197,6 +197,19 @@ final class TicketRepository
|
|||
$stmt->execute($slugs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Entfernt alle Tickets eines Projekts vollstaendig aus dem Kanban (Issue #43: bislang
|
||||
* blieben Tickets eines ueber das Projekte-Modal geloeschten Projekts sichtbar, nur die
|
||||
* Farbe wurde ueber clearColorForReposNotIn() geloescht). Wird von
|
||||
* RepoConfigController::delete() direkt nach dem Entfernen des repo_configs-Eintrags
|
||||
* aufgerufen, nicht erst beim naechsten periodischen Sync.
|
||||
*/
|
||||
public function deleteByRepo(string $repo): void
|
||||
{
|
||||
$stmt = $this->db->pdo()->prepare('DELETE FROM tickets WHERE repo = :repo');
|
||||
$stmt->execute(['repo' => $repo]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Billiger "Fingerabdruck" (Anzahl + juengster Aenderungszeitpunkt) fuer den SSE-Endpoint
|
||||
* (Realtime\ChangeFeed, F13) - aendert sich bei jedem Anlegen/Verschieben eines Tickets.
|
||||
|
|
|
|||
|
|
@ -8,17 +8,21 @@ use Monitor\Api\Controller\RepoConfigController;
|
|||
use Monitor\Api\Db\Database;
|
||||
use Monitor\Api\Http\Request;
|
||||
use Monitor\Api\Repository\RepoConfigRepository;
|
||||
use Monitor\Api\Repository\TicketRepository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class RepoConfigControllerTest extends TestCase
|
||||
{
|
||||
private RepoConfigController $controller;
|
||||
private TicketRepository $tickets;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$db = Database::inMemory();
|
||||
$db->pdo()->exec('DELETE FROM repo_configs');
|
||||
$this->controller = new RepoConfigController(new RepoConfigRepository($db));
|
||||
$db->pdo()->exec('DELETE FROM tickets');
|
||||
$this->tickets = new TicketRepository($db);
|
||||
$this->controller = new RepoConfigController(new RepoConfigRepository($db), $this->tickets);
|
||||
}
|
||||
|
||||
public function testIndexReturnsEmptyListInitially(): void
|
||||
|
|
@ -79,4 +83,24 @@ final class RepoConfigControllerTest extends TestCase
|
|||
self::assertSame(200, $response->status);
|
||||
self::assertTrue($response->body()['deleted']);
|
||||
}
|
||||
|
||||
public function testDeleteRemovesTicketsOfTheProjectFromTheKanban(): void
|
||||
{
|
||||
$created = $this->controller->create(Request::create('POST', '/api/v1/repo-configs', body: [
|
||||
'owner' => 'fds2610', 'repo' => 'monitor', 'color' => '#fbca04',
|
||||
]))->body()['repo'];
|
||||
|
||||
$orphaned = $this->tickets->create(['title' => 'A', 'repo' => 'fds2610/monitor']);
|
||||
$unrelated = $this->tickets->create(['title' => 'B', 'repo' => 'fds2610/anderes-projekt']);
|
||||
$manual = $this->tickets->create(['title' => 'C']);
|
||||
|
||||
$this->controller->delete(
|
||||
Request::create('DELETE', '/api/v1/repo-configs/' . $created['id']),
|
||||
['id' => (string) $created['id']]
|
||||
);
|
||||
|
||||
self::assertNull($this->tickets->find((int) $orphaned['id']));
|
||||
self::assertNotNull($this->tickets->find((int) $unrelated['id']));
|
||||
self::assertNotNull($this->tickets->find((int) $manual['id']));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,13 +81,15 @@ final class RepoConfigRepositoryTest extends TestCase
|
|||
$this->repository->update(999, ['owner' => 'a', 'repo' => 'b', 'color' => '#000000']);
|
||||
}
|
||||
|
||||
public function testDeleteRemovesEntry(): void
|
||||
public function testDeleteRemovesEntryAndReturnsIt(): void
|
||||
{
|
||||
$repo = $this->repository->create(['owner' => 'fds2610', 'repo' => 'monitor', 'color' => '#fbca04']);
|
||||
|
||||
$this->repository->delete((int) $repo['id']);
|
||||
$deleted = $this->repository->delete((int) $repo['id']);
|
||||
|
||||
self::assertSame([], $this->repository->all());
|
||||
self::assertSame('fds2610', $deleted['owner']);
|
||||
self::assertSame('monitor', $deleted['repo']);
|
||||
}
|
||||
|
||||
public function testDeleteUnknownIdThrowsNotFound(): void
|
||||
|
|
|
|||
|
|
@ -193,4 +193,17 @@ final class TicketRepositoryTest extends TestCase
|
|||
|
||||
self::assertNull($this->repository->find((int) $ticket['id'])['color']);
|
||||
}
|
||||
|
||||
public function testDeleteByRepoRemovesOnlyTicketsOfThatRepo(): void
|
||||
{
|
||||
$removed = $this->repository->create(['title' => 'A', 'repo' => 'fds2610/altes-projekt']);
|
||||
$kept = $this->repository->create(['title' => 'B', 'repo' => 'fds2610/monitor']);
|
||||
$manual = $this->repository->create(['title' => 'C']);
|
||||
|
||||
$this->repository->deleteByRepo('fds2610/altes-projekt');
|
||||
|
||||
self::assertNull($this->repository->find((int) $removed['id']));
|
||||
self::assertNotNull($this->repository->find((int) $kept['id']));
|
||||
self::assertNotNull($this->repository->find((int) $manual['id']));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue