Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion itexpert-content-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ spring:
jackson:
default-property-inclusion: non_empty
server:
port: 1080
port: 9080
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
package com.itexpert.content.core.config;
import com.itexpert.content.core.handlers.NotificationSocketHandler;

import com.itexpert.content.core.handlers.websockets.DatasSocketHandler;
import com.itexpert.content.core.handlers.websockets.NotificationSocketHandler;
import com.itexpert.content.core.handlers.websockets.RedisSocketHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;

import java.util.HashMap;
import java.util.Map;


@Configuration
public class WebSocketConfig {
private final NotificationSocketHandler handler;
private final NotificationSocketHandler notificationSocketHandler;
private final RedisSocketHandler redisSocketHandler;
private final DatasSocketHandler datasSocketHandler;


public WebSocketConfig(NotificationSocketHandler handler) {
this.handler = handler;
public WebSocketConfig(NotificationSocketHandler notificationSocketHandler, RedisSocketHandler redisSocketHandler, DatasSocketHandler datasSocketHandler) {
this.notificationSocketHandler = notificationSocketHandler;
this.redisSocketHandler = redisSocketHandler;
this.datasSocketHandler = datasSocketHandler;
}

@Bean
public HandlerMapping webSocketMapping() {
return new SimpleUrlHandlerMapping(Map.of("/ws/notifications", handler), -1);
Map<String, WebSocketHandler> map = new HashMap<>();

map.put("/ws/notifications", notificationSocketHandler);
map.put("/ws/owner/**", redisSocketHandler);
map.put("/ws/datas/contentCode/**", datasSocketHandler);

SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(-1); // priorité max

return mapping;
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,8 @@ public Mono<ResponseEntity<ContentNode>> save(@RequestBody ContentNode contentNo
contentNode.setModifiedBy(user);

return Mono.defer(() -> {
try {
return contentNodeHandler.save(contentNode);
} catch (CloneNotSupportedException ex) {
return Mono.error(new RuntimeException("Erreur lors du clone du contentNode", ex));
}

})
.flatMap(contentNodeHandler::setPublicationStatus)
.flatMap(saved -> redisHandler.releaseLock(resourceKey, user).thenReturn(saved))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ public Mono<ResponseEntity<Boolean>> deleteDefinitivelyVersion(@PathVariable Str
}



@PostMapping(value = "/code/{code}/activate")
public Mono<ResponseEntity<Boolean>> activate(@PathVariable String code, Authentication authentication) {
String user = authentication.getPrincipal().toString();
Expand Down Expand Up @@ -371,8 +370,8 @@ public Flux<Node> deploy(@PathVariable String code,
Optional.ofNullable(node.getContents()).orElse(List.of())
.forEach(contentNode -> {
log.debug(
"[EXPORT] ContentNode: code={}, status={}",
contentNode.getCode(), contentNode.getStatus());
"[EXPORT] ContentNode: code={}, status={}",
contentNode.getCode(), contentNode.getStatus());

contentNode.setModifiedBy(authentication.getPrincipal().toString());
});
Expand All @@ -393,10 +392,10 @@ public Flux<Node> deploy(@PathVariable String code,

@PostMapping(value = "/code/{code}/version/{version}/deploy")
public Mono<ResponseEntity<Boolean>> deployVersion(
@PathVariable String code,
@PathVariable String version,
@RequestParam(name = "environment", required = false) String environmentCode,
Authentication authentication) {
@PathVariable String code,
@PathVariable String version,
@RequestParam(name = "environment", required = false) String environmentCode,
Authentication authentication) {
String user = authentication.getPrincipal().toString();
Duration ttl = Duration.ofMinutes(30);

Expand Down Expand Up @@ -486,4 +485,10 @@ private String cleanStatusSnapshot(String input) {

return base + "?" + String.join("&", kept);
}

@PostMapping(value = "/propagateMaxHistoryToKeep/{nodeCodePatent}")
public Mono<Boolean> propagateMaxHistoryToKeep(
@PathVariable String nodeCodePatent) {
return nodeHandler.propagateMaxHistoryToKeep(nodeCodePatent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public Mono<Boolean> refresh(@PathVariable String code, Authentication authentic

@GetMapping("/owner/{code}")
public Mono<LockInfo> getOwner(@PathVariable String code, Authentication authentication) {
return redisHandler.getLockInfo(code, authentication);
return redisHandler.getLockInfo(code, authentication.getPrincipal().toString());
}

@PostMapping("/admin/release/{code}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples;

import java.time.Instant;
Expand Down Expand Up @@ -50,7 +51,7 @@ public Mono<ContentNode> findById(UUID uuid) {
return contentNodeRepository.findById(uuid).map(contentNodeMapper::fromEntity);
}

public Mono<ContentNode> save(ContentNode contentNode) throws CloneNotSupportedException {
public Mono<ContentNode> save(ContentNode contentNode) {
return Mono.just(contentNode).filter(model -> ObjectUtils.isEmpty(model.getId()))
.flatMap(model ->
contentNodeRepository.findByCodeAndStatus(contentNode.getCode(), StatusEnum.SNAPSHOT.name())
Expand Down Expand Up @@ -110,89 +111,84 @@ public Flux<ContentNode> findByNodeCodeAndStatus(String nodeCode, String status)
}

public Mono<ContentNode> publish(String code, Boolean publish, String modifiedBy) {
return this.contentNodeRepository.findByCodeAndStatus(code, StatusEnum.PUBLISHED.name())
.flatMap(alreadyPublished -> {
// Cas où un PUBLISHED existe
alreadyPublished.setStatus(StatusEnum.ARCHIVE);
alreadyPublished.setModificationDate(Instant.now().toEpochMilli());

return this.contentNodeRepository.save(alreadyPublished)
.flatMap(archived -> this.contentNodeRepository.findByCodeAndStatus(archived.getCode(), StatusEnum.SNAPSHOT.name()))
.flatMap(snapshotNode -> {
snapshotNode.setStatus(StatusEnum.PUBLISHED);
snapshotNode.setModifiedBy(modifiedBy);
snapshotNode.setModificationDate(Instant.now().toEpochMilli());
snapshotNode.setPublicationDate(snapshotNode.getModificationDate());

String content = snapshotNode.getContent();
String snapshotContent = content != null ? new String(content) : "";

snapshotNode.setContent(SnapshotUtils.clearSnapshotIfCode(snapshotNode.getContent()));

return this.contentNodeRepository.save(snapshotNode)
.flatMap(publishedContent -> {
try {
// Création du SNAPSHOT en arrière-plan
long version = ObjectUtils.isNotEmpty(publishedContent.getVersion())
? Long.parseLong(publishedContent.getVersion()) + 1L
: 1L;
com.itexpert.content.lib.entities.ContentNode newSnapshot = publishedContent.clone();
newSnapshot.setStatus(StatusEnum.SNAPSHOT);
newSnapshot.setModifiedBy(modifiedBy);
newSnapshot.setId(UUID.randomUUID());
newSnapshot.setVersion(Long.toString(version));
newSnapshot.setContent(snapshotContent);

return this.contentNodeRepository.save(newSnapshot)
.then(Mono.just(publishedContent));

} catch (CloneNotSupportedException e) {
return Mono.error(e);
}
});
});
})
.switchIfEmpty(Mono.defer(() -> createFirstPublication(code, publish)))

.map(contentNodeMapper::fromEntity)
.flatMap(model -> this.notify(model, NotificationEnum.DEPLOYMENT));
return Mono.fromCallable(() ->
publishContentSync(code, publish, modifiedBy)
).subscribeOn(Schedulers.boundedElastic());
}

private Mono<com.itexpert.content.lib.entities.ContentNode> createFirstPublication(String code, Boolean publish) {
if (publish) {
return this.contentNodeRepository.findByCodeAndStatus(code, StatusEnum.SNAPSHOT.name())
.flatMap(contentNode -> {
contentNode.setStatus(StatusEnum.PUBLISHED);
contentNode.setModificationDate(Instant.now().toEpochMilli());
contentNode.setPublicationDate(contentNode.getModificationDate());

String content = contentNode.getContent();
String snapshotContent = content != null ? new String(content) : "";

contentNode.setContent(SnapshotUtils.clearSnapshotIfCode(contentNode.getContent()));


return this.contentNodeRepository.save(contentNode)
.flatMap(savedPublishedNode -> {
// Crée le SNAPSHOT en flux réactif
return Mono.fromCallable(() -> {
com.itexpert.content.lib.entities.ContentNode newSnapshot = savedPublishedNode.clone();
newSnapshot.setId(UUID.randomUUID());
newSnapshot.setStatus(StatusEnum.SNAPSHOT);
long version = ObjectUtils.isNotEmpty(savedPublishedNode.getVersion())
? Long.parseLong(savedPublishedNode.getVersion()) + 1L
: 1L;
newSnapshot.setVersion(Long.toString(version));
newSnapshot.setContent(snapshotContent);
return newSnapshot;
})
.flatMap(newSnapshot -> this.contentNodeRepository.save(newSnapshot))
// On ignore le SNAPSHOT créé et on retourne le PUBLISHED
.thenReturn(savedPublishedNode);
});
});
@Transactional
public synchronized ContentNode publishContentSync(
String code,
boolean publish,
String modifiedBy
) {

// 1️⃣ Chercher un PUBLISHED existant
com.itexpert.content.lib.entities.ContentNode alreadyPublished =
contentNodeRepository
.findByCodeAndStatus(code, StatusEnum.PUBLISHED.name())
.block();

if (alreadyPublished != null) {
// 2️⃣ Archiver le PUBLISHED existant
alreadyPublished.setStatus(StatusEnum.ARCHIVE);
alreadyPublished.setModificationDate(Instant.now().toEpochMilli());
contentNodeRepository.save(alreadyPublished).block();
}

// 3️⃣ Chercher le SNAPSHOT à publier
com.itexpert.content.lib.entities.ContentNode snapshot =
contentNodeRepository
.findByCodeAndStatus(code, StatusEnum.SNAPSHOT.name())
.block();

if (snapshot == null) {
throw new IllegalStateException("Aucun SNAPSHOT à publier pour le content " + code);
}

// 4️⃣ Publier le SNAPSHOT
snapshot.setStatus(StatusEnum.PUBLISHED);
snapshot.setModifiedBy(modifiedBy);
snapshot.setModificationDate(Instant.now().toEpochMilli());
snapshot.setPublicationDate(snapshot.getModificationDate());

String originalContent = snapshot.getContent();
String snapshotContent = originalContent != null ? new String(originalContent) : "";

snapshot.setContent(SnapshotUtils.clearSnapshotIfCode(snapshot.getContent()));

com.itexpert.content.lib.entities.ContentNode published =
contentNodeRepository.save(snapshot).block();

if (published == null) {
throw new IllegalStateException("Échec publication content " + code);
}

// 5️⃣ Créer le NOUVEAU SNAPSHOT
try {
long version =
ObjectUtils.isNotEmpty(published.getVersion())
? Long.parseLong(published.getVersion()) + 1L
: 1L;

com.itexpert.content.lib.entities.ContentNode newSnapshot = published.clone();
newSnapshot.setId(UUID.randomUUID());
newSnapshot.setStatus(StatusEnum.SNAPSHOT);
newSnapshot.setModifiedBy(modifiedBy);
newSnapshot.setVersion(Long.toString(version));
newSnapshot.setContent(snapshotContent);

contentNodeRepository.save(newSnapshot).block();

} catch (CloneNotSupportedException e) {
throw new IllegalStateException("Impossible de cloner ContentNode", e);
}
return Mono.empty();

// 6️⃣ Notification
ContentNode model = contentNodeMapper.fromEntity(published);
notify(model, NotificationEnum.DEPLOYMENT).block();

return model;
}


Expand Down Expand Up @@ -396,6 +392,7 @@ public Mono<Boolean> deleteDefinitively(String code) {
.map(unused -> Boolean.TRUE)
.onErrorContinue((throwable, o) -> log.error(throwable.getMessage(), throwable));
}

public Mono<Boolean> deleteDefinitivelyVersion(String code, String version) {
return contentNodeRepository.findByCodeAndVersion(code, version)
.flatMap(node -> this.contentNodeRepository.delete(node)
Expand Down Expand Up @@ -504,5 +501,16 @@ public Mono<Boolean> publishVersion(String code, String version, String user) {
)
.defaultIfEmpty(false); // si la version n’existe pas
}

protected Mono<Boolean> setMaxHostoryToKeep(String code, Integer maxVersionsToKeep) {
return this.contentNodeRepository
.findByNodeCodeAndStatus(code, StatusEnum.SNAPSHOT.name())
.flatMap(contentNode -> {
contentNode.setMaxVersionsToKeep(maxVersionsToKeep);
return this.contentNodeRepository.save(contentNode);
})
.then(Mono.just(Boolean.TRUE));
}

}

Loading