4
4
5
5
namespace Neos \ContentRepositoryRegistry \Command ;
6
6
7
+ use Doctrine \DBAL \ArrayParameterType ;
7
8
use Doctrine \DBAL \Connection ;
8
9
use Neos \ContentGraph \DoctrineDbalAdapter \DoctrineDbalContentGraphProjection ;
10
+ use Neos \ContentRepository \Core \Factory \ContentRepositoryServiceFactoryDependencies ;
11
+ use Neos \ContentRepository \Core \Factory \ContentRepositoryServiceFactoryInterface ;
12
+ use Neos \ContentRepository \Core \Factory \ContentRepositoryServiceInterface ;
13
+ use Neos \ContentRepository \Core \Feature \ContentStreamEventStreamName ;
9
14
use Neos \ContentRepository \Core \Projection \CatchUpOptions ;
10
15
use Neos \ContentRepository \Core \SharedModel \ContentRepository \ContentRepositoryId ;
16
+ use Neos \ContentRepository \Core \SharedModel \Workspace \WorkspaceName ;
11
17
use Neos \ContentRepositoryRegistry \ContentRepositoryRegistry ;
12
18
use Neos \ContentRepositoryRegistry \Factory \EventStore \DoctrineEventStoreFactory ;
19
+ use Neos \EventStore \Model \Event \EventType ;
20
+ use Neos \EventStore \Model \Event \EventTypes ;
13
21
use Neos \EventStore \Model \Event \SequenceNumber ;
22
+ use Neos \EventStore \Model \EventEnvelope ;
23
+ use Neos \EventStore \Model \Events ;
24
+ use Neos \EventStore \Model \EventStream \EventStreamFilter ;
25
+ use Neos \EventStore \Model \EventStream \ExpectedVersion ;
14
26
use Neos \Flow \Annotations as Flow ;
15
27
use Neos \Flow \Cli \CommandController ;
16
28
use Symfony \Component \Console \Helper \ProgressBar ;
@@ -23,6 +35,44 @@ class NeosBetaMigrationCommandController extends CommandController
23
35
#[Flow \Inject()]
24
36
protected Connection $ connection ;
25
37
38
+ public function reorderNodeAggregateWasRemovedCommand (string $ contentRepository = 'default ' , string $ workspaceName = 'live ' ): void
39
+ {
40
+ $ contentRepositoryId = ContentRepositoryId::fromString ($ contentRepository );
41
+ $ this ->backup ($ contentRepositoryId );
42
+
43
+ $ workspace = $ this ->contentRepositoryRegistry ->get ($ contentRepositoryId )->findWorkspaceByName (WorkspaceName::fromString ($ workspaceName ));
44
+ if (!$ workspace ) {
45
+ $ this ->outputLine ('Workspace not found ' );
46
+ $ this ->quit (1 );
47
+ }
48
+
49
+ $ streamName = ContentStreamEventStreamName::fromContentStreamId ($ workspace ->currentContentStreamId )->getEventStreamName ();
50
+
51
+ $ internals = $ this ->getInternals ($ contentRepositoryId );
52
+
53
+ // get all NodeAggregateWasRemoved from the content stream
54
+ $ eventsToReorder = iterator_to_array ($ internals ->eventStore ->load ($ streamName , EventStreamFilter::create (EventTypes::create (EventType::fromString ('NodeAggregateWasRemoved ' )))), false );
55
+
56
+ // remove all the NodeAggregateWasRemoved events at their sequenceNumbers
57
+ $ eventTableName = DoctrineEventStoreFactory::databaseTableName ($ contentRepositoryId );
58
+ $ this ->connection ->beginTransaction ();
59
+ $ this ->connection ->executeStatement (
60
+ 'DELETE FROM ' . $ eventTableName . ' WHERE sequencenumber IN (:sequenceNumbers) ' ,
61
+ [
62
+ 'sequenceNumbers ' => array_map (fn (EventEnvelope $ eventEnvelope ) => $ eventEnvelope ->sequenceNumber ->value , $ eventsToReorder )
63
+ ],
64
+ [
65
+ 'sequenceNumbers ' => ArrayParameterType::STRING
66
+ ]
67
+ );
68
+ $ this ->connection ->commit ();
69
+
70
+ // reapply the NodeAggregateWasRemoved events at the end
71
+ $ internals ->eventStore ->commit ($ streamName , Events::fromArray (array_map (fn (EventEnvelope $ eventEnvelope ) => $ eventEnvelope ->event , $ eventsToReorder )), ExpectedVersion::ANY ());
72
+
73
+ $ this ->outputLine ('Reordered %d removals. Please replay and rebase your other workspaces. ' , [count ($ eventsToReorder )]);
74
+ }
75
+
26
76
public function fixReplayCommand (string $ contentRepository = 'default ' , bool $ resetProjection = true ): void
27
77
{
28
78
$ contentRepositoryId = ContentRepositoryId::fromString ($ contentRepository );
@@ -149,4 +199,21 @@ private function copyEventTable(string $backupEventTableName, ContentRepositoryI
149
199
FROM ' . $ eventTableName
150
200
);
151
201
}
202
+
203
+ private function getInternals (ContentRepositoryId $ contentRepositoryId ): ContentRepositoryServiceFactoryDependencies
204
+ {
205
+ // NOT API!!!
206
+ $ accessor = new class implements ContentRepositoryServiceFactoryInterface {
207
+ public ContentRepositoryServiceFactoryDependencies |null $ dependencies ;
208
+ public function build (ContentRepositoryServiceFactoryDependencies $ serviceFactoryDependencies ): ContentRepositoryServiceInterface
209
+ {
210
+ $ this ->dependencies = $ serviceFactoryDependencies ;
211
+ return new class implements ContentRepositoryServiceInterface
212
+ {
213
+ };
214
+ }
215
+ };
216
+ $ this ->contentRepositoryRegistry ->buildService ($ contentRepositoryId , $ accessor );
217
+ return $ accessor ->dependencies ;
218
+ }
152
219
}
0 commit comments